Repository navigation
Conversation
added 2 commits
September 28, 2026 23:21
…t skeleton
S2 ships the foundation that lets later slices (S3+) drop the
`mcode acp` child-process boundary:
* New `packages/webui/server/lib/runtime-host.js` exports
`createCatalogueHost` (long-lived, replaces the ACP singleton for
read-only catalogue traffic) and `createTurnHost` (per-turn wrapper
around `adapter.sendMessage`/`adapter.abortSession` with the R1
exception boundary and the R2 bounded abort). No route consumes them
yet — S3-S6 wire catalogue/turn traffic incrementally, S7 flips the
default. The default behaviour is byte-identical to `main`.
* New `MCODE_WEBUI_TRANSPORT` config knob (default `acp`, the
legacy `MCODE_USE_ACP=0` escape hatch still wins). Documented in
both `docs/webui.md` and `docs/webui.zh-CN.md` — full table of
accepted values, interaction with `MCODE_USE_ACP`, and the S3+
rollout stages. `server/lib/config.js` validates the value and
warns on unknown values rather than refusing to boot.
* `scripts/build.mjs` adds a `createRequire` banner to the webui
server bundle. The webui bundle's dependency tree pulls in
proper-lockfile (CJS), which requires `path` at module load. Without
a banner the synthetic `__require` shim throws
'Dynamic require of "path" is not supported' on first import —
the S1 acceptance-call hard blocker, finally fixed here. The CLI
bundle has had an equivalent banner since 0.5.4; the webui bundle
mirrors it via a new
`createWebuiBundleModuleLocationConfig` helper.
* `packages/webui/test/server/runtime-host.test.js` pins:
- boot → createSession → listSessions → close in an isolated tmp
dataDir with zero `mcode` child processes (R1 acceptance target),
- `close()` is bounded even when `apiHost.close()` hangs (R8),
- sendMessage iterator throws become stream error frames, never
propagated (R1 turn-host boundary — mutation-1 turns red when
dropped),
- `abortSession` returns within 5s without any subprocess kill
(R2 — mutation-2 turns red when the bounded race is removed).
Acceptance:
* `pnpm typecheck`, `pnpm --filter @mavis/webui webapp:typecheck`:
both 0.
* `pnpm --filter @mavis/webui test`: 1952/1954 pass (2 baseline skips
unrelated to this change). My four S2-RH tests all green.
* `pnpm check:source`: 4715 reviewed files after inventory regen.
* `pnpm check:tsconfig`: 132 package exports match.
* `pnpm test:release-tools`: 69/70 pass (1 baseline skip).
Dist-shape runtime assertion (no-banner probe → exits 1 with the
expected symptom; banner probe → exits 0 and `createCatalogueHost` +
`createTurnHost` resolve as functions on the bundled module): see
archive/evidence/s2-runtime-host/R1-dev.
…y + dead code) Acceptance of d49d919 (S2 R1) flagged three follow-ups. R2 closes them all without expanding scope. * docs/webui.md and docs/webui.zh-CN.md: rewrite the MCODE_WEBUI_TRANSPORT Turn-condition table so the exec entry reads 'no-op in S2 — same as default acp; exec today is reachable only via MCODE_USE_ACP=0', the same shape the runtime entry already had. Both languages. The Env-var table and the priority rule got the matching correction. * S2-RH-04 had two real defects that masked each other: (a) `assert.ok(abortSeen || true, ...)` was a tautology — verified the assertion was checked with an explicit strict-mode test that throws when abortSeen is false (mutator #1). (b) The 5s upper-bound branch was never activated because the stub settled the stream on abort. The new stub hangs forever after the first yield — the worst case the bounded drain exists to protect against. abortSession must time out at 5s, not return quickly. Mutator #3 (drop the 5s race) turns S2-RH-04 red. * Implementing the fixed test surfaced a real bug: safeSendMessage's signal param was undefined when the caller didn't supply one, so adapter.sendMessage was called without a signal. createTurnHost now owns a per-turn AbortController and safeSendMessage forwards turnController.signal when the caller doesn't supply one. abortSession also fires turnController.abort() to deliver the signal side of the cancellation. Documented inline. * packages/webui/server/lib/mcode-acp.js: drop engineModelKeyFromId. It was defined, mentioned in a doc comment, and NEVER exported or called anywhere in the tree (verified by grep). Pure baseline dead code — S2 didn't introduce it; the acceptance pass flagged it as optional cleanup. Deleting reduces cognitive load without behaviour change. Acceptance: * pnpm typecheck, pnpm --filter @mavis/webui webapp:typecheck: both 0. * pnpm --filter @mavis/webui test: 1952/1954 pass (2 baseline skips). S2-RH-04 reports ~5400ms every run, proving the bounded-drain branch fires on every invocation. * pnpm check:source: 4715 files. * pnpm check:tsconfig: 132 package exports match. * pnpm test:release-tools: 69/70 pass (1 baseline skip). Mutation verification (3 mutators; all turn the expected test red): mutation-1 (drop turn sendMessage inner try/catch) -> S2-RH-03 red. mutation-2 (drop catalogue close() bounded race) -> S2-RH-02 red. mutation-3 (drop abortSession bounded-drain race) -> S2-RH-04 red. Evidence: archive/evidence/s2-runtime-host/R2-dev.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First code slice of the runtime-first migration: stop routing the webui backend through
packages/tui/src/acp/agent.tsand let it talk to the runtime directly.This slice changes no behaviour. No route reads the new switch yet; the point is to make the in-process host exist and be testable, with every future capability migration (S3–S6) able to run behind a flag and fall back.
What lands
server/lib/runtime-host.js— two hosts with deliberately different lifetimes: a long-lived catalogue host for read-only lookups, and a per-turn turn host that is disposable. Both wrapTuiRuntimeAdapterover aCliServicecreated in-process bycreateLocalRuntimeHostV2. The module deliberately does not importconfig.js; configuration arrives as explicit options so nothing pollutesprocess.envat module-init time.MCODE_WEBUI_TRANSPORT— defaults toacp, which is exactly today's behaviour. An unrecognised value warns and falls back toacp; an unrecognised value never refuses to start.requireshim, and the adapter's dependency tree containsproper-lockfile(CJS), so the bundle built but crashed at module load withDynamic require of "path" is not supported. Fixed with the CLI's owncreateRequirebanner.Why the disposable turn host matters
Moving the engine in-process removes a process boundary. Today an engine crash costs one subprocess; in-process it could take the webui server with it. The turn host is the only hedge: every call inside a turn is wrapped so exceptions become that turn's failure state, a turn releases its host when it ends, and
close()drains with a bound rather than blocking graceful shutdown.Cancellation does not use
subprocess.kill— there is no process to kill.abortSessionreturning success means "delivered", not "stopped", so the path is abort → wait for the stream to settle (≤5s) → discard the host.Acceptance
Independent (
GLM-5.3-Flash, not the author): PASS-WITH-CONCERNS. Both acceptance findings were fixed in17ce2f1.Dynamic require of "node:os"; with it, exit 0. The crash moved frompathtonode:osonly because the import graph reaches a different CJS module first — same shim, same root cause.better-sqlite3requires through the banner at boot, verified against a real host that writes SQLite./api/modelsis byte-for-byte identical to the baseline;/api/stateand/api/settingsdiffer only in per-instance token and port.git difftouches zero files underroutes/, and the productionserver/has no reference to the new module.mcodesubprocesses, asserted with set-leveldeepEqualat three points — not "happened to be zero".try/catchturns exactlyS2-RH-03red; changingclose()to a bare await turns exactlyS2-RH-02red.Follow-ups taken in
17ce2f1MCODE_WEBUI_TRANSPORT=execas current behaviour when no route consumes that value — it is a no-op in this slice, and exec is still reachable only throughMCODE_USE_ACP=0. Corrected in both languages, the same wayruntimewas already labelled.S2-RH-04containedassert.ok(abortSeen || true)and never passed a signal, so the 5-second bound was never actually exercised. Replaced with strict assertions, and the stub now hangs forever on purpose so the bounded race fires every run (elapsed ≈ 5400ms).signalpassedundefinedstraight through. The turn host now owns anAbortControllerand uses the caller's signal when given, its own otherwise;safeAbortSessiondelivers on both paths.engineModelKeyFromIdfrommcode-acp.js— grep confirms zero callers and no export; a stub from an earlier ticket.Known blocker for S3 (recorded, not fixed here)
The banner anchors
import.meta.urltodist/webui/server.js, one level deeper than the CLI'sdist/cli.js.resolveAgentAssetsDir's candidate chain therefore misses in the real dist layout: with assets atdist/assets/agentsboot still throwsLocal Runtime V2 built-in Agent assets are missing; onlydist/webui/assets/agentsboots. Harmless in S2 (nothing is wired), but S3 will crash on a real dist deploy. Tracked in the S3 ticket.Checks
pnpm typecheck·webui:typecheck· webui full suite 1952 pass / 0 fail (after rebuildingwebapp/out) ·check:source4715 ·check:tsconfig132 ·test:release-tools69/70.🤖 Generated with Claude Code