mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
flow-visual-settle
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1706fb5c2a |
fix(boot-device): don't tear down a healthy-but-slow emulator on the final boot attempt (#305)
## Problem
On Linux (headless, software-GPU) I hit `boot-device` repeatedly failing
with
`PackageManager did not respond ... Emulator has been terminated`, even
though
the emulator had booted fine. Root cause: **Stage 5** of `attemptBoot`
ran a
single `pm path android` probe with a 10 s timeout and, on failure,
killed the
emulator and threw.
On a loaded host (or a freshly `-wipe-data`'d image still finishing its
first-boot package scan), `pm` can take well over 10 s to answer while
the guest
is perfectly healthy — it already reached `sys.boot_completed` and
registered
with adb. On the **final cold attempt** there is no fallback, so a
momentarily
slow PackageManager turned a usable emulator into a hard boot failure
(and
destroyed it, so the next try started from a cold boot again).
## Fix
- Stage 5 now **retries** `pm path android` within a budget instead of a
single
10 s window.
- Threads a `tearDownIfUnready` flag through `attemptBoot`:
- **hot-boot attempt** — tight ~10 s budget, tear down on failure so we
still
fall through to the cold boot.
- **final cold attempt** — retry up to 45 s and, if PM is still slow,
**return
the emulator as booted rather than destroying it**. gRPC
screenshots/gestures
work without PM, and killing it just guarantees failure with nothing to
fall
back to. A confirmed QEMU crash (mid-probe or via the early-exit racer)
still
tears down and rethrows the real cause.
Also adds a note to the `argent-android-emulator-setup` skill:
hard-killing qemu
(`pkill -9`) leaves the userdata image dirty — the usual cause of a cold
boot
that hangs for minutes with runaway writes — and `-wipe-data` resets it.
## Tests
`packages/tool-server/test/boot-device-hotboot.test.ts` — added a
regression
test asserting the final cold attempt returns the emulator as `booted`
(and does
**not** issue `adb emu kill`) when PM stays slow. All 28 boot-device
tests pass.
## Out of scope / follow-ups (found during the same Linux session)
- `simulator-server` (Rust) **panics** (`android_emulator.rs:104 "should
be
running"`) when asked to attach to a pre-existing emulator launched with
`-read-only` that never registered gRPC discovery. Better to return a
clear
"can't attach, reboot via Argent" error. Separate (cross-language)
change.
- `screenshot-diff` with `captureCurrent: true` fails reproducibly with
`"server response missing url or path"` while plain `screenshot` and
file-vs-file diff both work.
- `react-profiler-component-source` returns `found: false` for valid
top-level
`export function` components.
|
||
|
|
2be58a5cf1 |
fix(boot-device): five correctness fixes from post-merge Linux review (#296)
## Summary Five issues found in the post-merge code review of #249 (Linux support). All confirmed present on main; none fixed by #194. ### H1 — `awaitFirstRealFrame` timeout leaves emulator running `awaitFirstRealFrame` is called after the cold-boot `try/catch` closes. On a SurfaceFlinger timeout the emulator process stayed alive: `inFlightBoots.finally` cleared the map entry, so the next `boot-device` call found the stale serial via the `alreadyRunning` fast path, had to run `assertScreencapAlive`, killed it there, and then fell through to another full cold boot — doubling latency and surfacing a confusing "wedged framebuffer" error instead of the original timeout. **Fix:** wrap the `awaitFirstRealFrame` call in a try/catch that calls `killEmulatorQuietly` before re-throwing. ### H2 — `awaitFirstRealFrame` budget not clamped against `overallDeadline` Every `attemptBoot` stage clamps via `Math.min(params.attemptDeadline, …)`. `awaitFirstRealFrame` received the fixed `STAGE_BUDGET.firstRealFrame` (90 s) with no knowledge of `overallDeadline`, allowing total wall time to silently exceed `bootTimeoutMs` by up to 90 s — violating the documented contract and the Zod schema's `max(900_000)`. **Fix:** pass `Math.min(STAGE_BUDGET.firstRealFrame, Math.max(0, overallDeadline - Date.now()))`. ### H3 — `ARGENT_SIMULATOR_SERVER_DIR` semantic changed without migration hint The env var previously pointed directly at the directory containing `simulator-server`; it now points at the root of the per-platform tree. A user with the old flat layout gets a bare "binary not found" with no explanation of what changed. **Fix:** detect the old flat layout and include a migration hint in the error message; update the env var's doc comment. ### M4 — `ARGENT_EMULATOR_NO_WINDOW=false` silently enables headless mode `selectExtraEmulatorArgs` used a deny-list (`!== "0"`) so `"false"` and `"no"` — natural shell idioms for "disabled" — both mapped to `-no-window`. Test suite covered `""`, `" "`, `"0"` but not `"false"`. **Fix:** switch to an allowlist `["1", "true", "yes"]`; add `"false"` and `"no"` to the parameterized off-test. ### M5 — `bootstrapDylibPathTcp` / `nativeDevtoolsDylibPathTcp` missing `requireDarwin` All six non-TCP dylib/binary exports gained explicit `requireDarwin()` guards in #249. The two TCP dylib exports did not. A Linux caller gets "dylib not found" instead of "requires macOS host". **Fix:** add `requireDarwin()` as the first statement in both TCP dylib functions; add a test. ### S7 (bonus) — `selectGpuMode` validation fires mid-function after slow I/O `selectGpuMode()` and `selectExtraEmulatorArgs()` were called inside deep nested code after several seconds of I/O. A typo in `ARGENT_EMULATOR_GPU_MODE` surfaced late with a spurious "emulator has been terminated" suffix appended. **Fix:** call both once at the top of `bootAndroidImpl`, just after `ensureDep`, and reuse the captured values — eliminates the late error and the redundant double-call. ## Test plan - `npm run test -w @argent/tool-server` — **847 passed** (was 845; +2 for `false`/`no` NO_WINDOW cases) - `npm run test -w @argent/native-devtools-ios` — **10 passed** (was 8; +2 for TCP dylib darwin gate and migration hint) |
||
|
|
80661fad8f |
feat: Linux support (#249)
This PR makes Linux work.
# AI Summary
<details>
Brings Argent up on Linux as a host: per-platform npm bundle, renderer
selection that actually works on hybrid-GPU / Wayland / containerized
Linux, hot-boot guarding that survives the SwiftShader composite gap, a
real Linux preflight, and a Wayland-headless E2E job in CI. macOS stays
on
the path it was on before (no `-gpu` flag was passed; AVD config
decided);
nothing about the iOS Simulator behavior changes.
The diff is broader than "ship the Linux artifact" — calling it out
below
because several pieces affect macOS too and reviewers should be primed
for
that rather than skim past expecting "just packaging."
### What's actually in here
**npm packaging (the original framing — still load-bearing)**
| File | Change |
|---|---|
| `packages/argent/scripts/argent-simulator-server.cjs` (new) | Node
dispatcher exposed as the `argent-simulator-server` bin entry. Joins
`process.platform` into the bin path and execs. Forwards
SIGTERM/SIGINT/SIGHUP parent→child so non-TTY supervisors can't orphan
the binary. |
| `packages/argent/scripts/bundle-tools.cjs` | Copies per-platform
binaries from `packages/native-devtools-ios/bin/<platform>/` into
`packages/argent/bin/<platform>/`. darwin required, linux best-effort. |
| `packages/argent/package.json` | `bin.argent-simulator-server` points
at the dispatcher. |
| `packages/native-devtools-ios/src/index.ts` | Resolver joins
`process.platform` into the simulator-server bin path. iOS-only dylibs
and `ax-service` throw `"requires a macOS host"` on Linux callers so
they get the root cause, not "file not found". |
| `scripts/download-simulator-server.sh` | Downloads
`simulator-server-argent-{macos,linux}` into platform-keyed subdirs.
**Purges each platform dir before its download attempt** so a failed
download can't leave stale binaries in place from a prior run. |
| `scripts/dev.cjs` | Local dev copies the current host's binary into
`bin/<process.platform>/` to match the resolver layout. |
| `.github/workflows/publish{,-next}.yml` | Replaces single `test -x
darwin/...` with a per-platform inventory loop. darwin always required;
Linux gated by `ARGENT_REQUIRE_LINUX_BINARY=1` (flip once radon#118
lands). |
**Linux runtime — emulator boot path**
| File | Change |
|---|---|
| `packages/tool-server/src/tools/devices/boot-device.ts` |
`selectGpuMode()`: Linux defaults to `-gpu swiftshader` (`auto` lands on
lavapipe with ~10× cold-boot slowdown; `-gpu host` produces silent
black-window failures on Optimus / NVIDIA+libglvnd / Wayland-on-hybrid /
containers). macOS keeps `auto` (ANGLE→Metal).
`ARGENT_EMULATOR_GPU_MODE` overrides, **validated against `emulator
-help-gpu`'s whitelist** so a typoed value fails fast instead of burning
the whole boot budget. `ARGENT_EMULATOR_NO_WINDOW=1` appends
`-no-window` for headless contexts (CI / containers / Wayland sessions
with no Qt wayland plugin). |
| `packages/tool-server/src/tools/devices/boot-device.ts` (cont.) |
**Cold-boot:** `awaitFirstRealFrame` polls SwiftShader's lockscreen
composite (PNG byte-count via `screencap -p`); blocks `booted:true`
until a real frame shows up. Without this, callers chaining `boot-device
→ screenshot` get an all-black PNG because `sys.boot_completed=1` fires
5–60 s before the first composite. |
| `packages/tool-server/src/tools/devices/boot-device.ts` (cont.) |
**Hot-boot:** `assertScreencapAlive` polls within an 8 s budget instead
of single-probing. The earlier single-probe caught the sticky-blank
state but also killed the emulator on every transient snapshot-restore
blank, forcing "every boot is cold" cycles. Sticky vs transient is
discriminated by whether the budget exhausts. |
| `packages/tool-server/src/utils/linux-preflight.ts` (new) |
`linuxBootDiagnostics()` checks `/dev/kvm` access. `diagnoseAvdSizing()`
warns when a target AVD's `hw.ramSize`/`vm.heapSize` is below the floor
an aggressive RN/Expo app needs (2G/228M avdmanager defaults wedge under
load). |
| `packages/tool-server/src/utils/android-binary.ts` |
`defaultAndroidRoots()` probes typical SDK install locations when
`ANDROID_HOME`/`ANDROID_SDK_ROOT` are unset — **cross-platform**,
benefits macOS launches from GUI-launched MCP hosts (Claude Code Desktop
etc.) where shell rc env doesn't propagate. |
**Cross-platform fixes that landed alongside**
| File | Change |
|---|---|
| `packages/argent-installer/src/utils.ts` | `getLatestVersion()` now
redirects `npm view`'s stderr to `ignore` so `EBADDEVENGINES` warnings
from a consumer project's `package.json` (e.g. Bluesky's pnpm pin) don't
leak to the user's terminal. **Cross-platform fix**, not Linux-specific.
|
**CI**
| File | Change |
|---|---|
| `.github/workflows/wayland-e2e.yml` (new) | Boots an AVD through the
tool-server under headless Weston; asserts `hw.gpu.mode = swiftshader`
and that screenshots + gesture-tap work without a display. Currently
runs on `pull_request` for the affected paths + `workflow_dispatch`.
Pinned to `radon-daily` for the simulator-server until radon#118 ships a
Linux asset to `radon-main`. |
### Tests added in this PR
- `packages/argent/test/dispatcher.test.ts` — e2e against tmpdir-staged
dispatcher + fake `<platform>/simulator-server`: missing-binary error
path, exit-code passthrough, argv passthrough, SIGTERM forwarding.
- `packages/tool-server/test/native-devtools-ios-resolver.test.ts` —
`requireDarwin` gates for dylibs and `ax-service` on Linux, per-platform
`simulator-server` path resolution, missing-binary error,
`ARGENT_NATIVE_DEVTOOLS_DIR` override.
- `packages/tool-server/test/linux-preflight.test.ts` — KVM diagnostic
shape + `diagnoseAvdSizing` (ram low, heap low, both, neither, missing
keys).
- `packages/tool-server/test/boot-device-hotboot.test.ts` — extended
with `it.each([linux→swiftshader, darwin→auto])` so a single CI runner
exercises both GPU branches (was runner-platform-dependent, regressions
could pass on both runners individually). New cases for the wedged-frame
fallback (under fake timers because the polling budget is short but
real-time enough to exceed vitest's 5 s default), the GPU-mode whitelist
rejection, and the cold-boot composite-wait timeout.
- `packages/tool-server/test/android-binary.test.ts` — extended for
`defaultAndroidRoots` probe ordering and HOME pinning.
### Verification
- `tsc --build`: clean apart from pre-existing
`@argent/native-devtools-android` import errors unrelated to this
branch.
- `vitest run` in `packages/tool-server` and `packages/argent`: 689 + 11
= 700 tests passing; the 4 pre-existing test-file load failures
(native-devtools-android missing) reproduce on `main` and are out of
scope.
- `npm pack -w @swmansion/argent`: produces `swmansion-argent-0.8.0.tgz`
with the per-platform layout.
- Wayland-e2e: triggered on this branch — link will be in a PR comment
once the run completes.
- Manually verified end-to-end on a Wayland-on-hybrid-GPU developer
laptop: AVD boots through the tool-server with `-gpu swiftshader`,
screencap returns real frames, gesture tools work.
### Dependencies / what still gates this
- **radon#118** must land before the Linux artifact ships through the
default channel. The download script tolerates the asset being absent
today (best-effort), so this PR is technically safe to merge before —
Linux installs just won't get a working `simulator-server` until the
upstream artifact is published. Once it lands:
- Drop the `radon-daily` arg in `.github/workflows/wayland-e2e.yml`.
- Flip `ARGENT_REQUIRE_LINUX_BINARY` to `1` in both publish workflows.
- **wayland-e2e is not a required status check** today (no branch
protection on `main` requires it). Worth turning on before merging
anything else that touches the Linux boot path.
### Related
- radon#118 — produces the `simulator-server-argent-linux` artifact this
PR consumes.
- argent-private#11 — cross-builds the iOS Simulator dylibs on Linux.
Strictly orthogonal: those dylibs are macOS-runtime-only regardless of
where they're built, and this PR keeps them under `dylibs/` with a clean
error path on Linux callers.
</details>
Fixes #246
|
||
|
|
cb8db6d4ac |
feat(boot-device): pass canonical launch-hardening flags (#269)
## Summary argent's boot-device currently spawns `emulator -avd <name> -gpu auto` plus the snapshot flags — nothing else. Five flags every MCP-driven boot benefits from were missing; add them as `LAUNCH_HARDENING_ARGS` and spread into the snapshot probe, hot boot, and cold boot. ### Performance (3 flags) - `-noaudio` — guest skips pulseaudio init; saves one thread and ~50 MB RSS. - `-no-boot-anim` — skips the Pixel boot animation, a major CPU spike on software-rendered GPU modes for ~30 s. - `-netfast` — disables qemu network shaping; pure overhead for MCP use. Measured on a 4-core i5-6300U with a 4096 MB / 228 MB-heap AVD, warm-cache cold boot, no other workload, `-gpu swiftshader`: | | Boot time | Δ | |---|---|---| | Baseline (no extras) | 66 s | — | | + 3 perf flags | 49 s | −26% | | + all 5 (incl. dialog suppression) | 47 s | −29% | qemu RSS at +20 s post-boot drops ~190 MB (5%) with the perf flags. `android-emulator-runner` (the canonical CI launcher) defaults to the same three. ### Dialog suppression (2 flags) - `-crash-report-mode never` — keeps emulator crashes from popping a Qt consent dialog that blocks the next boot until a human dismisses it. Crash dumps still land in `/tmp/android-unknown/emu-crash-*.db`; only the modal popup is suppressed. - `-no-metrics` — same for the metrics-collection consent dialog. Unrelated to any argent profiler tool: those run guest-side via Perfetto/simpleperf or via Metro CDP, none of which depend on the emulator's host-side Google telemetry. ### Snapshot parity `-noaudio` and `-netfast` change qemu device topology. The snapshot's saved state has to match the runtime config the next boot uses or the snapshot validator rejects perfectly loadable snapshots — the same class of bug as the prior `-gpu auto` parity work that introduced `RENDERER_ARGS`. Plumb the hardening args into the same three call sites (probe `extraArgs`, hot spawn, cold spawn) so the probe agrees with both boot paths. ## Test plan - [x] Unit tests: extended `boot-device-hotboot.test.ts` with two new cases asserting the flags reach the hot-boot and cold-boot spawn argv; tightened the "same renderer args" parity test to also require `-noaudio`/`-netfast` on the probe. **712 tests pass.** - [x] Live-emulator verification on Linux: actual emulator boot with all 5 flags completes in 47 s, no flag-rejection warnings in the emulator log. - [ ] Confirm no regression on a macOS test host (please run locally before merging — I can only verify Linux here). - [ ] Confirm CI green. Pre-existing test failure in `android-binary.test.ts` on `origin/main` reproduces without this change — host-side `$PATH` leakage in the test setup, not in scope for this PR. |
||
|
|
324d8c6049 |
fix(android): stop forcing cold boot on every emulator launch (#251)
## Summary `hasDefaultBootSnapshot` enforced a 60s mtime-skew guard between `snapshot.pb` and `ram.bin`. In practice the emulator updates `snapshot.pb` metadata on every load (load count, last-loaded timestamp) even with `-no-snapshot-save`, so the two mtimes drift apart by hours or days after a few hot-boot sessions and the guard rejects every valid snapshot — forcing a cold boot every time. The bug hits macOS too; Linux just makes it painful because cold boot is slow there. Confirmed against my own `Pixel_6_API_34`, whose `snapshot.pb` is 4 days newer than `ram.bin` while the snapshot itself is fully usable. ## What changed - **Drop the mtime-skew guard.** `-check-snapshot-loadable` + the hot-boot spawn's `-force-snapshot-load` already make truncated / corrupt `ram.bin` fail loudly via `attemptBoot`'s `earlyExitError` race. The on-disk pre-check only needs to confirm both files exist and `ram.bin` is non-empty. - **Resolve the `.avd` folder via the AVD's `.ini` `path=` line**, matching what the emulator binary itself does (`external/qemu/android/android-emu/android/avd/util.cpp`). Snap-installed Studio on Linux and users who relocate AVDs to faster disks both put `.avd` outside the convention root, where the old `<root>/<name>.avd` assumption silently failed. - **Honor `$ANDROID_USER_HOME`, `$XDG_CONFIG_HOME`, `$ANDROID_SDK_HOME`** in priority order alongside `$ANDROID_AVD_HOME` and `$HOME/.android/avd`, mirroring the emulator binary's own lookup precedence. - **Reject whitespace-only and relative `path=` values.** The regex's non-greedy `(.+?)` still captures a single space for `path= `, which would have leaked past `if (!avdPath)` as truthy; a relative `path=` would have resolved against `process.cwd()` and silently mis-located snapshots. ## Test plan - [x] 18 new unit tests in `avd-snapshot.test.ts` covering: priority order across all five env-var candidates, relocated AVDs, drifted mtimes (4-day skew explicitly regression-tested), zero-length / missing / directory-shaped files, whitespace-only paths, relative paths, bad-then-good fallthrough. - [x] Full `npm run test -w @argent/tool-server` — 703 passing. - [x] `npm run typecheck:tests -w @argent/tool-server` — clean. - [x] `npm run build` from workspace root — clean. - [x] `npx prettier --check` on changed files — clean. - [x] End-to-end repro against a real local AVD: drifted `Pixel_3a_API_34`'s `snapshot.pb` mtime to 5 days newer than `ram.bin` (the exact pathology in the wild), drove `boot-device` through this branch's built code, observed `qemu-system-aarch64 ... -force-snapshot-load -no-snapshot-save` in the spawned process — i.e. the hot-boot path was taken with a snapshot the old check would have rejected. ## Risk Pre-check is now more lenient on disk shape. If `ram.bin` is non-empty but truncated/corrupt, the hot-boot spawn still catches it via `-force-snapshot-load` (loud early child-exit) and `attemptBoot` falls back to cold boot. So the worst case has not regressed; it just surfaces at spawn-time instead of pre-spawn. |
||
|
|
11df817616 |
test: drop real-time timeouts from the slowest unit tests (#236)
The slowest unit tests were waiting on real wall-clock timers, not doing
work. This makes them deterministic and fast with **no production
behavior change and no production test seams**.
## Speedup
**Suite-level**
| Scope | Before | After | Faster |
|---|---|---|---|
| tool-server — wall clock | 2.72s | ~1.3s | ~2× |
| tool-server — aggregate test time | 9.70s | 4.03s | 2.4× |
| argent-mcp — aggregate test time | 0.90s | 0.28s | 3.2× |
**Per file**
| File | Before | After | Faster |
|---|---|---|---|
| simulator-server-blueprint.test.ts | 2.55s | 0.05s | ~50× |
| boot-device-hardening.test.ts | 2.17s | 0.18s | 12× |
| boot-device-spawn-error.test.ts | 1.11s | 0.005s | ~200× |
| boot-device-hotboot.test.ts | 1.01s | 0.06s | 16× |
| fetch-timeout.test.ts | 0.81s | 0.18s | 4.5× |
| flow-tools.test.ts (delayMs test) | 0.31s | 0.04s | ~7× |
**Worst individual tests, before → after**
| Test | Before | After |
|---|---|---|
| rejects the boot promise…when spawn emits error | 1.06s | ~1ms |
| reports the emulator crash error… | 1.00s | ~2ms |
| falls back to cold boot when hot-boot child exits early | 1.00s | ~1ms
|
| reports a signal-terminated emulator (SIGSEGV)… | 1.00s | ~1ms |
| 5× simulator-server-blueprint tests | ~0.50s ea | ~0.001–0.02s ea |
| throws after retries when POST never responds | 0.60s | 0.14s |
| sleeps the step's delayMs | 0.31s | 0.04s |
No test exceeds ~0.7s anymore (the slowest,
`workspace-reader-integration`, is genuine subprocess I/O — `node
--version` etc. — left as-is on purpose; it is not a timeout).
## What changed
- **`boot-device.ts` (only production change):** the three hardcoded
poll cadences are extracted into a single immutable
`BOOT_POLL_INTERVALS_MS` constant (values unchanged: 1500/1000/500ms)
and referenced at the four `setTimeout` sites. Pure readability
extraction — **no mutable state, no exported test hooks, no runtime
behavior change.**
- **Boot-device tests:** the timing-sensitive tests now use vitest fake
timers (`vi.useFakeTimers()` + `vi.advanceTimersByTimeAsync`).
`boot-device-hardening` / `-spawn-error` add the same partial
`vi.mock("../src/utils/adb")` already used by
`boot-device-hotboot.test.ts`, so pre-flight does no real filesystem I/O
and the timers drive deterministically.
- **`simulator-server-blueprint.test.ts`:** `signalReady` now emits
`stream_ready` then `api_ready` (matching the real streaming server's
ordering) so the blueprint resolves immediately instead of waiting out
the 500ms `STREAM_GRACE_MS` fallback. Adds a dedicated fake-timer test
that explicitly covers the non-streaming grace path (coverage gained,
not lost).
- **`fetch-timeout.test.ts` / `flow-tools.test.ts`:** smaller timing
knobs; same behaviors asserted.
## No regressions
- 1011/1011 tests pass (tool-server 656, argent-mcp 53, argent 6,
registry 64, tools-client 1, installer 231); `tsc --build` and Prettier
clean. The converted boot-device, simulator-server-blueprint, and
flow-tools tests are deterministic under fake timers (verified across
stress runs).
- No production behavior change; `simulator-server.ts` is byte-identical
to `main`; net +1 test.
- Heads-up: `argent-mcp/test/fetch-timeout.test.ts` has a
**pre-existing** macOS flake (~10–20% rate; `connect EADDRNOTAVAIL
127.0.0.1:<port> - Local (0.0.0.0:0)` — kernel source-port exhaustion
under rapid-fire loopback fetches). Stress-testing showed it on `main`'s
pristine file too (2/10 failures vs 1/10 on this branch), so this PR
does not introduce or worsen it. Fixing it (e.g., EADDRNOTAVAIL retry,
or moving the test off real loopback fetch) is worth a follow-up.
|
||
|
|
837d088744 |
test: cover #148 (android) (#186)
This PR adds tests for #148. ## Summary Moves the Android emulator branch's test coverage out of #148 so that PR can be reviewed without the test churn. This branch adds the test files back on top of the (now test-stripped) base branch. Tests inseparable from source changes (`describe-tool`, `workspace-reader`, `workspace-reader-integration`) and the `boot-simulator.test.ts` deletion stayed on #148 - base PR's CI passes on its own. ## Test plan - [x] `npx tsc --build` — clean - [x] `npx prettier --check .` — clean - [x] `npm test --workspaces --if-present` — 585/585 tool-server tests pass; all other workspaces green --------- Co-authored-by: Paweł Fornagiel <pawel.fornagiel2@gmail.com> |