Skip to content

Shrink the desktop installer and speed up agent streaming - #110

Open
cu1ch3n wants to merge 9 commits into
mainfrom
perf/speed-and-size
Open

cu1ch3n wants to merge 9 commits into
mainfrom
perf/speed-and-size

Conversation

@cu1ch3n

@cu1ch3n cu1ch3n commented Nov 9, 2026

Copy link
Copy Markdown
Member

Summary

  • Desktop installer: 337 MiB → 184.5 MiB (macOS arm64 disk image, same zlib format, so it opens as fast as before).
    • The desktop app no longer bundles Codex. It uses the first codex on PATH that runs (no version check), like the other agents. Settings → Runtime → Agents and doctor say when none is found or none runs. The npm papilot command still installs its tested Codex.
    • Codex is no longer part of the shell fingerprint, so a Codex update no longer forces a new installer.
    • The review engine's copies of packages the server already has in the same version (mupdf, esbuild, fastify, React…) are removed when packaging (50.9 MB unpacked). Node resolves them from the server's node_modules, including through a layer's links.
  • Agent streaming: each event found its task by parsing every task (archived ones too), and each delta or tool message re-read the whole conversation. Now: an index on threadId, and a primary-key read of one message.
  • State polling: /api/state sends an ETag and the app sends back the tag of the state it shows; an unchanged poll gets an empty 304 instead of every task again (182 KB on a 50-task workspace, every 1–2.5 s while a task runs).

Measurements

macOS arm64 dmg Size First open (verify)
0.9.4 release 337 MiB —
This PR (zlib, electron-builder default) 184.5 MiB 8–10 s
LZMA (tried, not used) 138.8 MiB 39–56 s

LZMA was rejected because the first open takes several times longer. LZFSE (ULFO) was only 2 % smaller than zlib. Open times were measured at a load average of 20–40.

Testing

  • Unit tests for the changed areas (store, workspace, app, refresh, doctor, desktop layer, Codex lookup): 249 passed, 6 skipped (need a real Codex).
  • Full npm test ran at a load average of up to 167. The 16 failures were TeX, WSL and import timeouts. Rerunning those 10 files one at a time: 113/113 on this branch, while a clean origin/main had one WSL failure.
  • tsc for app and desktop, vite build, and i18n:check-app (all 30 scopes and locales) pass.
  • Packaged the macOS app: the installer contains no @openai/codex, the engine dependencies are deduplicated, and the app signature verifies.
  • Started a desktop-mode server with a broken Homebrew codex first on PATH. It skipped that one and ran ~/.local/bin/codex app-server, connected and signed in.

Notes

  • Users who relied only on the bundled Codex need to install Codex themselves (npm install -g @openai/codex). On Windows with WSL, Codex must be installed inside the distribution.
  • The new strings were translated by hand because agy was unavailable (its model stream kept getting interrupted).
  • README's requirements line still lists only Git and TeX; it can mention Codex in a separate change.

…olls

Each streamed agent event looked up its task by reading and parsing every
task, archived ones included, and each delta or tool message re-read the
whole conversation to find one item. Tasks are now found by thread through
an index on threadId and messages by their primary key.

/api/state answers with an ETag, and the app sends the tag of the state it
shows: an unchanged poll gets an empty 304 instead of every task again to
download, parse and compare (182 KB on a 50-task workspace, every 1-2.5 s).
Codex was the largest part of the installer (338 MB unpacked) and its
version was part of the shell fingerprint, so every Codex update meant a
new installer. The desktop app now uses the first codex on PATH that runs,
like the other agents; Settings > Runtime > Agents says when none is found
or none runs. The npm command keeps installing its tested Codex.

The review engine sits inside the server folder, so its copies of packages
the server already has in the same version (mupdf, esbuild, fastify, React)
are removed when packaging; Node resolves them from the server's
node_modules, also through a layer's links.

