From 4e262fff14bf338c16e84427090f6db005118132 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:29:15 -0700 Subject: [PATCH] style: format codebase --- .github/CONTRIBUTING.md | 1 - .github/workflows/daily-mac-build.yml | 1 - .github/workflows/hourly-mac-build.yml | 1 - .github/workflows/pullfrog.yml | 3 +- AGENTS.md | 7 +- README.md | 1 + config/reliability-gates.jsonc | 8 +- config/scripts/build-orcad-prebuilds.mjs | 16 +- config/scripts/build-orcad-prebuilds.test.mjs | 8 +- config/scripts/node-pty-job-ownership.cjs | 6 +- docs/STYLEGUIDE.md | 28 +-- docs/reference/headless-linux-server.md | 2 +- docs/reference/linux-glibc-compatibility.md | 10 +- docs/reference/orcad-operations.md | 39 ++-- docs/reference/remote-wire-compatibility.md | 4 +- docs/reference/ssh-host-key-verification.md | 37 ++-- docs/reference/windows-process-enumeration.md | 29 +-- docs/reference/windows-setup-shell.md | 2 +- docs/reference/worktree-scan-fingerprint.md | 62 +++--- docs/reference/wsl-command-execution.md | 2 +- docs/reference/wsl-runner-verification.md | 30 +-- docs/reference/xterm-patch-regeneration.md | 2 +- skill-guides/orca-emulator-android.md | 28 +-- skill-guides/orca-emulator.md | 32 +-- skill-guides/orca-per-workspace-env.md | 24 ++- ...ubprocess-foreground-degraded-scan.test.ts | 21 +- src/main/orcad/native-host-abi.ts | 8 +- src/main/orcad/node-pty-prebuilt-slot.ts | 11 +- src/main/ssh/build-toolchain-diagnosis.ts | 1 - src/main/ssh/orcad-activation-record.ts | 4 +- src/main/ssh/orcad-remote-deploy.test.ts | 4 +- src/main/ssh/orcad-remote-deploy.ts | 29 ++- src/main/ssh/orcad-remote-gc.test.ts | 4 +- src/main/ssh/orcad-remote-launch.ts | 9 +- src/main/ssh/orcad-remote-rollback.test.ts | 5 +- src/main/ssh/orcad-remote-rollback.ts | 10 +- ...-remote-shell-commands.integration.test.ts | 6 +- src/main/ssh/remote-install-gc.ts | 5 +- src/main/ssh/ssh-relay-versioned-install.ts | 6 +- .../rich-markdown-tab-key-handler.test.ts | 1 - .../skills/SkillBundleInstallFlow.tsx | 19 +- .../components/skills/SkillInstallDialog.tsx | 31 ++- .../skills/SkillInstallManagementDialog.tsx | 57 ++++- tests/e2e/fixtures/terminal-emoji-table.md | 196 +++++++++--------- tests/e2e/ssh-config-host-picker.PLAN.md | 114 +++++----- tests/tools/daemon-relocation-spike/README.md | 10 +- .../repro-watcher-crash-7547/fixed-child.cjs | 33 ++- tests/tools/repro-watcher-crash-7547/run.cjs | 4 +- tests/tools/win-update-e2e/README.md | 36 ++-- 49 files changed, 556 insertions(+), 451 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c95cd52dda4..491d0cac794 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -112,7 +112,6 @@ All stable kinds (`patch`, `minor`, `major`) are computed off the latest _stable The scheduled 2x/day RC cron in [`release-rc.yml`](../../actions/workflows/release-rc.yml) is independent and continues to run automatically from `main`. - ## Release Channels The public Homebrew cask tracks stable desktop releases: diff --git a/.github/workflows/daily-mac-build.yml b/.github/workflows/daily-mac-build.yml index e445045e67c..2ef3baed2fd 100644 --- a/.github/workflows/daily-mac-build.yml +++ b/.github/workflows/daily-mac-build.yml @@ -308,7 +308,6 @@ jobs: echo "tag=$TAG" >>"$GITHUB_OUTPUT" echo "notes_file=$notes_file" >>"$GITHUB_OUTPUT" - - name: Publish daily macOS artifacts if: steps.freshness.outputs.should_build == 'true' uses: nick-fields/retry@v4 diff --git a/.github/workflows/hourly-mac-build.yml b/.github/workflows/hourly-mac-build.yml index 16e2f05579c..e072b0f9a10 100644 --- a/.github/workflows/hourly-mac-build.yml +++ b/.github/workflows/hourly-mac-build.yml @@ -290,7 +290,6 @@ jobs: including back to Stable, works in-app from there." echo "tag=$TAG" >>"$GITHUB_OUTPUT" - - name: Publish hourly macOS artifacts if: steps.freshness.outputs.should_build == 'true' uses: nick-fields/retry@v4 diff --git a/.github/workflows/pullfrog.yml b/.github/workflows/pullfrog.yml index 19401170c0e..d5030252f88 100644 --- a/.github/workflows/pullfrog.yml +++ b/.github/workflows/pullfrog.yml @@ -34,8 +34,7 @@ jobs: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GOOGLE_GENERATIVE_AI_API_KEY: - ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }} + GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }} DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} diff --git a/AGENTS.md b/AGENTS.md index 307c975d219..1fbce202438 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,13 +7,15 @@ All UI work — layout, color, typography, spacing, component selection, UX beha Use the `$electron` skill and Playwright CDP for rendered Orca UI checks. Do not use computer-use for Orca UI validation. # Style + ## Reuse Before Reimplementing Before writing new logic at any scale — a function, component, IPC channel, state store, or whole subsystem/flow — check whether an existing implementation already does the job (or nearly does). Extend or generalize it instead of building a parallel version; only write from scratch when nothing fits. Keep the check proportionate: a quick search for trivial code, a real one before building anything substantial. ## Concise/Brief Non-obvious Comments ONLY - * DO NOT: be verbose, explain the obvious, walk through the code ("WHY not HOW") - * BE CONCISE. 1 LINE if possible + +- DO NOT: be verbose, explain the obvious, walk through the code ("WHY not HOW") +- BE CONCISE. 1 LINE if possible ## Lint Rules: Do Not Disable Max Lines @@ -32,6 +34,7 @@ Never use vague names like `helpers`, `utils`, `common`, `misc`, or `shared-stuf - **Lint**: `oxlint`, or `pnpm run check:code-quality:changed` for changed files (full `pnpm lint` is slow); format with `pnpm format` # Considerations + ## Worktree Safety Always use the primary working directory (the worktree) for all file reads and edits. Never follow absolute paths from subagent results that point to the main repo. diff --git a/README.md b/README.md index 8de0ff8d2b9..82dfdbaa5c9 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,7 @@ Want to contribute or run locally? See our [CONTRIBUTING.md](.github/CONTRIBUTIN

