Commit Graph

3 Commits

Author SHA1 Message Date
Ignacy Łątka acfe80ab6b chore: add ESLint with typescript-eslint (#349)
## What

Adds type-aware linting to the workspace. Until now the only static gate
on JS/TS was Prettier (formatting) and `tsc` — there was no linter, so
the whole class of bugs `tsc` doesn't catch (floating promises, misused
promises, throwing non-Errors, unsafe stringification, dead code) went
unguarded across ~450 TS files.

- **`eslint.config.mjs`** — flat config. `recommendedTypeChecked` runs
on the TS sources via an explicit `project` list (each package keeps
tests in a separate `tsconfig.test.json` that `projectService` can't
auto-discover, so it's listed explicitly). Type-checked rules are
disabled for the loosely-typed plain-JS build/dev scripts. The
`argent-private` submodule and build artifacts are ignored (mirrors
`.prettierignore`).
- **`lint` / `lint:fix`** npm scripts.
- **`.github/workflows/lint.yml`** — builds the workspace (type-aware
rules need the referenced projects' declarations) then runs `eslint .`.
Node 24 (ESLint 10 requires `^20.19 || ^22.13 || >=24`).

## Rule calibration

This is an adoption PR, so it's deliberately calibrated rather than
maximal:

- **Kept on (bug-catchers):** `no-floating-promises`,
`no-misused-promises` (with `checksVoidReturn: false` to drop the noisy
callback variant), `only-throw-error`, `no-base-to-string`,
`prefer-promise-reject-errors`, `no-unused-vars` (`_`-prefix escape
hatch), plus the ESLint recommended set.
- **Parked off as documented debt:** the high-volume style/`any`-family
rules — `no-unsafe-*`, `require-await`, `no-unnecessary-type-assertion`,
`restrict-template-expressions`, `unbound-method`, `no-explicit-any`,
etc. These flag broad classes of pre-existing code and are out of scope
here; each is commented as a ratchet target for follow-up passes.

All findings from the kept-on rules are fixed in this PR (dead
code/imports, error-cause chaining, **3 genuine floating promises**,
non-Error throws, unsafe stringification). Stale unused `eslint-disable`
directives left over in the tree were removed by `--fix`.

## Verification

Locally, all green: `eslint .` (0 errors), `tsc --build`,
`typecheck:tests`/`typecheck:scripts`, the full `vitest` suite (no
behavior change from the fixes), and `prettier --check`.

## Not included (intentionally)

Other static-analysis gates discussed (CodeQL, dependency-audit,
ShellCheck, actionlint) are out of scope for this PR.
2026-06-17 12:20:55 +02:00
filip131311 23cd32073a fix(native-devtools): re-apply DYLD env after out-of-band sim reboot (#346)
## Summary

After an iOS simulator loses its launchd `DYLD_INSERT_LIBRARIES` (any
reboot — out-of-band *or* an argent `boot-device`),
`native-devtools-status` keeps reporting `envSetup: true` /
`nextLaunchWillBeInjected: true` while the env is actually **empty**.
Freshly launched apps aren't injected and native feature tools
(`native-describe-screen`, `native-find-views`, …) fail with
`restart_required` — and `restart-app` couldn't break out of it.

## Root cause

`envSetup` is a **write-once latch**. `ensureEnvReady()` short-circuits
once it flips `true`, and nothing resets it, so the env is never
re-applied after a reboot clears it. Two paths relied on that latched
function and so never recovered:

1. `requiresAppRestart()` — written to recover from the reboot case, but
called the latched `ensureEnvReady()`.
2. `boot-device` — primes the native-devtools service after (re)booting,
but the simulator watcher usually initialized it earlier, so
`resolveService` returns the cached/latched instance and the wiped env
is never re-applied. A clean argent `boot-device` + `launch-app`
therefore produced an uninjected app and a spurious `restart_required`.

## Fix

- Split a latch-free `runEnsureEnv()` out of `ensureEnvReady()` and add
`reverifyEnv()` — re-applies the env even when the latch says it's set
(still honouring the give-up guard). `ensureEnv` is already idempotent,
so it's a cheap no-op when correct.
- `requiresAppRestart()` → uses `reverifyEnv()` (fixes the native
feature tools' recovery; precheck routes through it).
- `native-devtools-status` → calls `reverifyEnv()` when the app isn't
connected, so its flags reflect reality and a later launch is injected.
- `boot-device` → forces `reverifyEnv()` after the (re)boot, so a clean
argent boot injects on first launch with no restart.

The hot path (`ensureEnvReady`, app already connected) is unchanged — no
added latency on normal calls; the extra simctl round-trips only fire
when there's evidence the env is stale.

## Verified end-to-end on a real iOS 26.3 sim (patched tool-server)

- **Clean argent boot → launch → just works:** prime service,
`boot-device force`, `launch-app` → `connected:true`,
`requiresRestart:false`, `native-describe-screen` returns the full tree.
`DYLD_INSERT_LIBRARIES` is present immediately after `boot-device`
returns. ✅
- **Out-of-band reboot → `native-devtools-status`:** status call
self-heals the env; next launch is injected. ✅
- **Out-of-band reboot → launch → native tool → `restart-app`:** the
previously dead-end recovery now works. ✅
- **Before the fix (same hardware):** status claimed `envSetup:true`
with the env empty; launches uninjected; `restart-app` and `boot-device
force` did not recover.

## Tests

- New regression tests: `native-devtools-status` re-applies when
disconnected / does not when connected; `boot-device` forces a re-apply
after boot.
- Full `packages/tool-server` suite green (1155), typecheck + prettier
clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Radoniusz Cloudiusz <radoniusz@radoniusz.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:58:22 +02:00
Paweł Fornagiel 4255b0bd66 fix: stop simctl spawn loop on iOS init failure (#233)
Fixes #210 — `argent mcp` pinning CPU to 100% and spawning hundreds of
`simctl` processes that survive `kill -9`.

## The problems

1. **No retry cap.** The simulator watcher polls every 10s; on any
failure it forgets the UDID and re-classifies it as "new" on the next
tick. Each retry runs 4–5 `xcrun simctl spawn` calls in series. A
persistently failing simulator produces an unbounded loop — hundreds of
`entered ERROR state` log lines and a steady process pile-up.

2. **No timeouts on `simctl spawn`.** A degraded `CoreSimulatorService`
makes simctl block forever. The factory `await`s the hung call, never
rejects, never retries, but the stuck child process stays alive.

## How this fixes it

- **Bounded retries with terminal "given up" state.** Each
`NativeDevtools` service instance now tracks `{attempts, lastError,
givenUp}`. After 3 consecutive init failures the service stops trying
and reports `init_failed` to any caller. The watcher keeps polling but
skips UDIDs in the given-up state.

- **10s timeout on every `simctl spawn` call** in `ensureEnv`,
`ensureAccessibilityEnabled`, and `ensureAutomationEnabled`. Healthy CSS
responds in ~300ms; 10s is well above any plausible legit latency and
well below "hung forever." A hung simctl now rejects cleanly, increments
the attempt counter, and feeds the bounded retry instead of accumulating
children.

- **Tools surface `init_failed` as a typed result** instead of throwing.
`launch-app`, `boot-device`, `native-devtools-status`, and the rest of
the native tools now return a structured error with the failure reason
and a hint to restart the simulator, instead of throwing an opaque
exception.

- **Factory no longer throws on first env-init failure.** Previously a
single failed `simctl` call during factory creation leaked the socket
server. The factory now tolerates init failure; the watcher's bounded
retry handles the recovery.

Verified on live simulator and against test simulating `simctl`
timeouts. Happy path works as before, `restart-needed` is still
signalled and the new failure path works as well.
2026-05-20 08:56:33 +02:00