The macOS arm64 disk image goes from 337 MiB (0.9.4) to 184.5 MiB.
- Without Codex on a desktop host, the agent menu offered it anyway and new
  tasks went to it; only found agents are offered now, falling back to the
  first one.
- Closing Codex while it looks for an installation no longer starts an
  app-server afterwards.
- The --version probe uses the PATH Codex starts with, so a Node-wrapped
  codex found in a standard folder is not called broken.
- A source checkout's development Codex is found again.
- Agent errors in the connection notice and Agents card are translated.
- The setup screen and desktop README no longer say Codex is included;
  Codex's sign-in hint is codex login in the desktop app.
- doctor waits as long as the server for codex --version.
- Windows npm shims of scoped packages that stay are kept when packaging.
- Codex lookup tests no longer depend on the Codex installed on the host.
- The codex --version probe owns its deadline and kills the whole process
  tree, so a wrapper whose child holds the output open cannot hang
  connecting or Reconnect.
- /api/state keeps the access guard's Cache-Control: no-store (it carries
  the CSRF token); the ETag alone gives the empty 304.
- A Codex launch stopped during the lookup rejects, and starting again
  launches afresh instead of reusing the stale lookup.
- doctor searches and probes Codex like the server in the desktop app.
- Engine dedupe moves to scripts/desktop-dedupe.mjs, which the shell
  fingerprint now covers, and removes a package only when both lockfiles
  agree on its version and integrity.
- The desktop app also looks in pnpm, mise, fnm, Yarn and custom npm
  prefix folders, since Codex is no longer bundled.
- Codex lookup tests no longer depend on the host's installations.
- With no agent found, the new-task page says so and offers Reconnect and
  Add an agent (Settings > Agents) instead of a bare "codex" picker with an
  empty list; starting a task waits until an agent is set up, keeping the
  draft.
- The server and doctor share one Codex lookup (bin/codex-lookup.mjs);
  doctor checks the login with the same PATH and deadline as the probe.
- Closing Codex during the lookup kills the running probe and stops trying
  further installations.
- A custom npm prefix expands ${VAR} and, on Windows, adds the prefix
  folder itself, where npm puts global commands.
- On Windows the home folder is USERPROFILE again, as in agentDirs.
The message gets its own line instead of being squeezed beside two text
links; Add an agent is the primary button and Reconnect the secondary
one, with touch-sized targets on phones.
…iformly

- close() also clears ready, rejects pending requests and drops the child;
  a launch that was initializing when it was closed rejects instead of
  marking a newer launch ready, and a closed child's output is ignored.
- doctor's desktop Codex check is its own function: each candidate is
  probed once (version output reused), probes share a 20 s budget and the
  login check gets 10 s, so diagnostics finish before their 45 s limit.
  The npm-mode check is unchanged.
- Every agent is offered by the same rule: reported and not missing.
- Inside WSL, Codex is not looked for in folders on Windows drives
  (/mnt/<letter>): a Windows npm shim cannot run there, made Codex look
  installed, and its 9P folders are slow to scan.
- With no saved agent, new tasks go to the first agent that is connected
  and signed in, not to Codex by default; an explicit choice still wins.
- doctor's desktop check names only the Codex candidates it tried, and
  those it ran out of time for; npm-mode doctor probes as before the PR.
- The hanging-candidate doctor test allows for a loaded machine.
- With no saved agent, the choice waits until every agent has connected
  or failed, then takes the first ready one and saves it, so it no longer
  flips during startup or switches agents after a disconnect.
- The Codex lookup has a 30 s total budget across candidates, and
  doctor's desktop check fits probes and the login check into 20 s.
- Engine dedupe keeps a package unless its dependencies resolve to the
  same versions in both trees.
- Tagged state responses are compared by their ETag instead of
  stringifying the whole snapshot on the client.
- Saving a task also updates its project_id column, which request
  locking now reads.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant