…ontext
Move the fetch pipeline without changing request order, responses, headers, authentication, timeouts, upgrades or Portal startup gating. Keep only the socket peer variant; relay authentication is added by todo 8.
The context carries the existing per-server services and bound socket operations. Workspace routes receive only Pick<ServerContext, "timeout">. Add two isolated fake-context acceptance tests.
Pre-move bunServer references (the tool LSP failed on TypeScript 7; a read-only typescript-language-server probe using installed typescript-api recovered all 14 semantic references):
server/index.ts:294:26
server/index.ts:313:18
server/index.ts:407:58
server/index.ts:425:11
server/index.ts:457:30
server/index.ts:463:26
server/index.ts:497:24
server/index.ts:505:79
server/index.ts:515:73
server/index.ts:516:84
server/index.ts:518:78
server/index.ts:579:48
server/index.ts:607:86
server/index.ts:641:193
Complete bunServer text inventory, including the callee parameter:
../../herdr-web-ui-wt/refactor-server-dispatch/server/index.ts
294: async fetch(request, bunServer) {
313: const ip = bunServer.requestIP(request);
407: if (pathname.startsWith("/api/") && authenticated) bunServer.timeout(request, 60);
425: bunServer.timeout(request, pathname === "/api/machines/events" ? 0 : slowMachinePath(pathname.split("/").slice(4).join("/")) ? 180 : 80);
457: const upgraded = bunServer.upgrade(request, { headers, data: connectionData({ relay, deviceId, readOnly }) });
463: const upgraded = bunServer.upgrade(request, { headers, data: connectionData({ deviceId, readOnly, bridge: bridgeAuthorized }) });
497: const port = bunServer.port ?? DEFAULT_PORT;
505: return handlePortalRequest(request, pathname, options.portal, { port: bunServer.port ?? DEFAULT_PORT, ...portalGuards() });
515: if (pathname === "/api/usage/login") { setRequestRoute(pathname); bunServer.timeout(request, 30); return usageLogin.handle(request, collector.trackPane); }
516: if (pathname === "/api/cliproxy" || pathname.startsWith("/api/cliproxy/")) { bunServer.timeout(request, 60); return cliProxyRoutes.handle(request); }
518: if (pathname === "/api/voice" || pathname.startsWith("/api/voice/")) { bunServer.timeout(request, 120); return handleVoiceRequest(request, pathname, voice); }
579: return jsonResponse(await remoteAccess(bunServer.port ?? DEFAULT_PORT), 200, { "cache-control": "no-store" });
607: const workspaceResponse = await handleWorkspaceRequest(request, url, pathname, bunServer, broadcastAll, queueFirstMessage, () => {
641: return handleSessionResumeRequest(request, { readOnly, stores: { codexHome: options.codexHome, opencodeDb: options.opencodeDb, hermesHome: options.hermesHome }, holdOpen: (seconds) => bunServer.timeout(request, seconds) });
../../herdr-web-ui-wt/refactor-server-dispatch/server/workspace-routes.ts
39: export async function handleWorkspaceRequest(request: Request, url: URL, pathname: string, bunServer: Pick<Server<unknown>, "timeout">, broadcastAll: (message: ServerMessage) => void,
137: if (payload.agent) bunServer.timeout(request, 75);
193: bunServer.timeout(request, 75);
242: if (creating) bunServer.timeout(request, agent ? 150 : 75);
Complete pre-move rg -n "requestIP|\.timeout\(|\.upgrade\(" server inventory (58 hits; includes test fixtures and AbortSignal deadlines):
server/codex-daemon.test.ts:55: fetch(request, bun) { return bun.upgrade(request) ? undefined : new Response("no", { status: 400 }); },
server/grok.test.ts:298: const running = once(child.stdout!, "data", { signal: AbortSignal.timeout(10_000) });
server/grok.test.ts:337: const exited = once(child, "exit", { signal: AbortSignal.timeout(2000) });
server/machines-refresh.contract.test.ts:51: if (new URL(request.url).pathname === "/ws" && server.upgrade(request, { data: undefined })) return;
server/remote-bundle.ts:49: const response = await fetch(url, { signal: AbortSignal.timeout(30 * 60_000) });
server/telemetry.ts:385: signal: AbortSignal.any([abort.signal, AbortSignal.timeout(SEND_TIMEOUT_MS)]),
server/telemetry.ts:431: signal: AbortSignal.any([abort.signal, AbortSignal.timeout(SEND_TIMEOUT_MS)]),
server/request-log.contract.test.ts:98: if (server.upgrade(request)) return;
server/portal.ts:579: const response = await (this.options.fetch ?? fetch)(`${relay}/sdk/domain`, { signal: AbortSignal.timeout(RELAY_TIMEOUT_MS), redirect: "error" });
server/session-history.test.ts:1433: server.timeout(incoming, 4);
server/session-history.test.ts:1434: return handleSessionResumeRequest(new Request(incoming.url, { method: "POST", body }), { readOnly: false, ...base, tabs, holdOpen: (seconds: number) => { asked.push(seconds); server.timeout(incoming, seconds); } });
server/session-history.test.ts:1475: fetch: (incoming) => handleSessionResumeRequest(incoming, { readOnly: false, ...base, tabs, deadline: started.promise, holdOpen: (seconds: number) => server.timeout(incoming, seconds) }),
server/device-access.contract.test.ts:380: if (instance.upgrade(request)) return;
server/device-access.contract.test.ts:715: if (instance.upgrade(request, { data: { relay } })) return;
server/machine-relay.test.ts:62: fetch(request, server) { return server.upgrade(request) ? undefined : new Response("no", { status: 400 }); },
server/machine-relay.test.ts:100: fetch(request, server) { return server.upgrade(request) ? undefined : new Response("no", { status: 400 }); },
server/machine-relay.test.ts:146: fetch(request, server) { return server.upgrade(request) ? undefined : new Response("no", { status: 400 }); },
server/cliproxy.ts:114: ...init, redirect: "error", signal: AbortSignal.timeout(8000),
server/bridge.test.ts:210: if (new URL(request.url).pathname === "/ws") return srv.upgrade(request) ? undefined : new Response("no", { status: 400 });
server/bridge.test.ts:330: await expect(fetch(`http://127.0.0.1:${relayTo(closed)}/`, { signal: AbortSignal.timeout(5000) })).rejects.toThrow();
server/usage-login.ts:288: env: this.env, stdout: "pipe", stderr: "ignore", signal: AbortSignal.timeout(10_000),
server/push.ts:574: signal: AbortSignal.timeout(PUSH_TIMEOUT_MS),
server/omo-status.test.ts:560: const health = () => fetch(`http://127.0.0.1:${server.port}/api/health?scope=bridge`, { signal: AbortSignal.timeout(5000) });
server/workspace-routes.ts:137: if (payload.agent) bunServer.timeout(request, 75);
server/workspace-routes.ts:193: bunServer.timeout(request, 75);
server/workspace-routes.ts:242: if (creating) bunServer.timeout(request, agent ? 150 : 75);
server/machine-update.test.ts:77: if (path === "/ws" && server.upgrade(request)) return;
server/usage.ts:246: const response = await ctx.fetch(url, { ...init, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
server/pi-ask-bridge.ts:109: const timeout = AbortSignal.timeout(STATE_READ_MS);
server/pi-ask-bridge.ts:124: signal: AbortSignal.any([signal, AbortSignal.timeout(SUBMIT_MS)]),
server/windows-native.test.ts:270: await windowsHost.installBundle(ssh, "win32-x64", AbortSignal.timeout(60_000));
server/windows-native.test.ts:275: await expect(windowsHost.installBundle(ssh, "win32-x64", AbortSignal.timeout(60_000))).rejects.toThrow();
server/machines.ts:1038: signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(BRIDGE_ANSWER_MS)]) : AbortSignal.timeout(BRIDGE_ANSWER_MS) });
server/machines.ts:1114: signal: AbortSignal.any([job.abort.signal, AbortSignal.timeout(BRIDGE_ANSWER_MS)]),
server/machines.ts:1179: const deadline = AbortSignal.timeout(BRIDGE_ANSWER_MS);
server/machines.ts:1221: const r = await fetch(endpoint.url + "/api/session", { headers: { authorization: `Bearer ${endpoint.token}` }, signal: AbortSignal.timeout(15_000) });
server/machines.ts:1404: const response = await fetch(`${endpoint.url}/api/health`, { headers: { authorization: `Bearer ${endpoint.token}` }, signal: AbortSignal.any([abort.signal, AbortSignal.timeout(15_000)]) });
server/machine-diagnostics.test.ts:88: if (server.upgrade(request)) return;
server/supervisor.ts:142: const response = await fetch(`${origin}/api/health`, { signal: AbortSignal.timeout(1000) });
server/machine-api.ts:355: const response = await fetch(target, { method: request.method, headers, body: ["GET", "HEAD"].includes(request.method) ? undefined : request.body, redirect: "error", signal: AbortSignal.any([request.signal, abort.signal, AbortSignal.timeout(slowMachinePath(path) ? 165_000 : 75_000)]) });
server/codex-daemon.contract.test.ts:22: fetch(request, bun) { return bun.upgrade(request) ? undefined : new Response("no", { status: 400 }); },
server/ping.contract.test.ts:65: if (instance.upgrade(request, { data: { relay } })) return;
server/index.ts:313: const ip = bunServer.requestIP(request);
server/index.ts:407: if (pathname.startsWith("/api/") && authenticated) bunServer.timeout(request, 60);
server/index.ts:425: bunServer.timeout(request, pathname === "/api/machines/events" ? 0 : slowMachinePath(pathname.split("/").slice(4).join("/")) ? 180 : 80);
server/index.ts:457: const upgraded = bunServer.upgrade(request, { headers, data: connectionData({ relay, deviceId, readOnly }) });
server/index.ts:463: const upgraded = bunServer.upgrade(request, { headers, data: connectionData({ deviceId, readOnly, bridge: bridgeAuthorized }) });
server/index.ts:515: if (pathname === "/api/usage/login") { setRequestRoute(pathname); bunServer.timeout(request, 30); return usageLogin.handle(request, collector.trackPane); }
server/index.ts:516: if (pathname === "/api/cliproxy" || pathname.startsWith("/api/cliproxy/")) { bunServer.timeout(request, 60); return cliProxyRoutes.handle(request); }
server/index.ts:518: if (pathname === "/api/voice" || pathname.startsWith("/api/voice/")) { bunServer.timeout(request, 120); return handleVoiceRequest(request, pathname, voice); }
server/index.ts:641: return handleSessionResumeRequest(request, { readOnly, stores: { codexHome: options.codexHome, opencodeDb: options.opencodeDb, hermesHome: options.hermesHome }, holdOpen: (seconds) => bunServer.timeout(request, seconds) });
server/machines-refresh.test.ts:159: if (server.upgrade(request)) return;
server/machine-own-update.test.ts:157: if (path === "/ws" && server.upgrade(request, { data: undefined })) return;
server/prompt.ts:1154: const deadline = AbortSignal.any([request.signal, AbortSignal.timeout(4_000)]);
server/opencode-service.ts:106: signal: AbortSignal.any([AbortSignal.timeout(OPENCODE_HTTP_TIMEOUT_MS), ...(signal ? [signal] : [])]),
server/outcome.contract.test.ts:56: const event = (name: string) => once(events, name, { signal: AbortSignal.timeout(10_000) });
server/outcome.contract.test.ts:78: await once(frames, "snapshot", { signal: AbortSignal.timeout(10_000) });
server/outcome.contract.test.ts:81: const result = once(frames, `submit-${requestId}`, { signal: AbortSignal.timeout(10_000) });
Validation so far
Full bun test server/ and bun run check fast are assigned to the combined-branch PR-A gate by the orchestrator. Incomplete runs were canceled and their processes verified gone; no final counts are claimed for them.
Targeted baseline: timeout 1200 bun test server/api.contract.test.ts server/device-access.contract.test.ts server/request-log.contract.test.ts server/page-media.contract.test.ts server/output.contract.test.ts -> 220 pass, 6 fail, 0 skip, 226 total, exit 1.
Targeted after: same plus server/dispatch.test.ts -> 224 pass, 4 fail, 0 skip, 228 total, exit 1; no new failure names.
Typecheck: bun run typecheck -> exit 0.
Flakiness: timeout 120 bun test server/dispatch.test.ts -> 2 pass, 0 fail, 0 skip, exit 0 on each of two runs.
Manual QA: 200 before/after health; 403 before/after evil-origin POST.
Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
Change
Summary
Seams for an upcoming end-to-end-encrypted relay transport. No behaviour change.
Changes
handleRequest/ServerContextextraction: request routing moves out ofserver/index.tsintoserver/dispatch.ts, with a dispatch test.SocketLike(includingreadyState): core sockets are typed by the four members they use.RelayClienttype inserver/machine-relay.ts.WebSocketHandlerwrapper inserver/core.Move-only note
git diff --color-moved origin/main...HEAD -- server/index.ts server/dispatch.tsshows moved code plus context plumbing. The SocketLike commit is type-only, so this PR is not strictly move-only and waits for a normal review.No changelog fragment: there is no user-visible change.
Validation
Ran on this branch after rebasing onto origin/main 510b598 (head 5b92070):
bun install --frozen-lockfile: no changesbun run typecheck: passtimeout 1800 bun run check fast: exit 0 (workflow syntax, generated types, generated themes, typecheck, build, unit tests 9084 pass / 0 fail / 36 skip in 583 files)git diff --color-moved=dimmed-zebra origin/main...HEAD: only moved code, import paths, thebunServer->ctxcontext plumbing, the declaredSocketLikenarrowing and the newserver/dispatch.test.ts. Main's new fetch-body changes (statusline route and bridge path,/wstelemetry surface/ingress, artifacts route, prompt-answer tally,agentOfferedfor/api/agents) now live inserver/dispatch.ts;agentOfferedreaches it as one moreRequestServicesfield.Skipped here (run in CI): Integration and browser, Native Windows install.