Skip to content

feat(dev-launcher): harden web runtime and stabilise desktop instance identity - #67

Open
LIUXIN557 wants to merge 3 commits into
masterfrom
fix/dev-launcher-runtime-identity
Open

LIUXIN557 wants to merge 3 commits into
masterfrom
fix/dev-launcher-runtime-identity

Conversation

@LIUXIN557

Copy link
Copy Markdown
Collaborator

Implements the dev-launcher multi-instance optimization described in
docs/specs/2026-09-20-dev-launcher-multiinstance-optimization-requirements.md
(scope §2 A/B/C/D/E/G). Two OpenSpec changes cover the work:

  • harden-web-dev-launcher-runtime (Wave 1, pure Python)
  • stabilize-desktop-dev-instance-identity (Wave 2, includes C++)

Both validate with openspec validate <name> --strict and all their task
checkboxes are complete.

Wave 1 — Web quick mode

Item Change
C Worker stdout/stderr captured to <run-dir>/daemon-worker.log (timestamped header per spawn, 1 MiB rotation); early worker exit fails fast instead of burning the whole health timeout; both this log and the daemon's own daemon-startup.log are named on failure.
A A stale runtime is self-healed only when the recorded pid is provably dead — clean up via daemon stop --run-dir= and retry once. A live pid is never terminated; an unreadable pid file is skipped and reported.
B Default port derived from the runtime directory name via zlib.crc32 (28080–28280), linear wraparound, millisecond bind probes. New --port fails immediately when busy. The salted built-in hash() is deliberately not used.
D New prune target reports pruned / skipped-with-reason / total, exits 0 even when everything is skipped, and deletes only directories whose pid is provably dead.

Wave 2 — Desktop instance identity

  • is_valid_instance_id() and parse_allow_multiple_instances() added as header-only pure functions; plan_instance_startup() is untouched.
  • ACECODE_DESKTOP_INSTANCE_ID and ACECODE_DESKTOP_ALLOW_MULTIPLE_INSTANCES are read as process-level, dev-only overrides. No new CLI surface.
  • The C++ side validates and never rewrites: an invalid identity falls back to a random uuid, which is the safe direction and keeps product behaviour identical when the variables are unset.
  • The dev launcher injects a stable <worktree>-<commit12> identity so repeated runs reuse one instance directory. Only the child environment is touched — the user's global config is never read or written.

One real bug found while implementing

A character whitelist alone accepts . and ..: they are composed entirely of
allowed characters, but desktop-instances/.. resolves to the parent directory
and would let the instance run directory escape its container. is_valid_instance_id
therefore also rejects all-dot identities; a.b and ..a remain valid. Locked
down by RejectsAllDotIdentitiesThatResolveToADirectory and
RejectsAnythingThatCouldEscapeItsDirectory.

Unrelated fix included

run_build.py and run_cmake_configure.py only injected the SDK's ucrt, um
and shared include directories, omitting winrt/ where wrl.h and
EventToken.h live. Building acecode-desktop failed with C1083. Both
launchers now prepend winrt and cppwinrt. Verified by deleting the two
formerly-failing object files and rebuilding with the unmodified launcher:
both compile, link succeeds, exit 0. Worth noting run_cmake_configure.py
defaults to -DACECODE_BUILD_DESKTOP=ON, so this gap affected the standard
configure path.

Verification

  • tests/scripts: 93 unittest cases pass.
  • acecode_unit_tests: 4762 cases, 4756 pass. The 6 failures
    (AgentLoopTurnSteering, three Sandbox*, TcpProbe, BashToolShellTest)
    reproduce identically on a stashed baseline build and live in directories none
    of these commits touch — they are pre-existing and environment-specific on this
    machine, not regressions.
  • End-to-end against real binaries: 16/16 for Web quick mode
    (real acecode.exe) and 14/14 for desktop instance identity
    (real acecode-desktop.exe, observed through the real desktop log and
    run/desktop-instances/).
  • Confirmed the user's global config.json is byte-identical before and after a
    dev desktop launch.

Behaviour when unused

Without the two environment variables — i.e. running acecode-desktop directly,
as an installed build does — behaviour is byte-for-byte what it was before.

Trae User added 3 commits September 20, 2026 22:48
- Start the quick Web worker in the foreground and accept an existing
  runtime only when /api/health echoes the recorded pid, port and token,
  so a stale run directory can no longer point the UI at another daemon.
- Forward the daemon port and token to Vite so /api and /ws reach this
  worktree's daemon instead of a hard-coded 127.0.0.1:28080.
- Add --use-embedded-assets to dev_web.py and drop the implicit --yes
  from the shell launchers; --yes stays with the embedded workflow.
- On Windows replace the target before tightening its ACL, because the
  protected DACL applied to token.tmp blocked the rename and left the
  token next to an unusable temp file.
… identity

Wave 1 - Web quick mode (scripts/dev_environment.py):

- Capture the daemon worker's stdout/stderr into <run-dir>/daemon-worker.log
  with a timestamped header per spawn and 1 MiB rotation, so a failed start
  points at real diagnostics instead of a silent timeout.
- Fail fast when the worker exits before the health check elapses, and report
  both daemon-worker.log and the daemon's own daemon-startup.log.
- Self-heal a stale runtime only when the recorded pid is dead: clean it up via
  `daemon stop --run-dir=` and retry once. A live pid is never terminated and an
  unreadable pid file is skipped and reported.
- Derive the default port from the runtime directory name via zlib.crc32
  (28080-28280) with linear wraparound and millisecond bind probes, replacing
  the fixed 28080 plus 15-second health wait. Add --port for an explicit port
  that fails immediately when busy, and never use the salted built-in hash().
- Add a `prune` target that reports pruned/skipped/total, always exits 0, and
  deletes only directories whose pid is provably dead.

Wave 2 - Desktop instance identity (src/desktop, scripts/dev_desktop.py):

- Add header-only is_valid_instance_id() and parse_allow_multiple_instances().
  The latter whitelists 1/true/yes/on so a stray "0" cannot silently enable
  multi-instance. The former rejects anything outside [A-Za-z0-9_.-] and also
  rejects all-dot identities, because ".", ".." would otherwise pass the
  character whitelist and let the instance run directory escape its container.
- Read ACECODE_DESKTOP_INSTANCE_ID and ACECODE_DESKTOP_ALLOW_MULTIPLE_INSTANCES
  as process-level, dev-only overrides. The C++ side validates and never
  rewrites: an invalid identity falls back to a random uuid, so behaviour is
  unchanged when the variables are absent.
- Inject a stable <worktree>-<commit12> identity from the dev launcher so
  repeated runs reuse one instance directory. Only the child environment is
  touched; the user's global configuration is never read or written.

Also add the Windows SDK winrt/cppwinrt include directories to run_build.py and
run_cmake_configure.py: wrl.h and EventToken.h live there, and without them the
desktop WebView2 host fails with C1083.

Verification:
- tests/scripts: 93 unittest cases pass.
- acecode_unit_tests: 4762 cases, 4756 pass. The 6 failures also fail on a
  stashed baseline build and touch none of the changed files.
- End-to-end: 16/16 checks for Web quick mode against the real acecode.exe, and
  14/14 for the desktop identity against the real acecode-desktop.exe.
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