Merge branch 'ra/max-lines-bottom-19-local-pty-provider' into ra/max-lines-bottom-20-cdp-bridge

# Conflicts:
#	src/main/browser/cdp-bridge.ts
This commit is contained in:
Neil
2026-08-29 15:51:36 -07:00
236 changed files with 6795 additions and 2641 deletions
@@ -26,6 +26,7 @@ RUN apt-get update \
&& rm -rf /var/lib/apt/lists/*
RUN corepack enable \
&& corepack prepare pnpm@10.24.0 --activate
&& corepack prepare pnpm@12.0.0 --activate \
&& pnpm --version
WORKDIR /workspace
@@ -10,7 +10,7 @@ index 855bd4b86f0a3c18c7594212c0e42b6e35bc4001..33774e7ae296f0de39dd94156673c9e7
],
"conditions": [
['OS=="win"', {
@@ -15,14 +14,13 @@
@@ -15,12 +14,11 @@
"src/process_commandline.cc"
],
- "include_dirs": [],
File diff suppressed because one or more lines are too long
+58 -16
View File
@@ -6321,36 +6321,48 @@
},
{
"id": "terminal-session.startup-cwd-missing-dir-recovery",
"title": "Fresh local terminal creation cannot be bricked by a deleted startup cwd",
"title": "Terminal working-directory recovery stays scoped and provider-safe",
"maturity": "experimental",
"protection": "partial",
"owner": "terminal-runtime",
"layer": "shared-main-renderer-contract",
"surfaces": ["terminal lifecycle", "tab creation", "PTY spawn", "startup cwd persistence"],
"layer": "shared-main-renderer-and-posix-shell-contract",
"surfaces": [
"terminal lifecycle",
"tab creation",
"PTY spawn",
"startup cwd persistence",
"long-lived Bash and Zsh OMP launches"
],
"platforms": ["macos", "linux", "windows", "mobile"],
"providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"],
"coveredPlatforms": ["macos"],
"coveredProviders": ["local", "ssh", "remote-runtime"],
"coverageNotes": "Local macOS evidence covers the shared missing-dir fallback policy, main pty:spawn recovery and metadata, no-flag and reattach strictness, renderer IPC flag routing, SSH-tagged and remote-runtime omission, and the visibility-gated terminal notice. Daemon shares the same pre-provider main cwd decision but lacks a live daemon-provider run; WSL UNC paths are exempt from the probe by design and lack a live run; Linux/Windows and mobile/API strictness are gaps.",
"coverageNotes": "Local macOS evidence covers the shared missing-dir fallback policy, main pty:spawn recovery and metadata, no-flag and reattach strictness, renderer IPC flag routing, SSH-tagged and remote-runtime omission, and the visibility-gated terminal notice. Real Bash and Zsh node-pty tests replace the active cwd inode and exercise the shared POSIX OMP wrapper; generated-file snapshots pin identical local, daemon/SSH, and relay wrappers. Daemon shares the same pre-provider main cwd decision but lacks a live daemon-provider run; WSL UNC paths are exempt from the spawn probe and lack a live run; native PowerShell is unaffected; Linux/Windows and mobile/API strictness remain gaps.",
"motivatingLinks": [
"https://github.com/stablyai/orca/issues/7239",
"https://github.com/stablyai/orca/pull/7750",
"https://github.com/stablyai/orca/pull/7678"
"https://github.com/stablyai/orca/pull/7678",
"https://github.com/stablyai/orca/issues/16457",
"https://github.com/stablyai/orca/pull/17128"
],
"invariant": "A fresh local renderer terminal spawn may recover from a saved startup cwd whose directory no longer exists only by spawning at the selected workspace root and printing a generic in-terminal notice; existing directories — including ones outside the worktree (#7685) — spawn as requested, and reattach, SSH, remote-runtime, runtime/API, and mobile callers keep exact cwd semantics.",
"oracle": "The shared resolver falls back to the workspace root only when the injected existence probe reports the resolved cwd missing and the workspace root present, and never probes floating terminals or a cwd equal to the root. The renderer sends cwdFallback only for fresh local IPC spawns, main honors it only when connectionId and sessionId are absent, WSL UNC paths never engage the probe-based fallback, main returns fallback metadata only after an actual fallback, the IPC transport preserves that metadata, and the connection layer writes a generic notice that omits the missing path.",
"invariant": "A fresh local renderer terminal spawn may recover from a saved startup cwd whose directory no longer exists only by spawning at the selected workspace root and printing a generic in-terminal notice; existing directories — including ones outside the worktree (#7685) — spawn as requested, and reattach, SSH, remote-runtime, runtime/API, and mobile callers keep exact cwd semantics. A long-lived POSIX shell whose cwd inode was deleted and replaced may run a wrapped OMP command from the live path named by its logical PWD only in a subshell; the parent shell cwd, OMP argv, status extension, and exit status remain unchanged, while a genuinely unavailable path fails visibly without invoking OMP. A usable physical cwd remains authoritative when PWD is unset or names a removed logical symlink instead of being remapped to a workspace fallback.",
"oracle": "The shared resolver falls back to the workspace root only when the injected existence probe reports the resolved cwd missing and the workspace root present, and never probes floating terminals or a cwd equal to the root. The renderer sends cwdFallback only for fresh local IPC spawns, main honors it only when connectionId and sessionId are absent, WSL UNC paths never engage the probe-based fallback, main returns fallback metadata only after an actual fallback, the IPC transport preserves that metadata, and the connection layer writes a generic notice that omits the missing path. In real interactive Bash and Zsh PTYs, unset PWD and remove a logical symlink while the physical cwd stays usable, requiring OMP to remain there; then delete and recreate the active project path, require extension-enabled and extension-skipping invocations to observe the replacement inode with byte-exact argv and nonzero status preserved, require the parent shell to remain on the stale inode, unset PWD and require recovery through ORCA_WORKTREE_PATH, clear every logical fallback and require an actionable failure without invoking OMP, and finally delete the replacement and require the same for the missing PWD path.",
"commands": [
"pnpm exec vitest run --config config/vitest.config.ts src/shared/terminal-startup-cwd.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/pty-spawn-cwd-fallback.test.ts src/main/ipc/pty-wsl-cwd-validation.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-transport-connect-spawn.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-connection-runtime-owner-spawn-routing.test.ts"
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-connection-runtime-owner-spawn-routing.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/pty/omp-shell-wrapper.node-pty.test.ts src/main/pty/omp-shell-wrapper-alias-safety.test.ts src/main/shell-wrapper-generated-file-snapshot.test.ts"
],
"testFiles": [
"src/shared/terminal-startup-cwd.test.ts",
"src/main/ipc/pty-spawn-cwd-fallback.test.ts",
"src/main/ipc/pty-wsl-cwd-validation.test.ts",
"src/renderer/src/components/terminal-pane/pty-transport-connect-spawn.test.ts",
"src/renderer/src/components/terminal-pane/pty-connection-runtime-owner-spawn-routing.test.ts"
"src/renderer/src/components/terminal-pane/pty-connection-runtime-owner-spawn-routing.test.ts",
"src/main/pty/omp-shell-wrapper.node-pty.test.ts",
"src/main/pty/omp-shell-wrapper-alias-safety.test.ts",
"src/main/shell-wrapper-generated-file-snapshot.test.ts"
],
"assertionRefs": [
{
@@ -6392,6 +6404,25 @@
"startup cwd fallback metadata prints a generic in-terminal notice",
"remote-runtime worktree spawns are not marked with cwdFallback"
]
},
{
"file": "src/main/pty/omp-shell-wrapper.node-pty.test.ts",
"assertions": [
"real Bash and Zsh PTYs rebind OMP to a recreated cwd inode without changing the parent shell",
"an unset PWD with a usable cwd stays unset and does not remap OMP to ORCA_WORKTREE_PATH",
"a removed logical symlink does not invalidate its still-usable physical cwd",
"extension-skipping commands such as omp --version still recover the cwd without gaining extension argv",
"an unset PWD on a stale inode recovers through ORCA_WORKTREE_PATH",
"an unset PWD on a stale inode without a logical fallback fails visibly before invoking OMP",
"extension argv and a nonzero OMP exit status survive the recovery subshell",
"a subsequently missing logical cwd prints an actionable error and does not invoke OMP again"
]
},
{
"file": "src/main/shell-wrapper-generated-file-snapshot.test.ts",
"assertions": [
"local, daemon/SSH, and relay Bash/Zsh files contain the byte-identical shared OMP wrapper"
]
}
],
"evidenceRuns": [
@@ -6430,11 +6461,20 @@
"result": "passed",
"durationSeconds": 6.5,
"summary": "1 test file passed, 341 tests passed; covers local IPC marking, the generic terminal fallback notice, and remote-runtime omission."
},
{
"date": "2026-08-29",
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/pty/omp-shell-wrapper.node-pty.test.ts src/main/pty/omp-shell-wrapper-alias-safety.test.ts src/main/shell-wrapper-generated-file-snapshot.test.ts",
"result": "passed",
"durationSeconds": 3,
"summary": "3 test files passed; real Bash and Zsh PTYs covered usable physical cwd authority, stale-inode recovery with set and unset PWD, extension-enabled and extension-skipping argv, parent-shell isolation, exit status, missing-path visibility, alias safety, and generated local/daemon/relay parity."
}
],
"runtimeBudget": {
"p95Seconds": 30,
"scope": "focused unit and IPC contract tests"
"p95Seconds": 40,
"scope": "focused unit, IPC, generated-wrapper, and real Bash/Zsh PTY contracts"
},
"flakeHistory": {
"status": "unknown",
@@ -6442,23 +6482,25 @@
},
"redGreenEvidence": {
"status": "partial",
"evidence": "The main IPC missing-cwd tests fail with the provider's 'Working directory ... does not exist.' error when the fallback is removed and pass with it. Full live Electron reproduction from a production persisted session is not captured."
"evidence": "The main IPC missing-cwd tests fail with the provider's 'Working directory ... does not exist.' error when the spawn fallback is removed and pass with it. Before the OMP recovery, the stale-inode reproduction makes the child process fail to resolve its cwd; the candidate rebinds to the recreated path in real Bash and Zsh PTYs. Full live Electron reproduction from a production persisted session is not captured."
},
"performanceBudget": {
"required": true,
"evidence": "The runtime change adds at most two statSync probes on the fresh-local spawn path (the provider already stats the same paths during validation) and one bounded terminal write only when fallback actually occurs; no polling, provider listing, hidden-pane work, startup awaits, subprocesses, or render-loop work was added."
"evidence": "The spawn recovery adds at most two statSync probes on the fresh-local spawn path (the provider already stats the same paths during validation) and one bounded terminal write only when fallback actually occurs. Each wrapped OMP launch adds a constant number of shell filesystem predicates; only a detected stale/unusable cwd creates one short recovery subshell. No polling, retry, provider listing, hidden-pane work, startup awaits, extra OMP subprocesses, or render-loop work was added."
},
"promotionCriteria": [
"Attach CI evidence for all declared test files.",
"Add a live Electron regression that opens a local terminal whose persisted startupCwd was deleted and proves visible shell input/output at the workspace root.",
"Add WSL/mobile/API provider-contract coverage or explicitly narrow their risk scope."
"Add a live stale-inode OMP run on Linux or WSL and through an SSH/relay-generated wrapper.",
"Add WSL/mobile/API spawn provider-contract coverage or explicitly narrow their risk scope."
],
"knownGaps": [
"No live Electron fixture seeds a persisted tab whose startupCwd directory was deleted.",
"Daemon coverage is via the shared pre-provider main cwd decision, not a live daemon provider spawn.",
"WSL UNC paths bypass the probe by design and have no live existence-recovery run; Linux, Windows, and mobile/API strictness are not directly exercised."
"The stale-inode OMP oracle is local macOS Bash/Zsh plus shared generated-file parity; it does not execute through live SSH, relay, Linux, or WSL providers.",
"WSL UNC paths bypass the spawn probe by design and have no live existence-recovery run; Linux, Windows, and mobile/API spawn strictness are not directly exercised."
],
"demotionRule": "Demote or quarantine if the gate flakes without a product bug, if an existing directory is ever remapped away from the requested cwd, or if a reattach/remote/API caller can engage the fallback."
"demotionRule": "Demote or quarantine if the gate flakes without a product bug, if an existing directory is ever remapped away from the requested cwd, if a reattach/remote/API caller can engage the spawn fallback, or if OMP recovery remaps a usable cwd with unset PWD, changes argv/status/parent cwd, or invokes OMP when the logical path remains unavailable."
},
{
"id": "agent-status.pi-hook-liveness",
@@ -37,4 +37,16 @@ describe('client-hosted browser package coverage', () => {
expect(windowsStep.run).toContain(file)
}
})
// Why pinned: each of those files launches a full Electron stack twice under its own in-process
// deadline. Letting the runner interleave four of them starved the probes past those deadlines,
// which is the only way this step has ever failed.
it('gives each Linux Electron probe the runner to itself', () => {
const parsedWorkflow = parse(readFileSync(join(projectDir, '.github/workflows/pr.yml'), 'utf8'))
const linuxStep = parsedWorkflow.jobs.package.steps.find(
(step) => step.name === 'Test Linux Electron lifecycle boundary'
)
expect(linuxStep.run).toContain('--no-file-parallelism')
})
})
+39 -18
View File
@@ -12,7 +12,7 @@ describe('computer-use e2e workflow', () => {
expect(config).toContain('fileParallelism: false')
})
it('guards e2e source against fragile fixed waits and stale element indexes', () => {
it('guards e2e source against fragile waits and Windows Calculator drift', () => {
const driver = readFileSync(join(projectDir, 'tests/e2e/helpers/computer-driver.ts'), 'utf8')
const cliDriver = readFileSync(
join(projectDir, 'tests/e2e/helpers/computer-cli-driver.ts'),
@@ -31,11 +31,14 @@ describe('computer-use e2e workflow', () => {
expect(cliDriver).toContain('Could not read Orca runtime metadata')
expect(cliDriver).toContain("'serve', '--no-pairing', '--json'")
expect(windowsStoreE2e).toMatch(
/for \(const buttonName of \['One', 'Plus', 'Two', 'Equals'\]\) \{[\s\S]*findRoleIndex\(state\.result\.snapshot\.treeText, `button \$\{buttonName\}`\)[\s\S]*state = parseJsonOutput/
expect(windowsStoreE2e).toContain("app.bundleId === 'ApplicationFrameHost'")
expect(windowsStoreE2e).toContain("app.bundleId === 'win32calc'")
expect(windowsStoreE2e).toContain('buttonIndex >= 0')
expect(windowsStoreE2e).toContain('pane(?:\\s|$)/m')
expect(windowsStoreE2e).toContain('String(clickIndex)')
expect(windowsStoreE2e).not.toContain(
"for (const buttonName of ['One', 'Plus', 'Two', 'Equals'])"
)
expect(windowsStoreE2e).not.toMatch(/const one = findRoleIndex/)
expect(windowsStoreE2e).not.toMatch(/for \(const index of \[one, plus, two, equals\]\)/)
})
it('triggers on computer-use shared contracts, scripts, and agent skill changes', () => {
@@ -118,7 +121,7 @@ describe('computer-use e2e workflow', () => {
}
})
it('builds and tests the macOS helper on pull requests without TCC e2e', () => {
it('builds and tests the macOS helper on every trigger without hosted TCC e2e', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
@@ -129,7 +132,7 @@ describe('computer-use e2e workflow', () => {
(step) => step.uses === './.github/actions/install-node-dependencies'
)
expect(job.if).toBe("github.event_name == 'pull_request'")
expect(job.if).toBeUndefined()
expect(job['runs-on']).toBe('macos-15')
expect(checkout.with['persist-credentials']).toBe(false)
expect(install.with['native-runtime']).toBe('electron')
@@ -142,6 +145,7 @@ describe('computer-use e2e workflow', () => {
)
expect(runs).toContain('pnpm verify:computer-native')
expect(runs.join('\n')).not.toContain('test:e2e:computer')
expect(workflow.jobs.mac).toBeUndefined()
expect(workflow.on.pull_request.paths).toEqual(
expect.arrayContaining([
'config/scripts/macos-computer-helper-owner-loss-benchmark.mjs',
@@ -154,6 +158,29 @@ describe('computer-use e2e workflow', () => {
)
})
it('uses the cached Electron dependency path for scheduled Linux and Windows e2e', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
for (const jobName of ['linux', 'windows']) {
const job = workflow.jobs[jobName]
const checkout = job.steps.find((step) => step.uses === 'actions/checkout@v6')
const install = job.steps.find(
(step) => step.uses === './.github/actions/install-node-dependencies'
)
expect(checkout.with['persist-credentials'], jobName).toBe(false)
expect(install.with['native-runtime'], jobName).toBe('electron')
expect(
job.steps.some((step) => step.uses === 'pnpm/setup@v2'),
jobName
).toBe(false)
expect(
job.steps.some((step) => step.run === 'pnpm install --frozen-lockfile'),
jobName
).toBe(false)
}
})
it('runs deterministic macOS owner-loss benchmark cleanup coverage', () => {
const benchmark = readFileSync(
join(projectDir, 'config/scripts/macos-computer-helper-owner-loss-benchmark.mjs'),
@@ -244,7 +271,7 @@ describe('computer-use e2e workflow', () => {
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
for (const jobName of ['native-smoke', 'mac', 'linux', 'windows']) {
for (const jobName of ['native-smoke', 'linux', 'windows']) {
const runs = workflow.jobs[jobName].steps
.map((step) => step.run)
.filter((run) => typeof run === 'string')
@@ -274,7 +301,6 @@ describe('computer-use e2e workflow', () => {
.filter((run) => typeof run === 'string')
const allRuns = [
...nativeSmokeRuns,
...workflow.jobs.mac.steps.map((step) => step.run).filter((run) => typeof run === 'string'),
...workflow.jobs.linux.steps.map((step) => step.run).filter((run) => typeof run === 'string'),
...workflow.jobs.windows.steps
.map((step) => step.run)
@@ -286,30 +312,25 @@ describe('computer-use e2e workflow', () => {
expect(allRuns.join('\n')).not.toContain('test:e2e:computer -- --reporter')
})
it('runs macOS and Linux computer-use e2e files in scheduled jobs', () => {
it('runs Linux e2e on schedule without advertising hosted macOS TCC coverage', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
const triggerPaths = workflow.on.pull_request.paths
const macRuns = workflow.jobs.mac.steps
.map((step) => step.run)
.filter((run) => typeof run === 'string')
const linuxRuns = workflow.jobs.linux.steps
.map((step) => step.run)
.filter((run) => typeof run === 'string')
expect(triggerPaths).toEqual(
expect.arrayContaining([
'tests/e2e/computer-mac.e2e.ts',
'tests/e2e/computer-mac-safari.e2e.ts',
'tests/e2e/computer-linux.e2e.ts',
'tests/e2e/helpers/computer-cli-driver.ts',
'tests/e2e/helpers/computer-driver.ts'
])
)
expect(macRuns).toContain(
'pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-mac.e2e.ts tests/e2e/computer-mac-safari.e2e.ts'
)
expect(triggerPaths).not.toContain('tests/e2e/computer-mac.e2e.ts')
expect(triggerPaths).not.toContain('tests/e2e/computer-mac-safari.e2e.ts')
expect(workflow.jobs.mac).toBeUndefined()
expect(linuxRuns).toContain(
'xvfb-run --auto-servernum dbus-run-session -- pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-linux.e2e.ts'
)
+2 -1
View File
@@ -377,7 +377,8 @@ function rebuildNodeRuntimeModules(moduleNames) {
}
function runPnpm(args, { cwd = projectDir } = {}) {
const command = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
// cmd.exe resolves both Corepack's pnpm.cmd and pnpm 12's native pnpm.exe.
const command = 'pnpm'
const env =
process.platform === 'linux' && args.includes('node-gyp')
? { ...process.env, CXXFLAGS: `${process.env.CXXFLAGS ?? ''} -std=gnu++2a`.trim() }
@@ -24,10 +24,15 @@ function createFixture() {
for (const directory of [workspace, detachedCwd]) {
writeFileSync(join(directory, 'package.json'), '{"name":"fixture"}\n')
writeFileSync(join(directory, 'pnpm-lock.yaml'), 'lockfileVersion: 9\n')
writeFileSync(join(directory, 'pnpm-workspace.yaml'), 'packages: []\n')
}
expect(run('git', ['init', '-q'], { cwd: workspace }).status).toBe(0)
expect(run('git', ['add', 'package.json', 'pnpm-lock.yaml'], { cwd: workspace }).status).toBe(0)
expect(
run('git', ['add', 'package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'], {
cwd: workspace
}).status
).toBe(0)
const pnpm = join(bin, 'pnpm')
writeFileSync(pnpm, '#!/bin/sh\nexit 0\n')
@@ -76,7 +81,8 @@ describe('install-node-dependencies action', () => {
it.each([
['package.json', '{"name":"changed"}\n'],
['pnpm-lock.yaml', 'lockfileVersion: 9\nchanged: true\n']
['pnpm-lock.yaml', 'lockfileVersion: 9\nchanged: true\n'],
['pnpm-workspace.yaml', 'packages: []\nchanged: true\n']
])('rejects a changed %s when the composite step cwd is detached', (file, contents) => {
const fixture = createFixture()
try {
@@ -5032,5 +5032,50 @@
},
"auto.components.right.sidebar.AiVaultPanel.noAgentsSelected": {
"ko": "선택된 에이전트가 없습니다"
},
"dashboardPopout.title": {
"ko": "에이전트"
},
"dashboardPopout.total": {
"ko": "총 {{count}}개"
},
"dashboardPopout.close": {
"ko": "대시보드 닫기"
},
"dashboardPopout.placeholder.title": {
"ko": "에이전트 대시보드"
},
"dashboardPopout.placeholder.description": {
"ko": "모든 에이전트를 한눈에 볼 수 있는 곳입니다. 보드는 곧 제공됩니다."
},
"dashboardPopout.recoverableError.title": {
"ko": "Orca 대시보드에서 오류가 발생했습니다."
},
"dashboardPopout.recoverableError.description": {
"ko": "대시보드 렌더링을 완료하지 못했습니다. 다시 시도해 다시 마운트하거나 다시 열어 보세요."
},
"dashboardPopout.bucket.attention": {
"ko": "확인 필요"
},
"dashboardPopout.bucket.working": {
"ko": "작업 중"
},
"dashboardPopout.bucket.idle": {
"ko": "유휴"
},
"dashboardPopout.bucket.empty": {
"ko": "없음"
},
"dashboardPopout.card.you": {
"ko": "나"
},
"dashboardPopout.terminal.closed": {
"ko": "실시간 터미널이 없습니다 — 이 에이전트의 창이 닫혔습니다."
},
"dashboardPopout.terminal.focusWorktree": {
"ko": "워크트리 열기"
},
"dashboardPopout.terminal.close": {
"ko": "닫기"
}
}
@@ -8,14 +8,13 @@ import { relayArtifactFilenames } from '../../src/shared/relay-artifacts.ts'
const projectDir = resolve(import.meta.dirname, '../..')
const require = createRequire(import.meta.url)
const { createPackagedRuntimeNodeModuleResources } = require('../packaged-runtime-node-modules.cjs')
const packageJson = JSON.parse(readFileSync(join(projectDir, 'package.json'), 'utf8'))
const readProject = (file) => readFileSync(join(projectDir, file), 'utf8')
const packageJson = JSON.parse(readProject('package.json'))
const pnpmWorkspace = parse(readProject('pnpm-workspace.yaml'))
describe('Electron runtime package contract', () => {
it('keeps shared WebGL atlas invalidation reproducible from vendored source', () => {
const patch = readFileSync(
join(projectDir, 'config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch'),
'utf8'
)
const patch = readProject('config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch')
expect(patch).toContain('readonly clearModelGeneration: number')
expect(patch).toContain('const generation = this._atlas.clearModelGeneration')
@@ -26,7 +25,7 @@ describe('Electron runtime package contract', () => {
it('keeps root postinstall as the single Electron binary install owner', () => {
expect(packageJson.scripts.postinstall).toBe('node config/scripts/rebuild-native-deps.mjs')
expect(packageJson.pnpm.onlyBuiltDependencies).not.toContain('electron')
expect(pnpmWorkspace.allowBuilds).not.toHaveProperty('electron')
})
it('keeps the native Windows registry addon optional and platform-gated', () => {
@@ -41,7 +40,7 @@ describe('Electron runtime package contract', () => {
expect(packageJson.optionalDependencies['windows-native-registry']).toBe('3.2.2')
// Why: pnpm installs optional target architectures on every host; the root
// Windows-only rebuild owns this addon so macOS/Linux never run node-gyp for it.
expect(packageJson.pnpm.onlyBuiltDependencies).not.toContain('windows-native-registry')
expect(pnpmWorkspace.allowBuilds['windows-native-registry']).toBe(false)
// Why assert the guard and the member separately: the list now carries more
// than one addon, so pinning the whole literal only tested its formatting.
expect(rebuildScript).toContain("rebuildPlatform === 'win32'")
@@ -80,14 +79,14 @@ describe('Electron runtime package contract', () => {
expect(packageJson.optionalDependencies['@vscode/windows-process-tree']).toBe('0.8.0')
// Why: same rule as the registry addon -- pnpm installs optional deps on
// every host, so macOS/Linux must never run node-gyp for a Windows addon.
expect(packageJson.pnpm.onlyBuiltDependencies).not.toContain('@vscode/windows-process-tree')
expect(pnpmWorkspace.allowBuilds['@vscode/windows-process-tree']).toBe(false)
expect(rebuildScript).toContain("'@vscode/windows-process-tree'")
expect(ensureScript).toContain("'@vscode/windows-process-tree'")
// Why pin the patch: the upstream binding.gyp requires Spectre-mitigated
// libraries our build agents do not carry, and the enumeration stops after
// 1024 processes -- on a busy host that silently hides the very descendants
// teardown is looking for.
expect(packageJson.pnpm.patchedDependencies['@vscode/windows-process-tree@0.8.0']).toBe(
expect(pnpmWorkspace.patchedDependencies['@vscode/windows-process-tree@0.8.0']).toBe(
'config/patches/@vscode__windows-process-tree@0.8.0.patch'
)
const packageTargets = {
@@ -138,6 +138,12 @@ describe('PR E2E gate contract', () => {
expect(e2eWorkflow.jobs.e2e.if).toBe("inputs.test_files == ''")
expect(e2eWorkflow.jobs['changed-e2e'].if).toBe("inputs.test_files != ''")
expect(e2eWorkflow.jobs['changed-e2e'].strategy).toBeUndefined()
expect(e2eWorkflow.jobs.e2e.strategy.matrix.include).toEqual(
Array.from({ length: 14 }, (_, index) => ({
shard: `${index + 1}/14`,
shard_name: `${index + 1}-of-14`
}))
)
const changedRun = e2eWorkflow.jobs['changed-e2e'].steps.find(
(step) => step.name === 'Run changed E2E specs'
)
@@ -9,11 +9,13 @@ const dependencyAction = parse(
readFileSync('.github/actions/install-node-dependencies/action.yml', 'utf8')
)
const packageJson = JSON.parse(readFileSync('package.json', 'utf8'))
const pnpmWorkspace = parse(readFileSync('pnpm-workspace.yaml', 'utf8'))
const shellContractFiles = [
'src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts',
'src/main/daemon/shell-ready.test.ts',
'src/main/providers/local-pty-shell-ready-zsh-launch-environment.test.ts',
'src/main/providers/__tests__/shell-ready-framework-example.test.ts',
'src/main/pty/omp-shell-wrapper.node-pty.test.ts',
'src/main/shell-startup-feature-channel.test.ts',
'src/main/zsh-scoped-histfile.live-shell.test.ts',
'src/main/zsh-startup-hook-user-config-equivalence.live-shell.test.ts',
@@ -22,7 +24,6 @@ const shellContractFiles = [
]
const patchedNodePtyContractFiles = [
'src/main/daemon/node-pty-fd-leak.test.ts',
'src/main/pty/omp-shell-wrapper.node-pty.test.ts',
'src/shared/fish-query-reply-child-stdin.node-pty.test.ts'
]
const nativeShellContractFiles = [...shellContractFiles, ...patchedNodePtyContractFiles]
@@ -224,7 +225,10 @@ describe('PR workflow parallelism', () => {
expect(pnpmIndex).toBeLessThan(nodeIndex)
expect(pnpmIndex).toBeLessThan(requestedNodeIndex)
const packageManagerVersion = /^pnpm@([^+]+)/.exec(packageJson.packageManager)?.[1]
expect(steps[pnpmIndex].with.version).toBe(packageManagerVersion)
expect(packageManagerVersion).toBe('12.0.0')
expect(steps[pnpmIndex].uses).toBe('pnpm/setup@v2')
expect(steps[pnpmIndex].with.version).toBeUndefined()
expect(steps[pnpmIndex].with.install).toBe(false)
expect(steps[nodeIndex].with.cache).toBe('pnpm')
expect(steps[nodeIndex].if).toBe("inputs.node-version == ''")
expect(steps[requestedNodeIndex].if).toBe("inputs.node-version != ''")
@@ -232,6 +236,23 @@ describe('PR workflow parallelism', () => {
expect(steps[requestedNodeIndex].with.cache).toBe('pnpm')
})
it('uses the repository package-manager version for every direct pnpm setup', () => {
const directSetups = globSync('.github/workflows/*.yml').flatMap((workflowPath) => {
const parsed = parse(readFileSync(workflowPath, 'utf8'))
return Object.values(parsed.jobs ?? {}).flatMap((job) =>
(job.steps ?? [])
.filter((step) => step.uses === 'pnpm/setup@v2')
.map((step) => ({ workflowPath, step }))
)
})
expect(directSetups.length).toBeGreaterThan(0)
for (const { workflowPath, step } of directSetups) {
expect(step.with?.version, workflowPath).toBeUndefined()
expect(step.with?.install, workflowPath).toBe(false)
}
})
it('restores Electron downloads before preparing the package runtime', () => {
const steps = workflow.jobs.package.steps
const cacheIndex = steps.findIndex((step) => step.name === 'Cache electron-builder downloads')
@@ -276,15 +297,15 @@ describe('PR workflow parallelism', () => {
expect(dependencyInstall.run).toContain('--frozen-lockfile')
expect(dependencyInstall.run).not.toContain('--no-frozen-lockfile')
expect(dependencyInstall.run).toContain(
'git -C "$GITHUB_WORKSPACE" diff --exit-code -- package.json pnpm-lock.yaml'
'git -C "$GITHUB_WORKSPACE" diff --exit-code -- package.json pnpm-lock.yaml pnpm-workspace.yaml'
)
expect(dependencyInstall.run).toContain('--ignore-scripts')
expect(dependencyInstall.run).not.toContain('--os=')
expect(dependencyInstall.run).not.toContain('--cpu=')
expect(packageJson.pnpm.supportedArchitectures.os).toEqual(
expect(pnpmWorkspace.supportedArchitectures.os).toEqual(
expect.arrayContaining(['current', 'win32'])
)
expect(packageJson.pnpm.supportedArchitectures.cpu).toContain('current')
expect(pnpmWorkspace.supportedArchitectures.cpu).toContain('current')
const prepareRuntime = dependencyAction.runs.steps.find(
(step) => step.name === 'Prepare native runtime'
)
+9 -8
View File
@@ -22,13 +22,15 @@ const DEFAULT_REPO_ROOT = path.resolve(import.meta.dirname, '..', '..')
const MANIFEST_RELATIVE_PATH = path.join('config', 'patches', 'xterm-upstream.json')
/**
* Flags pnpm@10 passes to `git diff` in its own `diffFolders()`. A patch built
* Flags pnpm@12 passes to `git diff` in its own `diff_folders()`. A patch built
* with anything else is a patch pnpm may re-diff differently on the next
* `pnpm patch-commit`, so the byte-comparison gate would never settle.
*/
export const PNPM_DIFF_FLAGS = [
'-c',
'core.safecrlf=false',
'-c',
'core.quotePath=false',
'diff',
'--src-prefix=a/',
'--dst-prefix=b/',
@@ -38,7 +40,8 @@ export const PNPM_DIFF_FLAGS = [
'--no-index',
'--text',
'--no-ext-diff',
'--no-color'
'--no-color',
'--'
]
/**
@@ -47,14 +50,12 @@ export const PNPM_DIFF_FLAGS = [
*/
export const CHECKOUT_DIFF_FLAGS = PNPM_DIFF_FLAGS.filter((flag) => flag !== '--no-index')
/** Blanks the vars pnpm blanks so user and system git config cannot reach the diff. */
/** Applies pnpm's git config isolation so local machine settings cannot change the patch. */
export function pnpmDiffEnvironment(baseEnvironment = process.env) {
return {
...baseEnvironment,
GIT_CONFIG_NOSYSTEM: '1',
HOME: '',
XDG_CONFIG_HOME: '',
USERPROFILE: ''
GIT_CONFIG_GLOBAL: '/dev/null'
}
}
@@ -216,7 +217,7 @@ export function patchHash(patchText) {
function lockfilePatchHashPattern(packageKey) {
// Unscoped keys such as `node-pty@1.1.0` are emitted unquoted.
return new RegExp(`(^ '?${escapeRegExp(packageKey)}'?:\\n hash: )([0-9a-f]{64})$`, 'm')
return new RegExp(`(^ '?${escapeRegExp(packageKey)}'?: )([0-9a-f]{64})$`, 'm')
}
export function readLockfilePatchHash(lockfileText, packageKey) {
@@ -507,7 +508,7 @@ function diffFolders(folderA, folderB) {
/** The source of truth for the hand-written half: what the checkout itself holds. */
function diffCheckoutSource(packageRoot) {
return run('git', [...CHECKOUT_DIFF_FLAGS, '--', 'src/'], {
return run('git', [...CHECKOUT_DIFF_FLAGS, 'src/'], {
cwd: packageRoot,
env: pnpmDiffEnvironment(),
maxBuffer: 64 * 1024 * 1024
@@ -98,6 +98,8 @@ describe('pnpm diff format', () => {
expect(PNPM_DIFF_FLAGS).toEqual([
'-c',
'core.safecrlf=false',
'-c',
'core.quotePath=false',
'diff',
'--src-prefix=a/',
'--dst-prefix=b/',
@@ -107,18 +109,18 @@ describe('pnpm diff format', () => {
'--no-index',
'--text',
'--no-ext-diff',
'--no-color'
'--no-color',
'--'
])
})
it('blanks the config-bearing environment variables', () => {
it('matches pnpm git config isolation', () => {
const environment = pnpmDiffEnvironment({ PATH: '/usr/bin', HOME: '/Users/someone' })
expect(environment).toMatchObject({
PATH: '/usr/bin',
GIT_CONFIG_NOSYSTEM: '1',
HOME: '',
XDG_CONFIG_HOME: '',
USERPROFILE: ''
GIT_CONFIG_GLOBAL: '/dev/null',
HOME: '/Users/someone'
})
})
@@ -350,12 +352,8 @@ describe('manifest guards', () => {
describe('lockfile coupling', () => {
const lockfile = [
'patchedDependencies:',
" '@xterm/xterm@6.1.0-beta.287':",
` hash: ${'0'.repeat(64)}`,
' path: config/patches/@xterm__xterm@6.1.0-beta.287.patch',
' node-pty@1.1.0:',
` hash: ${'1'.repeat(64)}`,
' path: config/patches/node-pty@1.1.0.patch',
` '@xterm/xterm@6.1.0-beta.287': ${'0'.repeat(64)}`,
` node-pty@1.1.0: ${'1'.repeat(64)}`,
'snapshots:',
` '@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=${'0'.repeat(64)}))':`,
` '@xterm/xterm': 6.1.0-beta.287(patch_hash=${'0'.repeat(64)})`,
@@ -396,7 +394,10 @@ describe('lockfile coupling', () => {
it('reports a lockfile stale in its resolution keys alone', () => {
const key = '@xterm/xterm@6.1.0-beta.287'
const halfUpdated = lockfile.replace(`hash: ${'0'.repeat(64)}`, `hash: ${'a'.repeat(64)}`)
const halfUpdated = lockfile.replace(
`'@xterm/xterm@6.1.0-beta.287': ${'0'.repeat(64)}`,
`'@xterm/xterm@6.1.0-beta.287': ${'a'.repeat(64)}`
)
expect(readLockfilePatchHash(halfUpdated, key)).toBe('a'.repeat(64))
expect(lockfilePatchHashIsStale(halfUpdated, key, 'a'.repeat(64))).toBe(true)
@@ -34,7 +34,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
import { join, resolve } from 'node:path'
// Why @electron/asar: canonical replacement for the deprecated `asar` package.
// It's transitively available via electron-builder (and pnpm's
// `shamefully-hoist=true` in `.npmrc` flattens it into the root
// `shamefullyHoist: true` in `pnpm-workspace.yaml` flattens it into the root
// `node_modules`). If electron-builder ever drops it, promote this to a
// direct devDependency in package.json.
import { extractFile, listPackage } from '@electron/asar'
@@ -1,6 +1,7 @@
import { createHash } from 'node:crypto'
import { readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { parseAllDocuments } from 'yaml'
import { describe, expect, it } from 'vitest'
const projectDir = resolve(import.meta.dirname, '../..')
@@ -16,7 +17,9 @@ describe('Windows process-tree patch contract', () => {
const patchHash = createHash('sha256')
.update(patchBytes.toString('utf8').replaceAll('\r\n', '\n'))
.digest('hex')
const lockfile = readFileSync(join(projectDir, 'pnpm-lock.yaml'), 'utf8')
expect(lockfile).toContain(`hash: ${patchHash}`)
const lockfile = parseAllDocuments(readFileSync(join(projectDir, 'pnpm-lock.yaml'), 'utf8'))
.map((document) => document.toJS())
.find((document) => document.patchedDependencies)
expect(lockfile.patchedDependencies['@vscode/windows-process-tree@0.8.0']).toBe(patchHash)
})
})