perf(web): make the kimi web host usable on slow links and bound browser load - #3706
perf(web): make the kimi web host usable on slow links and bound browser load#3706REtoolsx wants to merge 10 commits into
Conversation
…ser load The browser UI served by `kimi web` shipped every asset uncompressed with no cache validators, streamed one WebSocket envelope per model token, let `GET /api/v1/sessions` return every session when `page_size` was omitted, and forced `no-cache` on assets tunnelled through Remote Control. Static assets: negotiate precompressed `.br`/`.gz` siblings (generated by the new precompress script during `pnpm build` and the native bundle workflow, gitignored), add weak ETag + 304 revalidation and `Vary`, fix wasm/woff/ttf/riv/map content types, and stat each file once. WebSocket: enable permessage-deflate and a 16 MiB max payload, expose the tuning knobs through `KIMI_CODE_WS_*`, replace the 100 ms forced flush with real backpressure that closes stalled peers with 1013, and always flush control frames. Append-only transcript ops are micro-batched before seq assignment so clients keep receiving contiguous seqs. REST: the sessions list is always paginated (default 50) with `busy` applied while collecting; the transcript ops catch-up accepts `limit` and reports `has_more`. Remote Control: keep upstream cache headers, revalidate rewritten HTML/JS/CSS through a versioned ETag, pass 204/304/HEAD through bodiless, add reconnect jitter, cap early frames, apply pause/resume backpressure on the WebSocket bridge, and add an experimental chunked response mode behind `KIMI_CODE_REMOTE_CONTROL_CHUNKED_RESPONSES`.
🦋 Changeset detectedLatest commit: e583d36 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b5ae21029a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…local hop The relay forwards the browser's Sec-WebSocket-Extensions header, but the loopback ws client runs with permessage-deflate disabled, so when the local server accepted the advertised extension the client rejected the upgrade. Strip the browser's handshake fields and let ws negotiate its own.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9818ce4089
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ses behind an experimental flag The always-paginated sessions list silently truncated callers that never passed page_size, which is a breaking API change under a patch changeset. Without page_size the listing now returns every eligible session again (archived_only keeps its historical page of 20); explicit page_size keeps the collect-while-filtering behaviour and accurate has_more. Remote Control chunked responses were toggled by a standalone env var that bypassed KIMI_CODE_EXPERIMENTAL_FLAG and the [experimental] config. The feature is now the `remote_control_chunked_responses` flag registered through registerFlagDefinition; `kimi web --rc` and the TUI /rc command resolve it through IFlagService and pass it to the tunnel as an option.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4ee1304589
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…tal flags to the CLI Pre-compressed asset selection now picks the accepted encoding with the highest q value, falling back to the built-in br-before-gzip order only on ties. RunningServer gains a `flags` handle so the CLI reads the remote-control chunked-responses flag through kap-server instead of importing the engine's IFlagService directly.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ce7b4dd7d6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ated tunnels Tunnels started through POST /api/v1/remote-control go through createRemoteControlManager, which never passed chunkedResponses. The manager now takes a chunkedResponses thunk resolved at each tunnel start, and kap-server wires it to the remote_control_chunked_responses flag via IFlagService (the manager is created after the engine core bootstraps).
7cc22b5 to
83d82bd
Compare
- ws v1: control frames no longer force-flush the deferred backlog above the high-water mark, and the slow-consumer clock resets while the peer drains - transcript ops catch-up: a capped response reports latest_seq as the last returned batch so cursors written against the old contract stay correct - transcript service: pending appends are flushed, not discarded, when a session is dropped or purged - kimi-inspect: seed from one unsized request, drain with before_id only when has_more, and keep the pages already collected when a later page fails - remote-control: the chunked-responses flag is excluded from the KIMI_CODE_EXPERIMENTAL_FLAG master switch - precompress: write siblings atomically, and --check honours the size threshold and sibling freshness - webAssets: stat precompressed siblings in parallel, reuse pickHeader and buildEtag, drop the redundant .riv case - reuse the shared env parsers for KIMI_CODE_WS_* and the ops batch window, simplify flushPendingOps, drop the unused --only flag and skip list, and share a header lookup in remote-control
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0d31b4b988
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…d responses Drop the excludeFromMaster escape hatch added for remote_control_chunked_responses so KIMI_CODE_EXPERIMENTAL_FLAG=1 enables it like every other experimental flag. The flag still defaults to off and the per-flag env var and [experimental] config keep precedence.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: be02b158e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const catchup = transcriptService.getOpsSince( | ||
| session_id, | ||
| query.agent_id, | ||
| query.since_seq, | ||
| query.limit ?? MAX_TRANSCRIPT_OPS_LIMIT, | ||
| ); |
There was a problem hiding this comment.
Preserve unsized transcript-ops catch-up semantics
Clients that omit limit previously received every journal batch after since_seq; this now silently returns only the first 500 while leaving complete: true. An existing caller that does not know the newly added has_more field will stop after that response and retain an incomplete transcript without triggering its existing refresh path. Keep the old unbounded behavior when limit is absent, or treat this public API break as a major release.
AGENTS.md reference: AGENTS.md:L64-L64
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in e583d36: omitting limit returns every journaled batch after since_seq again (the pre-limit behaviour) with has_more: false; only an explicit limit (1–500) caps the response. The route test now pushes more than 500 batches through an unsized request, and the API reference (en/zh) and the changeset say so.
Omitting limit on GET .../transcript/ops returns every journaled batch after since_seq again, as it did before limit existed; only an explicit limit caps the response and sets has_more.
Live test report (before / after)I ran both builds locally on Windows 11 / Node 25 and exercised them live:
Notes:
|
Related Issue
No linked issue. This PR comes from a performance review of the
kimi webhost for users on slow connections and low-end browsers.Problem
Using the browser UI over a slow or high-latency link was painful, and long sessions could overload the browser:
ETag, so each page load re-downloaded ~3.9 MB of entry JS/CSS;.wasm/.woff/.ttffell back toapplication/octet-stream, which brokeWebAssembly.instantiateStreamingundernosniffand made the Rive runtime download twice.GET /api/v1/sessionsapplied thebusyfilter after slicing, so filtered pages came back short with an inaccuratehas_more, and the transcript ops catch-up had no cap.Accept-Encoding, and forcedCache-Control: no-cacheon every rewritten asset, so hashed assets were re-fetched on every load.What changed
Static assets (
packages/kap-server/src/routes/webAssets.ts).br/.gzsiblings viaAccept-Encoding, ranked by the client'sqweights with the built-in br-before-gzip order only as a tie-breaker (identity when the header is absent, which keeps the Remote Control rewrite path intact); siblings older than the source are ignored.ETag+If-None-Match→ 304,Last-Modified,Vary: Accept-Encoding; complete MIME table; onestatper request.apps/kimi-code/scripts/precompress-web-assets.mjsruns inpnpm buildand in the native bundle workflow (with a--checkgate); the siblings are gitignored, never committed.WebSocket (
packages/kap-server/src/transport/ws/v1/)permessage-deflate(no context takeover, 1 KiB threshold) and a 16 MiBmaxPayload;server_hello.capabilities.compressionnow reflects negotiation.KIMI_CODE_WS_*env vars.bufferedAmountis above the high-water mark; a peer stalled for 15 s or with more than 4096 queued frames is closed with1013 slow consumer; control frames always flush.KIMI_CODE_TRANSCRIPT_OPS_BATCH_MS) before seq assignment, so one flush = one seq = one envelope and existing clients, which require contiguous seqs, need no change. Journal reads flush first, so REST watermarks stay exact.REST
GET /api/v1/sessions:busy,exclude_emptyandarchived_onlyare applied while collecting, so pages fill up topage_sizeandhas_moreis accurate. The public default is unchanged: an unsized request still returns the whole list (archived_onlykeeps its 20-per-page default).GET .../transcript/opsacceptslimit(1–500) and reportshas_more;completesemantics are unchanged.RunningServerexposes aflagshandle so the CLI reads experimental flags through kap-server instead of importing the engine.Remote Control (
packages/remote-control)public, no-cachewith a versionedETagso a cheap 304 reuses the browser copy while rewrite-rule changes still invalidate it. 204/304/HEAD pass through bodiless.perMessageDeflateoff on the loopback hop. The browser'sSec-WebSocket-*handshake headers are no longer forwarded to the loopbackwsclient, which used to reject upgrades when the local server acceptedpermessage-deflate.remote_control_chunked_responsesexperimental flag (KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL_CHUNKED_RESPONSES=1,[experimental]config, orKIMI_CODE_EXPERIMENTAL_FLAG=1; default off because the relay contract for multi-frame responses is not verified in this repo). It applies to tunnels started bykimi web --rc, the TUI/rccommand andPOST /api/v1/remote-controlalike.Docs: server API reference (heartbeat contract corrected, new fields), env vars, Remote Control guide (en/zh).
AGENTS.mdnote aboutdist-webtracking updated.Reviewer notes
modulepreload) lives in code-app and is out of scope here.kap-serversuite has pre-existing path-separator/symlink failures; all targeted suites plustranscript,remote-control,kimi-inspectand the new script tests pass, typecheck andcheck-no-commentsare clean.mainafter feat(kap-server): add flat entity message protocol (v3 WS + history API) #3532 (flat entity message protocol); the inspector-side catch-up paging from earlier revisions was dropped with that merge because the client no longer uses the transcript ops endpoint.Checklist
/approve).gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.