## Signed Builds + Windows code signing sponored/provided by [SignPath.io](https://signpath.io), certificate by [SignPath Foundation](https://signpath.org). ## License diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 02bd0b02bc2..77e9d6e79ed 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -2148,9 +2148,7 @@ }, { "file": "src/cli/runtime/serve-signal-exit-diagnostic.test.ts", - "assertions": [ - "late update handoff failure cannot rearm termination after child exit" - ] + "assertions": ["late update handoff failure cannot rearm termination after child exit"] }, { "file": "src/main/serve-update-handoff.test.ts", @@ -16840,9 +16838,7 @@ }, { "file": "src/main/browser/agent-browser-bridge-tab-routing.test.ts", - "assertions": [ - "closing a tab retires the exact named agent-browser session" - ] + "assertions": ["closing a tab retires the exact named agent-browser session"] }, { "file": "src/main/startup/serve-signal-handlers.test.ts", diff --git a/config/scripts/build-orcad-prebuilds.mjs b/config/scripts/build-orcad-prebuilds.mjs index 679625b9f6d..efbafbe9a1b 100644 --- a/config/scripts/build-orcad-prebuilds.mjs +++ b/config/scripts/build-orcad-prebuilds.mjs @@ -132,12 +132,16 @@ function compileNodePty(dir) { return built } console.log('[orcad-prebuilds] compiling node-pty from patched source ...') - const result = spawnSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['node-gyp', 'rebuild'], { - cwd: dir, - stdio: 'inherit', - env: process.env, - windowsHide: true - }) + const result = spawnSync( + process.platform === 'win32' ? 'npx.cmd' : 'npx', + ['node-gyp', 'rebuild'], + { + cwd: dir, + stdio: 'inherit', + env: process.env, + windowsHide: true + } + ) if (result.status !== 0) { throw new Error(`[orcad-prebuilds] node-gyp rebuild failed (status ${result.status})`) } diff --git a/config/scripts/build-orcad-prebuilds.test.mjs b/config/scripts/build-orcad-prebuilds.test.mjs index 84c0e83c18f..c91c7e6cc9d 100644 --- a/config/scripts/build-orcad-prebuilds.test.mjs +++ b/config/scripts/build-orcad-prebuilds.test.mjs @@ -11,7 +11,8 @@ import { slotName } from './build-orcad-prebuilds.mjs' -const PATCHED_BINDING_GYP = "'ldflags': ['-Wl,--no-as-needed,-l:libutil.so.1,-l:libpthread.so.0,--as-needed']" +const PATCHED_BINDING_GYP = + "'ldflags': ['-Wl,--no-as-needed,-l:libutil.so.1,-l:libpthread.so.0,--as-needed']" const PATCHED_PTY_CC = '__asm__(".symver openpty,openpty@" ORCA_GLIBC_COMPAT_VERSION);' const dirs = [] @@ -107,8 +108,9 @@ describe('mergeManifest', () => { it('does not duplicate a slot rebuilt twice', () => { const once = mergeManifest(null, { slot: 'darwin-arm64', version: '1.1.0', nodeAbi: '127' }) - expect(mergeManifest(once, { slot: 'darwin-arm64', version: '1.1.0', nodeAbi: '127' }).slots) - .toEqual(['darwin-arm64']) + expect( + mergeManifest(once, { slot: 'darwin-arm64', version: '1.1.0', nodeAbi: '127' }).slots + ).toEqual(['darwin-arm64']) }) }) diff --git a/config/scripts/node-pty-job-ownership.cjs b/config/scripts/node-pty-job-ownership.cjs index 35884c1414a..5ad578fd74a 100644 --- a/config/scripts/node-pty-job-ownership.cjs +++ b/config/scripts/node-pty-job-ownership.cjs @@ -1,10 +1,6 @@ 'use strict' -const NODE_PTY_JOB_EXPORTS = [ - 'listJobProcessIds', - 'terminateJob', - 'assignCurrentProcessToJob' -] +const NODE_PTY_JOB_EXPORTS = ['listJobProcessIds', 'terminateJob', 'assignCurrentProcessToJob'] function assertNodePtyJobOwnership({ nativeName, native, platform = process.platform }) { if (platform !== 'win32' || nativeName !== 'conpty') { diff --git a/docs/STYLEGUIDE.md b/docs/STYLEGUIDE.md index 06be8eda659..e21aaa89228 100644 --- a/docs/STYLEGUIDE.md +++ b/docs/STYLEGUIDE.md @@ -137,20 +137,20 @@ Browse `src/renderer/src/components/ui/` for the full list. Most wrap a Radix UI When a control has multiple plausible primitives, use this fork: -| You want… | Reach for | Don't use | -| ------------------------------------------------------------ | -------------------------------------------------------------------- | ------------------------------------- | -| Hover-only label on an icon-only button | `Tooltip` | `HoverCard` (too heavy), title attr | -| Hover preview of richer content (avatar + summary) | `HoverCard` | `Tooltip` (no rich content) | -| Click-revealed menu with actions | `DropdownMenu` | `Popover` with hand-rolled list | -| Right-click contextual actions | `ContextMenu` | `DropdownMenu` (different invocation) | -| Click-revealed surface with arbitrary content (form, picker) | `Popover` | `Dialog` (it traps focus and dims) | -| Modal that demands a decision before you continue | `Dialog` | `Popover`, inline overlay | -| Drawer / panel sliding in from an edge | `Sheet` | `Dialog` centered | -| Single choice from a known list | `Select` | Custom listbox | -| Single choice with search / fuzzy filtering | `Command` inside `Popover` | `Select` (no search) | -| Multi-select with search | `repo-multi-combobox` (mirror its pattern) | Roll a new one | -| Transient confirmation ("Saved", "Copied") | `sonner` toast | `Dialog`, inline banner | -| Persistent inline status ("3 errors") | inline text + `Badge` | toast (toasts disappear) | +| You want… | Reach for | Don't use | +| ------------------------------------------------------------ | ------------------------------------------ | ------------------------------------- | +| Hover-only label on an icon-only button | `Tooltip` | `HoverCard` (too heavy), title attr | +| Hover preview of richer content (avatar + summary) | `HoverCard` | `Tooltip` (no rich content) | +| Click-revealed menu with actions | `DropdownMenu` | `Popover` with hand-rolled list | +| Right-click contextual actions | `ContextMenu` | `DropdownMenu` (different invocation) | +| Click-revealed surface with arbitrary content (form, picker) | `Popover` | `Dialog` (it traps focus and dims) | +| Modal that demands a decision before you continue | `Dialog` | `Popover`, inline overlay | +| Drawer / panel sliding in from an edge | `Sheet` | `Dialog` centered | +| Single choice from a known list | `Select` | Custom listbox | +| Single choice with search / fuzzy filtering | `Command` inside `Popover` | `Select` (no search) | +| Multi-select with search | `repo-multi-combobox` (mirror its pattern) | Roll a new one | +| Transient confirmation ("Saved", "Copied") | `sonner` toast | `Dialog`, inline banner | +| Persistent inline status ("3 errors") | inline text + `Badge` | toast (toasts disappear) | If you find yourself styling around a primitive (`` to act like a ``, or vice versa), stop and reconsider — the focus-management semantics differ and a future contributor will be misled by the mismatch. diff --git a/docs/reference/headless-linux-server.md b/docs/reference/headless-linux-server.md index 0e07c06ebf3..af47e452a82 100644 --- a/docs/reference/headless-linux-server.md +++ b/docs/reference/headless-linux-server.md @@ -838,7 +838,7 @@ refuse to run there and print the command to run on the machine you want. - Clients cannot connect: make sure `--pairing-address` is an address reachable from the client, and make sure firewalls allow the selected `--port`. - Journal shows `Another Orca instance is already running for this userData - profile` and the unit exits `3`: another process already owns the profile, so +profile` and the unit exits `3`: another process already owns the profile, so `RestartPreventExitStatus=3` leaves the unit `failed` on purpose. Find the owner with `systemctl status orca-serve` and `pgrep -af orca`. Stop it (or keep it and leave the unit down), then run diff --git a/docs/reference/linux-glibc-compatibility.md b/docs/reference/linux-glibc-compatibility.md index bf98dbdcb80..a11506235fe 100644 --- a/docs/reference/linux-glibc-compatibility.md +++ b/docs/reference/linux-glibc-compatibility.md @@ -25,11 +25,11 @@ broke launch on Ubuntu 20.04 ([#9902](https://github.com/stablyai/orca/issues/99 The specific trap is glibc's 2.32–2.34 "libpthread/libutil merge", which moved several long-stable functions into libc under brand-new symbol versions: -| Symbol | New version | node-pty use | -| ----------------- | ------------- | ----------------------- | -| `pthread_sigmask` | `GLIBC_2.32` | reset child signal mask | -| `openpty` | `GLIBC_2.34` | allocate the pty | -| `forkpty` | `GLIBC_2.34` | fork the shell | +| Symbol | New version | node-pty use | +| ----------------- | ------------ | ----------------------- | +| `pthread_sigmask` | `GLIBC_2.32` | reset child signal mask | +| `openpty` | `GLIBC_2.34` | allocate the pty | +| `forkpty` | `GLIBC_2.34` | fork the shell | Electron itself (glibc 2.25) and the other bundled native modules (`sherpa-onnx`, `@parcel/watcher`, both prebuilt on old glibc) stay well under diff --git a/docs/reference/orcad-operations.md b/docs/reference/orcad-operations.md index 3ef3fdea37a..bbde9829514 100644 --- a/docs/reference/orcad-operations.md +++ b/docs/reference/orcad-operations.md @@ -10,12 +10,12 @@ Design background: `docs/design/shipping-orcad.html` §00c and §04. A deployment is **orcad** plus **the terminal daemon**. -| | orcad | terminal daemon | -| --- | --- | --- | -| Started by | the supervisor | orcad, detached | -| Owns | RPC, git, worktrees, persistence | every local PTY | -| Lifetime | one supervised run | **outlives orcad** | -| Endpoint | `ws://:` | `/daemon/daemon-v.sock` | +| | orcad | terminal daemon | +| ---------- | -------------------------------- | ------------------------------------- | +| Started by | the supervisor | orcad, detached | +| Owns | RPC, git, worktrees, persistence | every local PTY | +| Lifetime | one supervised run | **outlives orcad** | +| Endpoint | `ws://:` | `/daemon/daemon-v.sock` | The daemon outliving orcad is the property the whole peer model is recommended for (`docs/reference/ssh-execution-boundary.md`): daemon-backed PTYs stay `live` across a runtime @@ -52,13 +52,13 @@ The data root is `$ORCA_USER_DATA`, else `$XDG_DATA_HOME/Orca`, else `~/.orca`. Before the profile index or the store is touched, orcad takes `/orcad.lock`. It refuses to start when: -| Code | Meaning | -| --- | --- | -| `orcad_data_root_wrong_owner` | the root is owned by another uid (POSIX) | -| `orcad_data_root_shared` | the root is group/world accessible and could not be tightened | -| `orcad_instance_lock_held` | another live orcad owns this root | -| `orcad_instance_lock_foreign_identity` | the lock belongs to a different identity | -| `orcad_data_root_unusable` | the root cannot be created, stat'd or written | +| Code | Meaning | +| -------------------------------------- | ------------------------------------------------------------- | +| `orcad_data_root_wrong_owner` | the root is owned by another uid (POSIX) | +| `orcad_data_root_shared` | the root is group/world accessible and could not be tightened | +| `orcad_instance_lock_held` | another live orcad owns this root | +| `orcad_instance_lock_foreign_identity` | the lock belongs to a different identity | +| `orcad_data_root_unusable` | the root cannot be created, stat'd or written | A root that is merely too permissive and that we own is tightened to `0700` rather than refused — orcad stores credentials there unsealed (no OS keyring on this host), so the goal @@ -91,14 +91,15 @@ An external supervisor (systemd, launchd, a process manager). orcad conforms to and exits 1, so the failure stays attributable instead of arriving as an unlogged kill. - **Exit codes.** - | Code | Meaning | Supervisor should | - | --- | --- | --- | - | 0 | clean shutdown | restart per policy | - | 1 | startup or shutdown failure | restart with backoff | - | 78 | configuration fault (bind address, data root, instance lock) | **not** restart | + | Code | Meaning | Supervisor should | + | ---- | ------------------------------------------------------------ | -------------------- | + | 0 | clean shutdown | restart per policy | + | 1 | startup or shutdown failure | restart with backoff | + | 78 | configuration fault (bind address, data root, instance lock) | **not** restart | 78 is `EX_CONFIG`. Put it in systemd's `RestartPreventExitStatus`: restarting on a data root owned by someone else is a restart-spin, not a recovery. + - **Logs.** orcad writes human-readable diagnostics to **stderr** and its readiness contract to **stdout**; the supervisor owns capture and rotation. The daemon, being detached, writes its own NDJSON lifecycle log to `/logs/daemon.log` (suppressed by @@ -110,7 +111,7 @@ An external supervisor (systemd, launchd, a process manager). orcad conforms to - **Launch.** Forked detached from `daemon-entry.js` beside `orcad.js`, with its own PID record, token and socket under `/daemon`. - **Adoption before spawn.** A daemon already answering the endpoint is adopted, not - replaced, unless it is unhealthy, foreign, or built from a superseded bundle *and* owns no + replaced, unless it is unhealthy, foreign, or built from a superseded bundle _and_ owns no live sessions. Replacing a healthy daemon kills its PTYs, so code freshness always defers to live work. - **Restart.** The adapter respawns the daemon on death, transparently to callers. diff --git a/docs/reference/remote-wire-compatibility.md b/docs/reference/remote-wire-compatibility.md index 33a72d68d99..9f3d8d66472 100644 --- a/docs/reference/remote-wire-compatibility.md +++ b/docs/reference/remote-wire-compatibility.md @@ -113,7 +113,7 @@ getting that wrong turns a skew into a false "nothing is blocked". unverifiable, the pane was unreadable, or the agent probe did not answer in time. A new client against an old host sees the field absent, which is why absence must read as -*unknown* and never as *not waiting*. Collapsing absent into `null` at any hop — including a +_unknown_ and never as _not waiting_. Collapsing absent into `null` at any hop — including a convenience `?? null` in an RPC handler — makes an old or unreachable peer indistinguishable from a healthy idle worker, which is the exact failure the field exists to remove. @@ -180,7 +180,7 @@ has to be there before the first snapshot is interpreted, which is earlier than renderer could wait on. Every other viewer — a second desktop, the web client, which installs no page renderer at all, the dashboard pop-out, which is deliberately left unstamped — keeps tracking the host, which is the only reason a mirrored viewer shows anything but its first snapshot -forever. Improving what a *second* client sees still means fixing the publish, not the carve-out; +forever. Improving what a _second_ client sees still means fixing the publish, not the carve-out; the carve-out no longer stands in the way of it. The two failure fields above are deliberately left on the looser `placement?.kind !== 'client'` diff --git a/docs/reference/ssh-host-key-verification.md b/docs/reference/ssh-host-key-verification.md index a84ea7ee4ea..8f0384897a3 100644 --- a/docs/reference/ssh-host-key-verification.md +++ b/docs/reference/ssh-host-key-verification.md @@ -28,11 +28,11 @@ Three corrections to the first draft: verified. The ssh2 proxy-spawn at `:697` is effectively unreachable. Good news for migration, and the first draft's motivating example was simply wrong. - **Agent forwarding was overstated.** `agentForward` is gated on the user's `ForwardAgent yes` - (`ssh-connection-utils.ts:203-205`). `config.agent` is always set, but that is agent *auth*, whose + (`ssh-connection-utils.ts:203-205`). `config.agent` is always set, but that is agent _auth_, whose signatures bind the session id and cannot be replayed onward. The risk applies to users who opted into `ForwardAgent`, not everyone. - **Credential theft was understated, and the relay claim was backwards.** `isAgentFallbackError` - treats *any* auth error as agent fallback (`ssh-connection-utils.ts:59-61`), so a MITM that rejects + treats _any_ auth error as agent fallback (`ssh-connection-utils.ts:59-61`), so a MITM that rejects publickey walks the user to the password prompt (`ssh-connection.ts:844`) and the private-key **passphrase** prompt (`:834`), and `cachedPassword` is replayed without prompting on every reconnect (`:709`). Meanwhile the relay upload matters less than assumed — the attacker already @@ -65,7 +65,7 @@ Two consequences to own rather than discover: There is no `ssh`-only way out of this: `-F /dev/null` does NOT invert the exclusion, it reports built-in defaults, so a probe built on it looks permissive on every machine. Verified against - OpenSSH 10.2p1. So the file is read directly, answering a deliberately weaker question — *could* + OpenSSH 10.2p1. So the file is read directly, answering a deliberately weaker question — _could_ the site config be restricting host keys — where anything ambiguous (unreadable, an unresolvable `Include`, the directive present at all) keeps the refusal. Only a site config that demonstrably says nothing about host keys clears it, which is what stops the rule punishing every devcontainer, @@ -130,16 +130,16 @@ connect, training them to dismiss the one warning that matters. > **Corrected against a live client.** The premise above is wrong about OpenSSH, though the > conclusion survives. `check_key_in_hostkeys` is not type-scoped at all: ANY non-marker entry for -> the host that is not byte-equal produces `HOST_CHANGED`. Verified on 127.0.0.1:2223 — known_hosts +> the host that is not byte-equal produces `HOST_CHANGED`. Verified on 127.0.0.1:2223 — known*hosts > holding only `ssh-rsa` against an ed25519-only server prints `IDENTIFICATION HAS CHANGED` and -> refuses. So ssh does not avoid the false alarm by scoping; it avoids the *situation* via +> refuses. So ssh does not avoid the false alarm by scoping; it avoids the \_situation* via > `order_hostkeyalgs`, and hard-fails when the situation arises anyway. Our split into `mismatch` > and `unknown-type-known-host` therefore only chooses the wording — both refuse, which is ssh's > action. What the ordering below buys us is what it buys ssh: the situation mostly never arises. **But scoping alone is a downgrade vector, and this is the correction that most changes the design.** OpenSSH is safe here only because `order_hostkeyalgs()` reorders the client's proposed host-key -algorithms to put the types already in `known_hosts` first, and RFC 4253 gives the *client's* order +algorithms to put the types already in `known_hosts` first, and RFC 4253 gives the _client's_ order priority — so a server cannot choose a type the client deprioritised. ssh2 negotiates ed25519 first regardless. An attacker who cannot forge the RSA key on file simply presents ed25519 and receives a friendly first-contact prompt instead of a hard failure. @@ -147,7 +147,7 @@ friendly first-contact prompt instead of a hard failure. Therefore: **set ssh2's `algorithms.serverHostKey` to lead with the key types already known for that host.** Type scoping without algorithm ordering is not a safe design. -And when the presented type is unknown *while other types are known for this host*, that is +And when the presented type is unknown _while other types are known for this host_, that is `unknown-type-known-host` — never a plain TOFU prompt. It must say we already hold a different key for this host. @@ -193,7 +193,7 @@ means nothing is known. ### D5. Recovery must not live in the failure dialog -A "forget this host key" button *in* the mismatch dialog is D4's rejected "trust anyway" with one +A "forget this host key" button _in_ the mismatch dialog is D4's rejected "trust anyway" with one extra click. Recovery lives in target settings: a separate, deliberate surface, no auto-retry, and it shows the stored fingerprint so the user is choosing knowingly. @@ -201,7 +201,7 @@ Offer it only when **our** store is what disagreed; when `known_hosts` disagrees record cannot unblock the connect. Messages, written to avoid naming internals: > **Ours disagreed** — "The host key for `build-01` changed since you last connected from Orca. If you -> rebuilt or reprovisioned this machine, this is expected." → *Forget the saved key* / *Cancel* +> rebuilt or reprovisioned this machine, this is expected." → _Forget the saved key_ / _Cancel_ > **`known_hosts` disagreed** — "The host key for `build-01` does not match the entry in > `~/.ssh/known_hosts`. `ssh` and `git` will refuse this host too. Run `ssh-keygen -R build-01`." → @@ -239,7 +239,7 @@ machines, two targets can name one machine, and a re-created target must not los The store is a **dedicated file**, not the main persistence blob (`persistence.ts:7088`): a settings restore or rollback must not silently reset trust. Accept and mismatch events are logged. -`hostKeyFingerprint` is now security-relevant *and* wire-relevant — it is an isolation namespace sent +`hostKeyFingerprint` is now security-relevant _and_ wire-relevant — it is an isolation namespace sent to the host (`ssh-relay-session.ts:1298`, `managed-hook-owner-identity.ts:187`). It is `undefined` on the system transport, so **no trust logic may key off it**, and its format must not change (see Traps). @@ -247,11 +247,11 @@ Traps). ## Phasing — ship the defence before the dialog Review made the case that the riskiest part of this change is not the security model but the modal. -Startup restore fires eager connects for *all* previously-active targets in parallel (`App.tsx:1041`) +Startup restore fires eager connects for _all_ previously-active targets in parallel (`App.tsx:1041`) with a 15s timeout, while a prompt would live 120s — N unknown hosts means N stacked dialogs outliving the timeout that already deferred them. Runtime-owned ephemeral VMs (`ephemeral-vm-runtime-ssh.ts:31`) dial a freshly provisioned host with a brand-new key on every -launch. Paired-web connects run on the *host desktop* (`runtime/rpc/methods/ssh.ts:32`), so the +launch. Paired-web connects run on the _host desktop_ (`runtime/rpc/methods/ssh.ts:32`), so the dialog would open on someone else's screen while the web user watches a spinner. **Phase 1 — no new modal.** Consult `known_hosts` + our store. `match` connects. `unknown` persists @@ -301,10 +301,11 @@ Each of these makes the fix silently do nothing. All confirmed in our tree. ## Scope **In scope, corrected:** IPv6 literals and `[host]:port` bracket parsing. Review was right that this -is a *parser* requirement, not a scope call — getting it wrong means hosts `ssh` knows come back +is a _parser_ requirement, not a scope call — getting it wrong means hosts `ssh` knows come back `unknown`, which is the prompt-training harm D3 exists to avoid. **Out of scope, with consequences stated:** + - **`CheckHostIP`** — OpenSSH defaults it off; we form candidates from the hostname only. - **WSL** — `src/main/ssh/` has no WSL awareness; a distro's `known_hosts` is unreachable, so WSL users get first-contact treatment for hosts they already verified. @@ -337,7 +338,7 @@ verified by running an OpenSSH 10.2p1 client against a real `sshd` on `127.0.0.1 its verdict: - **The bare-host fallback pass never reports a change.** With `StrictHostKeyChecking=accept-new`, a - bare line holding a *different* key, dialed on a non-default port, made ssh connect and append a + bare line holding a _different_ key, dialed on a non-default port, made ssh connect and append a new `[127.0.0.1]:2222` line — first contact, no `IDENTIFICATION HAS CHANGED`. Reporting `mismatch` on that pass would refuse hosts ssh connects to happily, and would have looked like the cautious choice. @@ -355,16 +356,16 @@ subtly wrong.** Fixed after review: 1. **Our own store was type-downgradable.** The inline lookup filtered by key type first and could - only answer match/mismatch/unknown, so a record of a *different* type read as `unknown`. D3's + only answer match/mismatch/unknown, so a record of a _different_ type read as `unknown`. D3's downgrade, applied to the records we create ourselves. Stored types now also feed the algorithm ordering — without that the guard is only half present. 2. **We keyed on the Orca label.** `ssh -G` echoes its own argument back as `hostname` when no Host - block matches, so for a manual target `resolved.hostname` *is* the label — the one name D2 + block matches, so for a manual target `resolved.hostname` _is_ the label — the one name D2 forbids. We consulted no entries at all. 3. **A refused key still walked the credential ladder.** ssh2 reports a denial as a generic auth failure, so we went on to prompt for the passphrase and hand it to the host we had just refused. Rejections are now a typed error recognised before any fallback. -4. **Fail-closed nearly became fail-always.** "No readable known_hosts" counted a *missing* file the +4. **Fail-closed nearly became fail-always.** "No readable known*hosts" counted a \_missing* file the same as an unreadable one, so a profile that had never connected — everyone's first run — would have been refused, and the suite passed only because dev machines have a `known_hosts`. 5. **Ephemeral runtimes were refused for a policy they cannot satisfy.** The carve-out sat below the @@ -404,7 +405,7 @@ tooltip, and the terminal reconnect overlay never asked for it at all. Still ope `getPublicSshState`, so a paired-web client always sees exactly `SSH connection unavailable`. Pre-existing, but it makes the Phase 2 dialog message unreachable there without a change. Note the redaction is not web-only: any target owned by a paired runtime environment is redacted, so a - *desktop* user viewing a remote-Orca-server-owned host gets the same generic string. + _desktop_ user viewing a remote-Orca-server-owned host gets the same generic string. - **RPC fail-fast** becomes load-bearing the moment the dialog exists (see Phasing). **Known gaps that Phase 1 accepts, listed so they are choices and not surprises:** diff --git a/docs/reference/windows-process-enumeration.md b/docs/reference/windows-process-enumeration.md index 11e39b35b7e..4b8acd87aae 100644 --- a/docs/reference/windows-process-enumeration.md +++ b/docs/reference/windows-process-enumeration.md @@ -15,7 +15,10 @@ since removed. table. It wraps a Toolhelp32 snapshot from `@vscode/windows-process-tree`. ```ts -import { readWindowsProcessTable, readWindowsProcessTableFresh } from '../windows/windows-process-table' +import { + readWindowsProcessTable, + readWindowsProcessTableFresh +} from '../windows/windows-process-table' ``` - `readWindowsProcessTable()` — shared TTL cache. Use for anything periodic. @@ -31,11 +34,11 @@ survived its own teardown (#9045). Measured on Windows 11 with 1050 processes (p50 / p95): -| | p50 | p95 | -| --- | --- | --- | -| pid + ppid + name | 15.9 ms | 17.5 ms | -| + memory + command line | 30.6 ms | 33.7 ms | -| `Get-CimInstance` via PowerShell | 706 ms | 723 ms | +| | p50 | p95 | +| -------------------------------- | ------- | ------- | +| pid + ppid + name | 15.9 ms | 17.5 ms | +| + memory + command line | 30.6 ms | 33.7 ms | +| `Get-CimInstance` via PowerShell | 706 ms | 723 ms | Those CIM numbers are from a 1050-process host. The scan scales with process count: on a 1486-process Windows SSH host it measured **1.36 s** and produced @@ -62,7 +65,7 @@ Two guards, and they work together: the module refuses every further read until that read's callback fires. The wedge used to be a 30 s cooldown that let one probe through per window. That -bounded the *rate* of new callbacks but not the total: a permanently wedged +bounded the _rate_ of new callbacks but not the total: a permanently wedged reader retained one more closure every 30 s for as long as the app ran, and each probe also blocked its caller for the full 3 s deadline first. Gating on the outstanding read instead bounds retention at exactly one callback, and gives up @@ -90,7 +93,7 @@ checked on a real Windows SSH host with 1486 processes: **Installing it normally rebuilds from source, and that build fails.** The tarball carries a `binding.gyp`, so npm runs `node-gyp rebuild` regardless of -what is already compiled inside it. On a host that *already had* MSVC Build +what is already compiled inside it. On a host that _already had_ MSVC Build Tools 2022 installed, that build still failed: ``` @@ -103,7 +106,7 @@ would then break outright rather than degrade: `installNativeDeps` throws on failure, and the toolchain-skip retry is gated to Linux. **Skipping the build and using the shipped binary returns a truncated table.** -Contrary to what this file used to claim, the published 0.8.0 tarball *does* +Contrary to what this file used to claim, the published 0.8.0 tarball _does_ contain `build/Release/windows_process_tree.node` — an MSVC build directory that looks accidentally published (`.obj` and `.tlog` files ship with it). It is N-API, so it loads on any modern Node. But it predates our patch and still has @@ -151,14 +154,14 @@ it as an optional relay artifact. `config/scripts/build-windows-process-tree-relay-addon.mjs` builds it from the source pnpm has already patched, on a Windows runner, and refuses to run if any patch hunk is missing — the Spectre hunk fails loudly, the 1024-process -hunk fails *silently*, and the relative gyp path dies at configure on Windows. +hunk fails _silently_, and the relative gyp path dies at configure on Windows. The source is checked rather than the install trusted. It also reads the PE machine field of the output, because a cross-build that quietly emitted host arch would ship a binary the target cannot load. Windows arm64 cross-compiles from the x64 runner — verified on real hardware, producing `IMAGE_FILE_MACHINE_ARM64` (0xaa64) against x64's 0x8664. It needs the -optional *MSVC v143 ARM64 build tools* component; without it node-gyp fails with +optional _MSVC v143 ARM64 build tools_ component; without it node-gyp fails with `MSB8020`, which is why the addon build runs before the long packaging step. `ORCA_REQUIRE_RELAY_NATIVE_ADDONS` is a per-arch list so a future arch can be added best-effort before it is promoted to required. @@ -258,7 +261,7 @@ The per-PTY job deliberately does **not** set `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`. Measured on Windows 11: with that flag, releasing the handle when the shell exits also kills whatever the user left running, so typing `exit` in a pane reaped a `start /b` server that used to -survive. The job exists to make an *explicit* teardown exact, not to redefine +survive. The job exists to make an _explicit_ teardown exact, not to redefine what a clean exit means. Reaping a dead daemon's shells (#9195, #10415) is therefore a **second, nested @@ -283,7 +286,7 @@ kill-on-close job on the app, which is exactly what the crash-survival guarantee forbids. Once the shell exits, node-pty drops its handle record and closes the job, so a -terminated tree reports `null` rather than `[]`. Null means *unverifiable* in +terminated tree reports `null` rather than `[]`. Null means _unverifiable_ in the sense of [`ssh-execution-boundary.md`](./ssh-execution-boundary.md) — no job support, not a ConPTY, or no longer tracked. It is never evidence that processes died. diff --git a/docs/reference/windows-setup-shell.md b/docs/reference/windows-setup-shell.md index 6c93185b602..5e7e33ca0de 100644 --- a/docs/reference/windows-setup-shell.md +++ b/docs/reference/windows-setup-shell.md @@ -25,7 +25,7 @@ scripts: ## Why the script declares it, not the terminal preference -`terminalWindowsShell` says which shell *interactive terminals* open in. It says nothing about the +`terminalWindowsShell` says which shell _interactive terminals_ open in. It says nothing about the language a project's setup script is written in. Deriving the runner from it had two consequences: - Windows users with batch-syntax setup scripts silently switched to bash on upgrade, so `copy`, diff --git a/docs/reference/worktree-scan-fingerprint.md b/docs/reference/worktree-scan-fingerprint.md index f82d453a82c..fa0e26dac20 100644 --- a/docs/reference/worktree-scan-fingerprint.md +++ b/docs/reference/worktree-scan-fingerprint.md @@ -35,7 +35,7 @@ whichever poller arrives first afterwards pays a full-fleet `git worktree list` fan-out. The many high-frequency callers — `listTerminals` without a selector, `showTerminal`, `getWorktreePs`, `listManagedWorktrees`, `resolveWorktreeSelector`, orchestration authority refresh — only determine -*who* pays, not *how often*. Steady-state subprocess volume is therefore +_who_ pays, not _how often_. Steady-state subprocess volume is therefore `repos / 30 s`, independent of poll rate, which is exactly the observed ~1 sweep / 30.5 s. @@ -86,16 +86,16 @@ common directory without a subprocess (read `.git`; if it is a `gitdir:` file, follow it and then its `commondir`; if `.git` is absent, treat `repoPath` as a bare gitdir), then records: -| Input | External change it catches | -| --- | --- | -| sorted entry names of `/worktrees` | `worktree add`, `worktree remove`, `worktree prune` | -| existence of `repoPath` | main checkout deleted | -| `/packed-refs` mtime + size | a tip moved while its loose ref is packed away | -| `/reftable` mtime + size | a tip moved under the reftable backend | -| per checkout: `HEAD` contents | branch switch, detach (the detached oid is in HEAD itself) | -| per checkout: contents of the ref HEAD names | a plain `git commit`, `reset`, or `fetch` that moves the tip | -| per entry: `gitdir` contents | `worktree move`, `worktree repair` | -| per entry: `locked` presence | `worktree lock` / `unlock` | +| Input | External change it catches | +| -------------------------------------------------- | ------------------------------------------------------------- | +| sorted entry names of `/worktrees` | `worktree add`, `worktree remove`, `worktree prune` | +| existence of `repoPath` | main checkout deleted | +| `/packed-refs` mtime + size | a tip moved while its loose ref is packed away | +| `/reftable` mtime + size | a tip moved under the reftable backend | +| per checkout: `HEAD` contents | branch switch, detach (the detached oid is in HEAD itself) | +| per checkout: contents of the ref HEAD names | a plain `git commit`, `reset`, or `fetch` that moves the tip | +| per entry: `gitdir` contents | `worktree move`, `worktree repair` | +| per entry: `locked` presence | `worktree lock` / `unlock` | | per entry: existence of the path named by `gitdir` | a worktree directory deleted with `rm -rf` (flips `prunable`) | "per checkout" covers the main worktree and each linked worktree. Reading the @@ -159,21 +159,21 @@ rescans. Capturing after the scan would let that mutation be masked forever. Untouched. `invalidateWorktreeScanCacheForRepo` deletes the entry (fingerprint included) and bumps the generation, so every event-driven path still forces a -real scan on the next read. The fingerprint only ever *extends* an entry that +real scan on the next read. The fingerprint only ever _extends_ an entry that the TTL alone would have refreshed. ## Freshness budget -| Change | Before | After | -| --- | --- | --- | -| Orca-initiated create/remove/rename/sparse/repo edit | immediate (event) | immediate (event) | -| SSH reconnect / provider generation bump | immediate (event) | immediate (event) | -| External `worktree add/remove/move/prune/lock` | ≤ 30 s | ≤ 30 s | -| External `git checkout` / `commit` / `reset` in any worktree | ≤ 30 s | ≤ 30 s | -| External `rm -rf ` | ≤ 30 s | ≤ 30 s | -| External sparse-checkout pattern edit | ≤ 30 s | ≤ 5 min | -| Packed/reftable tip moved within one mtime tick at an equal file size | ≤ 30 s | ≤ 5 min | -| SSH / WSL repos, folder workspaces | unchanged | unchanged | +| Change | Before | After | +| --------------------------------------------------------------------- | ----------------- | ----------------- | +| Orca-initiated create/remove/rename/sparse/repo edit | immediate (event) | immediate (event) | +| SSH reconnect / provider generation bump | immediate (event) | immediate (event) | +| External `worktree add/remove/move/prune/lock` | ≤ 30 s | ≤ 30 s | +| External `git checkout` / `commit` / `reset` in any worktree | ≤ 30 s | ≤ 30 s | +| External `rm -rf ` | ≤ 30 s | ≤ 30 s | +| External sparse-checkout pattern edit | ≤ 30 s | ≤ 5 min | +| Packed/reftable tip moved within one mtime tick at an equal file size | ≤ 30 s | ≤ 5 min | +| SSH / WSL repos, folder workspaces | unchanged | unchanged | The two regressions are bounded by the reconciliation interval and are both changes Orca does not make itself. @@ -185,10 +185,10 @@ thread" in the naive sense — but they are not equally free there. Measured on macOS with a 1 ms interval sampling event-loop lag while each ran 30 times against a repo with 20 linked worktrees: -| | wall per call | main-thread stall per call | worst single stall | -| --- | --- | --- | --- | -| `git worktree list` | 18.66 ms | 2.69 ms | 3.02 ms | -| fingerprint probe | 1.66 ms | 0.01 ms | 0.04 ms | +| | wall per call | main-thread stall per call | worst single stall | +| ------------------- | ------------- | -------------------------- | ------------------ | +| `git worktree list` | 18.66 ms | 2.69 ms | 3.02 ms | +| fingerprint probe | 1.66 ms | 0.01 ms | 0.04 ms | `fs/promises` dispatches to libuv's threadpool, so ~99 % of the probe's latency is off-thread. Spawning Git does not: `uv_spawn`, fd and pipe setup, and stdout @@ -212,10 +212,10 @@ sparse-checkout probes already do. reported steady state — 10 idle local repos, a caller polling at 1 Hz for 30 simulated minutes — and counts `git worktree list` invocations: -| | `git worktree list` per 30 min | per hour | -| --- | --- | --- | -| TTL only (before) | 600 | 1,200 | -| fingerprint gate (after) | 60 | 120 | +| | `git worktree list` per 30 min | per hour | +| ------------------------ | ------------------------------ | -------- | +| TTL only (before) | 600 | 1,200 | +| fingerprint gate (after) | 60 | 120 | A 90 % reduction, with the remainder being the bounded reconciliation. Repos with genuine external activity keep rescanning at the 30 s cadence because the @@ -227,7 +227,7 @@ Extrapolating to the original trace's shape (10 repos, 3 h 27 min): 4,272 ## Rejected alternatives **Raise `WORKTREE_SCAN_CACHE_TTL_MS` to 5 min.** One line, same subprocess -reduction, but it degrades *every* external-change latency to 5 min, including +reduction, but it degrades _every_ external-change latency to 5 min, including the common "I ran `git worktree add` in a terminal" case. The fingerprint buys the same reduction without that regression. diff --git a/docs/reference/wsl-command-execution.md b/docs/reference/wsl-command-execution.md index c832ff201e5..92afcf344ad 100644 --- a/docs/reference/wsl-command-execution.md +++ b/docs/reference/wsl-command-execution.md @@ -26,7 +26,7 @@ exactly the case a POSIX script uses to mean a literal dollar. Build argv with `buildWslExecArgs()` in `src/shared/wsl-login-shell-command.ts`. A test walks the tree and fails if the `--` form reappears. -The `--` inside `sh -s -- ` is a *shell* argument separator and is unrelated; leave it alone. +The `--` inside `sh -s -- ` is a _shell_ argument separator and is unrelated; leave it alone. ## 2. Machine-read output must be fenced diff --git a/docs/reference/wsl-runner-verification.md b/docs/reference/wsl-runner-verification.md index 39cc56871a8..59667f513de 100644 --- a/docs/reference/wsl-runner-verification.md +++ b/docs/reference/wsl-runner-verification.md @@ -4,24 +4,24 @@ Three layers of coverage, because each catches what the others structurally cann ## 1. Unit — runs everywhere, every PR -| Suite | Pins | -|---|---| -| `src/main/wsl/wsl-runner.test.ts` | Separator, lane selection, fencing, WSLENV, guest cwd, script interpreter, budget split, refusal on unresolved PATH | -| `src/main/wsl/wsl-guest-environment.test.ts` | Burst collapse, per-distro isolation, malformed-payload rejection, transient vs permanent, retry windows, joiner budget | -| `src/main/wsl/wsl-w1-w3-contract.test.ts` | The W1→W3 chain end to end: absolute `wsl.exe`, argv array, bounded call, no `--`, script byte-identical, WSLENV, no shell on probe, login PATH still applied | -| `src/shared/source-scan/source-tree-scan.test.ts` | The guard helpers. A guard that under-reports is worse than none | +| Suite | Pins | +| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `src/main/wsl/wsl-runner.test.ts` | Separator, lane selection, fencing, WSLENV, guest cwd, script interpreter, budget split, refusal on unresolved PATH | +| `src/main/wsl/wsl-guest-environment.test.ts` | Burst collapse, per-distro isolation, malformed-payload rejection, transient vs permanent, retry windows, joiner budget | +| `src/main/wsl/wsl-w1-w3-contract.test.ts` | The W1→W3 chain end to end: absolute `wsl.exe`, argv array, bounded call, no `--`, script byte-identical, WSLENV, no shell on probe, login PATH still applied | +| `src/shared/source-scan/source-tree-scan.test.ts` | The guard helpers. A guard that under-reports is worse than none | ## 2. Ratchets — the goalposts, enforced continuously -| Guard | Measures | -|---|---| -| `wsl-invocation-boundary.test.ts` | Files spawning `wsl.exe` outside the runner, plus bash-only payloads that fail to declare `shell: 'bash'` | -| `windows-console-visibility.test.ts` | Direct child-process calls missing `windowsHide` | -| `child-process-import-boundary.test.ts` | Files importing `child_process` outside the chokepoint | -| `wsl-exec-mode-separator.test.ts` | The banned `--` separator | -| `pty-descendant-termination-job-coverage.test.ts` | Every sweep passes `terminateOwnedTree` | +| Guard | Measures | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `wsl-invocation-boundary.test.ts` | Files spawning `wsl.exe` outside the runner, plus bash-only payloads that fail to declare `shell: 'bash'` | +| `windows-console-visibility.test.ts` | Direct child-process calls missing `windowsHide` | +| `child-process-import-boundary.test.ts` | Files importing `child_process` outside the chokepoint | +| `wsl-exec-mode-separator.test.ts` | The banned `--` separator | +| `pty-descendant-termination-job-coverage.test.ts` | Every sweep passes `terminateOwnedTree` | -Each fails on a **new** offender *and* on a **stale** entry, so the count can only fall. Verify a guard by planting a violation and watching it get named — that step has found a bug in the guard itself three times. +Each fails on a **new** offender _and_ on a **stale** entry, so the count can only fall. Verify a guard by planting a violation and watching it get named — that step has found a bug in the guard itself three times. ## 3. Real-binary — the assertions nothing else can make @@ -48,4 +48,4 @@ Recorded rather than implied, because a guard that looks complete is worse than ### Verifying a guard change -Plant a violation and watch it fail. Every guard fix in this workstream that was verified only by reading was wrong — three consecutive attempts at an exact lexer each shipped a desync that *reduced* the offender count, which read as progress. Plant at least: a plain call, one in a template-literal-heavy file, one in a regex-heavy file, `windowsHide: false`, a ternary first argument, and a renamed import. +Plant a violation and watch it fail. Every guard fix in this workstream that was verified only by reading was wrong — three consecutive attempts at an exact lexer each shipped a desync that _reduced_ the offender count, which read as progress. Plant at least: a plain call, one in a template-literal-heavy file, one in a regex-heavy file, `windowsHide: false`, a ternary first argument, and a renamed import. diff --git a/docs/reference/xterm-patch-regeneration.md b/docs/reference/xterm-patch-regeneration.md index dead914ef74..b2da865406d 100644 --- a/docs/reference/xterm-patch-regeneration.md +++ b/docs/reference/xterm-patch-regeneration.md @@ -115,7 +115,7 @@ the resulting patch is silently wrong — the failure mode is a `.mjs` that is `forbiddenBuildScripts` in the manifest encodes this and the generator refuses to run a build step that names one of those scripts. -The generator also builds the *unmodified* commit first and asserts that it +The generator also builds the _unmodified_ commit first and asserts that it reproduces the published `lib/` byte for byte before it emits anything. A toolchain or build-order problem therefore surfaces as an explicit "did not reproduce the published bundles" error rather than as 7 MB of mystery diff. diff --git a/skill-guides/orca-emulator-android.md b/skill-guides/orca-emulator-android.md index e1372d62796..6c24b515a5f 100644 --- a/skill-guides/orca-emulator-android.md +++ b/skill-guides/orca-emulator-android.md @@ -89,20 +89,20 @@ issues `adb shell input` events; AVD names resolve to running adb serials. Use `--json` for agent-friendly output. Coordinates are **normalized 0..1** (top-left origin) — never pixels; Orca converts using the live screen size. -| Goal | Command | Notes | -|----------------------------|----------------------------------------------------------------|-------| -| List devices + AVDs | `ORCA emulator devices --json` | Cross-platform; shows iOS + Android with a platform column, booted vs shutdown. | -| Single tap | `ORCA emulator tap --device ` | Normalized 0..1. Preferred for single taps. | -| Swipe / gesture | `ORCA emulator gesture '' --device ` | adb approximates the path by its endpoints (start→end). | -| Type text | `ORCA emulator type "user@example.com" --device ` | US ASCII; spaces handled. No newlines. | -| Hardware button | `ORCA emulator button back --device ` | home, back, recents, power, volume_up, volume_down. | -| Rotate | `ORCA emulator rotate landscape_left --device ` | Sets user_rotation (disables auto-rotate). | -| Install an APK | `ORCA emulator install ./app-debug.apk --reinstall --device ` | `--reinstall` passes `-r`. | -| Launch an app | `ORCA emulator launch com.acme.app --activity .MainActivity --device ` | Omit `--activity` to launch the default LAUNCHER activity. | -| Grant a permission | `ORCA emulator permissions grant com.acme.app android.permission.CAMERA --device ` | grant / revoke / reset. | -| Accessibility tree | `ORCA emulator ax --device --json` | `uiautomator dump` parsed to a node tree. | -| Logcat (one-shot) | `ORCA emulator logcat --lines 200 --device ` | Dumps recent lines; parsed to entries. | -| Raw adb shell | `ORCA emulator exec --command "getprop ro.build.version.sdk" --device ` | Runs `adb -s shell `. | +| Goal | Command | Notes | +| ------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | +| List devices + AVDs | `ORCA emulator devices --json` | Cross-platform; shows iOS + Android with a platform column, booted vs shutdown. | +| Single tap | `ORCA emulator tap --device ` | Normalized 0..1. Preferred for single taps. | +| Swipe / gesture | `ORCA emulator gesture '' --device ` | adb approximates the path by its endpoints (start→end). | +| Type text | `ORCA emulator type "user@example.com" --device ` | US ASCII; spaces handled. No newlines. | +| Hardware button | `ORCA emulator button back --device ` | home, back, recents, power, volume_up, volume_down. | +| Rotate | `ORCA emulator rotate landscape_left --device ` | Sets user_rotation (disables auto-rotate). | +| Install an APK | `ORCA emulator install ./app-debug.apk --reinstall --device ` | `--reinstall` passes `-r`. | +| Launch an app | `ORCA emulator launch com.acme.app --activity .MainActivity --device ` | Omit `--activity` to launch the default LAUNCHER activity. | +| Grant a permission | `ORCA emulator permissions grant com.acme.app android.permission.CAMERA --device ` | grant / revoke / reset. | +| Accessibility tree | `ORCA emulator ax --device --json` | `uiautomator dump` parsed to a node tree. | +| Logcat (one-shot) | `ORCA emulator logcat --lines 200 --device ` | Dumps recent lines; parsed to entries. | +| Raw adb shell | `ORCA emulator exec --command "getprop ro.build.version.sdk" --device ` | Runs `adb -s shell `. | ## Critical gotchas (teach agents) diff --git a/skill-guides/orca-emulator.md b/skill-guides/orca-emulator.md index 139577b1cdb..73c12fd05eb 100644 --- a/skill-guides/orca-emulator.md +++ b/skill-guides/orca-emulator.md @@ -36,6 +36,7 @@ shell-neutral for POSIX shells, PowerShell, and cmd.exe. - The agent should use Orca's preview pane instead of external Simulator.app or raw serve-sim URLs. **When NOT to use** + - Android emulators → use the `orca-emulator-android` skill (same `ORCA emulator` namespace, cross-platform via adb/emulator). - Building or installing the app itself → use `xcodebuild`, `xcrun simctl install`, `expo run:ios`, etc. (launch the app, then use `ORCA emulator` to drive it). - In-app debugging (state, network, views) → use the app's own tools or the browser pane if it's a webview. @@ -75,6 +76,7 @@ An active emulator "session" for the worktree is required for most commands. Use ``` Orca owns: + - Starting/stopping the serve-sim helper (via --detach or direct). - Per-worktree "active" emulator (like active browser tab). - Explicit targeting with `--worktree`, `--device`, `--emulator `. @@ -82,26 +84,26 @@ Orca owns: Agents use the Orca executable chosen above (on PATH in Orca terminals) and never have to manage PIDs, state files in /tmp, or raw WS URLs themselves. -**For `pnpm dev` testing:** run `pnpm build:cli` first (rebuilds the CLI + ensures the `orca-dev` shim points at *this* worktree). Then inside the dev app use `orca-dev emulator ...` (or the direct `./config/scripts/orca-dev.mjs emulator ...` from the repo root). The orchestration preambles and dev launchers automatically select the dev command name so the CLI reaches your in-memory EmulatorBridge / runtime. Plain `orca` reaches a packaged install instead. +**For `pnpm dev` testing:** run `pnpm build:cli` first (rebuilds the CLI + ensures the `orca-dev` shim points at _this_ worktree). Then inside the dev app use `orca-dev emulator ...` (or the direct `./config/scripts/orca-dev.mjs emulator ...` from the repo root). The orchestration preambles and dev launchers automatically select the dev command name so the CLI reaches your in-memory EmulatorBridge / runtime. Plain `orca` reaches a packaged install instead. ## Common operations Use `--json` for agent-friendly output. Commands are workspace-scoped by default (current worktree's active emulator). -| Goal | Command | Notes | -|-----------------------------|----------------------------------------------|-------| -| List available / running | `ORCA emulator list [--worktree ]` | Shows Orca-managed + raw serve-sim streams. Use output for explicit --device/--emulator. | -| Attach / make active | `ORCA emulator attach "iPhone 16 Pro" [--worktree ] [--focus]` | Starts helper if needed (serve-sim --detach). Sets active for unqualified commands. --focus optional (does not auto-steal UI focus by default). | -| Single tap | `ORCA emulator tap [--device ]` | Normalized 0..1 coords. **Preferred over gesture for simple taps.** | -| Multi-step gesture | `ORCA emulator gesture ''` | See gestures reference (begin/move/end). Use tap for singles. | -| Type text | `ORCA emulator type "text" [--device ]` | US ASCII only. Supports stdin/file via exec if needed. | -| Hardware button | `ORCA emulator button home [--device ]` | home, swipe_home, app_switcher, lock, siri, side_button. | -| Rotate device | `ORCA emulator rotate landscape_left` | Remembers orientation for subsequent gestures. | -| Camera injection | `ORCA emulator camera com.acme.App --webcam` | Or --file, placeholder. Hot-swap with switch. May (re)launch app. | -| Permissions | `ORCA emulator permissions grant camera com.acme.App` | grant/revoke/reset/list. See full subcommand help. | -| Accessibility tree | `ORCA emulator ax [--device ]` | Raw serve-sim AX node tree (labels, roles, nested children, capped at 500 nodes; frames normalized 0..1 with top-left origin — tap an element at its frame center: x+width/2, y+height/2). Needs an active session. | -| Raw / advanced | `ORCA emulator exec --command "tap 0.5 0.7"` | Or "ca-debug blended on", "memory-warning", full serve-sim subcommands (no "serve-sim" prefix needed in the command string). Bridge injects active device context. | -| Stop | `ORCA emulator kill [--device ]` | Or let pane close / Orca quit clean up. | +| Goal | Command | Notes | +| ------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| List available / running | `ORCA emulator list [--worktree ]` | Shows Orca-managed + raw serve-sim streams. Use output for explicit --device/--emulator. | +| Attach / make active | `ORCA emulator attach "iPhone 16 Pro" [--worktree ] [--focus]` | Starts helper if needed (serve-sim --detach). Sets active for unqualified commands. --focus optional (does not auto-steal UI focus by default). | +| Single tap | `ORCA emulator tap [--device ]` | Normalized 0..1 coords. **Preferred over gesture for simple taps.** | +| Multi-step gesture | `ORCA emulator gesture ''` | See gestures reference (begin/move/end). Use tap for singles. | +| Type text | `ORCA emulator type "text" [--device ]` | US ASCII only. Supports stdin/file via exec if needed. | +| Hardware button | `ORCA emulator button home [--device ]` | home, swipe_home, app_switcher, lock, siri, side_button. | +| Rotate device | `ORCA emulator rotate landscape_left` | Remembers orientation for subsequent gestures. | +| Camera injection | `ORCA emulator camera com.acme.App --webcam` | Or --file, placeholder. Hot-swap with switch. May (re)launch app. | +| Permissions | `ORCA emulator permissions grant camera com.acme.App` | grant/revoke/reset/list. See full subcommand help. | +| Accessibility tree | `ORCA emulator ax [--device ]` | Raw serve-sim AX node tree (labels, roles, nested children, capped at 500 nodes; frames normalized 0..1 with top-left origin — tap an element at its frame center: x+width/2, y+height/2). Needs an active session. | +| Raw / advanced | `ORCA emulator exec --command "tap 0.5 0.7"` | Or "ca-debug blended on", "memory-warning", full serve-sim subcommands (no "serve-sim" prefix needed in the command string). Bridge injects active device context. | +| Stop | `ORCA emulator kill [--device ]` | Or let pane close / Orca quit clean up. | Most support `--worktree ` and explicit `--device ` or `--emulator ` (from list) for targeting. diff --git a/skill-guides/orca-per-workspace-env.md b/skill-guides/orca-per-workspace-env.md index 77c96d12dfc..d844f59ec65 100644 --- a/skill-guides/orca-per-workspace-env.md +++ b/skill-guides/orca-per-workspace-env.md @@ -76,7 +76,7 @@ a long time, or need the user at the keyboard. Never create an Orca workspace or - **Coding-agent CLI + account:** which agent runs in the VM (`codex`, `claude`, …) and that the user has an account for it — it gets logged in during the Phase-3 auth snapshot (§4). - **Git auth:** the token source for cloning a private repo (`GH_TOKEN`/`GITHUB_TOKEN` or `gh auth - token`; §5). +token`; §5). 3. **Check prerequisites (§2)** — detect the provider CLI + auth and confirm the items above are in place before any paid step. 4. **Scaffold scripts + state file** from §7 (worked Vercel example: §7f; SSH host: §7g; Docker SSH: @@ -221,9 +221,9 @@ reserve stdout for the final JSON and log progress to stderr. Include a shared ` - **Local-side** (`create`/`suspend`/`resume`/`destroy` + the base-snapshot/auth scripts the user invokes) runs **on the user's desktop**, so it must run on their OS. macOS/Linux: `#!/usr/bin/env - bash`, `set -euo pipefail`, quoted paths. **Windows:** a bare `.sh` won't run — scaffold `.ps1`/`.cmd` +bash`, `set -euo pipefail`, quoted paths. **Windows:** a bare `.sh` won't run — scaffold `.ps1`/`.cmd` or require WSL/Git-Bash and point `orca.yaml` at the right launcher. -- **Remote-side** (commands you `exec` *inside* the Linux VM) always runs in the VM's Linux shell, so +- **Remote-side** (commands you `exec` _inside_ the Linux VM) always runs in the VM's Linux shell, so bash is fine there regardless of the user's OS. ### 7a. Base-snapshot (`-base-snapshot.sh`) — Phase 2 @@ -303,7 +303,11 @@ There is **no `--host` flag**. `--project-root` must be an absolute directory on keeps serving: ```json -{ "schemaVersion": 1, "pairingCode": "", "projectRoot": "" } +{ + "schemaVersion": 1, + "pairingCode": "", + "projectRoot": "" +} ``` `pairingCode` is the pairing URL, already pointing at whatever you passed as `--pairing-address` — so set @@ -498,7 +502,7 @@ git checkout -B "$ORCA_REPO_BRANCH" "$ORCA_REPO_REF_HEAD" Fail if the requested schema is not `2`; do not silently fall back to the ordinary recipe shape. -**Networking → which `target` fields to set** (how *your desktop* reaches the box — there is no +**Networking → which `target` fields to set** (how _your desktop_ reaches the box — there is no `orca serve` URL in SSH mode): - Public IP / DNS, or a Tailscale/VPN address → `host`; SSH port → `port` (usually 22). @@ -511,7 +515,7 @@ Fail if the requested schema is not `2`; do not silently fall back to the ordina reconnect grace window. **Toolchain & agent auth on a persistent (no-snapshot) host — do this ONCE, by hand, before wiring the -recipe** (there's no base image to bake; the host *is* the base). Run the §7f Phase-2 install steps and +recipe** (there's no base image to bake; the host _is_ the base). Run the §7f Phase-2 install steps and the §7f Phase-3 ` login --device-auth` **directly over SSH on the host** (interactive, e.g. `ssh -t user@host ' login --device-auth'`). After that the host stays ready across workspaces. @@ -615,7 +619,7 @@ $ErrorActionPreference = 'Stop' # progress/errors → Write-Error / the error stream, never stdout. ``` -The remote-side commands you run *inside* the Linux VM stay bash regardless of the desktop OS. +The remote-side commands you run _inside_ the Linux VM stay bash regardless of the desktop OS. --- @@ -702,10 +706,10 @@ each stage so you can self-diagnose without asking the user to relay logs: ```json { "ok": false, - "checks": [ { "id": "recipe.provision", "status": "fail", "message": "…" } ], + "checks": [{ "id": "recipe.provision", "status": "fail", "message": "…" }], "provisionTranscript": { "provision": { "exitCode": 0, "signal": null, "stdout": "…", "stderr": "…", "parseError": "…" }, - "destroy": { "exitCode": 0, "signal": null, "stdout": "…", "stderr": "…" } + "destroy": { "exitCode": 0, "signal": null, "stdout": "…", "stderr": "…" } } } ``` @@ -743,7 +747,7 @@ startup-only `docker run` before the full clone/install path. - **Agent verified as "not logged in" despite a good login.** `codex login status` (and similar) print "Logged in …" to **stderr**; an stdout-only `grep` misses it. Prefer the status **exit code**; if you grep, fold stderr first (`status 2>&1 | grep …`) and match the exact success line — not `grep -qi - 'logged in'`, which also matches "not logged in". +'logged in'`, which also matches "not logged in". - **Headless agent login hangs.** Plain OAuth `login` starts a loopback callback server on a VM/container port the host browser can't reach. Use the **device-auth** flow (`login --device-auth`) — it prints a URL + code the user opens on the host. diff --git a/src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts b/src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts index 2144ca09831..8d37578bcf3 100644 --- a/src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts +++ b/src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts @@ -8,14 +8,19 @@ import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -const { spawnMock, isPwshAvailableMock, resolveAgentForegroundProcessMock, readConptyMock, jobReadableMock } = - vi.hoisted(() => ({ - spawnMock: vi.fn(), - isPwshAvailableMock: vi.fn(), - resolveAgentForegroundProcessMock: vi.fn(), - readConptyMock: vi.fn(), - jobReadableMock: vi.fn() - })) +const { + spawnMock, + isPwshAvailableMock, + resolveAgentForegroundProcessMock, + readConptyMock, + jobReadableMock +} = vi.hoisted(() => ({ + spawnMock: vi.fn(), + isPwshAvailableMock: vi.fn(), + resolveAgentForegroundProcessMock: vi.fn(), + readConptyMock: vi.fn(), + jobReadableMock: vi.fn() +})) vi.mock('node-pty', () => ({ spawn: spawnMock })) vi.mock('../pwsh', () => ({ isPwshAvailable: isPwshAvailableMock })) diff --git a/src/main/orcad/native-host-abi.ts b/src/main/orcad/native-host-abi.ts index 4afb8ac7bfa..2890bb07a15 100644 --- a/src/main/orcad/native-host-abi.ts +++ b/src/main/orcad/native-host-abi.ts @@ -117,11 +117,7 @@ export function parseUnmetGlibcVersion(loaderError: string): string | null { } /** `NODE_MODULE_VERSION 115 ... requires NODE_MODULE_VERSION 127` -> { built: '115', host: '127' }. */ -export function parseNodeAbiMismatch( - loaderError: string -): { built: string; host: string } | null { - const match = loaderError.match( - /NODE_MODULE_VERSION\s+(\d+)\D+NODE_MODULE_VERSION\s+(\d+)/ - ) +export function parseNodeAbiMismatch(loaderError: string): { built: string; host: string } | null { + const match = loaderError.match(/NODE_MODULE_VERSION\s+(\d+)\D+NODE_MODULE_VERSION\s+(\d+)/) return match ? { built: match[1], host: match[2] } : null } diff --git a/src/main/orcad/node-pty-prebuilt-slot.ts b/src/main/orcad/node-pty-prebuilt-slot.ts index 992cbe705ee..d0623689902 100644 --- a/src/main/orcad/node-pty-prebuilt-slot.ts +++ b/src/main/orcad/node-pty-prebuilt-slot.ts @@ -27,7 +27,12 @@ export type PrebuiltSlotManifest = { export type PrebuiltSlotOutcome = | { installed: true; slot: string; spawnHelper: boolean } - | { installed: false; slot: string; why: 'no-slot' | 'no-prebuilds-dir' | 'abi-mismatch'; detail?: string } + | { + installed: false + slot: string + why: 'no-slot' | 'no-prebuilds-dir' | 'abi-mismatch' + detail?: string + } /** * Where a deployment's prebuilds live: beside the bundle that is running. `argv[1]` is @@ -55,7 +60,9 @@ export function readPrebuiltSlotManifest(prebuildsDir: string): PrebuiltSlotMani module: typeof manifest.module === 'string' ? manifest.module : 'node-pty', version: manifest.version, nodeAbi: manifest.nodeAbi, - slots: Array.isArray(manifest.slots) ? manifest.slots.filter((s) => typeof s === 'string') : [] + slots: Array.isArray(manifest.slots) + ? manifest.slots.filter((s) => typeof s === 'string') + : [] } } catch { return null diff --git a/src/main/ssh/build-toolchain-diagnosis.ts b/src/main/ssh/build-toolchain-diagnosis.ts index 446601518c9..c64a41ead51 100644 --- a/src/main/ssh/build-toolchain-diagnosis.ts +++ b/src/main/ssh/build-toolchain-diagnosis.ts @@ -163,4 +163,3 @@ export function formatMissingToolchainError( ] return lines.join('\n') } - diff --git a/src/main/ssh/orcad-activation-record.ts b/src/main/ssh/orcad-activation-record.ts index 8c32e6c355d..42b1e6703a8 100644 --- a/src/main/ssh/orcad-activation-record.ts +++ b/src/main/ssh/orcad-activation-record.ts @@ -169,7 +169,5 @@ export function orcadGcPinnedDirNames( const versions = [record.active, record.previous, daemonEntryVersion ?? null].filter( (v): v is string => typeof v === 'string' && v.length > 0 ) - return [...new Set(versions)].map((version) => - remoteInstallDirName(ORCAD_INSTALL_MODEL, version) - ) + return [...new Set(versions)].map((version) => remoteInstallDirName(ORCAD_INSTALL_MODEL, version)) } diff --git a/src/main/ssh/orcad-remote-deploy.test.ts b/src/main/ssh/orcad-remote-deploy.test.ts index 799193f3384..6cab2dd0034 100644 --- a/src/main/ssh/orcad-remote-deploy.test.ts +++ b/src/main/ssh/orcad-remote-deploy.test.ts @@ -209,7 +209,9 @@ describe('deployOrcad', () => { const result = await deployOrcad(options()) expect(result).toMatchObject({ code: 'orcad_activation_daemon_degraded' }) expect( - vi.mocked(writeRelayFile).mock.calls.some((call) => String(call[2]).endsWith('orcad-active.json')) + vi + .mocked(writeRelayFile) + .mock.calls.some((call) => String(call[2]).endsWith('orcad-active.json')) ).toBe(false) }) diff --git a/src/main/ssh/orcad-remote-deploy.ts b/src/main/ssh/orcad-remote-deploy.ts index 1f8a4099a82..878e327c452 100644 --- a/src/main/ssh/orcad-remote-deploy.ts +++ b/src/main/ssh/orcad-remote-deploy.ts @@ -32,10 +32,7 @@ import { type OrcadActivationRecord, type OrcadStateSnapshot } from './orcad-activation-record' -import { - orcadActivationPath, - readOrcadActivationRecord -} from './orcad-activation-record-store' +import { orcadActivationPath, readOrcadActivationRecord } from './orcad-activation-record-store' import { evaluateOrcadActivation, type OrcadActivationVerdict } from './orcad-activation-gate' import { planOrcadUpdate, type OrcadTerminalCensus } from './orcad-update-plan' import { @@ -111,9 +108,11 @@ async function installOrcadBundle( fullVersion: string, remoteDir: string ): Promise { - if (await isRemoteInstallComplete(options.conn, ORCAD_INSTALL_MODEL, remoteDir, options.host, { - signal: options.signal - })) { + if ( + await isRemoteInstallComplete(options.conn, ORCAD_INSTALL_MODEL, remoteDir, options.host, { + signal: options.signal + }) + ) { return } await acquireInstallLock(options.conn, remoteDir, options.host, { signal: options.signal }) @@ -151,7 +150,12 @@ async function captureSnapshot( takenAt: Date ): Promise { const dirName = orcadSnapshotDirName(fullVersion, takenAt.getTime()) - const snapshotDir = joinRemotePath(options.host, baseDir(options), ORCAD_STATE_SNAPSHOT_DIR, dirName) + const snapshotDir = joinRemotePath( + options.host, + baseDir(options), + ORCAD_STATE_SNAPSHOT_DIR, + dirName + ) const capture = parseOrcadSnapshotCapture( await exec( options, @@ -287,9 +291,12 @@ export async function deployOrcad(options: OrcadDeployOptions): Promise { currentDirAbsPath: '/home/u/.orca-remote/orcad-0.2.0+bb', record: emptyOrcadActivationRecord() }) - const listCommand = mockExec.mock.calls.map((call) => String(call[1])).find((c) => c.includes('find')) + const listCommand = mockExec.mock.calls + .map((call) => String(call[1])) + .find((c) => c.includes('find')) expect(listCommand).toContain("-name 'orcad-*'") expect(listCommand).not.toContain("-name 'relay-*'") }) diff --git a/src/main/ssh/orcad-remote-launch.ts b/src/main/ssh/orcad-remote-launch.ts index e82be95faec..a76010ccbce 100644 --- a/src/main/ssh/orcad-remote-launch.ts +++ b/src/main/ssh/orcad-remote-launch.ts @@ -25,10 +25,7 @@ import type { ServeReadiness } from '../server/serve-readiness' export const ORCAD_READINESS_FILENAME = '.orcad-readiness' /** Stderr, including the bind-exposure line and every supervision message. */ export const ORCAD_LOG_FILENAME = 'orcad.log' -export { - ORCAD_PID_FILENAME, - OrcadRemoteLaunchUnsupportedError -} from './orcad-remote-host-support' +export { ORCAD_PID_FILENAME, OrcadRemoteLaunchUnsupportedError } from './orcad-remote-host-support' export type OrcadLaunchSpec = { remoteInstallDir: string @@ -51,7 +48,9 @@ export type OrcadLaunchSpec = { export function orcadLaunchCommand(host: RemoteHostPlatform, spec: OrcadLaunchSpec): string { assertPosixHost(host) const dir = shellEscape(spec.remoteInstallDir) - const readiness = shellEscape(joinRemotePath(host, spec.remoteInstallDir, ORCAD_READINESS_FILENAME)) + const readiness = shellEscape( + joinRemotePath(host, spec.remoteInstallDir, ORCAD_READINESS_FILENAME) + ) const log = shellEscape(joinRemotePath(host, spec.remoteInstallDir, ORCAD_LOG_FILENAME)) const pidFile = shellEscape(joinRemotePath(host, spec.remoteInstallDir, ORCAD_PID_FILENAME)) const entry = shellEscape(joinRemotePath(host, spec.remoteInstallDir, 'orcad.js')) diff --git a/src/main/ssh/orcad-remote-rollback.test.ts b/src/main/ssh/orcad-remote-rollback.test.ts index 59f3d46e330..b9c74158a6d 100644 --- a/src/main/ssh/orcad-remote-rollback.test.ts +++ b/src/main/ssh/orcad-remote-rollback.test.ts @@ -13,10 +13,7 @@ vi.mock('./ssh-relay-install-transfers', () => ({ import { execCommand } from './ssh-relay-deploy-helpers' import { writeRelayFile } from './ssh-relay-install-transfers' import { rollbackOrcad, type OrcadRollbackOptions } from './orcad-remote-rollback' -import { - emptyOrcadActivationRecord, - type OrcadActivationRecord -} from './orcad-activation-record' +import { emptyOrcadActivationRecord, type OrcadActivationRecord } from './orcad-activation-record' import { getRemoteHostPlatform } from './ssh-remote-platform' import type { SshConnection } from './ssh-connection' diff --git a/src/main/ssh/orcad-remote-rollback.ts b/src/main/ssh/orcad-remote-rollback.ts index 165ccfc037f..03df9479e4a 100644 --- a/src/main/ssh/orcad-remote-rollback.ts +++ b/src/main/ssh/orcad-remote-rollback.ts @@ -109,9 +109,7 @@ async function readStateWritesSinceActivation( return newest === null ? null : newest >= activatedAtSeconds } -export async function rollbackOrcad( - options: OrcadRollbackOptions -): Promise { +export async function rollbackOrcad(options: OrcadRollbackOptions): Promise { const now = options.now ?? ((): Date => new Date()) const snapshotPresent = options.record.snapshot ? ( @@ -183,11 +181,7 @@ export async function rollbackOrcad( } } - const targetDir = computeRemoteInstallDir( - ORCAD_INSTALL_MODEL, - options.remoteHome, - safety.target - ) + const targetDir = computeRemoteInstallDir(ORCAD_INSTALL_MODEL, options.remoteHome, safety.target) await exec( options, orcadLaunchCommand(options.host, { diff --git a/src/main/ssh/orcad-remote-shell-commands.integration.test.ts b/src/main/ssh/orcad-remote-shell-commands.integration.test.ts index d1f8ac0048f..f43f82949ee 100644 --- a/src/main/ssh/orcad-remote-shell-commands.integration.test.ts +++ b/src/main/ssh/orcad-remote-shell-commands.integration.test.ts @@ -143,9 +143,9 @@ describe('liveness and stop commands, run for real', () => { const exited = new Promise((resolve) => child.once('exit', (_code, signal) => resolve(signal)) ) - expect(parseOrcadStopOutcome(sh(stopOrcadCommand(host, versionDir, { waitSeconds: 10 })))).toBe( - 'stopped' - ) + expect( + parseOrcadStopOutcome(sh(stopOrcadCommand(host, versionDir, { waitSeconds: 10 }))) + ).toBe('stopped') expect(await exited).toBe('SIGTERM') expect(parseOrcadLiveness(sh(orcadLivenessProbeCommand(host, versionDir)))).toBe('DEAD') } finally { diff --git a/src/main/ssh/remote-install-gc.ts b/src/main/ssh/remote-install-gc.ts index 0d649cd0b03..b14b7e11fba 100644 --- a/src/main/ssh/remote-install-gc.ts +++ b/src/main/ssh/remote-install-gc.ts @@ -211,7 +211,9 @@ async function isCandidateSafeToRemove( if (!(await isRelayInstallLockStale(conn, lockDir, host))) { return false } - process.stderr.write?.(`[${model.id}] GC: lock at ${lockDir} is stale; treating as recoverable\n`) + process.stderr.write?.( + `[${model.id}] GC: lock at ${lockDir} is stale; treating as recoverable\n` + ) } // Legacy dirs predate .install-complete; skip the sentinel and rely on the live-socket probe alone. @@ -231,7 +233,6 @@ async function isCandidateSafeToRemove( return !(await options.isDirLive(dir)) } - /** * The relay's GC, bound to its own namespace and its own liveness probe (a live unix socket * or Windows pipe inside the version dir). diff --git a/src/main/ssh/ssh-relay-versioned-install.ts b/src/main/ssh/ssh-relay-versioned-install.ts index d2ddc62e64b..5bd111bdec1 100644 --- a/src/main/ssh/ssh-relay-versioned-install.ts +++ b/src/main/ssh/ssh-relay-versioned-install.ts @@ -92,7 +92,11 @@ export function computeRemoteInstallDir( pathFlavor === 'windows' ? getRemoteHostPlatform('win32-x64') : getRemoteHostPlatform('linux-x64') - return joinRemotePath(host, remoteHome, ...remoteInstallDirSegments(model, fullVersion, pathFlavor)) + return joinRemotePath( + host, + remoteHome, + ...remoteInstallDirSegments(model, fullVersion, pathFlavor) + ) } /** diff --git a/src/renderer/src/components/editor/rich-markdown-tab-key-handler.test.ts b/src/renderer/src/components/editor/rich-markdown-tab-key-handler.test.ts index 7904f2d673a..1d597023b3e 100644 --- a/src/renderer/src/components/editor/rich-markdown-tab-key-handler.test.ts +++ b/src/renderer/src/components/editor/rich-markdown-tab-key-handler.test.ts @@ -522,5 +522,4 @@ describe('rich markdown Tab key handler', () => { editor.destroy() } }) - }) diff --git a/src/renderer/src/components/skills/SkillBundleInstallFlow.tsx b/src/renderer/src/components/skills/SkillBundleInstallFlow.tsx index 2040b63682b..3663ac4e81a 100644 --- a/src/renderer/src/components/skills/SkillBundleInstallFlow.tsx +++ b/src/renderer/src/components/skills/SkillBundleInstallFlow.tsx @@ -193,7 +193,10 @@ export function SkillBundleInstallFlow(props: { } else if (operation.status !== 'ok') { setError( operation.status === 'reconnect-required' - ? translate('auto.components.skills.install.reconnectBeforeInstalling', 'Reconnect your Orca account before installing.') + ? translate( + 'auto.components.skills.install.reconnectBeforeInstalling', + 'Reconnect your Orca account before installing.' + ) : operation.message ) } else { @@ -208,7 +211,12 @@ export function SkillBundleInstallFlow(props: { } } catch (cause) { console.warn('[skills] bundle install failed:', cause) - setError(translate('auto.components.skills.install.bundleVerificationFailed', 'Installation failed before Orca could verify the requested bundle.')) + setError( + translate( + 'auto.components.skills.install.bundleVerificationFailed', + 'Installation failed before Orca could verify the requested bundle.' + ) + ) } finally { installProgress.finish() setBusy(false) @@ -225,7 +233,12 @@ export function SkillBundleInstallFlow(props: { ...(environmentId === 'local' || environmentId.startsWith('ssh:') ? {} : { environmentId }) }) if (!cancelled.cancelled) { - setError(translate('auto.components.skills.install.destinationAlreadyFinished', 'The destination had already finished this installation.')) + setError( + translate( + 'auto.components.skills.install.destinationAlreadyFinished', + 'The destination had already finished this installation.' + ) + ) } } diff --git a/src/renderer/src/components/skills/SkillInstallDialog.tsx b/src/renderer/src/components/skills/SkillInstallDialog.tsx index c5498ac6b3d..cc0ee06b198 100644 --- a/src/renderer/src/components/skills/SkillInstallDialog.tsx +++ b/src/renderer/src/components/skills/SkillInstallDialog.tsx @@ -91,7 +91,12 @@ export function SkillInstallDialog({ const resolveLink = useCallback(async (value: string): Promise => { const shareId = parseSkillShareId(value) if (!shareId) { - setError(translate('auto.components.skills.install.enterShareLink', 'Enter an Orca skill share link.')) + setError( + translate( + 'auto.components.skills.install.enterShareLink', + 'Enter an Orca skill share link.' + ) + ) return } setBusy(true) @@ -103,14 +108,22 @@ export function SkillInstallDialog({ setError( operation.status === 'unconfigured' ? operation.message - : translate('auto.components.skills.install.shareUnavailable', 'This share is unavailable. The link may be invalid, expired, or revoked.') + : translate( + 'auto.components.skills.install.shareUnavailable', + 'This share is unavailable. The link may be invalid, expired, or revoked.' + ) ) return } setPreview({ shareId, version: operation.value.version }) } catch (cause) { console.warn('[skills] share resolution failed:', cause) - setError(translate('auto.components.skills.install.shareUnavailable', 'This share is unavailable. The link may be invalid, expired, or revoked.')) + setError( + translate( + 'auto.components.skills.install.shareUnavailable', + 'This share is unavailable. The link may be invalid, expired, or revoked.' + ) + ) } finally { setBusy(false) } @@ -204,7 +217,10 @@ export function SkillInstallDialog({ if (operation.status !== 'ok') { setError( operation.status === 'reconnect-required' - ? translate('auto.components.skills.install.reconnectBeforeInstalling', 'Reconnect your Orca account before installing.') + ? translate( + 'auto.components.skills.install.reconnectBeforeInstalling', + 'Reconnect your Orca account before installing.' + ) : operation.message ) return @@ -236,7 +252,12 @@ export function SkillInstallDialog({ ...(environmentId === 'local' || environmentId.startsWith('ssh:') ? {} : { environmentId }) }) if (!cancelled.cancelled) { - setError(translate('auto.components.skills.install.destinationAlreadyFinished', 'The destination had already finished this installation.')) + setError( + translate( + 'auto.components.skills.install.destinationAlreadyFinished', + 'The destination had already finished this installation.' + ) + ) } } diff --git a/src/renderer/src/components/skills/SkillInstallManagementDialog.tsx b/src/renderer/src/components/skills/SkillInstallManagementDialog.tsx index a760af0335e..0479aae20fd 100644 --- a/src/renderer/src/components/skills/SkillInstallManagementDialog.tsx +++ b/src/renderer/src/components/skills/SkillInstallManagementDialog.tsx @@ -91,7 +91,12 @@ export function SkillInstallManagementDialog({ return } console.warn('[skills] managed install listing failed:', cause) - setError(translate('auto.components.skills.install.inspectManagedFailed', 'Orca could not inspect managed installs on this machine.')) + setError( + translate( + 'auto.components.skills.install.inspectManagedFailed', + 'Orca could not inspect managed installs on this machine.' + ) + ) } finally { if (generation === loadGeneration.current) { setBusy(false) @@ -121,7 +126,10 @@ export function SkillInstallManagementDialog({ if (operation.status !== 'ok') { setError( operation.status === 'reconnect-required' - ? translate('auto.components.skills.install.reconnectForVersionHistory', 'Reconnect your Orca account to load version history.') + ? translate( + 'auto.components.skills.install.reconnectForVersionHistory', + 'Reconnect your Orca account to load version history.' + ) : operation.message ) return @@ -133,7 +141,12 @@ export function SkillInstallManagementDialog({ return } console.warn('[skills] package history failed:', cause) - setError(translate('auto.components.skills.install.versionHistoryUnavailable', 'Version history is unavailable for this skill.')) + setError( + translate( + 'auto.components.skills.install.versionHistoryUnavailable', + 'Version history is unavailable for this skill.' + ) + ) } finally { if (generation === detailGeneration.current) { setBusy(false) @@ -169,7 +182,12 @@ export function SkillInstallManagementDialog({ installedNames.has(skill.name) ) if (selectedSkills.length === 0) { - setError(translate('auto.components.skills.install.bundleSkillsMissing', 'This version does not contain any of the installed bundle skills.')) + setError( + translate( + 'auto.components.skills.install.bundleSkillsMissing', + 'This version does not contain any of the installed bundle skills.' + ) + ) return } const operation = await window.api.skills.installBundlePackageVersion({ @@ -194,7 +212,10 @@ export function SkillInstallManagementDialog({ if (operation.status !== 'ok') { setError( operation.status === 'reconnect-required' - ? translate('auto.components.skills.install.reconnectBeforeVersionChange', 'Reconnect your Orca account before changing versions.') + ? translate( + 'auto.components.skills.install.reconnectBeforeVersionChange', + 'Reconnect your Orca account before changing versions.' + ) : operation.message ) return @@ -220,7 +241,10 @@ export function SkillInstallManagementDialog({ if (operation.status !== 'ok') { setError( operation.status === 'reconnect-required' - ? translate('auto.components.skills.install.reconnectBeforeVersionChange', 'Reconnect your Orca account before changing versions.') + ? translate( + 'auto.components.skills.install.reconnectBeforeVersionChange', + 'Reconnect your Orca account before changing versions.' + ) : operation.message ) return @@ -234,7 +258,12 @@ export function SkillInstallManagementDialog({ } } catch (cause) { console.warn('[skills] version installation failed:', cause) - setError(translate('auto.components.skills.install.versionVerificationFailed', 'Orca could not verify the requested version.')) + setError( + translate( + 'auto.components.skills.install.versionVerificationFailed', + 'Orca could not verify the requested version.' + ) + ) } finally { installProgress.finish() setBusy(false) @@ -250,7 +279,12 @@ export function SkillInstallManagementDialog({ ...(environmentId === 'local' || environmentId.startsWith('ssh:') ? {} : { environmentId }) }) if (!cancelled.cancelled) { - setError(translate('auto.components.skills.install.destinationAlreadyFinished', 'The destination had already finished this installation.')) + setError( + translate( + 'auto.components.skills.install.destinationAlreadyFinished', + 'The destination had already finished this installation.' + ) + ) } } @@ -295,7 +329,12 @@ export function SkillInstallManagementDialog({ } } catch (cause) { console.warn('[skills] managed removal failed:', cause) - setError(translate('auto.components.skills.install.removeFailed', 'Orca could not safely remove this skill.')) + setError( + translate( + 'auto.components.skills.install.removeFailed', + 'Orca could not safely remove this skill.' + ) + ) } finally { setBusy(false) } diff --git a/tests/e2e/fixtures/terminal-emoji-table.md b/tests/e2e/fixtures/terminal-emoji-table.md index 761ee11ce88..318512da337 100644 --- a/tests/e2e/fixtures/terminal-emoji-table.md +++ b/tests/e2e/fixtures/terminal-emoji-table.md @@ -1,98 +1,98 @@ -| Emoji | Name | Category | Unicode | Description | Mood | Popularity | Year Added | -|-------|------|----------|---------|-------------|------|------------|------------| -| 😀 | Grinning Face | Smileys | U+1F600 | A happy grinning face | Positive | 95 | 2015 | -| 🚀 | Rocket | Travel | U+1F680 | A soaring rocket | Exciting | 88 | 2010 | -| 🧑‍💻 | Technologist | People | U+1F9D1 U+200D U+1F4BB | Person at computer | Focused | 92 | 2020 | -| 🎉 | Party Popper | Events | U+1F389 | Celebration popper | Festive | 97 | 2010 | -| 🐱 | Cat Face | Animals | U+1F431 | A cute cat face | Cute | 85 | 2010 | -| 🍕 | Pizza | Food | U+1F355 | Slice of pizza | Hungry | 90 | 2010 | -| 💻 | Laptop | Objects | U+1F4BB | A personal computer | Productive | 78 | 2010 | -| 🌍 | Globe Europe-Africa | Travel | U+1F30D | Earth showing Europe/Africa | Wanderlust | 72 | 2010 | -| ❤️ | Red Heart | Symbols | U+2764 | A red heart symbol | Loving | 99 | 2010 | -| 🎵 | Musical Note | Music | U+1F3B5 | Single eighth note | Musical | 76 | 2010 | -| 🤖 | Robot Face | Smileys | U+1F916 | A mechanical robot | Neutral | 81 | 2016 | -| ☕ | Hot Beverage | Food | U+1F375 | A steaming coffee cup | Awake | 89 | 2010 | -| 🌈 | Rainbow | Nature | U+1F308 | A colorful rainbow | Hopeful | 83 | 2010 | -| 🦄 | Unicorn Face | Animals | U+1F984 | A fantasy unicorn | Magical | 91 | 2015 | -| ⚡ | High Voltage | Symbols | U+26A1 | Lightning bolt | Energetic | 86 | 2010 | -| 🎸 | Guitar | Music | U+1F3B8 | A six-string guitar | Rocking | 71 | 2010 | -| 🍀 | Four Leaf Clover | Nature | U+1F340 | A lucky clover | Lucky | 82 | 2010 | -| 🏆 | Trophy | Awards | U+1F3C6 | A gold championship cup | Victorious | 87 | 2010 | -| 🧠 | Brain | Body | U+1F9E0 | A human brain | Smart | 93 | 2018 | -| 🌮 | Taco | Food | U+1F32E | A folded taco | Tasty | 84 | 2015 | -| 🛸 | Flying Saucer | Travel | U+1F6F8 | An alien spacecraft | Mysterious | 69 | 2017 | -| 🎨 | Artist Palette | Arts | U+1F3A8 | A painter's palette | Creative | 77 | 2010 | -| 🐧 | Penguin | Animals | U+1F427 | A cute penguin | Chill | 79 | 2010 | -| 💎 | Gem Stone | Objects | U+1F48E | A sparkling diamond | Precious | 74 | 2010 | -| 🎮 | Video Game | Objects | U+1F3AE | A game controller | Gaming | 88 | 2010 | -| 🌊 | Water Wave | Nature | U+1F30A | A crashing ocean wave | Calm | 80 | 2010 | -| 🔥 | Fire | Nature | U+1F525 | A burning flame | Lit | 98 | 2010 | -| 🌟 | Glowing Star | Symbols | U+1F31F | A twinkling star | Bright | 85 | 2010 | -| 🎤 | Microphone | Music | U+1F3A4 | A stage microphone | Singing | 73 | 2010 | -| 🐶 | Dog Face | Animals | U+1F436 | A friendly dog face | Loyal | 86 | 2010 | -| 🍩 | Doughnut | Food | U+1F369 | A frosted doughnut | Sweet | 82 | 2010 | -| 🚲 | Bicycle | Travel | U+1F6B2 | A push bike | Active | 67 | 2010 | -| 🎭 | Performing Arts | Arts | U+1F3AD | Theater masks | Dramatic | 64 | 2010 | -| 🐉 | Dragon | Animals | U+1F409 | A mythical dragon | Powerful | 78 | 2010 | -| 🎪 | Circus Tent | Events | U+1F3AA | A striped circus tent | Fun | 61 | 2010 | -| 🧊 | Ice Cube | Objects | U+1F9CA | A block of ice | Cool | 70 | 2019 | -| 🎯 | Bullseye | Symbols | U+1F3AF | A dart hitting target | Focused | 83 | 2010 | -| 🐬 | Dolphin | Animals | U+1F42C | A leaping dolphin | Playful | 80 | 2010 | -| 🚁 | Helicopter | Travel | U+1F681 | A flying helicopter | Aerial | 63 | 2010 | -| 🦊 | Fox Face | Animals | U+1F98A | A clever fox | Sneaky | 79 | 2016 | -| 🎲 | Game Die | Objects | U+1F3B2 | A six-sided die | Random | 68 | 2010 | -| 🍔 | Hamburger | Food | U+1F354 | A classic burger | Juicy | 91 | 2010 | -| 🦋 | Butterfly | Animals | U+1F98B | A colorful butterfly | Graceful | 84 | 2016 | -| 🏔️ | Snow-Capped Mountain | Travel | U+1F3D4 | A snowy mountain peak | Adventurous | 71 | 2010 | -| 🎩 | Top Hat | Objects | U+1F3A9 | A formal top hat | Classy | 62 | 2010 | -| 🐳 | Spouting Whale | Animals | U+1F433 | A whale spraying water | Majestic | 76 | 2010 | -| 🚀 | Rocket | Travel | U+1F680 | A soaring rocket | Exciting | 88 | 2010 | -| 🎹 | Musical Keyboard | Music | U+1F3B9 | A piano keyboard | Melodic | 66 | 2010 | -| 🍉 | Watermelon | Food | U+1F349 | A slice of watermelon | Refreshing | 81 | 2010 | -| 🧩 | Puzzle Piece | Objects | U+1F9E9 | A jigsaw puzzle piece | Challenging | 75 | 2018 | -| 🦀 | Crab | Animals | U+1F980 | A red crab | Feisty | 68 | 2015 | -| 🏄 | Surfing | Sports | U+1F3C4 | Someone riding a wave | Gnarly | 65 | 2010 | -| 🧑‍🎤 | Singer | People | U+1F9D1 U+200D U+1F3A4 | A stage performer | Talented | 78 | 2020 | -| 🌵 | Cactus | Nature | U+1F335 | A desert cactus | Tough | 73 | 2010 | -| 🧲 | Magnet | Objects | U+1F9F2 | A horseshoe magnet | Attractive | 58 | 2018 | -| 🦆 | Duck | Animals | U+1F986 | A yellow duck | Quirky | 74 | 2016 | -| 🚇 | Metro | Travel | U+1F687 | A subway train | Urban | 59 | 2010 | -| 🧾 | Receipt | Objects | U+1F9FE | A paper receipt | Transactional | 56 | 2018 | -| 🫘 | Beans | Food | U+1FAD8 | A bowl of beans | Nutritious | 42 | 2021 | -| 🦕 | Sauropod | Animals | U+1F995 | A long-neck dinosaur | Prehistoric | 72 | 2016 | -| 🏵️ | Rosette | Awards | U+1F3F5 | A decorative rosette | Honorable | 51 | 2010 | -| 🦮 | Guide Dog | Animals | U+1F9AE | A seeing-eye dog | Helpful | 67 | 2019 | -| 🦴 | Bone | Body | U+1F9B4 | A dog bone | Gnawing | 60 | 2019 | -| 🥏 | Flying Disc | Sports | U+1F94F | A frisbee | Airborne | 48 | 2017 | -| 🦧 | Orangutan | Animals | U+1F9A7 | A red-haired ape | Wise | 55 | 2019 | -| 🥭 | Mango | Food | U+1F96D | A ripe tropical fruit | Tropical | 71 | 2018 | -| 🧋 | Bubble Tea | Food | U+1F9CB | Tapioca pearl drink | Trendy | 78 | 2020 | -| 🪀 | Yo-Yo | Objects | U+1FA80 | A string toy | Nostalgic | 44 | 2020 | -| 🦤 | Dodo | Animals | U+1F9A4 | An extinct flightless bird | Extinct | 53 | 2019 | -| 🪐 | Ringed Planet | Space | U+1FA90 | Saturn with rings | Astronomical | 77 | 2020 | -| 🛹 | Skateboard | Sports | U+1F6F9 | A kick push skateboard | Rad | 67 | 2019 | -| 🥲 | Smiling Face with Tear | Smileys | U+1F972 | Happy but crying inside | Bittersweet | 90 | 2020 | -| 🫠 | Melting Face | Smileys | U+1FAE0 | Face slowly melting | Uncomfortable | 69 | 2021 | -| 🪩 | Mirror Ball | Objects | U+1FAA9 | A disco ball | Groovy | 73 | 2022 | -| 🪭 | Folding Hand Fan | Objects | U+1FAAF | A collapsible hand fan | Elegant | 38 | 2023 | -| 🫎 | Moose | Animals | U+1FACE | A large antlered mammal | Majestic | 45 | 2023 | -| 🪸 | Coral | Nature | U+1FAB8 | A coral reef formation | Vibrant | 52 | 2023 | -| 🪼 | Jellyfish | Animals | U+1FABC | A floating gelatinous sea creature | Ethereal | 49 | 2023 | -| 🫏 | Donkey | Animals | U+1FACF | A gray donkey | Stubborn | 41 | 2023 | -| 🫅 | Person with Crown | People | U+1FAC5 | A crowned royal figure | Regal | 58 | 2022 | -| 🪇 | Maracas | Music | U+1FA87 | A handheld percussion shaker | Rhythmic | 43 | 2023 | -| 🫘 | Beans | Food | U+1FAD8 | A bowl of legumes | Hearty | 42 | 2021 | -| 🦪 | Oyster | Food | U+1F9AA | A closed shellfish | Briny | 54 | 2019 | -| 🪨 | Rock | Nature | U+1FAA8 | A grey stone | Solid | 47 | 2020 | -| 🥸 | Disguised Face | Smileys | U+1F978 | Face with fake nose/glasses | Sneaky | 66 | 2021 | -| 🦬 | Bison | Animals | U+1F9AC | A large North American bovine | Sturdy | 50 | 2019 | -| 🥟 | Dumpling | Food | U+1F95F | A folded filled dough | Steamy | 76 | 2018 | -| 🪴 | Potted Plant | Nature | U+1FAB4 | A houseplant in a pot | Green | 68 | 2020 | -| 🛼 | Roller Skate | Sports | U+1F6FC | A quad-wheeled skate | Retro | 56 | 2021 | -| 🥏 | Flying Disc | Sports | U+1F94F | A plastic throwing disc | Soaring | 48 | 2017 | -| 🧃 | Beverage Box | Food | U+1F9C3 | A juice box | Convenient | 63 | 2018 | -| 🦚 | Peacock | Animals | U+1F99A | A bird with iridescent tail | Proud | 75 | 2018 | -| 🫧 | Bubbles | Nature | U+1FAE7 | Floating iridescent spheres | Light | 57 | 2023 | -| 🥅 | Goal Net | Sports | U+1F945 | A soccer/hockey goal | Scoring | 44 | 2017 | -| 🦺 | Safety Vest | Objects | U+1F9BA | A high-visibility vest | Protected | 51 | 2019 | -| ✈️ | Airplane | Travel | U+2708 | A flying commercial jet | Traveling | 94 | 2010 | +| Emoji | Name | Category | Unicode | Description | Mood | Popularity | Year Added | +| ----- | ---------------------- | -------- | ---------------------- | ---------------------------------- | ------------- | ---------- | ---------- | +| 😀 | Grinning Face | Smileys | U+1F600 | A happy grinning face | Positive | 95 | 2015 | +| 🚀 | Rocket | Travel | U+1F680 | A soaring rocket | Exciting | 88 | 2010 | +| 🧑‍💻 | Technologist | People | U+1F9D1 U+200D U+1F4BB | Person at computer | Focused | 92 | 2020 | +| 🎉 | Party Popper | Events | U+1F389 | Celebration popper | Festive | 97 | 2010 | +| 🐱 | Cat Face | Animals | U+1F431 | A cute cat face | Cute | 85 | 2010 | +| 🍕 | Pizza | Food | U+1F355 | Slice of pizza | Hungry | 90 | 2010 | +| 💻 | Laptop | Objects | U+1F4BB | A personal computer | Productive | 78 | 2010 | +| 🌍 | Globe Europe-Africa | Travel | U+1F30D | Earth showing Europe/Africa | Wanderlust | 72 | 2010 | +| ❤️ | Red Heart | Symbols | U+2764 | A red heart symbol | Loving | 99 | 2010 | +| 🎵 | Musical Note | Music | U+1F3B5 | Single eighth note | Musical | 76 | 2010 | +| 🤖 | Robot Face | Smileys | U+1F916 | A mechanical robot | Neutral | 81 | 2016 | +| ☕ | Hot Beverage | Food | U+1F375 | A steaming coffee cup | Awake | 89 | 2010 | +| 🌈 | Rainbow | Nature | U+1F308 | A colorful rainbow | Hopeful | 83 | 2010 | +| 🦄 | Unicorn Face | Animals | U+1F984 | A fantasy unicorn | Magical | 91 | 2015 | +| ⚡ | High Voltage | Symbols | U+26A1 | Lightning bolt | Energetic | 86 | 2010 | +| 🎸 | Guitar | Music | U+1F3B8 | A six-string guitar | Rocking | 71 | 2010 | +| 🍀 | Four Leaf Clover | Nature | U+1F340 | A lucky clover | Lucky | 82 | 2010 | +| 🏆 | Trophy | Awards | U+1F3C6 | A gold championship cup | Victorious | 87 | 2010 | +| 🧠 | Brain | Body | U+1F9E0 | A human brain | Smart | 93 | 2018 | +| 🌮 | Taco | Food | U+1F32E | A folded taco | Tasty | 84 | 2015 | +| 🛸 | Flying Saucer | Travel | U+1F6F8 | An alien spacecraft | Mysterious | 69 | 2017 | +| 🎨 | Artist Palette | Arts | U+1F3A8 | A painter's palette | Creative | 77 | 2010 | +| 🐧 | Penguin | Animals | U+1F427 | A cute penguin | Chill | 79 | 2010 | +| 💎 | Gem Stone | Objects | U+1F48E | A sparkling diamond | Precious | 74 | 2010 | +| 🎮 | Video Game | Objects | U+1F3AE | A game controller | Gaming | 88 | 2010 | +| 🌊 | Water Wave | Nature | U+1F30A | A crashing ocean wave | Calm | 80 | 2010 | +| 🔥 | Fire | Nature | U+1F525 | A burning flame | Lit | 98 | 2010 | +| 🌟 | Glowing Star | Symbols | U+1F31F | A twinkling star | Bright | 85 | 2010 | +| 🎤 | Microphone | Music | U+1F3A4 | A stage microphone | Singing | 73 | 2010 | +| 🐶 | Dog Face | Animals | U+1F436 | A friendly dog face | Loyal | 86 | 2010 | +| 🍩 | Doughnut | Food | U+1F369 | A frosted doughnut | Sweet | 82 | 2010 | +| 🚲 | Bicycle | Travel | U+1F6B2 | A push bike | Active | 67 | 2010 | +| 🎭 | Performing Arts | Arts | U+1F3AD | Theater masks | Dramatic | 64 | 2010 | +| 🐉 | Dragon | Animals | U+1F409 | A mythical dragon | Powerful | 78 | 2010 | +| 🎪 | Circus Tent | Events | U+1F3AA | A striped circus tent | Fun | 61 | 2010 | +| 🧊 | Ice Cube | Objects | U+1F9CA | A block of ice | Cool | 70 | 2019 | +| 🎯 | Bullseye | Symbols | U+1F3AF | A dart hitting target | Focused | 83 | 2010 | +| 🐬 | Dolphin | Animals | U+1F42C | A leaping dolphin | Playful | 80 | 2010 | +| 🚁 | Helicopter | Travel | U+1F681 | A flying helicopter | Aerial | 63 | 2010 | +| 🦊 | Fox Face | Animals | U+1F98A | A clever fox | Sneaky | 79 | 2016 | +| 🎲 | Game Die | Objects | U+1F3B2 | A six-sided die | Random | 68 | 2010 | +| 🍔 | Hamburger | Food | U+1F354 | A classic burger | Juicy | 91 | 2010 | +| 🦋 | Butterfly | Animals | U+1F98B | A colorful butterfly | Graceful | 84 | 2016 | +| 🏔️ | Snow-Capped Mountain | Travel | U+1F3D4 | A snowy mountain peak | Adventurous | 71 | 2010 | +| 🎩 | Top Hat | Objects | U+1F3A9 | A formal top hat | Classy | 62 | 2010 | +| 🐳 | Spouting Whale | Animals | U+1F433 | A whale spraying water | Majestic | 76 | 2010 | +| 🚀 | Rocket | Travel | U+1F680 | A soaring rocket | Exciting | 88 | 2010 | +| 🎹 | Musical Keyboard | Music | U+1F3B9 | A piano keyboard | Melodic | 66 | 2010 | +| 🍉 | Watermelon | Food | U+1F349 | A slice of watermelon | Refreshing | 81 | 2010 | +| 🧩 | Puzzle Piece | Objects | U+1F9E9 | A jigsaw puzzle piece | Challenging | 75 | 2018 | +| 🦀 | Crab | Animals | U+1F980 | A red crab | Feisty | 68 | 2015 | +| 🏄 | Surfing | Sports | U+1F3C4 | Someone riding a wave | Gnarly | 65 | 2010 | +| 🧑‍🎤 | Singer | People | U+1F9D1 U+200D U+1F3A4 | A stage performer | Talented | 78 | 2020 | +| 🌵 | Cactus | Nature | U+1F335 | A desert cactus | Tough | 73 | 2010 | +| 🧲 | Magnet | Objects | U+1F9F2 | A horseshoe magnet | Attractive | 58 | 2018 | +| 🦆 | Duck | Animals | U+1F986 | A yellow duck | Quirky | 74 | 2016 | +| 🚇 | Metro | Travel | U+1F687 | A subway train | Urban | 59 | 2010 | +| 🧾 | Receipt | Objects | U+1F9FE | A paper receipt | Transactional | 56 | 2018 | +| 🫘 | Beans | Food | U+1FAD8 | A bowl of beans | Nutritious | 42 | 2021 | +| 🦕 | Sauropod | Animals | U+1F995 | A long-neck dinosaur | Prehistoric | 72 | 2016 | +| 🏵️ | Rosette | Awards | U+1F3F5 | A decorative rosette | Honorable | 51 | 2010 | +| 🦮 | Guide Dog | Animals | U+1F9AE | A seeing-eye dog | Helpful | 67 | 2019 | +| 🦴 | Bone | Body | U+1F9B4 | A dog bone | Gnawing | 60 | 2019 | +| 🥏 | Flying Disc | Sports | U+1F94F | A frisbee | Airborne | 48 | 2017 | +| 🦧 | Orangutan | Animals | U+1F9A7 | A red-haired ape | Wise | 55 | 2019 | +| 🥭 | Mango | Food | U+1F96D | A ripe tropical fruit | Tropical | 71 | 2018 | +| 🧋 | Bubble Tea | Food | U+1F9CB | Tapioca pearl drink | Trendy | 78 | 2020 | +| 🪀 | Yo-Yo | Objects | U+1FA80 | A string toy | Nostalgic | 44 | 2020 | +| 🦤 | Dodo | Animals | U+1F9A4 | An extinct flightless bird | Extinct | 53 | 2019 | +| 🪐 | Ringed Planet | Space | U+1FA90 | Saturn with rings | Astronomical | 77 | 2020 | +| 🛹 | Skateboard | Sports | U+1F6F9 | A kick push skateboard | Rad | 67 | 2019 | +| 🥲 | Smiling Face with Tear | Smileys | U+1F972 | Happy but crying inside | Bittersweet | 90 | 2020 | +| 🫠 | Melting Face | Smileys | U+1FAE0 | Face slowly melting | Uncomfortable | 69 | 2021 | +| 🪩 | Mirror Ball | Objects | U+1FAA9 | A disco ball | Groovy | 73 | 2022 | +| 🪭 | Folding Hand Fan | Objects | U+1FAAF | A collapsible hand fan | Elegant | 38 | 2023 | +| 🫎 | Moose | Animals | U+1FACE | A large antlered mammal | Majestic | 45 | 2023 | +| 🪸 | Coral | Nature | U+1FAB8 | A coral reef formation | Vibrant | 52 | 2023 | +| 🪼 | Jellyfish | Animals | U+1FABC | A floating gelatinous sea creature | Ethereal | 49 | 2023 | +| 🫏 | Donkey | Animals | U+1FACF | A gray donkey | Stubborn | 41 | 2023 | +| 🫅 | Person with Crown | People | U+1FAC5 | A crowned royal figure | Regal | 58 | 2022 | +| 🪇 | Maracas | Music | U+1FA87 | A handheld percussion shaker | Rhythmic | 43 | 2023 | +| 🫘 | Beans | Food | U+1FAD8 | A bowl of legumes | Hearty | 42 | 2021 | +| 🦪 | Oyster | Food | U+1F9AA | A closed shellfish | Briny | 54 | 2019 | +| 🪨 | Rock | Nature | U+1FAA8 | A grey stone | Solid | 47 | 2020 | +| 🥸 | Disguised Face | Smileys | U+1F978 | Face with fake nose/glasses | Sneaky | 66 | 2021 | +| 🦬 | Bison | Animals | U+1F9AC | A large North American bovine | Sturdy | 50 | 2019 | +| 🥟 | Dumpling | Food | U+1F95F | A folded filled dough | Steamy | 76 | 2018 | +| 🪴 | Potted Plant | Nature | U+1FAB4 | A houseplant in a pot | Green | 68 | 2020 | +| 🛼 | Roller Skate | Sports | U+1F6FC | A quad-wheeled skate | Retro | 56 | 2021 | +| 🥏 | Flying Disc | Sports | U+1F94F | A plastic throwing disc | Soaring | 48 | 2017 | +| 🧃 | Beverage Box | Food | U+1F9C3 | A juice box | Convenient | 63 | 2018 | +| 🦚 | Peacock | Animals | U+1F99A | A bird with iridescent tail | Proud | 75 | 2018 | +| 🫧 | Bubbles | Nature | U+1FAE7 | Floating iridescent spheres | Light | 57 | 2023 | +| 🥅 | Goal Net | Sports | U+1F945 | A soccer/hockey goal | Scoring | 44 | 2017 | +| 🦺 | Safety Vest | Objects | U+1F9BA | A high-visibility vest | Protected | 51 | 2019 | +| ✈️ | Airplane | Travel | U+2708 | A flying commercial jet | Traveling | 94 | 2010 | diff --git a/tests/e2e/ssh-config-host-picker.PLAN.md b/tests/e2e/ssh-config-host-picker.PLAN.md index ebb7519616e..ddd44b907bd 100644 --- a/tests/e2e/ssh-config-host-picker.PLAN.md +++ b/tests/e2e/ssh-config-host-picker.PLAN.md @@ -16,15 +16,15 @@ Commits under test (vs main): ## Already covered (do **not** re-test in E2E) -| Area | Where | -|------|--------| -| `listConfigHosts` / `resolveConfigHost` IPC registration | `src/main/ipc/ssh.test.ts` | -| Search, result limit, suppressed aliases, alreadyInOrca | `ssh-config-host-picker.test.ts` | -| Generation guard, freeze-while-resolving, late resolve | `AddRemoteHostDialog.config-picker.test.tsx` | -| Bulk `importConfig()` without `reAdopt` | `add-remote-host-ssh-actions.test.ts` | -| Alias folding / duplicate save check | `ssh-target-duplicate.test.ts` | -| `configured-only` host registry / setup fail-closed | unit tests in shared + project-host-workspace-target | -| Settings modal viewport stability | `ssh-host-form-modal.spec.ts` | +| Area | Where | +| -------------------------------------------------------- | ---------------------------------------------------- | +| `listConfigHosts` / `resolveConfigHost` IPC registration | `src/main/ipc/ssh.test.ts` | +| Search, result limit, suppressed aliases, alreadyInOrca | `ssh-config-host-picker.test.ts` | +| Generation guard, freeze-while-resolving, late resolve | `AddRemoteHostDialog.config-picker.test.tsx` | +| Bulk `importConfig()` without `reAdopt` | `add-remote-host-ssh-actions.test.ts` | +| Alias folding / duplicate save check | `ssh-target-duplicate.test.ts` | +| `configured-only` host registry / setup fail-closed | unit tests in shared + project-host-workspace-target | +| Settings modal viewport stability | `ssh-host-form-modal.spec.ts` | E2E is reserved for real Electron HOME isolation, real `~/.ssh/config` parse, real `ssh -G` resolve, and user-visible DOM outcomes. @@ -66,74 +66,74 @@ Optional second file if the Settings Import case grows: ### P1 — Empty config empty state -| | | -|--|--| -| **Setup** | Do not create `~/.ssh/config` (or write empty file). | -| **Steps** | Open Add SSH host → Fill from ~/.ssh/config… | +| | | +| ---------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| **Setup** | Do not create `~/.ssh/config` (or write empty file). | +| **Steps** | Open Add SSH host → Fill from ~/.ssh/config… | | **Expect** | Dialog title **Choose from ~/.ssh/config**; body **No hosts in ~/.ssh/config**; **Add all to Orca** disabled; **Back** returns to form. | ### P2 — Seeded hosts listed with summary lines -| | | -|--|--| -| **Setup** | Write config with ≥2 concrete Hosts, e.g. `e2e-alpha` / `e2e-bravo` with HostName, User, Port. | -| **Steps** | Open picker. | +| | | +| ---------- | --------------------------------------------------------------------------------------------------------------------- | +| **Setup** | Write config with ≥2 concrete Hosts, e.g. `e2e-alpha` / `e2e-bravo` with HostName, User, Port. | +| **Steps** | Open picker. | | **Expect** | Host list `SSH config hosts` shows both aliases; subtitle `user@hostname:port`; button **Add all 2 to Orca** enabled. | ### P3 — Select host prefills form (and Save persists) -| | | -|--|--| -| **Setup** | Config Host `e2e-prod` → HostName `prod.example.test`, User `deploy`, Port `2222`. | -| **Steps** | Pick `e2e-prod` → wait for form → click **Save**. | -| **Expect** | After pick: Host/alias field = `prod.example.test`, Username `deploy`, Port `2222`, Label `e2e-prod` (or alias); optional toast *Filled from e2e-prod*; Identity file may stay empty with config hint. After Save: dialog closes; target appears in Settings → SSH (or listTargets shows matching host). | +| | | +| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Setup** | Config Host `e2e-prod` → HostName `prod.example.test`, User `deploy`, Port `2222`. | +| **Steps** | Pick `e2e-prod` → wait for form → click **Save**. | +| **Expect** | After pick: Host/alias field = `prod.example.test`, Username `deploy`, Port `2222`, Label `e2e-prod` (or alias); optional toast _Filled from e2e-prod_; Identity file may stay empty with config hint. After Save: dialog closes; target appears in Settings → SSH (or listTargets shows matching host). | ### P4 — Filter narrows list -| | | -|--|--| -| **Setup** | Hosts `e2e-alpha`, `e2e-bravo`. | -| **Steps** | Open picker; filter `bravo`. | +| | | +| ---------- | ------------------------------------------------------------------------ | +| **Setup** | Hosts `e2e-alpha`, `e2e-bravo`. | +| **Steps** | Open picker; filter `bravo`. | | **Expect** | Only bravo row; alpha gone; **No matching hosts** if filter is nonsense. | ### P5 — Already-in-Orca badge + disabled row -| | | -|--|--| -| **Setup** | Config hosts alpha + bravo. Seed Orca target with `configHost`/`label` matching alpha (via `ssh.addTarget`). | -| **Steps** | Open picker. | -| **Expect** | Alpha shows **In Orca** badge and is not clickable; bravo still selectable; **Add all 1 to Orca** (not 2). | +| | | +| ---------- | ------------------------------------------------------------------------------------------------------------ | +| **Setup** | Config hosts alpha + bravo. Seed Orca target with `configHost`/`label` matching alpha (via `ssh.addTarget`). | +| **Steps** | Open picker. | +| **Expect** | Alpha shows **In Orca** badge and is not clickable; bravo still selectable; **Add all 1 to Orca** (not 2). | ### P6 — Add all N to Orca imports new hosts only -| | | -|--|--| -| **Setup** | Config with 2 new hosts; no Orca targets for them. | -| **Steps** | **Add all 2 to Orca** → wait for success toast / return to form or list refresh. | +| | | +| ---------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| **Setup** | Config with 2 new hosts; no Orca targets for them. | +| **Steps** | **Add all 2 to Orca** → wait for success toast / return to form or list refresh. | | **Expect** | Both targets exist (DOM in Settings SSH and/or listTargets); re-open picker shows **All hosts already in Orca** / both **In Orca**. | ### P7 — Add all does **not** re-adopt deleted hosts -| | | -|--|--| -| **Setup** | Config with alpha + bravo; Add all → remove alpha via API (creates suppress tombstone). | -| **Steps** | Re-open picker; note count; optionally click Add all again. | +| | | +| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| **Setup** | Config with alpha + bravo; Add all → remove alpha via API (creates suppress tombstone). | +| **Steps** | Re-open picker; note count; optionally click Add all again. | | **Expect** | Alpha absent from picker (suppressed) or not re-created; only new hosts counted; `listTargets` still lacks deleted alpha after second Add all. | ### P8 — Back discards pending pick path -| | | -|--|--| -| **Setup** | Seeded config. | -| **Steps** | Open picker → **Back** without selecting. | +| | | +| ---------- | ------------------------------------------------------ | +| **Setup** | Seeded config. | +| **Steps** | Open picker → **Back** without selecting. | | **Expect** | Form fields still empty (Host blank); no filled toast. | ### P9 — Settings Import re-adopts (contrast with P7) -| | | -|--|--| -| **Setup** | Same as P7 after delete. | -| **Steps** | Settings → SSH → **Import** (explicit reAdopt path). | +| | | +| ---------- | ---------------------------------------------------------------------- | +| **Setup** | Same as P7 after delete. | +| **Steps** | Settings → SSH → **Import** (explicit reAdopt path). | | **Expect** | Deleted config host reappears as an Orca target; toast sync count ≥ 1. | --- @@ -155,17 +155,17 @@ Skip: 100-host truncation, resolve races, GSSAPI system-default, composer host-a ## Implementation status (done) -| Case | Spec | -|------|------| -| P1 empty state | `ssh-config-host-picker.spec.ts` | -| P2 list + Add all enabled | `ssh-config-host-picker.spec.ts` | +| Case | Spec | +| ------------------------------------- | -------------------------------- | +| P1 empty state | `ssh-config-host-picker.spec.ts` | +| P2 list + Add all enabled | `ssh-config-host-picker.spec.ts` | | P3 select + Save (+ N3 identity hint) | `ssh-config-host-picker.spec.ts` | -| P4 filter | `ssh-config-host-picker.spec.ts` | -| P5 In Orca badge / count | `ssh-config-host-import.spec.ts` | -| P6 Add all imports | `ssh-config-host-import.spec.ts` | -| P7 no re-adopt after delete | `ssh-config-host-import.spec.ts` | -| P8 Back without select | `ssh-config-host-picker.spec.ts` | -| P9 Settings Import re-adopts | `ssh-config-host-import.spec.ts` | +| P4 filter | `ssh-config-host-picker.spec.ts` | +| P5 In Orca badge / count | `ssh-config-host-import.spec.ts` | +| P6 Add all imports | `ssh-config-host-import.spec.ts` | +| P7 no re-adopt after delete | `ssh-config-host-import.spec.ts` | +| P8 Back without select | `ssh-config-host-picker.spec.ts` | +| P9 Settings Import re-adopts | `ssh-config-host-import.spec.ts` | Shared helpers: `tests/e2e/helpers/ssh-config-host-picker.ts` diff --git a/tests/tools/daemon-relocation-spike/README.md b/tests/tools/daemon-relocation-spike/README.md index a85bcf3d0ad..0cf861c1b45 100644 --- a/tests/tools/daemon-relocation-spike/README.md +++ b/tests/tools/daemon-relocation-spike/README.md @@ -58,11 +58,11 @@ dir holding `conpty.dll` + `OpenConsole.exe`). Tiers differ only in which top-level `*.dll` files they carry: -| Tier | Top-level DLLs | -| --------- | ---------------------------------------------------------------- | -| `full` | **all** top-level `*.dll` | +| Tier | Top-level DLLs | +| --------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `full` | **all** top-level `*.dll` | | `no-gpu` | all **except** GPU/render DLLs (`libEGL`, `libGLESv2`, `vk_swiftshader`, `vulkan-1`, `d3dcompiler_47`); **keeps** `ffmpeg.dll` | -| `minimal` | **none** (exe + data blobs + daemon bundle + node-pty only) | +| `minimal` | **none** (exe + data blobs + daemon bundle + node-pty only) | Trimming further is a config change (edit `TIER_DEFINITIONS` / `GPU_DLLS`), not a code change. @@ -95,7 +95,7 @@ The reverted #7421 added a `node-pty` patch that reads `config/patches/node-pty@1.1.0.patch` does NOT contain that override** — it was reverted. The spike therefore relies on **layout preservation** (copying the node-pty tree at its default relative path) rather than the env override. The -spike still *sets* `ORCA_NODE_PTY_NATIVE_DIR` to the relocated native dir so it +spike still _sets_ `ORCA_NODE_PTY_NATIVE_DIR` to the relocated native dir so it keeps working if pointed at a build that carries the patch, but on this branch the var is inert. diff --git a/tests/tools/repro-watcher-crash-7547/fixed-child.cjs b/tests/tools/repro-watcher-crash-7547/fixed-child.cjs index f45d65bc7a9..99a5d782cc3 100644 --- a/tests/tools/repro-watcher-crash-7547/fixed-child.cjs +++ b/tests/tools/repro-watcher-crash-7547/fixed-child.cjs @@ -243,13 +243,19 @@ async function finalHealthCheck(baseDir) { let gotEvent = false let sub = null try { - sub = await client.subscribeViaWatcherProcess(dir, (err, events) => { - if (!err && events.length > 0) { - gotEvent = true - } - }, OPTS) + sub = await client.subscribeViaWatcherProcess( + dir, + (err, events) => { + if (!err && events.length > 0) { + gotEvent = true + } + }, + OPTS + ) } catch (err) { - process.stderr.write(`[fixed-harness] health subscribe attempt ${attempt} failed: ${err.message}\n`) + process.stderr.write( + `[fixed-harness] health subscribe attempt ${attempt} failed: ${err.message}\n` + ) rmrf(dir) await sleep(5000) continue @@ -279,10 +285,13 @@ async function main() { // Watchdog: a stuck lane must fail loudly, and a premature natural exit // (empty event loop) must not read as success. - const watchdog = setTimeout(() => { - process.stderr.write('[fixed-harness] FAIL: watchdog — scenarios did not complete\n') - process.exit(7) - }, durationMs * 4 + 60_000) + const watchdog = setTimeout( + () => { + process.stderr.write('[fixed-harness] FAIL: watchdog — scenarios did not complete\n') + process.exit(7) + }, + durationMs * 4 + 60_000 + ) process.on('exit', (code) => { if (!completed && code === 0) { process.exitCode = 8 @@ -313,7 +322,9 @@ async function main() { `interruptions=${stats.interruptions} healthy=${healthy} fallback=${stats.fallbackDetected}\n` ) if (stats.fallbackDetected) { - process.stderr.write('[fixed-harness] FAIL: in-process fallback used — isolation not exercised\n') + process.stderr.write( + '[fixed-harness] FAIL: in-process fallback used — isolation not exercised\n' + ) process.exitCode = 6 return } diff --git a/tests/tools/repro-watcher-crash-7547/run.cjs b/tests/tools/repro-watcher-crash-7547/run.cjs index 0d10b56a2f5..e5bf851c07e 100644 --- a/tests/tools/repro-watcher-crash-7547/run.cjs +++ b/tests/tools/repro-watcher-crash-7547/run.cjs @@ -52,7 +52,9 @@ async function main() { const { code, signal } = await runOnce(s, durationMs) const elapsed = Date.now() - started const meaning = KNOWN[code] || (code === 0 ? 'clean exit' : 'unexpected') - console.log(`=== [${s}] iter ${i}: exit=${code} (${hex(code ?? -1)}) signal=${signal} ${meaning} after ${elapsed}ms`) + console.log( + `=== [${s}] iter ${i}: exit=${code} (${hex(code ?? -1)}) signal=${signal} ${meaning} after ${elapsed}ms` + ) results.push({ scenario: s, iteration: i, code, signal, elapsed }) if (code !== 0 && code !== null) { crashed = true diff --git a/tests/tools/win-update-e2e/README.md b/tests/tools/win-update-e2e/README.md index f6668351dcf..b7f83909f24 100644 --- a/tests/tools/win-update-e2e/README.md +++ b/tests/tools/win-update-e2e/README.md @@ -99,13 +99,13 @@ and the safety guards above would (correctly) refuse to run. **Isolated mode** real install. **The /D mechanism.** electron-builder's NSIS honors the standard NSIS `/D=` -override for the install *directory* (`node_modules/app-builder-lib/templates/nsis/multiUser.nsh`). +override for the install _directory_ (`node_modules/app-builder-lib/templates/nsis/multiUser.nsh`). `/D` is special: it must be the **last** argument and **cannot be quoted**, so the path must be absolute and **spaces-free** (validated by `validateInstallDir`). The installer's kill-sweep only matches processes under its own `$INSTDIR`, so a separate directory never touches the real install's app or daemon processes. -**Why registry/shortcut backup-restore exists.** `/D` relocates *files only*. +**Why registry/shortcut backup-restore exists.** `/D` relocates _files only_. Regardless of `/D`, the installer writes `InstallLocation` + the uninstall entry to the **same per-user HKCU keys** as the real install (`HKCU\Software\` and @@ -191,22 +191,22 @@ powershell -File tests/tools/win-update-e2e/window-enum.ps1 ## Files -| File | Responsibility | -| -------------------------- | ----------------------------------------------------------------------------- | -| `run.mjs` | Orchestrator + CLI entry | -| `cli-args.mjs` | Argument parsing / validation | -| `preflight.mjs` | win32/elevation checks, pre-existing-app refusal, baseline snapshot | -| `installer-steps.mjs` | Silent install/update/uninstall, exe discovery, gh download | -| `registry-shortcut-backup.mjs` | Isolated mode: snapshot/restore the shared HKCU keys + Orca shortcuts | -| `app-driver.mjs` | Playwright Electron launch + terminal driving (production-safe DOM selectors) | -| `interactivity-probes.mjs` | Sentinel-file echo / heartbeat / Ctrl+C probes | -| `daemon-processes.mjs` | Daemon PID discovery (command-line marker + pid file), scoped | -| `window-enum.ps1` | Shared visible-top-level-window enumerator (P/Invoke `EnumWindows`) | -| `window-watch.ps1` | Background baseline-diff watch loop → JSONL | -| `window-watch.mjs` | Node wrapper: start/stop watch, `--selftest`, baseline capture | -| `assertions.mjs` | Window-event classification + profile PASS/FAIL table | -| `platform-guard.mjs` | `assertWin32`, elevation detection | -| `powershell-runner.mjs` | Windows PowerShell 5.1 spawn helpers | +| File | Responsibility | +| ------------------------------ | ----------------------------------------------------------------------------- | +| `run.mjs` | Orchestrator + CLI entry | +| `cli-args.mjs` | Argument parsing / validation | +| `preflight.mjs` | win32/elevation checks, pre-existing-app refusal, baseline snapshot | +| `installer-steps.mjs` | Silent install/update/uninstall, exe discovery, gh download | +| `registry-shortcut-backup.mjs` | Isolated mode: snapshot/restore the shared HKCU keys + Orca shortcuts | +| `app-driver.mjs` | Playwright Electron launch + terminal driving (production-safe DOM selectors) | +| `interactivity-probes.mjs` | Sentinel-file echo / heartbeat / Ctrl+C probes | +| `daemon-processes.mjs` | Daemon PID discovery (command-line marker + pid file), scoped | +| `window-enum.ps1` | Shared visible-top-level-window enumerator (P/Invoke `EnumWindows`) | +| `window-watch.ps1` | Background baseline-diff watch loop → JSONL | +| `window-watch.mjs` | Node wrapper: start/stop watch, `--selftest`, baseline capture | +| `assertions.mjs` | Window-event classification + profile PASS/FAIL table | +| `platform-guard.mjs` | `assertWin32`, elevation detection | +| `powershell-runner.mjs` | Windows PowerShell 5.1 spawn helpers | ## Known limitations