Merge branch 'brennanb2025/settle-split-1' into brennanb2025/settle-split-2

Brings in main 6741f4f165 through part 1. Main's history page now reads with a
HistoryReader, so the published current work moves to agent-session-published-work.ts and
lays over each finished page. Main's new Stop path (chat-stop target, acceptance, unconfirmed
note, targeted cancel) and the per-subagent row writer read what runs through the host's
current-work projection, as the rest of this part does.
This commit is contained in:
Brennan Benson
2026-10-13 21:46:36 -07:00
4320 changed files with 139796 additions and 52807 deletions
+1
View File
@@ -46,6 +46,7 @@
# normalisation on a Windows checkout would rewrite the evidence the fixture exists to be.
/src/main/runtime/__fixtures__/*.txt -text
/src/main/daemon/__fixtures__/pty-transcripts/*.txt -text
/src/renderer/src/components/terminal-pane/terminal-link-transcripts/*.txt -text whitespace=cr-at-eol
# Generated runtime English subset: compared byte-for-byte by
# verify:localization-runtime-catalog, so a CRLF checkout would fail the gate.
/src/renderer/src/i18n/en-runtime-required.json linguist-generated=true text eol=lf
+7
View File
@@ -30,6 +30,13 @@ Ordinary installs include native optional dependencies for the current OS and CP
Before a cross-architecture build (including `pnpm build:mac`, which produces both x64 and
arm64 artifacts by default), run `pnpm install:release` to add the other CPU's variants.
Linux `pnpm build`, `pnpm build:native`, and Linux packaging require a running Docker
daemon to build the clipboard helper against Ubuntu 20.04 rather than the host's
newer libraries. A verified cached helper can be reused without Docker. For native
Wayland development, run `pnpm build:native` before `pnpm dev` to make text copying
available. See the [clipboard build guide](../native/wayland-clipboard/README.md#building-and-testing).
macOS and Windows builds do not use this helper or require Docker for it.
## Branch Naming
Use a clear, descriptive branch name that reflects the change.
@@ -54,7 +54,7 @@ runs:
env:
NATIVE_RUNTIME: ${{ inputs.native-runtime }}
NODE_VERSION: ${{ inputs.node-version }}
NATIVE_SOURCE_HASH: ${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', '.npmrc', '.pnpmfile.cjs', '.github/actions/prepare-native-runtime/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/scripts/node-pty-job-ownership.cjs', 'config/scripts/windows-pe-machine.cjs', 'config/scripts/windows-process-tree-gyp-rebuild.mjs', 'config/scripts/windows-process-tree-creation-time.cjs', 'config/scripts/install-electron-package-binary.mjs', 'config/scripts/electron-platform-path.mjs', 'config/scripts/zip-extractor-command.mjs', 'src/shared/zip-extractor-command.ts', 'config/scripts/shared-electron-dist-cache.mjs', 'config/scripts/space-sharing-copy.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch', 'native/windows-registry/src/addon.cc', 'native/windows-registry/binding.gyp', 'native/windows-registry/package.json', 'native/windows-registry/index.js') }}
NATIVE_SOURCE_HASH: ${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', '.npmrc', '.pnpmfile.cjs', '.github/actions/prepare-native-runtime/action.yml', 'config/scripts/ensure-native-runtime.mjs', 'config/scripts/rebuild-native-deps.mjs', 'config/scripts/node-pty-job-ownership.cjs', 'config/scripts/windows-pe-machine.cjs', 'config/scripts/windows-process-tree-gyp-rebuild.mjs', 'config/scripts/windows-process-tree-creation-time.cjs', 'config/scripts/install-electron-package-binary.mjs', 'config/scripts/electron-platform-path.mjs', 'config/scripts/zip-extractor-command.mjs', 'src/shared/zip-extractor-command.ts', 'config/scripts/shared-electron-dist-cache.mjs', 'config/scripts/space-sharing-copy.mjs', 'config/patches/node-pty@1.1.0.patch', 'config/patches/@vscode__windows-process-tree@0.8.0.patch', 'native/windows-registry/src/addon.cc', 'native/windows-registry/binding.gyp', 'native/windows-registry/package.json', 'native/windows-registry/index.js', 'native/windows-security/src/addon.cc', 'native/windows-security/binding.gyp', 'native/windows-security/package.json', 'native/windows-security/index.js') }}
run: |
scope="${ImageOS:-$RUNNER_OS}"
if [ -r /etc/os-release ]; then
@@ -69,6 +69,7 @@ runs:
path<<ORCA_NATIVE_BUILD_DIRECTORIES
node_modules/.pnpm/node-pty@*/node_modules/node-pty/build
native/windows-registry/build
native/windows-security/build
node_modules/.pnpm/@vscode+windows-process-tre*/node_modules/@vscode/windows-process-tree/build
ORCA_NATIVE_BUILD_DIRECTORIES
ORCA_NATIVE_CACHE_PATHS
@@ -82,6 +83,7 @@ runs:
path: |
node_modules/.pnpm/node-pty@*/node_modules/node-pty/build
native/windows-registry/build
native/windows-security/build
node_modules/.pnpm/@vscode+windows-process-tre*/node_modules/@vscode/windows-process-tree/build
key: ${{ steps.native-cache-scope.outputs.key }}
@@ -93,6 +95,7 @@ runs:
path: |
node_modules/.pnpm/node-pty@*/node_modules/node-pty/build
native/windows-registry/build
native/windows-security/build
node_modules/.pnpm/@vscode+windows-process-tre*/node_modules/@vscode/windows-process-tree/build
key: ${{ steps.native-cache-scope.outputs.key }}
@@ -0,0 +1,27 @@
name: Prepare the Windows agent hook
description: Compiles native/windows-agent-hook into native/windows-agent-hook/.build/orca-agent-hook.exe, which electron-builder --win refuses to package without.
runs:
using: composite
steps:
- name: Cache the Windows agent hook build
if: runner.os == 'Windows'
uses: actions/cache@v5
with:
path: |
native/windows-agent-hook/.build
native/windows-agent-hook/target
~/.cargo/registry
key: windows-agent-hook-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/windows-agent-hook/**', 'config/scripts/build-windows-agent-hook.mjs', 'config/scripts/windows-rust-executable-build.mjs', 'resources/build/icon.ico', 'package.json') }}
# Why explicit: a runner image without cargo would otherwise fail inside electron-builder's verify.
- name: Build the Windows agent hook
if: runner.os == 'Windows'
shell: pwsh
run: |
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
rustup toolchain install stable --profile minimal
rustup default stable
}
node config/scripts/build-windows-agent-hook.mjs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
@@ -17,7 +17,7 @@ runs:
.build/windows-cli-launcher
native/windows-cli-launcher/target
~/.cargo/registry
key: orcad-windows-cli-launcher-${{ runner.arch }}-${{ inputs.arches }}-${{ hashFiles('native/windows-cli-launcher/**', 'config/scripts/build-windows-cli-launcher.mjs', 'resources/build/icon.ico', 'package.json') }}
key: orcad-windows-cli-launcher-${{ runner.arch }}-${{ inputs.arches }}-${{ hashFiles('native/windows-cli-launcher/**', 'config/scripts/build-windows-cli-launcher.mjs', 'config/scripts/windows-rust-executable-build.mjs', 'resources/build/icon.ico', 'package.json') }}
# Why explicit: a runner image without cargo would otherwise fail inside the orcad build.
- name: Build the launcher for each architecture
@@ -0,0 +1,79 @@
name: Warm shared CI platform cache jobs
on:
workflow_call:
permissions:
contents: read
jobs:
warm-linux-arm:
concurrency:
group: ci-cache-warmup-linux-arm-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name != 'schedule' }}
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
env:
ORCA_BACKGROUND_LAUNCH: '1'
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
node-version: '24'
cache-electron-package: 'true'
cache-pnpm-store-lookup-only: 'true'
- name: Populate shared Electron archive
run: node config/scripts/install-electron-package-binary.mjs
- name: Verify native cache is usable
run: node config/scripts/ensure-native-runtime.mjs --check-only
warm-windows:
concurrency:
group: ci-cache-warmup-windows-${{ matrix.os }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name != 'schedule' }}
strategy:
fail-fast: false
matrix:
os: [windows-2022, windows-11-arm]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
env:
ORCA_BACKGROUND_LAUNCH: '1'
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
cache-pnpm-store-lookup-only: 'true'
- name: Verify native cache is usable
run: node config/scripts/ensure-native-runtime.mjs --check-only
warm-linux-package-fixtures:
concurrency:
group: ci-cache-warmup-linux-package-fixtures-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name != 'schedule' }}
runs-on: ubuntu-latest
timeout-minutes: 10
env:
ORCA_BACKGROUND_LAUNCH: '1'
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/prepare-linux-package-fixture
with:
fixture: daemon-shutdown-descendants
save-cache: ${{ github.event_name != 'pull_request' }}
- uses: ./.github/actions/prepare-linux-package-fixture
with:
fixture: headless-serve-shutdown
save-cache: ${{ github.event_name != 'pull_request' }}
- uses: ./.github/actions/prepare-linux-package-fixture
with:
fixture: cli-launch-contract
save-cache: ${{ github.event_name != 'pull_request' }}
@@ -0,0 +1,48 @@
name: Warm shared CI platform caches
on:
push:
branches: [main]
paths:
- '.github/workflows/ci-cache-warmup.yml'
- '.github/workflows/ci-cache-warmup-platform-jobs.yml'
- '.github/workflows/ci-cache-warmup-platforms.yml'
- '.github/actions/install-node-dependencies/**'
- '.github/actions/restore-pnpm-verification/**'
- '.github/actions/prepare-native-runtime/**'
- '.github/actions/prepare-git-compatibility/**'
- '.github/actions/prepare-linux-package-fixture/**'
- 'config/docker/headless-serve-shutdown/**'
- 'config/docker/cli-launch-contract/**'
- 'config/docker/daemon-shutdown-descendants/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.npmrc'
- '.pnpmfile.cjs'
- 'config/scripts/ensure-native-runtime.mjs'
- 'config/scripts/rebuild-native-deps.mjs'
- 'config/scripts/node-pty-job-ownership.cjs'
- 'config/scripts/windows-pe-machine.cjs'
- 'config/scripts/windows-process-tree-gyp-rebuild.mjs'
- 'config/scripts/windows-process-tree-creation-time.cjs'
- 'config/scripts/install-electron-package-binary.mjs'
- 'config/scripts/electron-platform-path.mjs'
- 'config/scripts/zip-extractor-command.mjs'
- 'src/shared/zip-extractor-command.ts'
- 'config/scripts/shared-electron-dist-cache.mjs'
- 'config/scripts/space-sharing-copy.mjs'
- 'config/patches/**'
- 'native/windows-registry/**'
pull_request:
paths:
- '.github/workflows/ci-cache-warmup.yml'
- '.github/workflows/ci-cache-warmup-platforms.yml'
- '.github/workflows/ci-cache-warmup-platform-jobs.yml'
permissions:
contents: read
jobs:
warm-platforms:
uses: ./.github/workflows/ci-cache-warmup-platform-jobs.yml
+11 -71
View File
@@ -9,6 +9,8 @@ on:
branches: [main]
paths:
- '.github/workflows/ci-cache-warmup.yml'
- '.github/workflows/ci-cache-warmup-platform-jobs.yml'
- '.github/workflows/ci-cache-warmup-platforms.yml'
- '.github/actions/install-node-dependencies/**'
- '.github/actions/restore-pnpm-verification/**'
- '.github/actions/prepare-native-runtime/**'
@@ -38,27 +40,23 @@ on:
- 'config/scripts/space-sharing-copy.mjs'
- 'config/patches/**'
- 'native/windows-registry/**'
- 'native/windows-security/**'
pull_request:
paths:
- '.github/workflows/ci-cache-warmup.yml'
- '.github/actions/prepare-git-compatibility/**'
- '.github/workflows/ci-cache-warmup-platform-jobs.yml'
- '.github/workflows/ci-cache-warmup-platforms.yml'
- '.github/actions/prepare-headless-compiler/**'
- 'config/scripts/headless-detector-compiler-cache.mjs'
- '.github/actions/prepare-linux-package-fixture/**'
- 'config/docker/headless-serve-shutdown/**'
- 'config/docker/cli-launch-contract/**'
- 'config/docker/daemon-shutdown-descendants/**'
permissions:
contents: read
concurrency:
group: ci-cache-warmup-${{ github.event.pull_request.number || github.ref }}
# Scheduled retries must let an active warmer finish publishing its caches.
cancel-in-progress: ${{ github.event_name != 'schedule' }}
jobs:
warm:
concurrency:
group: ci-cache-warmup-linux-x64-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name != 'schedule' }}
runs-on: ubuntu-latest
timeout-minutes: 10
env:
@@ -85,64 +83,6 @@ jobs:
- uses: ./.github/actions/prepare-git-compatibility
warm-linux-arm:
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
env:
ORCA_BACKGROUND_LAUNCH: '1'
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
node-version: '24'
cache-electron-package: 'true'
cache-pnpm-store-lookup-only: 'true'
- name: Populate shared Electron archive
run: node config/scripts/install-electron-package-binary.mjs
- name: Verify native cache is usable
run: node config/scripts/ensure-native-runtime.mjs --check-only
warm-windows:
strategy:
fail-fast: false
matrix:
os: [windows-2022, windows-11-arm]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
env:
ORCA_BACKGROUND_LAUNCH: '1'
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
cache-pnpm-store-lookup-only: 'true'
- name: Verify native cache is usable
run: node config/scripts/ensure-native-runtime.mjs --check-only
warm-linux-package-fixtures:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
ORCA_BACKGROUND_LAUNCH: '1'
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/prepare-linux-package-fixture
with:
fixture: daemon-shutdown-descendants
save-cache: ${{ github.event_name != 'pull_request' }}
- uses: ./.github/actions/prepare-linux-package-fixture
with:
fixture: headless-serve-shutdown
save-cache: ${{ github.event_name != 'pull_request' }}
- uses: ./.github/actions/prepare-linux-package-fixture
with:
fixture: cli-launch-contract
save-cache: ${{ github.event_name != 'pull_request' }}
warm-platforms:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
uses: ./.github/workflows/ci-cache-warmup-platform-jobs.yml
@@ -51,6 +51,8 @@ jobs:
token_format: access_token
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -102,6 +102,8 @@ jobs:
service_account: ${{ env.TOPOLOGY_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -42,6 +42,8 @@ jobs:
service_account: ${{ vars.PRODUCTION_GCP_RELAY_DEPLOY_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -240,6 +240,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -192,6 +192,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -343,6 +345,8 @@ jobs:
service_account: ${{ vars.PRODUCTION_GCP_RELAY_DEPLOY_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -111,6 +111,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -233,6 +233,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -135,6 +135,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -423,6 +423,8 @@ jobs:
service_account: ${{ vars.PRODUCTION_GCP_RELAY_DEPLOY_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -129,6 +129,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -62,6 +62,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -156,6 +156,8 @@ jobs:
fi
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -102,6 +102,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- name: Verify exact-audience admin identity
env:
@@ -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-C34 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-C35 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-c34)
production-gce-c30|production-gce-c31|production-gce-c34|production-gce-c35)
# No earlier proof binds a later cell's generation; its own canary below rolls it back on failure.
canary_cell="${TARGET_CELL_IDS}"
;;
@@ -267,6 +267,8 @@ jobs:
test "$(jq -r '.imageDigest' <<< "${runtime}")" = "${IMAGE_DIGEST}"
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -330,7 +332,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-c34)
production-gce-c30|production-gce-c31|production-gce-c32|production-gce-c33|production-gce-c34|production-gce-c35)
verify_cells="${CANARY_CELL}"
expected_states="{\"${CANARY_CELL}\":\"general\"}"
;;
@@ -92,6 +92,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- name: Write or preview the cell's switch object
env:
@@ -173,6 +173,8 @@ jobs:
test "${COUNT}" = 0
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -94,6 +94,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- name: Write or preview the cell's switch object
env:
@@ -61,6 +61,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -91,6 +91,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -637,6 +639,8 @@ jobs:
id_token_include_email: true
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
@@ -65,6 +65,8 @@ jobs:
service_account: ${{ vars.PRODUCTION_GCP_RELAY_DEPLOY_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: docker/setup-buildx-action@v3
+2
View File
@@ -78,6 +78,8 @@ jobs:
service_account: ${{ vars.PRODUCTION_GCP_PUSH_DEPLOY_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: docker/setup-buildx-action@v3
@@ -86,6 +86,8 @@ jobs:
service_account: ${{ vars.STAGING_GCP_RELAY_CAPACITY_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v2
with:
skip_install: true
- uses: ./.github/actions/cloud-sql-rollout-lease
with:
+5
View File
@@ -10,6 +10,7 @@ on:
- 'config/scripts/build-native-for-platform.test.mjs'
- 'config/scripts/pnpm-cli-invocation.mjs'
- 'config/scripts/build-windows-cli-launcher.mjs'
- 'config/scripts/windows-rust-executable-build.mjs'
- 'config/scripts/build-windows-cli-launcher.test.mjs'
- 'config/scripts/macos-computer-helper-owner-loss-benchmark.mjs'
- 'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs'
@@ -30,6 +31,10 @@ on:
- 'config/build-plugins/**'
- 'src/main/daemon/**'
- '!src/main/daemon/**/*.test.ts'
- '!src/main/daemon/daemon-init-dependency-mocks.ts'
- '!src/main/daemon/daemon-init-test-harness.ts'
- '!src/main/daemon/daemon-init-fresh-import.ts'
- '!src/main/daemon/daemon-init-mock-types.ts'
- 'src/main/providers/local-pty-provider.ts'
- 'src/main/providers/windows-shell-args.ts'
- 'src/main/providers/windows-shell-preflight-runtime.windows.test.ts'
+5 -2
View File
@@ -426,6 +426,9 @@ jobs:
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
# COMPAT(legacy-relay-bridge): the bridge spec builds 1.4.224's relay from its tagged sources.
- name: Fetch the 1.4.224 relay sources
run: git fetch --no-tags --depth=1 origin +refs/tags/v1.4.224:refs/tags/v1.4.224
- name: Install headless tools
run: sudo apt-get update && sudo apt-get install -y build-essential ripgrep xvfb openbox x11-utils
- uses: ./.github/actions/install-node-dependencies
@@ -491,11 +494,11 @@ jobs:
- name: Build the e2e app, CLI, orcad slot and template
shell: bash
run: |
pnpm run build:relay
pnpm run build:wsl-guest
pnpm exec electron-vite build --mode e2e
pnpm run build:cli
pnpm build:orcad
node config/scripts/build-windows-process-tree-relay-addon.mjs --arch=x64
node config/scripts/build-windows-process-tree-addon.mjs --arch=x64
node config/scripts/build-orcad-template.mjs --targets win32-x64
# A profile-less Windows session has no roaming AppData; Electron 43 used to crash natively there.
- name: Start serve with no AppData folder
@@ -38,6 +38,10 @@ jobs:
permissions:
contents: write
outputs:
publish_release: ${{ steps.release.outputs.publish_release }}
version: ${{ steps.release.outputs.version }}
defaults:
run:
working-directory: mobile
@@ -231,3 +235,76 @@ jobs:
android/app/build/outputs/apk/release/*.apk \
"${{ steps.apk.outputs.checksum }}"
fi
# Why a separate job: the release is already published here, so a failed bump only leaves
# the site's APK download on the previous release and must not fail the release run.
bump-apk-download:
needs: android-build
if: needs.android-build.outputs.publish_release == 'true'
runs-on: ubuntu-latest
continue-on-error: true
permissions:
contents: read
env:
VERSION: ${{ needs.android-build.outputs.version }}
SITE_REPO: stablyai/orca-marketing-website
BRANCH: bump/android-apk-${{ needs.android-build.outputs.version }}
steps:
- name: Checkout orca
uses: actions/checkout@v6
with:
sparse-checkout: config/scripts
# Why the buf0-bot app, as in homebrew-bump.yml: GITHUB_TOKEN cannot push to another
# repo, and a PR it opened would not run the site's checks.
- name: Generate buf0-bot token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: 2590194
private-key: ${{ secrets.BUFO_BOT_PRIVATE_KEY }}
owner: stablyai
repositories: orca-marketing-website
permission-contents: write
permission-pull-requests: write
- name: Checkout marketing site
uses: actions/checkout@v6
with:
repository: stablyai/orca-marketing-website
path: site
token: ${{ steps.app-token.outputs.token }}
- name: Open the version bump PR
working-directory: site
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
node ../config/scripts/bump-android-apk-download-version.mjs src/lib/downloadTargets.ts "$VERSION"
if git diff --quiet; then
echo "The site's main branch already has Android APK $VERSION or newer." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
title="Point the Android APK download at $VERSION"
git config user.name "buf0-bot[bot]"
git config user.email "252831055+buf0-bot[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git commit -am "$title"
# Why --force: a rerun rebuilds the branch on the site's current main.
git push --force origin "$BRANCH"
# Why: a rerun of the same release reuses its open PR instead of opening another.
pr="$(gh pr list --repo "$SITE_REPO" --head "$BRANCH" --state open --json url --jq '.[].url')"
if [ -z "$pr" ]; then
pr="$(gh pr create --repo "$SITE_REPO" --base main --head "$BRANCH" --title "$title" \
--body "Opened by the [Android release run]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) for mobile-android-v$VERSION. Merging points /download/android and every APK link on the site at the new APK.")"
fi
echo "Android APK download bump: $pr" >> "$GITHUB_STEP_SUMMARY"
- name: Report a failed bump
if: failure()
run: |
echo "::warning::The release is published, but the site's Android APK download was not bumped to $VERSION."
printf '### Android APK download not bumped\n\nThe release is published, but /download/android still serves the previous APK. Rerun this job, or set `ANDROID_RELEASE_VERSION` in `src/lib/downloadTargets.ts` of %s to `%s` by hand.\n' "$SITE_REPO" "$VERSION" >> "$GITHUB_STEP_SUMMARY"
+32 -3
View File
@@ -81,6 +81,7 @@ jobs:
# from the Gemfile, which is how the release jobs could land on different
# fastlane versions in the first place. Fail here instead.
BUNDLE_FROZEN: 'true'
ORCA_MOBILE_TYPECHECK_COMPILER: 'native'
defaults:
run:
@@ -94,6 +95,7 @@ jobs:
- uses: ./.github/actions/install-node-dependencies
with:
cache-pnpm-store: 'false'
cache-dependency-path: |
pnpm-lock.yaml
mobile/pnpm-lock.yaml
@@ -114,6 +116,8 @@ jobs:
# proves the pinned fastlane the release workflow depends on still
# resolves — before a release run finds out.
- name: Setup Ruby and fastlane
id: ruby-tools
background: true
if: steps.ruby-scope.outputs.should_run != 'false'
uses: ruby/setup-ruby@v1
with:
@@ -128,17 +132,21 @@ jobs:
- name: Typecheck
id: production-types
background: true
run: node node_modules/typescript/bin/tsc --noEmit
env:
GOMAXPROCS: '2'
run: node scripts/typecheck-mobile.mjs
# Why a ratchet and not the raw typecheck: mobile/tsconfig.json excludes test files, so until
# tsconfig.test.json existed nothing checked them, and at introduction 127 of the 632 had
# drifted. This fails when a test file that checks today stops checking, when a test leaves
# the program, and on @ts-nocheck; the baseline may only shrink.
- name: Typecheck tests (ratchet)
id: test-types
background: true
env:
GOMAXPROCS: '2'
run: node scripts/check-tests-typecheck-ratchet.mjs
- wait: production-types
# This includes the bridged replay of the whole recording corpus, which used to be a second
# step of its own behind RPC_FOUNDATION_BRIDGE=1. A gate nobody can forget to set is the point:
# it fails when a divergence class grows, when a divergence lands in no class at all, or when
@@ -168,6 +176,9 @@ jobs:
ORCA_BACKGROUND_LAUNCH: '1'
run: node ../config/scripts/run-mobile-ci-tests.mjs
# All checks read the completed install; compilers need not hold up test workers.
- wait: [production-types, test-types, ruby-tools]
# Reports, never gates: the goldens are content-addressed JSON, so their raw diff is hashes.
# This decodes which recorded behaviour the pull request moves into the job summary, and runs
# after a red Test too, when a reviewer most wants it.
@@ -204,6 +215,8 @@ jobs:
main-tests:
name: Mobile tests on main
env:
ORCA_MOBILE_TYPECHECK_COMPILER: 'native'
if: github.event_name == 'push'
# Why ARM: the same pure-Node Vitest run `verify` makes on a pull request.
runs-on: ubuntu-24.04-arm
@@ -227,6 +240,20 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
id: production-types
background: true
env:
GOMAXPROCS: '2'
run: node scripts/typecheck-mobile.mjs
- name: Typecheck tests (ratchet)
id: test-types
background: true
env:
GOMAXPROCS: '2'
run: node scripts/check-tests-typecheck-ratchet.mjs
# The whole suite, not only the recordings: the mutant and page-bridge suites read the goldens
# too. A red run here names each golden and the `rpc:record` command; the author of the merge
# that turned it red re-records in a follow-up.
@@ -239,3 +266,5 @@ jobs:
env:
ORCA_BACKGROUND_LAUNCH: '1'
run: node ../config/scripts/run-mobile-ci-tests.mjs
- wait: [production-types, test-types]
+26 -4
View File
@@ -165,12 +165,12 @@ jobs:
cache-pnpm-store: ${{ runner.os != 'Windows' }}
cache-pnpm-store-lookup-only: 'true'
# Design D7 upgrade and rollback: the last Bun orcad, built from a main commit that shipped
# it, beside this checkout's Node slot; the live-terminal hand-over skips once PROTOCOL_VERSION
# it, using the current Bun pin beside this checkout's Node slot; hand-over skips once PROTOCOL_VERSION
# moves past the Bun daemon's. Install its pinned dependencies independently of this checkout.
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
if: runner.os == 'Linux'
with:
bun-version: 1.4.2
bun-version-file: config/.bun-version
- name: Build the last Bun orcad for the cross-runtime tests
id: bun-orcad
background: true
@@ -219,8 +219,8 @@ jobs:
if: inputs.build_template && matrix.os == 'windows-2022'
shell: bash
run: |
node config/scripts/build-windows-process-tree-relay-addon.mjs --arch=x64
node config/scripts/build-windows-process-tree-relay-addon.mjs --arch=arm64
node config/scripts/build-windows-process-tree-addon.mjs --arch=x64
node config/scripts/build-windows-process-tree-addon.mjs --arch=arm64
node -e "
const { assertWindowsProcessTreeCreationTime } = require('./config/scripts/windows-process-tree-creation-time.cjs')
const addon = require('./.build/windows-process-tree/x64/windows-process-tree.node')
@@ -250,6 +250,23 @@ jobs:
if-no-files-found: error
retention-days: 7
overwrite: true
# orcad's data-root privacy check fails closed without it, so every Windows slot needs one.
- name: Build the Windows security addons for the desktop template
if: inputs.build_template && matrix.os == 'windows-2022'
shell: bash
run: |
node config/scripts/build-windows-security-addon.mjs --arch=x64
node config/scripts/build-windows-security-addon.mjs --arch=arm64
node -e "require('./.build/windows-security/x64/windows-security.node').currentUserSid()"
- name: Keep the Windows security addons for the desktop template
if: inputs.build_template && matrix.os == 'windows-2022'
uses: actions/upload-artifact@v7
with:
name: orcad-windows-security
path: .build/windows-security/*/windows-security.node
if-no-files-found: error
retention-days: 7
overwrite: true
- uses: actions/setup-node@v6
if: runner.arch == 'X64'
with:
@@ -497,6 +514,11 @@ jobs:
with:
name: orcad-windows-cli-launcher
path: .build/windows-cli-launcher
- name: Collect the Windows security addons
uses: actions/download-artifact@v8
with:
name: orcad-windows-security
path: .build/windows-security
- name: Merge the lanes and gate the full slot matrix
shell: bash
run: |
+2 -2
View File
@@ -42,7 +42,7 @@ jobs:
env:
VITE_EXPOSE_STORE: 'true'
run: |
pnpm run build:relay
pnpm run build:wsl-guest
pnpm exec electron-vite build --mode e2e
pnpm run build:web-from-renderer
- name: Run both mixed-version directions
@@ -86,7 +86,7 @@ jobs:
env:
VITE_EXPOSE_STORE: 'true'
run: |
pnpm run build:relay
pnpm run build:wsl-guest
pnpm exec electron-vite build --mode e2e
pnpm run build:cli
pnpm run build:web-from-renderer
@@ -12,6 +12,8 @@ on:
- 'config/oxlint-plugins/*performance.mjs'
- 'config/oxlint-plugins/quadratic-buffer-concat.mjs'
- 'config/scripts/*-plugin.test.mjs'
- '!config/scripts/renderer-scrollbar-style-plugin.test.mjs'
- '!config/scripts/mobile-pairing-qrcode-import-plugin.test.mjs'
# Keep in sync with the contract list in config/vitest.performance.config.ts;
# without these a rename lands green and only breaks the next nightly.
- 'src/main/sqlite/sync-database.test.ts'
+87 -36
View File
@@ -206,7 +206,7 @@ jobs:
- name: Set up Bun for preflight checks
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.4.3
bun-version-file: config/.bun-version
- name: Check process-host imports
if: '!cancelled()'
@@ -217,7 +217,18 @@ jobs:
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
bun --bun run check:process-host-imports
bun config/scripts/check-import-boundaries.mjs
- name: Check main-process sync launches
if: '!cancelled()'
id: main-sync-spawn-ratchet
background: true
env:
PREFLIGHT_PHASE_SELECTED: ${{ needs.code_paths.outputs.static_analysis == 'true' }}
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
bun --bun run check:main-sync-spawn-ratchet
# Keep each check in its own log while sharing this runner.
- name: Lint
@@ -290,7 +301,7 @@ jobs:
PREFLIGHT_PRIOR_SUCCESS: ${{ job.status == 'success' }}
run: |
if [ "$PREFLIGHT_PHASE_SELECTED" != true ] || [ "$PREFLIGHT_PRIOR_SUCCESS" != true ]; then exit 0; fi
pnpm run typecheck:ci
pnpm run typecheck:ci --bun-concurrency=2 --bun-threads=2
- name: Check Zustand selector fan-out budget
if: needs.code_paths.outputs.static_analysis == 'true'
@@ -344,11 +355,7 @@ jobs:
- name: Enforce layout-writer ratchets
if: needs.code_paths.outputs.static_analysis == 'true'
run: pnpm run check:layout-writer-ratchets
- name: Check compatibility-folder imports
if: needs.code_paths.outputs.static_analysis == 'true'
run: pnpm run check:compat-boundary-imports
run: bun config/scripts/check-layout-writer-ratchets.mjs
- name: Check Node runtime pin
if: needs.code_paths.outputs.static_analysis == 'true'
@@ -356,6 +363,7 @@ jobs:
# Why: extraction writes sorted evidence to an isolated temporary path,
# so feature PRs need one normalized AST pass rather than a three-OS matrix.
# Use preflight's pinned Bun even when the optional local pin differs.
- name: Verify localization extraction
if: '!cancelled()'
id: localization-extraction
@@ -372,7 +380,7 @@ jobs:
scope="$(node config/scripts/localization-extraction-change-scope.mjs "$RUNNER_TEMP/localization-changes")" && [ "$scope" = false ]; then
echo "Localization extraction inputs are unchanged."
else
pnpm run verify:localization-extraction
bun config/scripts/verify-localization-extraction.mjs
fi
# Why both: the ratchet proves nothing reachable from the runtime imports electron,
@@ -422,7 +430,7 @@ jobs:
- name: Verify localization coverage
if: needs.code_paths.outputs.static_analysis == 'true'
run: pnpm run verify:localization-coverage
run: bun config/scripts/audit-localization-coverage.mjs --check
- wait: [localization-catalogs, localization-extraction]
@@ -471,6 +479,8 @@ jobs:
- wait: process-host-imports
- wait: main-sync-spawn-ratchet
- wait: unit-plan
- uses: actions/upload-artifact@v7
@@ -893,7 +903,7 @@ jobs:
- name: Install WebKit for the drawer check
id: webkit
background: true
run: pnpm exec playwright install --with-deps webkit
run: pnpm exec playwright-core install --with-deps webkit
# The entry lives in mobile/ so one React resolves; without this every RN import is nothing.
- uses: ./.github/actions/install-mobile-dependencies
@@ -1110,7 +1120,7 @@ jobs:
status=0
pnpm run build:cli || status=1
scripts=(build:relay build:electron-vite:parallel)
scripts=(build:wsl-guest build:electron-vite:parallel)
pids=()
for script in "${scripts[@]}"; do
pnpm run "$script" &
@@ -1159,6 +1169,27 @@ jobs:
rpm_marker="$(rpm2cpio "$rpm" | cpio --quiet --extract --to-stdout ./opt/Orca/resources/package-type)"
[[ "$deb_marker" == deb ]] || { echo "Expected deb marker, got: $deb_marker"; exit 1; }
[[ "$rpm_marker" == rpm ]] || { echo "Expected rpm marker, got: $rpm_marker"; exit 1; }
# Only the AppImage carries the pinned Node (config/scripts/appimage-node-runtime.cjs).
if dpkg-deb --fsys-tarfile "$deb" | tar -t | grep '/resources/node-runtime' >/dev/null; then
echo "The deb must not carry the AppImage's Node"; exit 1
fi
if rpm2cpio "$rpm" | cpio --quiet -t | grep '/resources/node-runtime' >/dev/null; then
echo "The rpm must not carry the AppImage's Node"; exit 1
fi
appimage_root="$(mktemp -d)"
(cd "$appimage_root" && "$GITHUB_WORKSPACE/dist/orca-linux.AppImage" --appimage-extract 'resources/node-runtime/bin/node' >/dev/null)
test -x "$appimage_root/squashfs-root/resources/node-runtime/bin/node"
"$appimage_root/squashfs-root/resources/node-runtime/bin/node" --version
rm -rf "$appimage_root"
ls -l dist/orca-linux.AppImage "$deb" "$rpm"
# Kept briefly so a reviewer can run this exact AppImage on a real Linux desktop.
- uses: actions/upload-artifact@v7
with:
name: orca-linux-appimage-${{ github.event.pull_request.head.sha }}
path: dist/orca-linux.AppImage
retention-days: 3
compression-level: 0
- wait: shutdown-fixture-cache
@@ -1236,6 +1267,41 @@ jobs:
run: >-
pnpm exec vitest run --config config/vitest.config.ts
config/scripts/nsis-process-check.test.mjs
config/scripts/idle-cpu-process-sampling.test.mjs
- name: Cache Windows CLI launcher
uses: actions/cache@v5
with:
# Why the cargo directories ride along: a hit on .build skips the build
# entirely, but a miss otherwise recompiles the resource crate from a
# cold registry.
path: |
native/windows-cli-launcher/.build
native/windows-cli-launcher/target
native/windows-agent-hook/.build
native/windows-agent-hook/target
~/.cargo/registry
key: windows-cli-launcher-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/windows-cli-launcher/**', 'native/windows-agent-hook/**', 'config/scripts/build-windows-cli-launcher.mjs', 'config/scripts/build-windows-agent-hook.mjs', 'config/scripts/windows-rust-executable-build.mjs', 'resources/build/icon.ico', 'package.json') }}
# Why an explicit step rather than trusting the runner image: the packaged
# CLI launcher is a native Rust binary, and a host without cargo fails deep
# inside electron-builder's native hook instead of here.
- name: Ensure the Rust toolchain for the Windows CLI launcher
shell: pwsh
run: |
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
rustup toolchain install stable --profile minimal
rustup default stable
}
cargo --version
# Why before the tests: the orca-agent-hook.exe suites then share one prebuilt binary rather
# than each starting its own build mid-run.
- name: Build the Windows agent hook
run: node config/scripts/build-windows-agent-hook.mjs
- name: Build the Windows CLI launcher
run: node config/scripts/build-windows-cli-launcher.mjs
- name: Test Windows-specific boundaries
run: >-
@@ -1248,8 +1314,10 @@ jobs:
config/scripts/verify-packaged-node-pty-job-ownership.test.mjs
config/scripts/windows-pe-machine.test.mjs
config/scripts/build-windows-cli-launcher.test.mjs
config/scripts/verify-packaged-windows-agent-hook.test.mjs
config/scripts/script-module-dependencies.test.mjs
src/main/windows-registry-addon.test.ts
src/shared/windows-security-addon.win32.test.ts
config/scripts/windows-process-tree-gyp-path.test.mjs
config/scripts/windows-process-tree-gyp-rebuild.test.mjs
config/scripts/package-electron-runtime-contract.test.mjs
@@ -1266,11 +1334,15 @@ jobs:
src/shared/__tests__/process-host/windows-cmd-shim-resolution.test.ts
src/shared/__tests__/process-host/windows-cmd-shim-resolution.win32.test.ts
src/main/agent-hooks/windows-hook-payload-delivery.test.ts
src/main/agent-hooks/managed-hook-stdin-lifecycle.test.ts
src/main/jcode/hook-gate-script.test.ts
src/main/agent-hooks/windows-direct-cmd-hook-command.test.ts
src/main/agent-hooks/windows-native-hook-exe-hosts.test.ts
src/main/claude/windows-native-hook-registration.test.ts
src/main/agent-hooks/windows-cmd-hook-command-unicode.test.ts
src/main/agent-hooks/windows-batch-hook-launcher.test.ts
src/main/agent-hooks/windows-powershell-hook-launcher.test.ts
src/main/claude/claude-status-line-windows.test.ts
src/main/codex/windows-hook-command.test.ts
src/main/codex/windows-hook-upgrade.test.ts
src/main/codex/hook-service-managed-install.test.ts
@@ -1280,6 +1352,9 @@ jobs:
src/main/windows/windows-host-job.win32.test.ts
src/main/windows/windows-process-tree-command-line-patch.test.ts
src/main/windows/windows-process-table-native-addon.win32.test.ts
src/main/windows/windows-administrator-status.win32.test.ts
src/main/windows/windows-administrator-restart.win32.test.ts
src/main/daemon/daemon-pid-identity-native.win32.test.ts
src/main/persistence/profile-state/profile-state-access-windows-native.win32.test.ts
src/main/windows-live-tree-kill.win32.test.ts
src/main/wsl/wsl-runner.test.ts
@@ -1308,30 +1383,6 @@ jobs:
# Why the :parallel variant: identical to build:release except the three
# electron-vite targets overlap instead of running back to back. The Linux package
# job already packages and smoke-tests an AppImage built that way.
- name: Cache Windows CLI launcher
uses: actions/cache@v5
with:
# Why the cargo directories ride along: a hit on .build skips the build
# entirely, but a miss otherwise recompiles the resource crate from a
# cold registry.
path: |
native/windows-cli-launcher/.build
native/windows-cli-launcher/target
~/.cargo/registry
key: windows-cli-launcher-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/windows-cli-launcher/**', 'config/scripts/build-windows-cli-launcher.mjs', 'resources/build/icon.ico', 'package.json') }}
# Why an explicit step rather than trusting the runner image: the packaged
# CLI launcher is a native Rust binary, and a host without cargo fails deep
# inside electron-builder's native hook instead of here.
- name: Ensure the Rust toolchain for the Windows CLI launcher
shell: pwsh
run: |
if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) {
rustup toolchain install stable --profile minimal
rustup default stable
}
cargo --version
- name: Build package inputs
run: pnpm run build:release:parallel
+47 -2
View File
@@ -27,7 +27,7 @@ jobs:
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.4.3
bun-version-file: config/.bun-version
- name: Compare native and Bun typechecking
run: node config/scripts/benchmark-preflight-compilers.mjs
- uses: actions/upload-artifact@v7
@@ -49,7 +49,7 @@ jobs:
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.4.3
bun-version-file: config/.bun-version
- name: Compare anti-slop patch and script runtimes
run: node config/scripts/benchmark-preflight-static-analysis.mjs
- uses: actions/upload-artifact@v7
@@ -58,3 +58,48 @@ jobs:
name: preflight-static-analysis
path: .build/preflight-benchmark/
retention-days: 30
compatibility-imports:
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 2
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: none
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: config/.bun-version
- name: Compare compatibility-import scan and runtimes
run: node config/scripts/benchmark-preflight-compat-boundary.mjs
- uses: actions/upload-artifact@v7
if: always()
with:
name: preflight-compat-boundary
path: .build/preflight-benchmark/
retention-days: 30
localization-extraction:
runs-on: ubuntu-24.04-arm
timeout-minutes: 12
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: none
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: config/.bun-version
- name: Compare localization extraction runtimes and complete catalogs
run: node config/scripts/benchmark-preflight-localization-extraction.mjs
- uses: actions/upload-artifact@v7
if: always()
with:
name: preflight-localization-extraction
path: .build/preflight-benchmark/
retention-days: 30
@@ -0,0 +1,42 @@
name: Preflight critical-path comparison
on:
pull_request:
paths:
- .github/workflows/preflight-critical-path-benchmark.yml
- config/scripts/benchmark-ci-preflight-critical-path.mjs
workflow_dispatch:
permissions:
contents: read
concurrency:
group: preflight-critical-path-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
ORCA_BACKGROUND_LAUNCH: '1'
jobs:
compare:
runs-on: ubuntu-24.04-arm
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
comparison: [smoke, sync-spawn, typecheck]
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 2
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
node-version: '24'
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: config/.bun-version
- name: Compare checks and verify their results
run: node config/scripts/benchmark-ci-preflight-critical-path.mjs ${{ matrix.comparison }}
- uses: actions/upload-artifact@v7
if: always()
with:
name: preflight-critical-path-${{ matrix.comparison }}
path: .build/preflight-benchmark/
retention-days: 30
+1 -1
View File
@@ -2145,7 +2145,7 @@ jobs:
# in some runs, yet it is the file most at risk of losing its
# signature in the NSIS rebuild. Verify it in every release.
$targets = @(Get-Content 'inner-signing-list.txt')
foreach ($requiredTarget in @('Orca.exe', 'resources\bin\orca.exe')) {
foreach ($requiredTarget in @('Orca.exe', 'resources\bin\orca.exe', 'resources\bin\orca-agent-hook.exe')) {
if ($targets -notcontains $requiredTarget) { $targets += $requiredTarget }
}
if ($targets -notcontains 'resources\elevate.exe') {
+3
View File
@@ -13,6 +13,9 @@ on:
- 'src/main/ssh/ssh-relay-*'
- 'src/main/ssh/*runtime*'
- 'src/main/ssh/orcad-*'
- 'src/main/orcad/orcad-artifact-materializer.ts'
- 'src/main/orcad/orcad-host-unavailable.ts'
- 'src/main/node-runtime/**'
- 'src/main/ssh/remote-install-*'
- 'src/main/ssh/ssh-remote-*'
- 'src/main/ssh/ssh-hostile-host-*'
+10 -3
View File
@@ -11,6 +11,9 @@ on:
- 'src/main/ssh/ssh-relay-*'
- 'src/main/ssh/*runtime*'
- 'src/main/ssh/orcad-*'
- 'src/main/orcad/orcad-artifact-materializer.ts'
- 'src/main/orcad/orcad-host-unavailable.ts'
- 'src/main/node-runtime/**'
- 'src/main/ssh/remote-install-*'
- 'src/main/ssh/ssh-remote-*'
- 'src/main/ssh/ssh-hostile-host-*'
@@ -24,14 +27,18 @@ on:
- 'config/scripts/orcad-prebuild-*.mjs'
- 'config/scripts/orcad-windows-process-tree.mjs'
- 'config/scripts/orcad-windows-cli-launcher.mjs'
- 'config/scripts/orcad-windows-security.mjs'
- 'config/scripts/build-windows-security-addon.mjs'
- 'native/windows-security/**'
- 'config/scripts/build-windows-cli-launcher.mjs'
- 'config/scripts/windows-rust-executable-build.mjs'
- 'native/windows-cli-launcher/**'
- 'src/main/orcad/orcad-cli-launcher.ts'
- '.github/actions/prepare-windows-cli-launcher/**'
- 'config/patches/node-pty*'
- 'config/patches/@vscode__windows-process-tree*'
- 'config/scripts/build-windows-process-tree-relay-addon.mjs'
- 'config/scripts/relay-windows-process-tree-prepared-addon*.mjs'
- 'config/scripts/build-windows-process-tree-addon.mjs'
- 'config/scripts/windows-process-tree-prepared-addon*.mjs'
- 'config/scripts/windows-process-tree-gyp-rebuild.mjs'
- 'src/shared/windows-breakaway-launch*.ts'
- 'src/main/ipc/ssh-host-server-*.ts'
@@ -143,7 +150,7 @@ jobs:
if [ "$REUSE_PREPARED_RUNTIME" = true ]; then
reuse_args+=(--reuse-prepared-runtime)
fi
node config/scripts/build-windows-process-tree-relay-addon.mjs --arch=${{ matrix.arch }} "${reuse_args[@]}"
node config/scripts/build-windows-process-tree-addon.mjs --arch=${{ matrix.arch }} "${reuse_args[@]}"
- uses: ./.github/actions/prepare-orcad-prebuilds
with:
resolve-windows-cache: ${{ github.event_name == 'pull_request' }}
+1 -1
View File
@@ -101,7 +101,7 @@ jobs:
env:
VITE_EXPOSE_STORE: 'true'
run: |
pnpm run build:relay
pnpm run build:wsl-guest
pnpm exec electron-vite build --mode e2e
pnpm run build:web-from-renderer
- name: Run native Wayland Hangul terminating digit
+1
View File
@@ -73,6 +73,7 @@ jobs:
- name: Test shard
env:
NODE_COMPILE_CACHE: ${{ runner.temp }}/orca-node-compile-cache-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.node }}-${{ matrix.shard.index }}
ORCA_BALANCE_UNIT_SHARDS: '1'
ORCA_BACKGROUND_LAUNCH: '1'
run: |
@@ -119,6 +119,9 @@ jobs:
- uses: ./.github/actions/install-mobile-dependencies
if: steps.cache-installer.outputs.cache-hit != 'true'
- uses: ./.github/actions/prepare-windows-agent-hook
if: steps.cache-installer.outputs.cache-hit != 'true'
- name: Build Windows installer (unsigned)
if: steps.cache-installer.outputs.cache-hit != 'true'
run: |
@@ -57,6 +57,9 @@ jobs:
- uses: ./.github/actions/install-mobile-dependencies
if: steps.cache-installer.outputs.cache-hit != 'true'
- uses: ./.github/actions/prepare-windows-agent-hook
if: steps.cache-installer.outputs.cache-hit != 'true'
# Before the template exists: packaging verifies a present template for every target.
- name: Build Windows installer (unsigned)
if: steps.cache-installer.outputs.cache-hit != 'true'
@@ -86,7 +89,7 @@ jobs:
run: |
node config/scripts/ensure-native-runtime.mjs --runtime=node
pnpm build:orcad
node config/scripts/build-windows-process-tree-relay-addon.mjs --arch=x64
node config/scripts/build-windows-process-tree-addon.mjs --arch=x64
node config/scripts/build-orcad-template.mjs --targets win32-x64
- name: Switch serve hosts on the installed app's profile
@@ -101,6 +101,9 @@ jobs:
- uses: ./.github/actions/install-mobile-dependencies
if: steps.cache-installer.outputs.cache-hit != 'true'
- uses: ./.github/actions/prepare-windows-agent-hook
if: steps.cache-installer.outputs.cache-hit != 'true'
- name: Build Windows installer (unsigned)
if: steps.cache-installer.outputs.cache-hit != 'true'
run: |
@@ -499,7 +499,7 @@ jobs:
}
$targets = @(Get-Content 'inner-signing-list.txt')
foreach ($requiredTarget in @('Orca.exe', 'resources\bin\orca.exe')) {
foreach ($requiredTarget in @('Orca.exe', 'resources\bin\orca.exe', 'resources\bin\orca-agent-hook.exe')) {
if ($targets -notcontains $requiredTarget) { $targets += $requiredTarget }
}
foreach ($relative in $targets) {
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
native-runtime: electron
- name: Build relay and Electron
run: |
pnpm run build:relay
pnpm run build:wsl-guest
if ($LASTEXITCODE -ne 0) { throw 'Relay build failed' }
pnpm exec electron-vite build --mode e2e
if ($LASTEXITCODE -ne 0) { throw 'Electron build failed' }
+220
View File
@@ -0,0 +1,220 @@
name: WSL orcad E2E
# WSL via orcad (step C10) on real WSL2 distros: a GitHub-hosted windows-2025 runner imports a
# pinned Ubuntu 24.04 rootfs twice (systemd off and on) and runs
# src/main/wsl/wsl-orcad-real-distro-lane.test.ts against both: deploy and launch, the wsl.exe
# stdio bridge, runtime RPC, liveness recovery, holder survival and teardown. Not a required check.
# Hosted WSL2 was proven in #27783: WSL 2.7.14, import 13-18 s, cold boot 8-14 s.
on:
pull_request:
paths:
- "src/main/wsl/**"
- "src/main/ssh/orcad-*"
- "src/main/ssh/remote-install-transport.ts"
- ".github/workflows/wsl-orcad-e2e.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: wsl-orcad-e2e-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
# wsl.exe prints UTF-16LE to pipes unless told otherwise.
WSL_UTF8: "1"
ROOTFS_URL: https://releases.ubuntu.com/24.04.4/ubuntu-24.04.4-wsl-amd64.wsl
ROOTFS_SHA256: 9b2f7730dc68227dd04a9f3e5eab86ad85caf556b8606ad94f1f29ff5c4fd3f5
WSL_MSI_URL: https://github.com/microsoft/WSL/releases/download/3.0.1/wsl.3.0.1.0.x64.msi
WSL_MSI_SHA256: 28b1a0d013640a2ac95898ea705fa186e5b4ff767a1c1b49257161bc106599c6
PLAIN_DISTRO: orca-lane-plain
SYSTEMD_DISTRO: orca-lane-systemd
jobs:
# The distro is linux-x64-glibc; its node-pty slot only builds on Linux, and nothing else is needed.
template:
name: Build the linux-x64-glibc orcad template
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
- name: Build the template
run: |
pnpm build:orcad-prebuilds
pnpm build:orcad-prebuilds --require-slots linux-x64-glibc
pnpm build:orcad
node config/scripts/build-orcad-template.mjs --targets linux-x64-glibc
- uses: actions/upload-artifact@v7
with:
name: wsl-orcad-template
path: out/orcad-template/
# The template carries dot-directories (`.claude-plugin`) its manifest checksums.
include-hidden-files: true
retention-days: 1
if-no-files-found: error
wsl2:
name: WSL2 distros (systemd off and on)
needs: template
runs-on: windows-2025
timeout-minutes: 45
env:
ORCA_BACKGROUND_LAUNCH: "1"
ORCA_WSL_ORCAD_LANE_RECEIPTS: ${{ github.workspace }}\test-results\wsl-orcad-lane
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
# The image ships WSL 2.7.14, where stopping a systemd distro drops the VM-wide WSLInterop
# entry (microsoft/WSL#13885, fixed in 3.0.1). The lane asserts the fixed behavior, so it
# installs a pinned 3.0.1 MSI; `wsl --update` asks the GitHub API and gets rate-limited (403).
- name: Restore the pinned WSL package
id: wsl-msi-cache
uses: actions/cache/restore@v5
with:
path: ${{ runner.temp }}/wsl-msi
key: wsl-orcad-msi-${{ env.WSL_MSI_SHA256 }}
- name: Install WSL 3.0.1
timeout-minutes: 8
run: |
"ImageOS=$env:ImageOS ImageVersion=$env:ImageVersion"
wsl.exe --version
$dir = Join-Path $env:RUNNER_TEMP 'wsl-msi'
New-Item -ItemType Directory -Force $dir | Out-Null
$msi = Join-Path $dir 'wsl.msi'
if (-not (Test-Path $msi)) { & "$env:WINDIR\System32\curl.exe" --fail --location --silent --show-error --output $msi $env:WSL_MSI_URL }
if ((Get-FileHash $msi -Algorithm SHA256).Hash.ToLowerInvariant() -ne $env:WSL_MSI_SHA256) {
Remove-Item $msi; throw 'WSL package checksum mismatch'
}
$install = Start-Process msiexec.exe -ArgumentList @('/i', $msi, '/qn', '/norestart', '/l*v', (Join-Path $env:RUNNER_TEMP 'wsl-msi.log')) -Wait -PassThru
if ($install.ExitCode -notin 0, 3010) { throw "WSL install failed: $($install.ExitCode)" }
wsl.exe --shutdown
$version = [version](((wsl.exe --version) -join "`n") -replace '(?s)^\D*?(\d+\.\d+\.\d+).*$', '$1')
wsl.exe --version
if ($version -lt [version]'3.0.1') { throw "WSL $version is older than 3.0.1" }
- name: Save the pinned WSL package
if: steps.wsl-msi-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: ${{ runner.temp }}/wsl-msi
key: wsl-orcad-msi-${{ env.WSL_MSI_SHA256 }}
- name: Restore the pinned rootfs
id: rootfs-cache
uses: actions/cache/restore@v5
with:
path: ${{ runner.temp }}/wsl-rootfs
key: wsl-orcad-rootfs-${{ env.ROOTFS_SHA256 }}
# Runs beside the dependency install; the lane waits for it below.
- name: Import both distros as WSL2
id: import
background: true
timeout-minutes: 10
run: |
$cache = Join-Path $env:RUNNER_TEMP 'wsl-rootfs'
New-Item -ItemType Directory -Force $cache | Out-Null
$rootfs = Join-Path $cache 'ubuntu.wsl'
if (-not (Test-Path $rootfs)) { Invoke-WebRequest $env:ROOTFS_URL -OutFile $rootfs }
if ((Get-FileHash $rootfs -Algorithm SHA256).Hash.ToLowerInvariant() -ne $env:ROOTFS_SHA256) {
Remove-Item $rootfs; throw 'rootfs checksum mismatch'
}
foreach ($d in @(@{ Name = $env:PLAIN_DISTRO; Systemd = 'false' }, @{ Name = $env:SYSTEMD_DISTRO; Systemd = 'true' })) {
$sw = [Diagnostics.Stopwatch]::StartNew()
# No WSL1 fallback: the lane exists to prove WSL2.
wsl.exe --import $d.Name (Join-Path $env:RUNNER_TEMP $d.Name) $rootfs --version 2
if ($LASTEXITCODE -ne 0) { throw "WSL2 import of $($d.Name) failed: $LASTEXITCODE" }
"$($d.Name) import_ms=$($sw.ElapsedMilliseconds)"
if (-not ((wsl.exe --list --verbose) -match "$($d.Name)\s+\S+\s+2\s*$")) { throw "$($d.Name) is not a WSL2 distro" }
# A non-root default user, as on a real install; git for the lane's repo.
$setup = "set -e; id orca >/dev/null 2>&1 || useradd -m -s /bin/bash orca; " +
"printf '[boot]\nsystemd=$($d.Systemd)\n[user]\ndefault=orca\n' > /etc/wsl.conf; " +
"command -v git >/dev/null || (apt-get update -qq && apt-get install -y -qq git)"
wsl.exe -d $d.Name -u root --exec sh -c $setup
if ($LASTEXITCODE -ne 0) { throw "provisioning $($d.Name) failed: $LASTEXITCODE" }
wsl.exe --terminate $d.Name
$sw.Restart()
wsl.exe -d $d.Name --exec sh -c 'echo "user=$(id -un) pid1=$(ps -p 1 -o comm=) kernel=$(uname -r)"'
if ($LASTEXITCODE -ne 0) { throw "first boot of $($d.Name) failed: $LASTEXITCODE" }
"$($d.Name) boot_ms=$($sw.ElapsedMilliseconds)"
wsl.exe --terminate $d.Name
}
wsl.exe --list --verbose
- uses: ./.github/actions/install-node-dependencies
with:
native-runtime: node
- uses: actions/download-artifact@v8
with:
name: wsl-orcad-template
path: ${{ runner.temp }}/orcad-template
- wait: import
# Saved even when the lane fails; the import step verified its checksum.
- name: Save the pinned rootfs
if: steps.rootfs-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: ${{ runner.temp }}/wsl-rootfs
key: wsl-orcad-rootfs-${{ env.ROOTFS_SHA256 }}
- name: "Phase 1: deploy, RPC, kill orcad, terminate distro"
timeout-minutes: 20
env:
ORCA_RUN_WSL_ORCAD_LANE: "1"
ORCA_WSL_ORCAD_LANE_PHASE: launch
ORCA_ORCAD_TEMPLATE_PATH: ${{ runner.temp }}/orcad-template
run: |
New-Item -ItemType Directory -Force $env:ORCA_WSL_ORCAD_LANE_RECEIPTS | Out-Null
node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/wsl/wsl-orcad-real-distro-lane.test.ts --reporter=verbose 2>&1 | Tee-Object (Join-Path $env:ORCA_WSL_ORCAD_LANE_RECEIPTS 'launch.log')
if ($global:LASTEXITCODE -ne 0) { throw "launch phase failed: $global:LASTEXITCODE" }
# A new process: the holders must have kept both distros Running since phase 1 exited.
- name: "Phase 2: holders survive, reconnect, teardown"
timeout-minutes: 10
env:
ORCA_RUN_WSL_ORCAD_LANE: "1"
ORCA_WSL_ORCAD_LANE_PHASE: survive
ORCA_ORCAD_TEMPLATE_PATH: ${{ runner.temp }}/orcad-template
run: |
node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/wsl/wsl-orcad-real-distro-lane.test.ts --reporter=verbose 2>&1 | Tee-Object (Join-Path $env:ORCA_WSL_ORCAD_LANE_RECEIPTS 'survive.log')
if ($global:LASTEXITCODE -ne 0) { throw "survive phase failed: $global:LASTEXITCODE" }
# Belt and braces for the runner: a missing VM-wide interop entry is restored, then fails.
- name: WSLInterop is intact
if: ${{ !cancelled() && steps.import.outcome == 'success' }}
timeout-minutes: 3
run: |
$state = wsl.exe -d $env:PLAIN_DISTRO -u root --exec sh -c 'if [ -e /proc/sys/fs/binfmt_misc/WSLInterop ]; then echo present; else echo :WSLInterop:M::MZ::/init:P > /proc/sys/fs/binfmt_misc/register && echo restored; fi'
"WSLInterop: $state"
wsl.exe --terminate $env:PLAIN_DISTRO
if ("$state".Trim() -ne 'present') { throw "WSLInterop was missing ($state)" }
- name: Collect orcad logs
if: failure()
timeout-minutes: 3
run: |
$out = $env:ORCA_WSL_ORCAD_LANE_RECEIPTS
New-Item -ItemType Directory -Force $out | Out-Null
foreach ($d in @($env:PLAIN_DISTRO, $env:SYSTEMD_DISTRO)) {
wsl.exe -d $d --exec sh -c 'for f in $(find "$HOME/.orca-remote" -name "*.log*" -type f 2>/dev/null); do echo "=== $f"; tail -n 400 "$f"; done' > (Join-Path $out "$d-orcad-logs.txt") 2>&1
}
wsl.exe --list --verbose
- name: Summary
if: always()
run: |
$dir = $env:ORCA_WSL_ORCAD_LANE_RECEIPTS
$lines = @("### WSL orcad lane — $env:ImageOS $env:ImageVersion", '')
foreach ($phase in 'launch', 'survive') {
$file = Join-Path $dir "$phase.json"
if (Test-Path $file) { $lines += "#### $phase", '```json', (Get-Content $file -Raw), '```' }
}
$lines | Add-Content $env:GITHUB_STEP_SUMMARY
- uses: actions/upload-artifact@v7
if: always()
with:
name: wsl-orcad-lane-receipts
path: test-results/wsl-orcad-lane/
retention-days: 7
if-no-files-found: ignore
+5 -1
View File
@@ -29,9 +29,13 @@ out/
release/
native/**/.build/
native/windows-cli-launcher/target/
# node-gyp output for the vendored Windows registry addon; generated per host and ABI.
native/windows-agent-hook/target/
# node-gyp output for the Windows native addons; generated per host and ABI.
native/windows-registry/build/
native/windows-registry/bin/
native/windows-security/build/
native/windows-security/bin/
.build/windows-security/
# pnpm
.pnpm-store/
+16 -1
View File
@@ -21,6 +21,10 @@
{
"name": "renderer-scrollbar-style",
"specifier": "./config/oxlint-plugins/renderer-scrollbar-style.mjs"
},
{
"name": "native-chat-design-tokens",
"specifier": "./config/oxlint-plugins/native-chat-design-tokens.mjs"
}
],
"categories": {
@@ -181,6 +185,14 @@
]
}
},
{
"files": ["src/renderer/src/components/native-chat/**/*.{ts,tsx}"],
"rules": {
"native-chat-design-tokens/no-arbitrary-font-size": "error",
"native-chat-design-tokens/no-softened-foreground": "error",
"native-chat-design-tokens/no-weakened-focus-ring": "error"
}
},
{
"files": ["src/shared/**/*.ts"],
"rules": {
@@ -198,7 +210,10 @@
"rules": {
"react/globals": "off",
"quadratic-buffer-concat/no-loop-carried-concat": "off",
"renderer-scrollbar-style/require-styled-vertical-scrollbar": "off"
"renderer-scrollbar-style/require-styled-vertical-scrollbar": "off",
"native-chat-design-tokens/no-arbitrary-font-size": "off",
"native-chat-design-tokens/no-softened-foreground": "off",
"native-chat-design-tokens/no-weakened-focus-ring": "off"
}
},
{
+1 -1
View File
@@ -1,6 +1,6 @@
# Design System
All UI work — layout, color, typography, spacing, component selection, UX behavior — must follow [`docs/STYLEGUIDE.md`](./docs/STYLEGUIDE.md). Most of it is linted: `pnpm run check:code-quality:changed` fails on new restyles of a `components/ui/` primitive, raw palette colors, and computed `className` strings; `pnpm lint` fails on any class Tailwind cannot generate. See the Enforcement section of the style guide before suppressing either. Use the tokens defined in `src/renderer/src/assets/main.css` (the canonical source) and the shadcn primitives in `src/renderer/src/components/ui/`. Don't invent new color values, font sizes, or shadow tiers when a documented one already covers the role. When STYLEGUIDE.md is silent, follow the resolution order in its final section.
All UI work — layout, color, typography, spacing, component selection, UX behavior — must follow [`docs/STYLEGUIDE.md`](./docs/STYLEGUIDE.md). Most of it is linted: `pnpm run check:code-quality:changed` fails on new restyles of a `components/ui/` primitive, raw palette colors, and computed `className` strings; `pnpm lint` fails on any class Tailwind cannot generate. See the Enforcement section of the style guide before suppressing either. Use the tokens defined in `src/renderer/src/assets/main.css` (the canonical source) and the shadcn primitives in `src/renderer/src/components/ui/`. Don't invent new color values, font sizes, or shadow tiers when a documented one already covers the role. When STYLEGUIDE.md is silent, follow the resolution order in its final section. In `components/native-chat/`, the "Chat type scale" and "Chat text colour, hover and focus" sections are binding, and `pnpm lint` fails on arbitrary font sizes, softened `text-foreground`, and weakened focus rings there.
## Electron UI Validation
+2 -2
View File
@@ -36,7 +36,7 @@
Monitor and steer your agents from your phone — get notified when an agent finishes and send follow-ups from anywhere.
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK 0.0.52](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.52/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile)
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://www.onorca.dev/download/android) · [Docs →](https://www.onorca.dev/docs/mobile)
</td>
<td width="50%">
@@ -234,7 +234,7 @@ yay -S stably-orca-bin
Pair with your desktop app to monitor and steer your agents from your phone.
- **iOS:** [Download on the App Store](https://apps.apple.com/us/app/orca-ide/id6766130217)
- **Android:** [Download APK 0.0.52](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.52/app-release.apk) · [Install guide](https://www.onorca.dev/docs/android-apk)
- **Android:** [Download APK](https://www.onorca.dev/download/android) · [Install guide](https://www.onorca.dev/docs/android-apk)
---
@@ -41,7 +41,8 @@ const SHAPES = {
['production-gce-c31'],
['production-gce-c32'],
['production-gce-c33'],
['production-gce-c34']
['production-gce-c34'],
['production-gce-c35']
]
}
}
@@ -560,7 +560,7 @@ test('accepts only reviewed Asia admission waves', () => {
].map((mode) => [mode, cellId])),
...['inspect', 'verify', 'register', 'registered', 'promote', 'recover-promotion', 'rollback']
.map((mode) => [mode, 'production-gce-c34']),
...['inspect', 'verify', 'register', 'registered', 'rollback']
...['inspect', 'verify', 'register', 'registered', 'promote', 'recover-promotion', 'rollback']
.map((mode) => [mode, 'production-gce-c35']),
['inspect', 'production-gce-c27,production-gce-c28,production-gce-c29,production-gce-c30,production-gce-c31,production-gce-c32,production-gce-c33,production-gce-c34,production-gce-c35']
]
@@ -572,9 +572,8 @@ test('accepts only reviewed Asia admission waves', () => {
)
}
const rejected = [
// The C35 spare stays migration-only: no reviewed promotion wave names it.
['promote', 'production-gce-c35'],
['recover-promotion', 'production-gce-c35'],
['promote', 'production-gce-c34,production-gce-c35'],
['promote', 'production-gce-c31,production-gce-c35'],
['register', 'production-gce-c34,production-gce-c35'],
['inspect', 'production-gce-c27,production-gce-c28,production-gce-c29,production-gce-c30,production-gce-c31,production-gce-c32,production-gce-c33,production-gce-c34'],
['rollback', 'production-gce-c27,production-gce-c28,production-gce-c29,production-gce-c30,production-gce-c31,production-gce-c32,production-gce-c33,production-gce-c34'],
@@ -728,6 +727,26 @@ test('promotes the registered C34 spare to general and leaves every other cell a
assert.deepEqual(subject.selector().membership.general, [...general, 'production-gce-c34'].sort())
})
test('promotes the registered C35 spare to general and leaves every other cell alone', async () => {
const general = [
...launchCells, 'production-gce-c30', 'production-gce-c31', 'production-gce-c32', 'production-gce-c33',
'production-gce-c34'
]
const membership = {
existingOnly: [],
migrationOnly: ['production-gce-c17', 'production-gce-c35'],
general: [...general]
}
const subject = harness({ generation: 345, membership })
const result = await operateRelayAsiaAdmission({
environment: 'production', mode: 'promote', cells: ['production-gce-c35'],
expectedGeneration: 345, imageDigest: digest, attemptId: 'asia_promote_c35', token: 'not-logged'
}, subject)
assert.deepEqual(result.states, { 'production-gce-c35': 'general' })
assert.deepEqual(subject.selector().membership.migrationOnly, ['production-gce-c17'])
assert.deepEqual(subject.selector().membership.general, [...general, 'production-gce-c35'].sort())
})
const usRegions = { 'production-gce-c32': 'us-central1', 'production-gce-c33': 'us-central1' }
test('registers C32 and C33 one at a time in us-central1 at the Asia shape', async () => {
@@ -868,7 +887,7 @@ test('runs each later cell\'s own canary with load aimed at that cell\'s region'
for (const [cellId, region] of [
['production-gce-c30', 'asia-east2'], ['production-gce-c31', 'asia-east2'],
['production-gce-c32', 'us-central1'], ['production-gce-c33', 'us-central1'],
['production-gce-c34', 'asia-east2']
['production-gce-c34', 'asia-east2'], ['production-gce-c35', 'asia-east2']
]) {
const outputs = promotionOutputs(cellId)
assert.equal(outputs?.canary, 'true', cellId)
@@ -878,8 +897,9 @@ test('runs each later cell\'s own canary with load aimed at that cell\'s region'
}
assert.equal(promotionOutputs('production-gce-c32,production-gce-c33'), null)
assert.equal(promotionOutputs('production-gce-c33,production-gce-c34'), null)
assert.equal(promotionOutputs('production-gce-c34,production-gce-c35'), null)
const canaryStart = workflowBlock('case "${CANARY_CELL}" in', '\n esac')
for (const cellId of ['production-gce-c32', 'production-gce-c33', 'production-gce-c34']) {
for (const cellId of ['production-gce-c32', 'production-gce-c33', 'production-gce-c34', 'production-gce-c35']) {
const result = spawnSync('bash', ['-euo', 'pipefail', '-c',
`${canaryStart}\necho "\${verify_cells} \${expected_states}"`], {
env: { ...process.env, CANARY_CELL: cellId }, encoding: 'utf8'
@@ -97,9 +97,9 @@ describe('production Relay capacity cell admission', () => {
it('admits the same-cap Asia and migration-only cells only under the same-cap allowlist', () => {
for (const cellId of [
'production-gce-c27', 'production-gce-c28', 'production-gce-c29', 'production-gce-c30',
'production-gce-c31', 'production-gce-c34',
'production-gce-c31', 'production-gce-c34', 'production-gce-c35',
// Migration-only canaries: the US-only capacity rollout never touches them either.
'production-gce-c17', 'production-gce-c18', 'production-gce-c35',
'production-gce-c17', 'production-gce-c18',
// US cells at the Asia shape: the 1,000-cap capacity rollout never touches them.
'production-gce-c32', 'production-gce-c33'
]) {
@@ -27,6 +27,9 @@ const PRODUCTION_CANARIES = {
},
'production-gce-c34': {
kind: 'production-c34-canary', origin: 'https://c34.relay.onorca.dev', region: ASIA_REGION
},
'production-gce-c35': {
kind: 'production-c35-canary', origin: 'https://c35.relay.onorca.dev', region: ASIA_REGION
}
}
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/
@@ -415,6 +415,19 @@ test('builds a C34 canary that only C34 placement satisfies', () => {
assert.throws(() => buildProductionCanaryEvidence(onC31), /C34 canary load was not placed only on C34/)
})
test('builds a C35 canary that only C35 placement satisfies', () => {
const c35 = 'production-gce-c35'
const evidence = buildProductionCanaryEvidence(canaryInput({}, c35))
assert.equal(evidence.kind, 'production-c35-canary')
assert.equal(verifyRolloutEvidence(
evidence, workflowRun(evidence),
verifyExpected('production-c35-canary', { cellIds: [c35], selectorGeneration: 9 })
), evidence)
const onC34 = canaryInput({}, c35)
onC34.loadReport.assignedCellOrigins = ['https://c34.relay.onorca.dev']
assert.throws(() => buildProductionCanaryEvidence(onC34), /C35 canary load was not placed only on C35/)
})
test('records but does not gate organic US-targeted fallbacks during a canary', () => {
// Mirrors C31's 2026-10-01 canary: US-targeted fallbacks, none targeting Asia.
const input = canaryInput({}, 'production-gce-c31')
@@ -4,10 +4,7 @@ import { requireSameEvidenceCode } from './relay-evidence-code-provenance.mjs'
// Migration-only by policy: zero hosts and no reservation, so a wave rolls one without
// displacing anybody. It enters and must leave migration-only, never general.
// C35 is an Asia spare that stays migration-only until a reviewed change promotes it.
export const SAME_CAP_MIGRATION_ONLY_CELLS = [
'production-gce-c17', 'production-gce-c18', 'production-gce-c35'
]
export const SAME_CAP_MIGRATION_ONLY_CELLS = ['production-gce-c17', 'production-gce-c18']
export const SAME_CAP_CELLS = [
'production-gce-c7', 'production-gce-c8', 'production-gce-c9', 'production-gce-c10',
@@ -16,6 +13,7 @@ export const SAME_CAP_CELLS = [
'production-gce-c23', 'production-gce-c24', 'production-gce-c25', 'production-gce-c26',
'production-gce-c27', 'production-gce-c28', 'production-gce-c29', 'production-gce-c30',
'production-gce-c31', 'production-gce-c32', 'production-gce-c33', 'production-gce-c34',
'production-gce-c35',
...SAME_CAP_MIGRATION_ONLY_CELLS
]
@@ -136,14 +136,11 @@ test('the wave workflow chains exactly ten serial cell jobs', () => {
assert.doesNotMatch(dispatch, /\n cell_11:/)
})
test('lists the C35 spare as migration-only beside C17 and C18, and C30-C34 as general', () => {
assert.deepEqual(
SAME_CAP_MIGRATION_ONLY_CELLS,
['production-gce-c17', 'production-gce-c18', 'production-gce-c35']
)
test('lists only C17 and C18 as migration-only now that C35 is promoted, and C30-C35 as general', () => {
assert.deepEqual(SAME_CAP_MIGRATION_ONLY_CELLS, ['production-gce-c17', 'production-gce-c18'])
for (const cellId of [
'production-gce-c30', 'production-gce-c31', 'production-gce-c32', 'production-gce-c33',
'production-gce-c34'
'production-gce-c34', 'production-gce-c35'
]) {
assert.equal(SAME_CAP_CELLS.includes(cellId), true, cellId)
}
@@ -221,6 +218,19 @@ test('rolls the migration-only cells but never mixes the two classes in one wave
confirmation: `ROLL_RELAY_SAME_CAP ${targetDigest} ${c34Mixed}`,
canaryRunId: '42'
}), /all general or all migration-only/)
// C35 is general once its Asia canary promotes it, so a rollback restores it general.
assert.equal(entryAdmission('production-gce-c35'), 'general')
assert.equal(selectorWaveDelta('production-gce-c35'), 2)
const c35Mixed = 'production-gce-c35,production-gce-c17'
assert.throws(() => validateSameCapWave({
mode: 'batch-apply',
cellIds: c35Mixed,
targetDigest,
rollbackDigest,
drainPaceWindowMs: '300000',
confirmation: `ROLL_RELAY_SAME_CAP ${targetDigest} ${c35Mixed}`,
canaryRunId: '42'
}), /all general or all migration-only/)
const c31Mixed = 'production-gce-c31,production-gce-c18'
assert.throws(() => validateSameCapWave({
mode: 'batch-apply',
@@ -669,7 +679,7 @@ test('keeps Asia and migration-only cells on the default pace', () => {
)
for (const cellId of SAME_CAP_CELLS) {
const fast = SAME_CAP_FAST_DRAIN_PACE_CELLS.includes(cellId)
const asia = ['c27', 'c28', 'c29', 'c30', 'c31', 'c34'].some(
const asia = ['c27', 'c28', 'c29', 'c30', 'c31', 'c34', 'c35'].some(
(cell) => cellId === `production-gce-${cell}`
)
assert.equal(fast, entryAdmission(cellId) === 'general' && !asia, cellId)
@@ -279,10 +279,9 @@ describe('same-cap roll scripts accept every same-cap cell', () => {
const trusted = SAME_CAP_CELLS.filter((cell) => REHOME_SOURCE_CELLS.has(cell))
// Only a declared rehome source may roll at a trusted protocol at all; the job refuses
// the rest before it plans, and the next test covers them at protocol 0.
// The C35 spare is a rehome source that stays migration-only.
assert.deepEqual(
SAME_CAP_CELLS.filter((cell) => !REHOME_SOURCE_CELLS.has(cell)),
SAME_CAP_MIGRATION_ONLY_CELLS.filter((cell) => cell !== 'production-gce-c35')
SAME_CAP_MIGRATION_ONLY_CELLS
)
for (const [cellId, protocol] of trusted.flatMap((cell) => [[cell, 1], [cell, 3]])) {
const { cap, pool } = cellShape(cellId)
@@ -29,7 +29,8 @@ export const FLEET_POOL_CELL_IDS = [
'production-gce-c29',
'production-gce-c30',
'production-gce-c31',
'production-gce-c34'
'production-gce-c34',
'production-gce-c35'
]
export const SHADOW_GATE_THRESHOLDS = {
@@ -279,6 +279,7 @@ function productionLikeEntries() {
...metricSamples({ cellId: 'production-gce-c30', from: '2026-09-20T20:20:00Z', count: 20 }),
...metricSamples({ cellId: 'production-gce-c31', from: '2026-09-20T20:20:00Z', count: 20 }),
...metricSamples({ cellId: 'production-gce-c34', from: '2026-09-20T20:20:00Z', count: 20 }),
...metricSamples({ cellId: 'production-gce-c35', from: '2026-09-20T20:20:00Z', count: 20 }),
// One director instance's samples from ten minutes before the drain to the window's end.
...directorSamples({ from: '2026-09-20T19:50:00Z', count: 80, payload: {} })
]
@@ -340,10 +341,10 @@ function gcloudSeam(entries = productionLikeEntries()) {
}
}
test('reads every promoted asia-east2 cell as fleet pool, C34 included', () => {
test('reads every promoted asia-east2 cell as fleet pool, C35 included', () => {
assert.deepEqual(FLEET_POOL_CELL_IDS, [
'production-gce-c27', 'production-gce-c28', 'production-gce-c29', 'production-gce-c30',
'production-gce-c31', 'production-gce-c34'
'production-gce-c31', 'production-gce-c34', 'production-gce-c35'
])
})
@@ -365,6 +366,7 @@ test('a healthy roll reads as PASS and names the instance it proved serving', as
'fleetPool:production-gce-c30',
'fleetPool:production-gce-c31',
'fleetPool:production-gce-c34',
'fleetPool:production-gce-c35',
'nonDrain503Budget'
])
assert.deepEqual(report.drain, {
+10 -7
View File
@@ -228,13 +228,16 @@ first rolled to the director's image as a migration-only same-cap wave. Once its
it, it moved to the same-cap general list and the fleet pool list together, so the same-cap job now
rolls it as a general cell and a rollback restores it general. It is a declared rehome source.
C35 is a seventh Asia cell at the C34 shape in `asia-east2-a`, starting a third zone round. It is
its own topology wave on image F (`sha256:41cba995…`), registers alone as migration-only, and then
the director is configured with `cell-ids` set to C35. It has no promotion wave yet: the Asia admission
script and workflow refuse `promote` for it. It is a declared rehome source, sits in the same-cap
migration-only list, and stays out of the fleet pool list. Its pool of 16 leaves 3 connections
under the usable Cloud SQL ceiling (`relay-cloud-sql-connection-budget.mjs`). Promoting it takes its
own reviewed change adding a promotion wave and canary entry, as C34 did.
C35 is a seventh Asia cell at the C34 shape in `asia-east2-a`, starting a third zone round. It was
its own topology wave on image F (`sha256:41cba995…`), registered alone as migration-only, and then
the director was configured with `cell-ids` set to C35. It launched as a migration-only spare and has
a promotion wave of its own, with the same five-minute canary C30, C31 and C34 ran. Promotion
compares the director's serving digest and C35's runtime digest with the one `image-digest` input,
so C35 was first rolled to the director's image as a migration-only same-cap wave. Once its canary
promoted it, it moved to the same-cap general list and the fleet pool list together, so the same-cap
job now rolls it as a general cell and a rollback restores it general. It is a declared rehome source.
Its pool of 16 leaves 3 connections under the usable Cloud SQL ceiling
(`relay-cloud-sql-connection-budget.mjs`).
Rollback returns
Asia cells to migration-only; it does not destroy the network or use
existing-only. The production topology dispatch remains unavailable until the
@@ -468,7 +468,7 @@ relay_gce_cells = {
connection_hard_cap = 3000
connection_unobserved_bound = 60
}
# Asia spare: registered migration-only as a drain landing zone; a starts the third zone round.
# Seventh Asia cell: launched as a migration-only spare, now general; a starts the third zone round.
"production-gce-c35" = {
hostname = "c35"
region = "asia-east2"
+1 -1
View File
@@ -1 +1 @@
1.4.2
1.4.3
@@ -0,0 +1,60 @@
import { resolve } from 'node:path'
import { ORCAD_LOCAL_SERVE_SELECTION_ENTRY } from '../../src/shared/orcad-local-serve-selection'
import { CLI_MAIN_ENTRY_NAMES } from './plain-node-entry-guard'
/** Main-build Rollup inputs: output file name (without .js) to its source entry. */
export const MAIN_BUILD_ENTRY_POINTS: Record<string, string> = {
index: resolve('src/main/index.ts'),
// Why: sandboxed webview preloads cannot load Rollup helper chunks.
'browser-window-close-preload': resolve('src/preload/browser-window-close.ts'),
'doc-preview-link-preload': resolve('src/preload/doc-preview-link.ts'),
'daemon-entry': resolve('src/main/daemon/daemon-entry.ts'),
'plugin-host-entry': resolve('src/main/plugins/plugin-host-entry.ts'),
'computer-sidecar': resolve('src/main/computer/sidecar-entry.ts'),
'stt-worker': resolve('src/main/speech/stt-worker.ts'),
'warp-theme-parser-worker': resolve('src/main/warp-themes/warp-theme-parser-worker.ts'),
'foreign-sqlite-reader-entry': resolve(
'src/main/foreign-sqlite-readers/foreign-sqlite-reader-entry.ts'
),
'session-scanner-service-entry': resolve('src/main/ai-vault/session-scanner-service-entry.ts'),
'wsl-transcript-fs-process-entry': resolve(
'src/main/native-chat/wsl-transcript-fs-process-entry.ts'
),
// Why: libuv spawns processes inline on the calling loop, so the port
// scan's probe commands run on a worker thread instead of the UI one.
'port-scan-command-worker-entry': resolve('src/main/ports/port-scan-command-worker-entry.ts'),
// Why (#27362): the Codex usage probe spawns on window focus; a cold
// CreateProcessW must not block the UI loop.
'worker-thread-child-process-entry': resolve(
'src/main/worker-thread-child-process/worker-thread-child-process-entry.ts'
),
// Why: the Claude/Codex/OpenCode usage scans walk whole history
// corpora and read SQLite synchronously; a worker thread keeps that
// off the main-process event loop.
'usage-scan-worker-entry': resolve('src/main/usage/usage-scan-worker-entry.ts'),
// Why: a first account setup can merge a large history tree with sync fs calls.
'claude-profile-setup-worker-entry': resolve(
'src/main/claude-accounts/claude-profile-setup-worker-entry.ts'
),
'profile-state-backup-worker-entry': resolve(
'src/main/persistence/profile-state/profile-state-backup-worker-entry.ts'
),
'profile-state-writer-worker-entry': resolve(
'src/main/persistence/profile-state/profile-state-writer-worker-entry.ts'
),
// Why: forked with ELECTRON_RUN_AS_NODE so @parcel/watcher faults
// can't take down the main process (issue #7547).
'parcel-watcher-process-entry': resolve('src/main/ipc/parcel-watcher-process-entry.ts'),
// Why: `orca serve` runs it under ELECTRON_RUN_AS_NODE so the CLI never bundles orcad prep.
[ORCAD_LOCAL_SERVE_SELECTION_ENTRY]: resolve(
'src/main/orcad/orcad-local-serve-selection-entry.ts'
),
// Why: a worker thread survives the macOS 26 AppKit main-thread deadlock
// without paying for another Electron process.
'main-thread-hang-watchdog-entry': resolve(
'src/main/hang-watchdog/main-thread-hang-watchdog-entry.ts'
),
...Object.fromEntries(
CLI_MAIN_ENTRY_NAMES.map((module) => [module, resolve(`src/main/${module}.ts`)])
)
}
@@ -62,6 +62,7 @@ const WORKER_THREAD_ENTRY_NAMES = [
'foreign-sqlite-reader-entry',
'main-thread-hang-watchdog-entry',
'port-scan-command-worker-entry',
'worker-thread-child-process-entry',
'usage-scan-worker-entry',
'claude-profile-setup-worker-entry',
'profile-state-backup-worker-entry',
@@ -38,6 +38,8 @@ export XDG_CONFIG_HOME="$state_dir/config"
export XDG_CACHE_HOME="$state_dir/cache"
export XDG_RUNTIME_DIR="$state_dir/runtime"
export LIBGL_ALWAYS_SOFTWARE=1
# Every entrypoint must serve the profile the caller chose, not the default one.
export ORCA_USER_DATA_PATH="$state_dir/profile"
mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_RUNTIME_DIR"
chmod 700 "$XDG_RUNTIME_DIR"
@@ -81,6 +83,12 @@ if [[ -z "$ready_line" ]]; then
exit 1
fi
if [[ ! -f "$ORCA_USER_DATA_PATH/orca-runtime.json" || -e "$XDG_CONFIG_HOME/orca/orca-runtime.json" ]]; then
ls -la "$ORCA_USER_DATA_PATH" "$XDG_CONFIG_HOME" >&2 || true
echo "FAIL: serve ignored ORCA_USER_DATA_PATH; its runtime metadata is not in $ORCA_USER_DATA_PATH" >&2
exit 1
fi
registered_cli_verified=false
if [[ "$entrypoint_kind" == appimage ]]; then
registered_cli="$HOME/.local/bin/orca-ide"
@@ -96,6 +104,20 @@ if [[ "$entrypoint_kind" == appimage ]]; then
printf '%s\n' "$registered_help" >&2
exit 1
fi
# Pre-lease builds prune every generation under sha256(AppImage path); this build must not extract there.
installed_target=$(readlink "$XDG_CACHE_HOME/orca/appimage/launcher/installed" 2>/dev/null || true)
installed_namespace=$(basename "$(dirname "$(dirname "$(dirname "$(dirname "$installed_target")")")")")
legacy_namespace=$(printf '%s' /input/orca.AppImage | sha256sum | cut -c1-24)
if [[ ! -x "$installed_target" || "$installed_namespace" == "$legacy_namespace" ]]; then
echo "FAIL: installed CLI payload ${installed_target:-missing} is in the legacy namespace or missing" >&2
exit 1
fi
# A fresh cache means this serve unpacked the AppImage first, with progress on stderr.
if ! grep -q '\[serve\] AppImage copy ready after' "$stderr_log"; then
cat "$stderr_log" >&2
echo "FAIL: first AppImage serve did not report unpacking progress" >&2
exit 1
fi
registered_cli_verified=true
fi
+51 -7
View File
@@ -24,6 +24,13 @@ const {
verifyPackagedMainRuntimeDeps
} = require('./packaged-runtime-node-modules.cjs')
const { verifyLinuxGlibcFloor } = require('./scripts/verify-linux-glibc-floor.cjs')
const {
verifyPackagedWindowsAgentHook
} = require('./scripts/verify-packaged-windows-agent-hook.cjs')
const {
ensureBundledWaylandClipboard,
finalizePackagedWaylandClipboard
} = require('./wayland-clipboard-resources.cjs')
const { writeMacBuildCompatibility } = require('./scripts/mac-build-compatibility.cjs')
const {
MOBILE_WEB_BUNDLE_DIR,
@@ -48,6 +55,11 @@ const {
} = require('./scripts/packaged-orcad-template.cjs')
const { verifySkillsCliRuntime } = require('./scripts/verify-skills-cli-runtime.cjs')
const { verifyStaticAppImagePackage } = require('./scripts/static-appimage-package-contract.cjs')
const {
appImageNodeRuntimeFpmArgs,
placeAppImageNodeRuntime,
verifyAppImageNodeRuntimeServesPty
} = require('./scripts/appimage-node-runtime.cjs')
const { signWindowsUninstallerViaSignPath } = require('./scripts/windows-uninstaller-signing.cjs')
// Why: dev-channel builds must carry the *release* identity — same bundle id,
@@ -190,7 +202,7 @@ const TABULAR_FILE_EXTENSIONS = ['csv', 'tsv']
// Why: the config must load on a host-only install without resolving unused Windows addons.
// This is load-time tolerance only; beforePack enforces that the target's natives are installed.
// Why one package: @vscode/windows-process-tree is the only os: win32 npm addon;
// @orca/windows-registry is a workspace link present on every host, so its presence proves nothing.
// the @orca/windows-* addons are workspace links present on every host, so their presence proves nothing.
const windowsRuntimeResources = existsSync(
join(__dirname, '..', 'node_modules', '@vscode', 'windows-process-tree', 'package.json')
)
@@ -375,12 +387,19 @@ module.exports = {
},
// electron-builder calls this with the context alone. The second parameter is the bundle root,
// so a test can point the guard at a scratch bundle instead of needing the repo's out/ built.
beforePack: (context, mobileWebBundleDir = MOBILE_WEB_BUNDLE_DIR) => {
beforePack: (
context,
mobileWebBundleDir = MOBILE_WEB_BUNDLE_DIR,
ensureWaylandClipboard = ensureBundledWaylandClipboard
) => {
assertPackagedNativeVariantsInstalled(context.electronPlatformName, context.arch)
assertBundledRipgrepInstalled()
assertProcessHostOutputBuilt()
assertOrcadTemplateBuilt()
assertMobileWebBundleBuilt(mobileWebBundleDir)
if (context.electronPlatformName === 'linux') {
ensureWaylandClipboard(context.arch)
}
},
afterPack: async (context) => {
const resourcesDir =
@@ -426,12 +445,15 @@ module.exports = {
// Why after the prune: `pnpm install:release` widens the CPU set for cross-builds,
// so an arm64 slice can still carry the x64 @parcel/watcher until
// prunePackagedRuntimeNodeModules drops it.
let appImageNodeRuntime = null
if (context.electronPlatformName === 'linux') {
const targetArch = { 1: 'x64', 3: 'arm64' }[context.arch]
// Before the floor gate, so the bundled Node is held to the same glibc floor.
appImageNodeRuntime = await placeAppImageNodeRuntime(resourcesDir, targetArch)
// Why the arch is passed: symbol-version checks pass happily on a wrong-architecture binary,
// so a cross-built slice could ship the host's pty.node and only fail at runtime.
verifyLinuxGlibcFloor(context.appOutDir, {
targetArch: { 1: 'x64', 3: 'arm64' }[context.arch]
})
finalizePackagedWaylandClipboard(resourcesDir)
verifyLinuxGlibcFloor(context.appOutDir, { targetArch })
}
verifyPackagedMainRuntimeDeps(resourcesDir)
// Why: boot the packaged daemon-entry under plain Node, but only for the
@@ -444,6 +466,7 @@ module.exports = {
const canExecuteTargetArch = context.arch === hostArchEnum || context.arch === 4
if (context.electronPlatformName === 'win32') {
verifyPackagedWindowsNodePty(resourcesDir, context.arch, { canExecuteTargetArch })
verifyPackagedWindowsAgentHook(resourcesDir, context.arch)
}
verifySkillsCliRuntime(join(resourcesDir, 'app.asar.unpacked', 'out'), resourcesDir, {
executeCommands: canExecuteTargetArch
@@ -455,6 +478,9 @@ module.exports = {
}
if (canExecuteTargetArch) {
verifyPackagedDaemonEntryBoots(resourcesDir)
if (appImageNodeRuntime) {
await verifyAppImageNodeRuntimeServesPty(resourcesDir, appImageNodeRuntime)
}
} else {
// Why: a cross-arch slice can't be booted by the host Node, but the
// unpacked entry must still exist — its absence is a layout regression
@@ -534,6 +560,16 @@ module.exports = {
from: 'native/windows-cli-launcher/.build/orca.exe',
to: 'bin/orca.exe'
},
{
from: 'native/windows-cli-launcher/.build/orca-administrator-restart.exe',
to: 'bin/orca-administrator-restart.exe'
},
// Why resources/bin: SignPath signs every unsigned PE under win-unpacked, and the hook
// installer copies this signed file into ~/.orca/agent-hooks (#20913).
{
from: 'native/windows-agent-hook/.build/orca-agent-hook.exe',
to: 'bin/orca-agent-hook.exe'
},
{
from: 'node_modules/agent-browser/bin/agent-browser-win32-x64.exe',
to: 'agent-browser-win32-x64.exe'
@@ -671,6 +707,10 @@ module.exports = {
...commonExtraResources,
...createPackagedRuntimeNodeModuleResources('linux'),
linuxSpeechNativeResource,
{
from: 'native/wayland-clipboard/.build/${arch}/orca-wayland-clipboard',
to: 'bin/orca-wayland-clipboard'
},
{
from: 'resources/linux/bin/orca-ide',
to: 'bin/orca-ide'
@@ -714,7 +754,9 @@ module.exports = {
// GUI-triggered and can never run on a server, so without this the CLI is
// unreachable from the shell on exactly the hosts that need it.
afterInstall: 'resources/linux/packaging/after-install.sh',
afterRemove: 'resources/linux/packaging/after-remove.sh'
afterRemove: 'resources/linux/packaging/after-remove.sh',
// Why: only an AppImage's files can vanish under a running daemon (see appimage-node-runtime.cjs).
fpm: appImageNodeRuntimeFpmArgs
},
rpm: {
packageName: 'orca-ide',
@@ -731,7 +773,9 @@ module.exports = {
],
// Why: same headless CLI-on-PATH registration as deb; rpm runs these via fpm.
afterInstall: 'resources/linux/packaging/after-install.sh',
afterRemove: 'resources/linux/packaging/after-remove.sh'
afterRemove: 'resources/linux/packaging/after-remove.sh',
// Why: only an AppImage's files can vanish under a running daemon (see appimage-node-runtime.cjs).
fpm: appImageNodeRuntimeFpmArgs
},
beforeBuild: electronBuilderNativeRebuild,
// Why: must be true so that electron-builder rebuilds native modules
-1
View File
@@ -333,7 +333,6 @@
2 src/renderer/src/lib/launch-agent-web-host-tab.ts
2 src/renderer/src/lib/launch-ai-vault-session.ts
1 src/renderer/src/lib/launch-structured-agent-session.ts
1 src/renderer/src/lib/launch-worktree-background-terminals.ts
1 src/renderer/src/lib/linear-issue-url-lookup.ts
1 src/renderer/src/lib/local-preflight-context.ts
3 src/renderer/src/lib/open-agent-message-sender.ts
+1
View File
@@ -11,6 +11,7 @@
"src/main/warp-themes/warp-theme-parser-worker.ts",
"src/main/foreign-sqlite-readers/foreign-sqlite-reader-entry.ts",
"src/main/ports/port-scan-command-worker-entry.ts",
"src/main/worker-thread-child-process/worker-thread-child-process-entry.ts",
"src/main/ipc/parcel-watcher-process-entry.ts",
"src/main/hang-watchdog/main-thread-hang-watchdog-entry.ts",
"src/main/agent-hooks/managed-agent-hook-controls.ts",
+54
View File
@@ -0,0 +1,54 @@
# Synchronous child-process launches in production src/main and src/shared, per file: uses of
# execFileSync/spawnSync/execSync, runProcessSync and the repo sync wrappers listed in
# config/scripts/check-main-sync-spawn-ratchet.mjs (synchronous ACL rewrites and grants, which
# can block on a whole directory subtree).
# Some entries run in the daemon or orcad process, or are test harnesses outside the test-path
# conventions. This is a RATCHET: counts may only go DOWN.
# Prune after removing launches: pnpm check:main-sync-spawn-ratchet --prune
1 src/main/agent-hooks/installer-utils.ts
2 src/main/browser/browser-cookie-key.ts
1 src/main/browser/browser-route-egress-electron-launch.ts
1 src/main/browser/browser-route-partition-binding-store.ts
1 src/main/browser/browser-route-persisted-worker-electron-process.ts
1 src/main/claude-accounts/runtime-auth-service.ts
3 src/main/codex-accounts/fs-utils.ts
2 src/main/codex-accounts/legacy-wsl-runtime-auth-drain-recovery-script-harness.ts
2 src/main/codex-accounts/legacy-wsl-runtime-auth-drain-script-harness.ts
1 src/main/codex-accounts/runtime-home-service-home-routing.ts
1 src/main/codex-accounts/runtime-home-service-wsl.ts
2 src/main/codex-accounts/service.ts
1 src/main/computer/macos-computer-use-permissions.ts
2 src/main/daemon/daemon-cgroup-scope.ts
1 src/main/daemon/daemon-process-identity-query.ts
1 src/main/daemon/daemon-process-start-time.ts
1 src/main/daemon/daemon-user-manager-lifetime.ts
1 src/main/daemon/pty-process-ceiling-runtime.ts
1 src/main/daemon/pty-process-ceiling.ts
1 src/main/daemon/pty-subprocess/shell-launch-plan.ts
1 src/main/emulator/serve-sim-runtime-materializer.ts
1 src/main/git/command-runner/git-exec-file.ts
1 src/main/git/repo-detection-command.ts
1 src/main/git/runner.ts
1 src/main/ipc/worktree-logic.ts
2 src/main/macos-press-and-hold-default.ts
1 src/main/network/macos-tailscale-dns-diagnostic.ts
1 src/main/opencode/legacy-plugin-acl-retry.ts
2 src/main/orcad/node-pty-precondition.ts
1 src/main/orcad/orcad-data-root-privacy.ts
2 src/main/persistence/profile-state/profile-state-access-identity.ts
1 src/main/persistence/profile-state/profile-state-recovery-copy.ts
1 src/main/plugins/plugin-private-marketplace-ssh-shim.cjs
1 src/main/pty/posix-pty-foreground-group.ts
1 src/main/pty/posix-pty-process-groups.ts
1 src/main/pwsh.ts
1 src/main/runtime/runtime-skill-install-authority.ts
1 src/main/runtime/tls-certificate.ts
1 src/main/skills/skill-discovery-target.ts
2 src/main/startup/cli-launch-redirect.ts
1 src/main/startup/linux-secret-service-probe.ts
1 src/main/worktree-runner-script.ts
3 src/main/wsl.ts
2 src/main/zsh-startup-hook-pty-harness.ts
1 src/shared/fish-binary-requirement.ts
1 src/shared/openssh-default-shell.ts
1 src/shared/secure-file.ts
+1
View File
@@ -3,6 +3,7 @@ import original from '../mobile/vitest.config.ts'
// Keep real process, native, and runtime-specific contracts on Node.
const nodeFiles = [
'scripts/check-tests-typecheck-incremental-cache.test.ts',
'src/tasks/mobile-agent-launch-architecture.test.ts',
'src/tasks/agent-launch-mobile-replay.test.ts',
'src/mock-server-key-pair.test.ts',
@@ -0,0 +1,49 @@
import { plainClassName } from './renderer-scrollbar-style.mjs'
const CHECKS = {
'no-arbitrary-font-size': {
pattern: /^text-\[(?:length:)?[\d.]+(?:px|rem|em)\]$/,
message:
'Use a chat type step (text-sm, text-xs, text-2xs, text-3xs, text-chat-code), not an arbitrary size. See "Chat type scale" in docs/STYLEGUIDE.md.'
},
'no-softened-foreground': {
pattern: /^text-foreground\/\d+$/,
message:
"Don't soften text-foreground by hand: use text-chat-foreground on the chat canvas or text-muted-foreground on a lifted surface. See docs/STYLEGUIDE.md."
},
'no-weakened-focus-ring': {
pattern: /^ring-ring\/\d+$/,
message:
'A focus ring at reduced opacity fails 3:1 contrast on the chat canvas: use ring-ring. See docs/STYLEGUIDE.md.'
}
}
function literalTexts(node) {
if (node.type === 'Literal') {
return typeof node.value === 'string' ? [node.value] : []
}
return node.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw)
}
function createRule({ pattern, message }) {
return {
create(context) {
const check = (node) => {
const offending = literalTexts(node)
.flatMap((text) => text.split(/\s+/))
.find((token) => pattern.test(plainClassName(token)))
if (offending) {
context.report({ node, message: `${offending}: ${message}` })
}
}
return { Literal: check, TemplateLiteral: check }
}
}
}
export default {
meta: { name: 'native-chat-design-tokens' },
rules: Object.fromEntries(
Object.entries(CHECKS).map(([name, check]) => [name, createRule(check)])
)
}
+19 -2
View File
@@ -27,6 +27,7 @@ const PACKAGED_RUNTIME_PACKAGE_ROOTS = [
'posthog-node',
'proper-lockfile',
'qrcode',
'smol-toml',
'ssh2',
'tweetnacl',
'ws',
@@ -36,9 +37,15 @@ const PACKAGED_RUNTIME_PACKAGE_ROOTS = [
// Why macOS only: serve-sim drives the iOS Simulator, and its native addon is a Mach-O that
// Windows signing rejects as a PE file.
const DARWIN_PACKAGED_RUNTIME_PACKAGE_ROOTS = ['serve-sim']
/** The compiled binary each workspace Windows addon loads, relative to its package. */
const WORKSPACE_WINDOWS_ADDON_BINARIES = [
['@orca/windows-registry', 'build/Release/orca_windows_registry.node'],
['@orca/windows-security', 'build/Release/orca_windows_security.node']
]
const WINDOWS_PACKAGED_RUNTIME_PACKAGE_ROOTS = [
'@vscode/windows-process-tree',
'@orca/windows-registry'
'@orca/windows-registry',
'@orca/windows-security'
]
const NODE_PTY_PREBUILD_PREFIX_BY_PLATFORM = {
@@ -612,11 +619,21 @@ function assertPackagedNativeVariantsInstalled(electronPlatformName, electronArc
}
// Why one package: @vscode/windows-process-tree is the only os: win32 npm addon;
// @orca/windows-registry is a workspace link present on every host, so its presence proves nothing.
// the @orca/windows-* addons are workspace links present on every host, so their presence proves nothing.
const missingWindowsAddons = []
if (electronPlatformName === 'win32' && !isInstalled('@vscode/windows-process-tree')) {
missingWindowsAddons.push('@vscode/windows-process-tree')
}
// Why the binary, not the package: the link exists everywhere, and a missing or foreign-arch
// .node would otherwise surface only at runtime (secure files unrestricted, orcad refusing to start).
if (electronPlatformName === 'win32') {
for (const [name, binary] of WORKSPACE_WINDOWS_ADDON_BINARIES) {
const addon = join(nodeModulesDir, name, binary)
if (!existsSync(addon) || readPeMachine(addon) !== PE_MACHINE[architecture]) {
missingWindowsAddons.push(`${name} (${binary} for ${architecture})`)
}
}
}
if (missing.length === 0 && missingWindowsAddons.length === 0) {
return
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+20
View File
@@ -0,0 +1,20 @@
diff --git a/esm/vs/editor/browser/widget/diffEditor/utils/editorGutter.js b/esm/vs/editor/browser/widget/diffEditor/utils/editorGutter.js
index cbe9bb8e8bb97ca69e1026d2335857ec7ae32a16..86c4f393b76a4d433be297833188058c99a168e8 100644
--- a/esm/vs/editor/browser/widget/diffEditor/utils/editorGutter.js
+++ b/esm/vs/editor/browser/widget/diffEditor/utils/editorGutter.js
@@ -27,6 +27,15 @@ class EditorGutter extends Disposable {
this.editorOnDidContentSizeChange = observableSignalFromEvent('onDidContentSizeChange', this._editor.onDidContentSizeChange);
this.domNodeSizeChanged = observableSignal('domNodeSizeChanged');
this.views = new Map();
+ // Orca patch (backport of monaco-editor 0.56.0): dispose the views still on screen, or each
+ // view's toolbar menu stays registered on the global MenuRegistry and retains the whole editor.
+ this._register(toDisposable(() => {
+ for (const view of this.views.values()) {
+ view.gutterItemView.dispose();
+ view.domNode.remove();
+ }
+ this.views.clear();
+ }));
this._domNode.className = 'gutter monaco-editor';
const scrollDecoration = this._domNode.appendChild(h('div.scroll-decoration', { role: 'presentation', ariaHidden: 'true', style: { width: '100%' } })
.root);
+39 -15
View File
@@ -710,15 +710,16 @@ diff --git a/src/win/conpty.cc b/src/win/conpty.cc
index 7b286d3d64..5239ba4e40 100644
--- a/src/win/conpty.cc
+++ b/src/win/conpty.cc
@@ -18,6 +18,7 @@
@@ -18,6 +18,8 @@
#include <iostream>
#include <string>
#include <thread>
+#include <mutex>
+#include <atomic>
#include <vector>
#include <Windows.h>
#include <strsafe.h>
@@ -44,15 +45,37 @@ struct pty_baton {
@@ -44,15 +46,39 @@ struct pty_baton {
HANDLE hOut;
HPCON hpc;
@@ -751,6 +752,8 @@ index 7b286d3d64..5239ba4e40 100644
static std::vector<std::unique_ptr<pty_baton>> ptyHandles;
+// Orca: every table access shares the exit watcher's lock; handles can be recycled.
+static std::mutex ptyJobMutex;
+// Orca: 0 = no per-terminal ceiling (the default); set once at daemon start.
+static std::atomic<uint32_t> ptyActiveProcessLimit{0};
static volatile LONG ptyCounter;
-static pty_baton* get_pty_baton(int id) {
@@ -758,7 +761,7 @@ index 7b286d3d64..5239ba4e40 100644
auto it = std::find_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {
return ptyHandle->id == id;
});
@@ -62,7 +85,7 @@ static pty_baton* get_pty_baton(int id) {
@@ -62,7 +88,7 @@ static pty_baton* get_pty_baton(int id) {
return nullptr;
}
@@ -767,7 +770,7 @@ index 7b286d3d64..5239ba4e40 100644
auto it = std::remove_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {
return ptyHandle->id == id;
});
@@ -102,8 +125,31 @@ void SetupExitCallback(Napi::Env env, Napi::Function cb, pty_baton* baton) {
@@ -102,8 +128,31 @@ void SetupExitCallback(Napi::Env env, Napi::Function cb, pty_baton* baton) {
// Get process exit code.
GetExitCodeProcess(baton->hShell, (LPDWORD)(&exit_event->exit_code));
// Clean up handles
@@ -801,7 +804,7 @@ index 7b286d3d64..5239ba4e40 100644
auto status = tsfn.BlockingCall(exit_event, callback); // In main thread
switch (status) {
@@ -242,6 +288,20 @@ HRESULT CreateNamedPipesAndPseudoConsole(const Napi::CallbackInfo& info,
@@ -242,6 +291,20 @@ HRESULT CreateNamedPipesAndPseudoConsole(const Napi::CallbackInfo& info,
return HRESULT_FROM_WIN32(GetLastError());
}
@@ -822,7 +825,7 @@ index 7b286d3d64..5239ba4e40 100644
static Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
@@ -301,8 +361,10 @@ static Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {
@@ -301,8 +364,10 @@ static Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {
// We were able to instantiate a conpty
const int ptyId = InterlockedIncrement(&ptyCounter);
marshal.Set("pty", Napi::Number::New(env, ptyId));
@@ -835,7 +838,7 @@ index 7b286d3d64..5239ba4e40 100644
} else {
throw Napi::Error::New(env, "Cannot launch conpty");
}
@@ -350,11 +412,15 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
@@ -350,11 +415,15 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
const bool useConptyDll = info[4].As<Napi::Boolean>().Value();
Napi::Function exitCallback = info[5].As<Napi::Function>();
@@ -855,7 +858,7 @@ index 7b286d3d64..5239ba4e40 100644
// Prepare command line
std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(cmdline.length() + 1);
@@ -409,6 +475,15 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
@@ -409,6 +478,15 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
throw errorWithCode(info, "UpdateProcThreadAttribute failed");
}
@@ -871,7 +874,7 @@ index 7b286d3d64..5239ba4e40 100644
PROCESS_INFORMATION piClient{};
fSuccess = !!CreateProcessW(
nullptr,
@@ -416,7 +491,10 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
@@ -416,7 +494,10 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
false, // bInheritHandles VERY IMPORTANT that this is false
@@ -883,7 +886,7 @@ index 7b286d3d64..5239ba4e40 100644
envArg, // lpEnvironment
mutableCwd.get(), // lpCurrentDirectory
&siEx.StartupInfo, // lpStartupInfo
@@ -426,8 +504,48 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
@@ -426,8 +507,54 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
throw errorWithCode(info, "Cannot create process");
}
@@ -909,6 +912,12 @@ index 7b286d3d64..5239ba4e40 100644
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobLimits{};
+ jobLimits.BasicLimitInformation.LimitFlags =
+ handle->allowJobBreakaway ? JOB_OBJECT_LIMIT_BREAKAWAY_OK : 0;
+ // Orca: an opt-in ceiling; forks past it fail inside this terminal's tree only.
+ const uint32_t activeProcessLimit = ptyActiveProcessLimit.load();
+ if (activeProcessLimit > 0) {
+ jobLimits.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS;
+ jobLimits.BasicLimitInformation.ActiveProcessLimit = activeProcessLimit;
+ }
+ if (!SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jobLimits, sizeof(jobLimits)) ||
+ !AssignProcessToJobObject(hJob, piClient.hProcess)) {
+ // Why tolerate failure: an outer job without JOB_OBJECT_LIMIT_BREAKAWAY_OK
@@ -934,7 +943,7 @@ index 7b286d3d64..5239ba4e40 100644
if (useConptyDll && fLoadedDll)
{
PFNRELEASEPSEUDOCONSOLE const pfnReleasePseudoConsole = (PFNRELEASEPSEUDOCONSOLE)GetProcAddress(
@@ -438,8 +556,12 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
@@ -438,8 +565,12 @@ static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
}
}
@@ -949,7 +958,7 @@ index 7b286d3d64..5239ba4e40 100644
// Close the thread handle to avoid resource leak
CloseHandle(piClient.hThread);
@@ -472,9 +594,10 @@ static Napi::Value PtyResize(const Napi::CallbackInfo& info) {
@@ -472,9 +603,10 @@ static Napi::Value PtyResize(const Napi::CallbackInfo& info) {
SHORT rows = static_cast<SHORT>(info[2].As<Napi::Number>().Uint32Value());
const bool useConptyDll = info[3].As<Napi::Boolean>().Value();
@@ -962,7 +971,7 @@ index 7b286d3d64..5239ba4e40 100644
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
bool fLoadedDll = hLibrary != nullptr;
if (fLoadedDll)
@@ -513,9 +636,10 @@ static Napi::Value PtyClear(const Napi::CallbackInfo& info) {
@@ -513,9 +645,10 @@ static Napi::Value PtyClear(const Napi::CallbackInfo& info) {
return env.Undefined();
}
@@ -975,7 +984,7 @@ index 7b286d3d64..5239ba4e40 100644
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
bool fLoadedDll = hLibrary != nullptr;
if (fLoadedDll)
@@ -544,29 +668,215 @@ static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
@@ -544,29 +677,229 @@ static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
int id = info[0].As<Napi::Number>().Int32Value();
const bool useConptyDll = info[1].As<Napi::Boolean>().Value();
@@ -1201,17 +1210,32 @@ index 7b286d3d64..5239ba4e40 100644
+ hHostJob = job;
+ return Napi::Boolean::New(env, true);
+}
+
+/**
+ * Orca: setActiveProcessLimit(limit) applies JOB_OBJECT_LIMIT_ACTIVE_PROCESS to
+ * every ConPTY job created afterwards. 0 turns it off. Existing jobs keep theirs.
+ */
+static Napi::Value PtySetActiveProcessLimit(const Napi::CallbackInfo& info) {
+ Napi::Env env(info.Env());
+ Napi::HandleScope scope(env);
+ if (info.Length() != 1 || !info[0].IsNumber()) {
+ throw Napi::Error::New(env, "Usage: pty.setActiveProcessLimit(limit)");
+ }
+ ptyActiveProcessLimit.store(info[0].As<Napi::Number>().Uint32Value());
+ return Napi::Boolean::New(env, true);
+}
+
/**
* Init
*/
@@ -577,6 +887,9 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
@@ -577,6 +910,10 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("resize", Napi::Function::New(env, PtyResize));
exports.Set("clear", Napi::Function::New(env, PtyClear));
exports.Set("kill", Napi::Function::New(env, PtyKill));
+ exports.Set("terminateJob", Napi::Function::New(env, PtyTerminateJob));
+ exports.Set("listJobProcessIds", Napi::Function::New(env, PtyListJobProcessIds));
+ exports.Set("assignCurrentProcessToJob", Napi::Function::New(env, PtyAssignCurrentProcessToJob));
+ exports.Set("setActiveProcessLimit", Napi::Function::New(env, PtySetActiveProcessLimit));
return exports;
};
@@ -30,3 +30,49 @@ index af5f08a5002fb26f94414be13f1ad848ae73d522..eb95ebc44fcb4b4760b2afe097a114cb
environment = createTypeAliasEnvironment(
node,
context.sourceCode.visitorKeys,
diff --git a/src/rules/no-widen-then-assert.ts b/src/rules/no-widen-then-assert.ts
--- a/src/rules/no-widen-then-assert.ts
+++ b/src/rules/no-widen-then-assert.ts
@@ -325,11 +325,12 @@
},
},
createOnce(context) {
- let scopes: Parameters<typeof resolvedVariableForIdentifier>[0] = [];
+ let scopes: Parameters<typeof resolvedVariableForIdentifier>[0] | null = null;
const checkAssertion = (node: ESTree.TSAsExpression | ESTree.TSTypeAssertion) => {
const expression = assertedExpression(node);
if (expression.type !== "Identifier") return;
+ scopes ??= context.sourceCode.scopeManager.scopes;
const variable = resolvedVariableForIdentifier(scopes, expression);
if (variable === null) return;
@@ -357,7 +358,7 @@
return {
Program() {
- scopes = context.sourceCode.scopeManager.scopes;
+ scopes = null;
},
TSAsExpression: checkAssertion,
TSTypeAssertion: checkAssertion,
diff --git a/src/rules/no-module-mocking.ts b/src/rules/no-module-mocking.ts
--- a/src/rules/no-module-mocking.ts
+++ b/src/rules/no-module-mocking.ts
@@ -39,7 +39,6 @@
function moduleMockCall(sourceCode: SourceCode, callee: ESTree.Expression): boolean {
if (!("property" in callee) || !("object" in callee) || !("computed" in callee)) return false;
- if (!isTestFrameworkObject(sourceCode, callee.object)) return false;
const property = callee.property;
const method = callee.computed
? property.type === "Literal" &&
@@ -51,7 +50,7 @@
: property.type === "Identifier"
? property.name
: null;
- return method !== null && moduleMockMethods.has(method);
+ return method !== null && moduleMockMethods.has(method) && isTestFrameworkObject(sourceCode, callee.object);
}
/** Ban test framework module mocking in favor of real dependency seams. */
+16 -16
View File
@@ -2,7 +2,7 @@
"$schemaNote": "Consumed by config/scripts/regenerate-xterm-patches.mjs. See docs/reference/xterm-patch-regeneration.md.",
"upstream": {
"repository": "https://github.com/xtermjs/xterm.js.git",
"commit": "d3e32b344dfe7dd6015cff6a9aeaaeaeccdc2789",
"commit": "c58ea3637f3968e0e6e79cd92cf9aace7ef89ee2",
"commitSource": "bin/publish.js stamps package.json.commit before npm publish, so the published tarball names its own commit. The generator asserts the two agree."
},
"sourcemaps": {
@@ -19,11 +19,11 @@
"packages": [
{
"name": "@xterm/xterm",
"version": "6.1.0-beta.303",
"version": "6.1.0-beta.304",
"packageDir": ".",
"versionStampFile": "src/common/Version.ts",
"sourcePatch": "config/patches/xterm-src/@xterm__xterm@6.1.0-beta.303.src.patch",
"patch": "config/patches/@xterm__xterm@6.1.0-beta.303.patch",
"sourcePatch": "config/patches/xterm-src/@xterm__xterm@6.1.0-beta.304.src.patch",
"patch": "config/patches/@xterm__xterm@6.1.0-beta.304.patch",
"generatedPaths": ["lib/"],
"build": [
{
@@ -35,11 +35,11 @@
},
{
"name": "@xterm/addon-webgl",
"version": "0.20.0-beta.299",
"version": "0.20.0-beta.300",
"packageDir": "addons/addon-webgl",
"$note": "No versionStampFile: publish.js stamps the addon's package.json, which overlayBuildOutput never patches. The root `build` is required because the addon's own tsgo -p . has empty files/include and only project references, so it emits nothing on its own; `package` is the addon's webpack (CJS half) and the root `esbuild-package` emits the ESM half.",
"sourcePatch": "config/patches/xterm-src/@xterm__addon-webgl@0.20.0-beta.299.src.patch",
"patch": "config/patches/@xterm__addon-webgl@0.20.0-beta.299.patch",
"sourcePatch": "config/patches/xterm-src/@xterm__addon-webgl@0.20.0-beta.300.src.patch",
"patch": "config/patches/@xterm__addon-webgl@0.20.0-beta.300.patch",
"generatedPaths": ["lib/"],
"build": [
{
@@ -61,12 +61,12 @@
},
{
"name": "@xterm/addon-search",
"version": "0.17.0-beta.300",
"version": "0.17.0-beta.301",
"packageDir": "addons/addon-search",
"$note": "No versionStampFile: publish.js stamps the addon's package.json, which overlayBuildOutput never patches. The root `build` is required because the addon's own tsgo -p . has empty files/include and only project references, so it emits nothing on its own; `package` is the addon's webpack (CJS half) and the root `esbuild-package` emits the ESM half.",
"$upstream": "Submitted as https://github.com/xtermjs/xterm.js/pull/6149 (issue #6148). Once a release ships it, bump the addon and drop this entry.",
"sourcePatch": "config/patches/xterm-src/@xterm__addon-search@0.17.0-beta.300.src.patch",
"patch": "config/patches/@xterm__addon-search@0.17.0-beta.300.patch",
"sourcePatch": "config/patches/xterm-src/@xterm__addon-search@0.17.0-beta.301.src.patch",
"patch": "config/patches/@xterm__addon-search@0.17.0-beta.301.patch",
"generatedPaths": ["lib/"],
"build": [
{
@@ -88,11 +88,11 @@
},
{
"name": "@xterm/addon-serialize",
"version": "0.15.0-beta.300",
"version": "0.15.0-beta.301",
"packageDir": "addons/addon-serialize",
"$note": "No versionStampFile: publish.js stamps the addon's package.json, which overlayBuildOutput never patches. The root `build` is required because the addon's own tsgo -p . has empty files/include and only project references, so it emits nothing on its own; `package` is the addon's webpack (CJS half) and the root `esbuild-package` emits the ESM half.",
"sourcePatch": "config/patches/xterm-src/@xterm__addon-serialize@0.15.0-beta.300.src.patch",
"patch": "config/patches/@xterm__addon-serialize@0.15.0-beta.300.patch",
"sourcePatch": "config/patches/xterm-src/@xterm__addon-serialize@0.15.0-beta.301.src.patch",
"patch": "config/patches/@xterm__addon-serialize@0.15.0-beta.301.patch",
"generatedPaths": ["lib/"],
"build": [
{
@@ -114,10 +114,10 @@
},
{
"name": "@xterm/addon-image",
"version": "0.10.0-beta.300",
"version": "0.10.0-beta.301",
"packageDir": "addons/addon-image",
"sourcePatch": "config/patches/xterm-src/@xterm__addon-image@0.10.0-beta.300.src.patch",
"patch": "config/patches/@xterm__addon-image@0.10.0-beta.300.patch",
"sourcePatch": "config/patches/xterm-src/@xterm__addon-image@0.10.0-beta.301.src.patch",
"patch": "config/patches/@xterm__addon-image@0.10.0-beta.301.patch",
"generatedPaths": ["lib/"],
"build": [
{
@@ -6,7 +6,6 @@ src/cli/handlers/core.ts
src/cli/handlers/interactive-login-interruption.ts
src/cli/handlers/skills.ts
src/cli/runtime/launch.ts
src/cli/runtime/serve-update-supervisor.ts
src/main/agent-hooks/managed-hook-owner-identity.ts
src/main/agent-hooks/wsl-hook-relay-launch.ts
src/main/agent-hooks/wsl-hook-relay-link.ts
@@ -111,15 +110,9 @@ src/main/ssh/system-ssh-forward-process.ts
src/main/ssh/system-ssh-operation-lifecycle.ts
src/main/startup/ensure-virtual-display.ts
src/main/startup/hydrate-shell-path.ts
src/main/startup/windows-install-dir-acl-probe.ts
src/main/startup/windows-user-data-acl.ts
src/main/win32-utils.ts
src/main/window/clipboard-ipc-handlers.ts
src/main/windows-process-tree-kill.ts
src/main/workspace-space-analysis.ts
src/main/wsl-availability.ts
src/main/wsl-unc-delete.ts
src/main/wsl.ts
src/main/zsh-startup-hook-pty-harness.ts
src/shared/ephemeral-vm-recipe-process.ts
src/shared/ephemeral-vm-recipe-runner.ts
+139 -31
View File
@@ -10,6 +10,124 @@
}
},
"gates": [
{
"id": "agent-session.delayed-terminal-launch",
"title": "Delayed terminal launches retain their harness preparation and recover process discovery",
"maturity": "experimental",
"protection": "partial",
"owner": "terminal-runtime",
"layer": "host-renderer",
"surfaces": [
"terminal startup commands",
"typed agent commands",
"hidden terminal startup",
"agent hooks and managed accounts"
],
"platforms": [
"macos",
"linux",
"windows"
],
"providers": [
"local",
"daemon",
"ssh",
"remote-runtime",
"wsl"
],
"coveredPlatforms": [
"macos"
],
"coveredProviders": [
"local",
"daemon"
],
"coverageNotes": "Shared preparation is exercised with real local plugin files and account environment plus mocked host spawn contracts. Captured real Claude startup title drives renderer recovery. Physical Linux, Windows, SSH and WSL delayed launches remain unproved.",
"motivatingLinks": [
"docs/bug-reproductions/delayed-agent-launch.md"
],
"invariant": "A literal sleep followed by && must not cause the later harness to lose hook/account preparation or hidden renderer query delivery. Preparation and typed-command hints are not live process evidence. Fresh agent startup output may request one bounded host confirmation after the initial command read window has expired.",
"oracle": "All 45 configured harnesses keep hidden startup query eligibility. Delayed OpenCode 2 installs v2 hooks rather than v1, disabled harnesses do not fall back, selected managed accounts survive, and Pi/OMP/Prime preparation targets the requested harness. A captured Claude startup title at 30 seconds yields process-confirmed routing, never title-authorized routing. A title storm causes at most three additional reads; close and untrackable providers cause none.",
"commands": [
"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-command-line-intent.test.ts src/shared/pi-agent-kind.test.ts src/shared/opencode-launch-command.test.ts src/main/ipc/pty/host-env/opencode-hook-installation.test.ts src/main/ipc/pty-spawn-env-terminal-basics.test.ts src/main/managed-data-accounts/launch-environment.test.ts src/renderer/src/components/terminal-pane/terminal-startup-command-classifier.test.ts src/renderer/src/components/terminal-pane/pane-delayed-agent-startup.test.ts src/renderer/src/components/terminal-pane/pty-connection-foreground-agent-routing.test.ts src/renderer/src/components/terminal-pane/pty-connection-hidden-codex-queries.test.ts --maxWorkers=2"
],
"testFiles": [
"src/shared/agent-command-line-intent.test.ts",
"src/shared/pi-agent-kind.test.ts",
"src/shared/opencode-launch-command.test.ts",
"src/main/ipc/pty/host-env/opencode-hook-installation.test.ts",
"src/main/ipc/pty-spawn-env-terminal-basics.test.ts",
"src/main/managed-data-accounts/launch-environment.test.ts",
"src/renderer/src/components/terminal-pane/terminal-startup-command-classifier.test.ts",
"src/renderer/src/components/terminal-pane/pane-delayed-agent-startup.test.ts",
"src/renderer/src/components/terminal-pane/pty-connection-foreground-agent-routing.test.ts",
"src/renderer/src/components/terminal-pane/pty-connection-hidden-codex-queries.test.ts"
],
"assertionRefs": [
{
"file": "src/main/ipc/pty/host-env/opencode-hook-installation.test.ts",
"assertions": [
"explicit %s with disabled"
]
},
{
"file": "src/renderer/src/components/terminal-pane/terminal-startup-command-classifier.test.ts",
"assertions": [
"keeps hidden %s startup queries live after sleep"
]
},
{
"file": "src/renderer/src/components/terminal-pane/pane-delayed-agent-startup.test.ts",
"assertions": [
"bounds a startup title storm to one extra read ladder and never adopts the title as identity",
"cancels delayed startup discovery on close"
]
},
{
"file": "src/renderer/src/components/terminal-pane/pty-connection-foreground-agent-routing.test.ts",
"assertions": [
"confirms a delayed Claude from fresh startup output after the first read window expires"
]
}
],
"evidenceRuns": [
{
"date": "2026-10-13",
"runner": "local",
"platform": "macos",
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-command-line-intent.test.ts src/shared/pi-agent-kind.test.ts src/shared/opencode-launch-command.test.ts src/main/ipc/pty/host-env/opencode-hook-installation.test.ts src/main/ipc/pty-spawn-env-terminal-basics.test.ts src/main/managed-data-accounts/launch-environment.test.ts src/renderer/src/components/terminal-pane/terminal-startup-command-classifier.test.ts src/renderer/src/components/terminal-pane/pane-delayed-agent-startup.test.ts src/renderer/src/components/terminal-pane/pty-connection-foreground-agent-routing.test.ts src/renderer/src/components/terminal-pane/pty-connection-hidden-codex-queries.test.ts --maxWorkers=2",
"result": "passed",
"durationSeconds": 1.86,
"summary": "214 assertions pass across nine files, including all 45 harness classifications, actual hook files, delayed account preparation, late process routing, hidden query delivery and bounded recovery."
}
],
"runtimeBudget": {
"p95Seconds": 30,
"scope": "Focused deterministic gate; no CI soak established."
},
"performanceBudget": {
"required": true,
"evidence": "Prefix scanning is capped at 4096 characters; ordinary output has no additional work. Startup title recovery uses one existing three-read ladder per command and joins active reads. Deterministic counts cover storms, close and untrackable providers."
},
"flakeHistory": {
"status": "not-started",
"evidence": "Focused macOS runs passed; no cross-platform soak."
},
"redGreenEvidence": {
"status": "complete",
"evidence": "Before the fix, delayed preparation produced 52 failing assertions and the captured late Claude startup title left process routing unset. The corresponding tests passed after the change."
},
"promotionCriteria": [
"Collect repeated independent runs and physical provider/platform evidence before promotion."
],
"knownGaps": [
"Real Claude hooks completed normally in the original macOS reproduction; no blanket hook outage was reproduced.",
"Literal numeric sleep prefixes only; arbitrary shell scripts, variables, separators, background jobs and pipelines remain unmodeled.",
"Physical Linux, Windows, SSH, WSL and remote-runtime delayed launches and all-harness live CLI runs were not performed.",
"A late agent emitting neither an identifiable startup title nor hook evidence still needs a later visibility/input observation."
],
"demotionRule": "Keep experimental until soak evidence; investigate failures without widening polling windows, trusting titles or weakening hook/account assertions."
},
{
"id": "native-chat.transcript-consumed-boundary",
"title": "Transcript replacement remains visible when it races message publication",
@@ -3595,12 +3713,11 @@
"invariant": "A first-install relay transfer must complete in an attempt-owned fixed staging slot before acquiring the shared version install lock. Reservation, promotion, confirmed cleanup, and stale recovery must reject path replacement, persisted-identity mismatch, POSIX symlinks, and Windows reparse points. A newly acquired install lock atomically records the execution host's boot identity; only a verified identity change or the existing stale-age proof may replace it, while legacy, missing, malformed, and unreadable identity state must retain the conservative stale fallback. Recovery examines only eight fixed slot/claim/delete names and removes at most one stale valid stage per call; eight unclaimable states fail with an explicit manual-recovery message. Split-SFTP hosts must prove the stage identity on the exact transfer session, only payload contents may be promoted under the shared lock, and cancellation must boundedly join SFTP, stream, local file-handle, and transfer settlement.",
"oracle": "Pause a real ssh2 SFTP relay.js write after one remotely acknowledged chunk, prove the remote file is partial, abort the live transfer, and require no shared .install-lock, leaked local descriptor, or foreign-process termination. Separately inject two unconfirmed cancellations, require an independent deployment to install, launch, answer relay RPC, and read a real repository HEAD. Keep a fresh install lock on the current POSIX or Windows boot and require takeover to fail; replace its bounded identity with a prior-boot value and race concurrent recoverers, requiring exactly one atomic winner, a current successor identity, and no tombstone residue; omit the marker and require the legacy lock to remain fenced. Replace one retained fixed slot with an old-mtime same-owner directory while preserving the original, add a fixed-slot POSIX symlink, and require installed-path recovery to skip both while reclaiming a valid stale slot behind them. Add 15,197 unrelated relay-shaped names and run the real version GC, requiring bounded stdout and no removal. Unit and wire contracts cover exact POSIX and native PowerShell 0/1/7/8/9+ quota behavior, no-follow identity fencing, payload symlink/reparse rejection, one-item repeated draining, zero lock acquisition before upload settlement, joined transfer/channel teardown including never-settling failures, SFTP redirection, package.json namespace ownership, promotion only after the lock, cross-version isolation, and system-SSH behavior.",
"commands": [
"node config/scripts/run-ssh-staged-upload-reliability.mjs --powershell <PowerShell-7.6.4-executable> src/main/ssh/sftp-upload.test.ts src/main/ssh/ssh-file-transfer-abort.test.ts src/main/ssh/ssh-relay-install-namespace.test.ts src/main/ssh/sftp-namespace-resolution.test.ts src/main/ssh/ssh-connection-sftp-wire.test.ts src/main/ssh/ssh-remote-commands.test.ts"
"node config/scripts/run-ssh-staged-upload-reliability.mjs --powershell <PowerShell-7.6.4-executable> src/main/ssh/sftp-upload.test.ts src/main/ssh/ssh-file-transfer-abort.test.ts src/main/ssh/sftp-namespace-resolution.test.ts src/main/ssh/ssh-connection-sftp-wire.test.ts src/main/ssh/ssh-remote-commands.test.ts"
],
"testFiles": [
"src/main/ssh/sftp-upload.test.ts",
"src/main/ssh/ssh-file-transfer-abort.test.ts",
"src/main/ssh/ssh-relay-install-namespace.test.ts",
"src/main/ssh/sftp-namespace-resolution.test.ts",
"src/main/ssh/ssh-connection-sftp-wire.test.ts",
"src/main/ssh/ssh-remote-commands.test.ts"
@@ -3632,7 +3749,7 @@
"date": "2026-07-31",
"runner": "local",
"platform": "macos",
"command": "node config/scripts/run-ssh-staged-upload-reliability.mjs --powershell <PowerShell-7.6.4-executable> src/main/ssh/sftp-upload.test.ts src/main/ssh/ssh-file-transfer-abort.test.ts src/main/ssh/ssh-relay-install-namespace.test.ts src/main/ssh/sftp-namespace-resolution.test.ts src/main/ssh/ssh-connection-sftp-wire.test.ts src/main/ssh/ssh-remote-commands.test.ts",
"command": "node config/scripts/run-ssh-staged-upload-reliability.mjs --powershell <PowerShell-7.6.4-executable> src/main/ssh/sftp-upload.test.ts src/main/ssh/ssh-file-transfer-abort.test.ts src/main/ssh/sftp-namespace-resolution.test.ts src/main/ssh/ssh-connection-sftp-wire.test.ts src/main/ssh/ssh-remote-commands.test.ts",
"result": "passed",
"durationSeconds": 57.9,
"summary": "Eleven focused files passed 152 tests with 3 platform skips using exact POSIX sh, native ARM macOS PowerShell 7.6.4, real ssh2 split-SFTP wire sessions, fixed-slot identity races, bounded recovery, joined cancellation teardown, and cross-version isolation."
@@ -13600,10 +13717,10 @@
"https://github.com/stablyai/orca/pull/17170"
],
"invariant": "Composition, native text forwarding, synthetic input, paste, and platform keyboard bypass paths must not send preedit/control bytes before commit and must commit text exactly once to the intended PTY. A live preedit must stay visible across an IME-resumed transaction, preserve every committed row-tail cell hidden by its opaque overlay, mask only a structurally verified Codex or Claude placeholder and never a row carrying typed text, keep the themed insertion caret and native candidate anchor inside the terminal at the final column, and clean up without stale overlay children or ownership classes. Layout-native Option text must preserve negotiated CSI-u alternate/associated fields and lock modifiers, distinguish repeat from press, and resolve releases with the press's primary identity plus the actual keyup modifiers without exposing a partial or stale layout refresh. An in-focus macOS input-source change must invalidate both renderer authorities before the next key can use the old source. When the snapshot source proves kitty state, macOS Preview commits must use the application's effective flags after the sequenced snapshot and every proven replay/live transition, and a claimed press delivered under report_event_types must produce exactly one matching release regardless of whether keyup arrives before or after insertText and regardless of xterm's defensive internal reset.",
"oracle": "Exact outbound bytes from the REAL native-text forwarder behind installPreviewImeBridge after initial snapshot restore, replacement resync, live transitions, and both keydown->insertText->keyup and keydown->keyup->insertText orderings; a wiring-only mock is supporting evidence, not the oracle. The current renderer-unit slice asserts native text commits route to the intended PTY once, composition/preedit bookkeeping does not leak premature text, resumed Windows event traces reactivate the visible preedit, an opaque mid-line overlay reproduces the committed row tail it covers, arbitrary DIM output remains visible, and final-column layout keeps both caret and textarea candidate anchor within the available screen width. The candidate-anchor slice reads back the inline `textarea.style.left` xterm's own listener and Orca's anchor listener both write during one composition event, so the assertion is the position the OS would sample rather than either writer's intent. A Codex frame whose prompt row is bold and whose dimmed model footer sits below a blank gap acquires the mask class while composing; the same placeholder text without that footer, a framed-versus-unframed Claude prompt, a row of arbitrary dimmed output, and a row carrying typed text do not. The class is gone after composition end, after a blur that is not followed by a session end, and after disposal. Rendered Electron/CDP geometry independently proves the overlay reproduces every committed cell it hides, that a real Codex placeholder at column 2 is masked rather than repeated, and that the final-column caret, preedit, and textarea right edges all land inside the screen. Input-source classification handles synthetic/native paths, paste/runtime forwarding avoids duplicate terminal payloads for covered fixtures, and Linux/Sogou candidate Space/digit selectors do not leak keydown/keypress/keyup while ordinary and long-held letter-to-digit typing remains available. A main/preload/renderer notification chain proves every live window invalidates immediately, the old native read settles before a coalesced refresh signal, and older refreshes are generation-fenced. The Electron/CDP live-PTY repro verifies Sogou-style Space and digit selectors submit only the committed Chinese text, while the legacy orphaned-letter-keyup sequence sends no selector byte to the PTY. Real legacy IME commit preservation, native candidate-window placement, and the full CJK/Vietnamese/Arabic/JIS-yen matrix run in follow-up platform soak where automation is possible.",
"oracle": "Exact outbound bytes from the REAL native-text forwarder behind installPreviewImeBridge after initial snapshot restore, replacement resync, live transitions, and both keydown->insertText->keyup and keydown->keyup->insertText orderings; a wiring-only mock is supporting evidence, not the oracle. The current renderer-unit slice asserts native text commits route to the intended PTY once, composition/preedit bookkeeping does not leak premature text, resumed Windows event traces reactivate the visible preedit, an opaque mid-line overlay reproduces the committed row tail it covers, arbitrary DIM output remains visible, and final-column layout keeps both caret and textarea candidate anchor within the available screen width. The candidate-anchor slice reads back the inline `textarea.style.left` xterm's own listener and Orca's anchor listener both write during one composition event, so the assertion is the position the OS would sample rather than either writer's intent. A Codex frame whose prompt row is bold and whose dimmed model footer sits below a blank gap acquires the mask class while composing; the same placeholder text without that footer, a framed-versus-unframed Claude prompt, a row of arbitrary dimmed output, and a row carrying typed text do not. The class is gone after composition end, after a blur that is not followed by a session end, and after disposal. Rendered Electron/CDP geometry independently proves the overlay reproduces every committed cell it hides, that a real Codex placeholder at column 2 is masked rather than repeated, and that the final-column caret, preedit, and textarea right edges all land inside the screen. Input-source classification handles synthetic/native paths, paste/runtime forwarding avoids duplicate terminal payloads for covered fixtures, and real fcitx4/Sogou captures send only the committed candidate for Space/digit picks while ordinary typing, literal Space and digits stay available. A main/preload/renderer notification chain proves every live window invalidates immediately, the old native read settles before a coalesced refresh signal, and older refreshes are generation-fenced. The Electron/CDP live-PTY repro verifies Sogou-style Space and digit selectors submit only the committed Chinese text. Real legacy IME commit preservation, native candidate-window placement, and the full CJK/Vietnamese/Arabic/JIS-yen matrix run in follow-up platform soak where automation is possible.",
"commands": [
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-ime-native-text-forwarder.test.ts src/renderer/src/components/terminal-pane/terminal-ime-substituted-text-commit.test.ts src/renderer/src/components/terminal-pane/terminal-ime-macos-keybinding-dict-trace.test.ts src/renderer/src/components/terminal-pane/terminal-paste-runtime.test.ts src/renderer/src/components/terminal-pane/terminal-ime-composition-tracker.test.ts src/renderer/src/components/terminal-pane/terminal-ime-candidate-key-release-guard.test.ts src/renderer/src/components/terminal-pane/xterm-bypass-policy-non-mac.test.ts src/renderer/src/components/terminal-pane/xterm-bypass-policy.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-ime-linux-fcitx4-trace-replay.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-ime-xterm-midline-preedit-tail.test.ts src/renderer/src/components/terminal-pane/terminal-ime-xterm-resumed-preedit-visibility.test.ts src/renderer/src/components/terminal-pane/terminal-ime-xterm-trailing-preedit-occlusion.test.ts src/renderer/src/lib/pane-manager/terminal-ime-candidate-anchor.test.ts src/renderer/src/components/terminal-pane/terminal-ime-composer-placeholder-mask.test.ts src/shared/terminal-composer-draft.test.ts src/main/daemon/terminal-cursor-line-context.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/dashboard-popout/preview-terminal-ime-bridge-kitty-bytes.test.ts src/renderer/src/components/dashboard-popout/preview-terminal-snapshot-replay.test.ts src/renderer/src/components/dashboard-popout/AgentTerminalPreview.test.tsx src/renderer/src/components/dashboard-popout/AgentTerminalPreview.option-dead-key.test.tsx src/shared/terminal-kitty-keyboard-mode-tracker.test.ts src/main/ipc/terminal-preview.test.ts src/main/ipc/terminal-preview-output-stream.test.ts src/renderer/src/runtime/remote-runtime-terminal-snapshot-kitty-flags.test.ts src/main/daemon/daemon-pty-adapter.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-kitty-csi-u-encoding.test.ts src/renderer/src/components/terminal-pane/terminal-shortcut-option-compose.test.ts src/renderer/src/components/terminal-pane/terminal-option-kitty-release.test.ts src/renderer/src/lib/keyboard-layout/layout-base-character.test.ts src/renderer/src/lib/keyboard-layout/option-as-alt-probe.test.ts src/main/ipc/macos-keyboard-layout-snapshot.test.ts src/main/ipc/macos-keyboard-layout-change-notifications.test.ts src/preload/app-restart-checkpoint-routing.test.ts src/renderer/src/web/web-preload-api-ui.test.ts",
@@ -13619,7 +13736,7 @@
"src/renderer/src/components/terminal-pane/terminal-paste-runtime.test.ts",
"src/renderer/src/components/terminal-pane/terminal-ime-composition-tracker.test.ts",
"src/renderer/src/components/terminal-pane/terminal-ime-candidate-key-release-guard.test.ts",
"src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.test.ts",
"src/renderer/src/components/terminal-pane/terminal-ime-linux-fcitx4-trace-replay.test.ts",
"src/renderer/src/components/terminal-pane/terminal-ime-xterm-midline-preedit-tail.test.ts",
"src/renderer/src/components/terminal-pane/terminal-ime-xterm-resumed-preedit-visibility.test.ts",
"src/renderer/src/components/terminal-pane/terminal-ime-xterm-trailing-preedit-occlusion.test.ts",
@@ -13695,11 +13812,11 @@
]
},
{
"file": "src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.test.ts",
"file": "src/renderer/src/components/terminal-pane/terminal-ime-linux-fcitx4-trace-replay.test.ts",
"assertions": [
"an orphaned plain-letter keyup arms exactly the next bare digit guard",
"ordinary, overlapping, shifted, and long-held letter keydowns keep following digits available",
"physical letter tracking survives cross-pane focus handoff and clears on renderer-window blur"
"real fcitx4 Sogou captures (GTK module and XIM) send only the committed candidate for Space and digit picks, under legacy keys and kitty flags 1 and 5",
"a real fcitx4 Google Pinyin in-client preedit still commits through the composition path",
"an XIM pick's code-less keydown pairs with its keyup so kitty event reporting sends one release per pick"
]
},
{
@@ -13974,22 +14091,13 @@
"summary": "8 test file(s) passed, 143 tests, after adding the OS key-binding substitution replay. Discrimination checked by mutation rather than assumed: suppressing the structural claim, and separately removing the single character the older punctuation table happened to list, each make the replay send the raw layout character while both paired negatives stay green."
},
{
"date": "2026-07-11",
"date": "2026-10-13",
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.test.ts",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-ime-linux-fcitx4-trace-replay.test.ts",
"result": "passed",
"durationSeconds": 0.619,
"summary": "1 state test file and 10 tests passed on macOS, including orphan-keyup, shifted/modifier-changed releases, intervening-key cancellation, cross-pane focus handoff, window/terminal blur cleanup, and long-held-letter coverage; the complete 8-file slice also passed 158 tests."
},
{
"date": "2026-07-11",
"runner": "local",
"platform": "linux",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.test.ts",
"result": "passed",
"durationSeconds": 0.747,
"summary": "1 state test file and 10 tests passed in Debian 12 arm64 Docker under Node 24, including orphan-keyup, shifted/modifier-changed releases, intervening-key cancellation, cross-pane focus handoff, window/terminal blur cleanup, and long-held-letter coverage; the complete 8-file slice also passed 158 tests."
"durationSeconds": 1.99,
"summary": "1 replay test file and 37 tests passed on macOS: real fcitx4 Sogou (GTK module and XIM) and Google Pinyin captures through the full Linux pane keyboard stack under legacy keys and kitty flags 1 and 5, plus XIM release pairing under kitty flags 3. The same captures were also replayed with real fcitx4 + Sogou in an Ubuntu 22.04 container, with every scenario sending only the committed text."
},
{
"date": "2026-07-11",
@@ -17829,8 +17937,8 @@
"node config/scripts/runtime-file-watcher-resource-probe.mjs",
"node config/scripts/runtime-file-watcher-fault-harness.mjs",
"ELECTRON_RUN_AS_NODE=1 pnpm exec electron config/scripts/runtime-file-watcher-fault-harness.mjs",
"pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/ssh-relay-versioned-install.test.ts config/scripts/package-electron-runtime-contract.test.mjs",
"pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/ssh-relay-versioned-install.test.ts config/scripts/package-electron-runtime-contract.test.mjs"
"pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/remote-versioned-install.test.ts config/scripts/package-electron-runtime-contract.test.mjs",
"pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/remote-versioned-install.test.ts config/scripts/package-electron-runtime-contract.test.mjs"
],
"testFiles": [
"src/main/daemon/daemon-pty-adapter.test.ts",
@@ -17876,7 +17984,7 @@
"src/shared/promise-settlement-waiters.test.ts",
"config/scripts/runtime-file-watcher-fault-harness.mjs",
"config/scripts/runtime-file-watcher-resource-probe.mjs",
"src/main/ssh/ssh-relay-versioned-install.test.ts",
"src/main/ssh/remote-versioned-install.test.ts",
"config/scripts/package-electron-runtime-contract.test.mjs"
],
"assertionRefs": [
@@ -18198,9 +18306,9 @@
]
},
{
"file": "src/main/ssh/ssh-relay-versioned-install.test.ts",
"file": "src/main/ssh/remote-versioned-install.test.ts",
"assertions": [
"remote completeness requires relay.js, relay-watcher.js, and the install sentinel"
"remote completeness requires every model artifact and the install sentinel"
]
},
{
@@ -18275,7 +18383,7 @@
"date": "2026-07-13",
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/ssh-relay-versioned-install.test.ts config/scripts/package-electron-runtime-contract.test.mjs",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/remote-versioned-install.test.ts config/scripts/package-electron-runtime-contract.test.mjs",
"result": "passed",
"durationSeconds": 10.01,
"summary": "Six relay registry, filesystem/Git handler, bundled subprocess, install, and package contract files passed with 226 tests."
@@ -18329,7 +18437,7 @@
"date": "2026-07-13",
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/ssh-relay-versioned-install.test.ts config/scripts/package-electron-runtime-contract.test.mjs",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/remote-versioned-install.test.ts config/scripts/package-electron-runtime-contract.test.mjs",
"result": "passed",
"durationSeconds": 10.69,
"summary": "Six relay registry, filesystem/Git handler, bundled subprocess, install, and package contract files passed with 226 tests, including watcher/PTY removal fencing and recursive deletion failure behavior."
@@ -18383,7 +18491,7 @@
"date": "2026-07-13",
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/ssh-relay-versioned-install.test.ts config/scripts/package-electron-runtime-contract.test.mjs",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/remote-versioned-install.test.ts config/scripts/package-electron-runtime-contract.test.mjs",
"result": "passed",
"durationSeconds": 9.79,
"summary": "Six relay registry, filesystem/Git handler, bundled subprocess, install, and package contract files passed with 225 tests, including terminal removal notification and recursive deletion fencing."
-6
View File
@@ -1,6 +0,0 @@
# Modules reachable from the Orca runtime or structured-chat code that import `electron`.
# Generated by config/scripts/check-runtime-electron-ratchet.mjs.
# This list is EMPTY and must stay that way: both run in orcad, the headless runtime,
# on plain Node (see `pnpm run build:orcad`). Any entry means that code got less portable;
# migrate the module behind a host port instead (src/main/host/).
@@ -12,7 +12,7 @@ import fs from 'node:fs'
import nodeModule from 'node:module'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { fileURLToPath, pathToFileURL } from 'node:url'
if (!process.execArgv.includes('--experimental-transform-types')) {
const result = spawnSync(
@@ -51,13 +51,17 @@ for (const paneCount of PANE_COUNTS) {
}
const { nextCadenceInspectionDelayMs } = await import(
path.join(ROOT, 'src/renderer/src/components/terminal-pane/agent-completion-poll-interval.ts')
pathToFileURL(
path.join(ROOT, 'src/renderer/src/components/terminal-pane/agent-completion-poll-interval.ts')
).href
)
const { POLL_TIER_INTERVAL_MS } = await import(
path.join(ROOT, 'src/renderer/src/components/terminal-pane/agent-completion-poll-cadence.ts')
pathToFileURL(
path.join(ROOT, 'src/renderer/src/components/terminal-pane/agent-completion-poll-cadence.ts')
).href
)
const { PROCESS_TABLE_SNAPSHOT_MAX_STALENESS_MS } = await import(
path.join(ROOT, 'src/shared/process-table-snapshot-reader.ts')
pathToFileURL(path.join(ROOT, 'src/shared/process-table-snapshot-reader.ts')).href
)
// Pre-change: independent ±10% jitter per pane, re-rolled on every reschedule.
@@ -0,0 +1,48 @@
import { cpSync, mkdtempSync, rmSync } from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import { afterAll, expect, it } from 'vitest'
import { runOxlintPluginOnSource } from './oxlint-plugin-test-runner.mjs'
const directory = mkdtempSync(path.join(import.meta.dirname, '.anti-slop-scope-'))
const source = path.dirname(createRequire(import.meta.url).resolve('oxlint-plugin-anti-slop'))
cpSync(source, directory, { recursive: true })
afterAll(() => rmSync(directory, { recursive: true, force: true }))
function lint(source, rule) {
return runOxlintPluginOnSource({
pluginName: 'anti-slop',
pluginPath: path.join(directory, 'index.ts'),
rules: { [`anti-slop/${rule}`]: 'error' },
source,
extension: 'ts'
})
}
it.each([
['const value = {}; const wide: unknown = value; wide as { id: string };', 1],
['const value = {}; const alias = value; const wide: unknown = alias; <{ id: string }>wide;', 1],
['function parse(input: unknown) { return input as { id: string }; }', 0],
['const value = {}; let wide: unknown = value; wide as { id: string };', 0],
['const value = {}; const wide: unknown = value; function read() { return wide as {}; }', 0],
[
'const value = {}; const wide: unknown = value; function read(wide: unknown) { return wide as {}; }',
0
],
['const value = {}; const wide: unknown = value; wide as unknown;', 0]
])('preserves widening evidence and lexical boundaries: %s', (source, count) => {
expect(lint(source, 'no-widen-then-assert')).toHaveLength(count)
})
it.each([
["import { vi as test } from 'vitest'; test.mock('module');", 1],
["import { jest as test } from '@jest/globals'; test['doMock']('module');", 1],
["jest.unstable_mockModule('module', () => ({}));", 1],
["vi['mock']('module');", 1],
["import { vi } from 'vitest'; vi.mocked(value);", 0],
["const unrelated = { mock() {} }; unrelated.mock('module');", 0],
["const vi = { mock() {} }; vi.mock('module');", 0],
["import { vi as test } from 'vitest'; function run(test) { test.mock('module'); }", 0]
])('preserves mock method and owner resolution: %s', (source, count) => {
expect(lint(source, 'no-module-mocking')).toHaveLength(count)
})
+61
View File
@@ -0,0 +1,61 @@
/**
* Linux builds place the pinned Node at resources/node-runtime/bin/node in afterPack, before the
* glibc-floor gate scans the tree. deb and rpm drop it again through fpm, so only the AppImage
* ships it: an AppImage's files vanish with its mount, an installed package's do not.
*/
const { chmodSync, copyFileSync, mkdirSync } = require('node:fs')
const { join, resolve } = require('node:path')
const { pathToFileURL } = require('node:url')
const {
APPIMAGE_NODE_RUNTIME_FPM_EXCLUDE,
APPIMAGE_NODE_RUNTIME_SEGMENTS,
appImageNodeRuntimeTarget
} = require('../../src/shared/appimage-node-runtime.ts')
const { runTerminalHostRoundTrip } = require('./verify-packaged-daemon-entry.cjs')
const appImageNodeRuntimeFpmArgs = ['--exclude', APPIMAGE_NODE_RUNTIME_FPM_EXCLUDE]
const PTY_ROUND_TRIP_TIMEOUT_MS = 20_000
async function ensurePinnedNode(target) {
const { ensurePinnedNodeExecutable } = await import(
pathToFileURL(resolve(__dirname, 'pinned-node-downloads.mjs')).href
)
return ensurePinnedNodeExecutable({ target })
}
/** `arch` is a Node arch name; the copy is hash-checked by the pinned download it comes from. */
async function placeAppImageNodeRuntime(resourcesDir, arch, ensureNode = ensurePinnedNode) {
const target = appImageNodeRuntimeTarget(arch)
if (!target) {
throw new Error(`[appimage-node-runtime] the Node pin has no glibc build for Linux ${arch}`)
}
const destination = join(resourcesDir, ...APPIMAGE_NODE_RUNTIME_SEGMENTS)
mkdirSync(join(destination, '..'), { recursive: true })
copyFileSync(await ensureNode(target), destination)
chmodSync(destination, 0o755)
return destination
}
/** The bundled Node must load the packaged node-pty and serve one PTY, as the daemon will. */
async function verifyAppImageNodeRuntimeServesPty(resourcesDir, runtimePath) {
const stderr = []
try {
await runTerminalHostRoundTrip({
command: [runtimePath],
entry: join(resourcesDir, 'app.asar.unpacked', 'out', 'main', 'daemon-entry.js'),
deadline: Date.now() + PTY_ROUND_TRIP_TIMEOUT_MS,
stderr
})
} catch (error) {
throw new Error(
`[appimage-node-runtime] the bundled Node did not serve a PTY: ${error.message}\n${stderr.join('').slice(-2000)}`
)
}
console.log('[appimage-node-runtime] OK — the bundled Node served a PTY')
}
module.exports = {
appImageNodeRuntimeFpmArgs,
placeAppImageNodeRuntime,
verifyAppImageNodeRuntimeServesPty
}

Some files were not shown because too many files have changed in this diff Show More