* feat(webserver): add resource routes and in-process readResource
A webserver route or the fallback seat may now answer through a resource,
a function from a Fetch Request to a Response, besides the existing
node:http handler. register accepts either route form and registerFallback
accepts a handler or { resource }; existing handler callers are unchanged.
HTTP GET and HEAD requests reach a resource; webServer.readResource(request,
peer) follows the same match order and reaches only resources, so handler
routes stay unreachable to in-process readers.
client-modules registers /plugins as the fetchBundle resource, and
frontend-static claims the fallback seat with a resource that renders the
index for a supplied Peer, or runs Connection's existing authorizeIndex for
an HTTP request and renders for the browser Peer.
* fix(webserver): flush resource headers and keep one drain listener per response
Resource responses send their status and headers before the first body
chunk, register one drain/close listener pair per response so the gzip
middleware's zlib stream does not accumulate listeners across backpressure
waits, and readResource drops the body of a HEAD read as HTTP does. HTTP
requests and readResource share one responder lookup.
* chore(scripts): prune the retired client-modules unknown-cast baseline entry
* feat(frontend-static): let browsers revalidate dist files except the index
Every dist file other than the rendered index now carries
cache-control: no-cache and a weak ETag from a SHA-1 of its bytes; a
request whose If-None-Match equals that ETag receives an empty 304. The
index keeps sending no cache headers. In-process reads through
webServer.readResource revalidate the same way.
* fix(webserver): stop reading Host for resource URLs and copy no held body
A resource request's URL is the request target resolved against the same
placeholder origin the route match uses, so a missing or unparsable Host no
longer turns a dist or /plugins request into 400. The webserver ends a
resource response itself and treats any earlier close as an abort, which
also covers the Web Worker tunnel marking an aborted response ended first.
client-modules bundle bodies and frontend-static file bodies are the held
bytes themselves instead of copies, and frontend-static no longer exports
its internal serveStatic helper.
* docs(webserver): document resource route types and the handler-or-resource fallback
Paste WebResource and WebResourceRoute beside WebRoute on the web-server
subsystem page and link the catalog signatures to it, replace the remaining
'fallback handler' wording now that the seat takes a handler or a resource,
and note why the drain listener removal is a no-op under gzip.
Correct the downlink-write activity contract for compressed frames: a write
that does not complete at once counts, and a frame above the threshold
deflates asynchronously so its completion always counts. The Host compresses
only its own outbound frames; uplink compression remains the peer's choice.
Stabilize the compressed-write liveness test with a 500 ms heartbeat interval
and assert the bounded half: after the downlink stops, missing Pongs still
terminate the socket.
Refs #6476
Restore the `ws` default `maxPayload`: the pre-change mux passed no limit, so
the 1 MiB ceiling was an unrelated breaking change. Remove the exported
constant and the oversize-frame test, and drop the matching docs.
Pin the per-frame compression threshold and the accepted compressed-write
liveness consequence with regression tests, correct the queued-write
documentation, move the Agent Note to `implemented/`, and sync the Remote
duplex stream note with `websocketCompression`.
Refs #6476
Add a `websocketCompression` Gateway config field (`'deflate'` by default,
`'none'` to disable) and negotiate `permessage-deflate` on `/api/remote.mux`.
Frames whose JSON text reaches 1024 characters are compressed in the mux send
path, because `ws` ignores its `threshold` option under the default context
takeover. `maxPayload` is bounded by `max(streamInboxBytes, 1 MiB)` so one
compressed frame cannot inflate past the fixed ceiling.
Fixes#6476
The caller-connection work — forwarded-event opt-in, physical socket
ownership, and the Node/MCP artifact-only consumer fixture — moves to a
stacked PR on top of this one. This branch now changes only how Remote
definitions are generated (Typert artifacts, lazy method info,
consumer-selected input strictness, tool-schema projection) and how the
existing Registry consumes them.
A write that completes at once only reached the local send buffer, so
counting it delayed dead-peer detection until that buffer filled. Only a
write that waited in the full buffer now resets the count, and the
connection owns all three activity signals.
A congested remote.mux link can hold the Pong behind data frames, so the
Host terminated sockets that were still moving data and the Client then
reconnected and resent. Any received frame or completed downlink write now
resets the missed-heartbeat count.
Delete the now-unused llm-retry history helper, drop the unreachable
credentials-local refresh rejection handler, and add direct tests for the
gateway SRC skip of unprovided Services and llm-retry projection dedupe.
Use Gateway event stream availability before dispatch without changing Peer or manifest synchronization APIs. Keep the response deadline after disconnection and cover connected, offline, and disconnect-after-dispatch behavior.
Tests bring the changed sources to full per-file coverage, the READMEs and
docs describe the uplink, the invocation context, and the Peer registry in
both languages, and the generated catalogs are refreshed.
Every Typert Remote stream accepts client-to-Host items on the same
logical stream. A method declares the uplink item type in its return
type, RemoteStream<Out, In>, and reads the items through
this.ctx.invocation.uplink(); the invocation also carries the calling
Peer. Client generated methods return a handle that iterates the
downlink and exposes send/end/dispose for the uplink.