@chances-ai/wire 24.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/rpc/acp/adapter.d.ts +32 -0
- package/dist/rpc/acp/adapter.d.ts.map +1 -0
- package/dist/rpc/acp/adapter.js +185 -0
- package/dist/rpc/acp/adapter.js.map +1 -0
- package/dist/rpc/acp/engine-driver.d.ts +128 -0
- package/dist/rpc/acp/engine-driver.d.ts.map +1 -0
- package/dist/rpc/acp/engine-driver.js +550 -0
- package/dist/rpc/acp/engine-driver.js.map +1 -0
- package/dist/rpc/acp/event-map.d.ts +22 -0
- package/dist/rpc/acp/event-map.d.ts.map +1 -0
- package/dist/rpc/acp/event-map.js +205 -0
- package/dist/rpc/acp/event-map.js.map +1 -0
- package/dist/rpc/acp/load-sdk.d.ts +3 -0
- package/dist/rpc/acp/load-sdk.d.ts.map +1 -0
- package/dist/rpc/acp/load-sdk.js +24 -0
- package/dist/rpc/acp/load-sdk.js.map +1 -0
- package/dist/rpc/acp/workspace-query.d.ts +41 -0
- package/dist/rpc/acp/workspace-query.d.ts.map +1 -0
- package/dist/rpc/acp/workspace-query.js +89 -0
- package/dist/rpc/acp/workspace-query.js.map +1 -0
- package/dist/rpc/driver.d.ts +42 -0
- package/dist/rpc/driver.d.ts.map +1 -0
- package/dist/rpc/driver.js +7 -0
- package/dist/rpc/driver.js.map +1 -0
- package/dist/rpc/event-map.d.ts +8 -0
- package/dist/rpc/event-map.d.ts.map +1 -0
- package/dist/rpc/event-map.js +91 -0
- package/dist/rpc/event-map.js.map +1 -0
- package/dist/rpc/index.d.ts +13 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/rpc/index.js +18 -0
- package/dist/rpc/index.js.map +1 -0
- package/dist/rpc/lines.d.ts +2 -0
- package/dist/rpc/lines.d.ts.map +1 -0
- package/dist/rpc/lines.js +24 -0
- package/dist/rpc/lines.js.map +1 -0
- package/dist/rpc/protocol.d.ts +315 -0
- package/dist/rpc/protocol.d.ts.map +1 -0
- package/dist/rpc/protocol.js +70 -0
- package/dist/rpc/protocol.js.map +1 -0
- package/dist/rpc/rpc-server.d.ts +56 -0
- package/dist/rpc/rpc-server.d.ts.map +1 -0
- package/dist/rpc/rpc-server.js +305 -0
- package/dist/rpc/rpc-server.js.map +1 -0
- package/dist/rpc/stdout-guard.d.ts +5 -0
- package/dist/rpc/stdout-guard.d.ts.map +1 -0
- package/dist/rpc/stdout-guard.js +31 -0
- package/dist/rpc/stdout-guard.js.map +1 -0
- package/dist/rpc/writer.d.ts +34 -0
- package/dist/rpc/writer.d.ts.map +1 -0
- package/dist/rpc/writer.js +85 -0
- package/dist/rpc/writer.js.map +1 -0
- package/dist/serve/acp-session-host.d.ts +120 -0
- package/dist/serve/acp-session-host.d.ts.map +1 -0
- package/dist/serve/acp-session-host.js +276 -0
- package/dist/serve/acp-session-host.js.map +1 -0
- package/dist/serve/auth.d.ts +21 -0
- package/dist/serve/auth.d.ts.map +1 -0
- package/dist/serve/auth.js +58 -0
- package/dist/serve/auth.js.map +1 -0
- package/dist/serve/highlight.d.ts +25 -0
- package/dist/serve/highlight.d.ts.map +1 -0
- package/dist/serve/highlight.js +28 -0
- package/dist/serve/highlight.js.map +1 -0
- package/dist/serve/index.d.ts +14 -0
- package/dist/serve/index.d.ts.map +1 -0
- package/dist/serve/index.js +23 -0
- package/dist/serve/index.js.map +1 -0
- package/dist/serve/pairing.d.ts +25 -0
- package/dist/serve/pairing.d.ts.map +1 -0
- package/dist/serve/pairing.js +10 -0
- package/dist/serve/pairing.js.map +1 -0
- package/dist/serve/relay-frames.d.ts +29 -0
- package/dist/serve/relay-frames.d.ts.map +1 -0
- package/dist/serve/relay-frames.js +54 -0
- package/dist/serve/relay-frames.js.map +1 -0
- package/dist/serve/relay.d.ts +146 -0
- package/dist/serve/relay.d.ts.map +1 -0
- package/dist/serve/relay.js +475 -0
- package/dist/serve/relay.js.map +1 -0
- package/dist/serve/replay-hub.d.ts +102 -0
- package/dist/serve/replay-hub.d.ts.map +1 -0
- package/dist/serve/replay-hub.js +176 -0
- package/dist/serve/replay-hub.js.map +1 -0
- package/dist/serve/tls.d.ts +20 -0
- package/dist/serve/tls.d.ts.map +1 -0
- package/dist/serve/tls.js +64 -0
- package/dist/serve/tls.js.map +1 -0
- package/dist/serve/ws-transport.d.ts +64 -0
- package/dist/serve/ws-transport.d.ts.map +1 -0
- package/dist/serve/ws-transport.js +92 -0
- package/dist/serve/ws-transport.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (v17 M0 / docs/6.1 §3.1) The `chances serve` relay — a THIN local HTTP/WS
|
|
3
|
+
* server projecting the engine onto web + mobile clients. The relay carries NO
|
|
4
|
+
* agent logic (auth, framing, replay, static serving only — all agent work
|
|
5
|
+
* stays in the engine via the `EngineHost` seam — projected onto the ACP wire by
|
|
6
|
+
* `AcpEngineDriver`/`AcpSessionHost` (M1 binding; `/rpc`→`/acp` hard cutover at M3).
|
|
7
|
+
*
|
|
8
|
+
* Runtime: the relay runs on a RUNTIME-PORTABLE stack — `node:http` for HTTP
|
|
9
|
+
* and the `ws` package for the WebSocket control channel. Both run identically
|
|
10
|
+
* under Node, Bun, and `bun --compile` binaries, so `chances serve` works from
|
|
11
|
+
* the npm `chances` (Node) bin as well as the compiled binary. (Earlier M0/M1
|
|
12
|
+
* used `Bun.serve`/`Bun.file`, which threw `Bun is not defined` under the Node
|
|
13
|
+
* entry — v18 fix.) The transport adapters in `ws-transport.ts` were already
|
|
14
|
+
* runtime-agnostic; only this file changed.
|
|
15
|
+
*
|
|
16
|
+
* The bind default is `127.0.0.1` and NEVER `0.0.0.0` (R1 N1 / §7): LAN exposure
|
|
17
|
+
* is an explicit, loud opt-in that lands together with auth in M3 — there is no
|
|
18
|
+
* auth yet, so binding the wider interface would be a footgun.
|
|
19
|
+
*
|
|
20
|
+
* Pure pieces (`resolveBindAddress`, `handleRequest`, `serveStatic`) stay
|
|
21
|
+
* `(Request) → Response`/`Promise<Response | null>`, so routing + static
|
|
22
|
+
* resolution are unit-tested without a real socket (§11 — no timing flakiness).
|
|
23
|
+
* `startRelay` bridges them onto `node:http` (`IncomingMessage` ⇆ `Request`,
|
|
24
|
+
* `Response` ⇆ `ServerResponse`).
|
|
25
|
+
*
|
|
26
|
+
* M2 (docs/6.2) wires the WS `/acp` control channel onto ONE persistent
|
|
27
|
+
* {@link AcpSessionHost} (built once when `control` is present), NOT a per-socket
|
|
28
|
+
* session. Each accepted socket *attaches* to that session — replaying the gap
|
|
29
|
+
* since its `Last-Event-ID` cursor (read from the `?last_event_id=N` query
|
|
30
|
+
* param), then joining the fan-out. The M1 single-controller `409` is GONE:
|
|
31
|
+
* every socket may drive, the engine serializes turns (`AGENT_BUSY`), and a
|
|
32
|
+
* half-dead socket is a harmless dead fan-out target (dropped on a guarded send)
|
|
33
|
+
* rather than a lockout. The relay still carries no agent logic of its own.
|
|
34
|
+
*/
|
|
35
|
+
import type { EngineHost } from "../rpc/index.js";
|
|
36
|
+
/** Loopback — the only safe default before auth (M3). */
|
|
37
|
+
export declare const LOOPBACK = "127.0.0.1";
|
|
38
|
+
/** Default relay port. Arbitrary high port; overridable via `--port`. */
|
|
39
|
+
export declare const DEFAULT_PORT = 4517;
|
|
40
|
+
export interface BindOptions {
|
|
41
|
+
/** Listen port. Omitted ⇒ {@link DEFAULT_PORT}. */
|
|
42
|
+
port?: number;
|
|
43
|
+
/** (v21 M4) Listen interface. Omitted ⇒ {@link LOOPBACK}. A non-loopback host
|
|
44
|
+
* REQUIRES `tls` + a pairing token (fail-closed in {@link startRelay}). */
|
|
45
|
+
host?: string;
|
|
46
|
+
/** (v21 M4) Self-signed TLS material (PEM). When present the relay serves
|
|
47
|
+
* HTTPS/WSS; the CLI generates + caches it for a non-loopback bind. */
|
|
48
|
+
tls?: {
|
|
49
|
+
cert: string;
|
|
50
|
+
key: string;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export interface BindAddress {
|
|
54
|
+
hostname: string;
|
|
55
|
+
port: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the bind address. Defaults to {@link LOOPBACK} (unchanged); a
|
|
59
|
+
* non-loopback `host` is the v21 M4 opt-in for LAN exposure — {@link startRelay}
|
|
60
|
+
* then REQUIRES TLS + a pairing token (fail-closed), so a wider bind can never run
|
|
61
|
+
* unencrypted/unauthenticated.
|
|
62
|
+
*/
|
|
63
|
+
export declare function resolveBindAddress(opts?: BindOptions): BindAddress;
|
|
64
|
+
/** Loopback aliases that need no TLS/auth — the trust boundary is the machine
|
|
65
|
+
* itself (any local process is already you). */
|
|
66
|
+
export declare function isLoopbackHost(host: string): boolean;
|
|
67
|
+
export interface RelayDeps {
|
|
68
|
+
/** Agent version surfaced in `/health` + the ACP `initialize` agent info. */
|
|
69
|
+
version: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The relay's HTTP request handler. Routes only `GET /health`; the WS `/acp`
|
|
73
|
+
* upgrade is handled by `startRelay`'s `upgrade` listener (it needs the raw
|
|
74
|
+
* socket) and static SPA assets are tried before this fallback. Pure
|
|
75
|
+
* `(Request) → Response`, so routing is unit-testable without a socket.
|
|
76
|
+
*/
|
|
77
|
+
export declare function handleRequest(req: Request, deps: RelayDeps): Response;
|
|
78
|
+
/**
|
|
79
|
+
* Serve a built SPA from `staticDir` (the web-ui `vite build` output): an exact
|
|
80
|
+
* file match, else the SPA `index.html` fallback for extension-less client
|
|
81
|
+
* routes (so a deep link / refresh still loads the app). Returns `null` when
|
|
82
|
+
* nothing matches, so the caller can fall through to {@link handleRequest}.
|
|
83
|
+
*
|
|
84
|
+
* Path traversal is rejected by resolving the candidate and requiring it to
|
|
85
|
+
* stay within `staticDir`. Async (file existence) but called only on the
|
|
86
|
+
* non-upgrade path, so it never blocks the WS handshake.
|
|
87
|
+
*/
|
|
88
|
+
export declare function serveStatic(req: Request, staticDir: string): Promise<Response | null>;
|
|
89
|
+
export interface RelayHandle extends BindAddress {
|
|
90
|
+
/** `http://<hostname>:<port>` — the base URL the relay is listening on. */
|
|
91
|
+
url: string;
|
|
92
|
+
/** Gracefully shut the persistent session (cancel turn, deny pending, flush
|
|
93
|
+
* the final frames to still-open sockets) THEN close sockets + the server +
|
|
94
|
+
* release the port. Idempotent. Awaitable so the caller can let the engine
|
|
95
|
+
* settle before the runtime is disposed (codex M2 SHOULD). */
|
|
96
|
+
stop(): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Control-channel wiring for the WS `/acp` endpoint (M1; `/rpc`→`/acp` at M3).
|
|
100
|
+
* When present, the
|
|
101
|
+
* relay accepts a control socket and binds it to the engine via the `EngineHost`
|
|
102
|
+
* seam (projected onto ACP by `AcpEngineDriver`/`AcpSessionHost`). Omitted ⇒ a
|
|
103
|
+
* health-only relay (M0 behaviour).
|
|
104
|
+
*/
|
|
105
|
+
export interface RpcRelayDeps {
|
|
106
|
+
/** The engine seam. The relay wraps it with {@link perConnectionHost} so a
|
|
107
|
+
* single socket close never tears down the shared runtime. */
|
|
108
|
+
host: EngineHost;
|
|
109
|
+
/** Surfaced in the ACP `initialize` agent info response. */
|
|
110
|
+
agent: {
|
|
111
|
+
name: string;
|
|
112
|
+
version: string;
|
|
113
|
+
};
|
|
114
|
+
/** Auto-approve every tool permission (trusted automation). Default false. */
|
|
115
|
+
autoApprove?: boolean;
|
|
116
|
+
/** stderr-style sink for routed `log` events + relay diagnostics. */
|
|
117
|
+
logSink?: (line: string) => void;
|
|
118
|
+
/** (v21 M4) Pairing token required on every WS `/acp` upgrade. When present, a
|
|
119
|
+
* connect without / with a wrong token is rejected with `401` at the edge
|
|
120
|
+
* (constant-time check, before the session). Undefined ⇒ loopback, no auth
|
|
121
|
+
* (the trust boundary is the local machine) — backward-compatible. */
|
|
122
|
+
pairingToken?: string;
|
|
123
|
+
/** (v21 M4 §5) Enable the single-controller lease (default false ⇒ fan-out:
|
|
124
|
+
* any paired device drives, the engine serializes turns via AGENT_BUSY). */
|
|
125
|
+
controllerLease?: boolean;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Start the relay on `node:http` + `ws`. Thin wrapper over {@link handleRequest}
|
|
129
|
+
* + {@link serveStatic} for HTTP routes; when `control` is supplied it also
|
|
130
|
+
* serves the WS `/acp` control channel (M1). `async` because `node:http`'s
|
|
131
|
+
* `listen` is asynchronous — the returned handle's `port`/`url` reflect the
|
|
132
|
+
* ACTUALLY-bound port (resolving `port: 0` → a random free port). Returns a
|
|
133
|
+
* handle the CLI keeps alive until SIGINT.
|
|
134
|
+
*/
|
|
135
|
+
export declare function startRelay(opts: BindOptions & RelayDeps & {
|
|
136
|
+
control?: RpcRelayDeps;
|
|
137
|
+
staticDir?: string;
|
|
138
|
+
}): Promise<RelayHandle>;
|
|
139
|
+
/** Parse a `--port <n>` flag out of a positional token list (CLI helper). Returns
|
|
140
|
+
* undefined when absent or malformed (caller falls back to {@link DEFAULT_PORT}). */
|
|
141
|
+
export declare function parsePortFlag(tokens: readonly string[]): number | undefined;
|
|
142
|
+
/** Parse a `--host <addr>` flag (CLI helper, v21 M4). Returns undefined when
|
|
143
|
+
* absent (caller stays on {@link LOOPBACK}); a value beginning with `-` is the
|
|
144
|
+
* next flag, not an address, so it is rejected. */
|
|
145
|
+
export declare function parseHostFlag(tokens: readonly string[]): string | undefined;
|
|
146
|
+
//# sourceMappingURL=relay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../../src/serve/relay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AASH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMlD,yDAAyD;AACzD,eAAO,MAAM,QAAQ,cAAc,CAAC;AACpC,yEAAyE;AACzE,eAAO,MAAM,YAAY,OAAO,CAAC;AAEjC,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;gFAC4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;4EACwE;IACxE,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CACrC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,WAAgB,GAAG,WAAW,CAKtE;AAED;iDACiD;AACjD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOpD;AAED,MAAM,WAAW,SAAS;IACxB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,QAAQ,CASrE;AA6BD;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAuB3F;AA0BD,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ;;;mEAG+D;IAC/D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B;mEAC+D;IAC/D,IAAI,EAAE,UAAU,CAAC;IACjB,4DAA4D;IAC5D,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;;;2EAGuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;iFAC6E;IAC7E,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAmFD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG;IAAE,OAAO,CAAC,EAAE,YAAY,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7E,OAAO,CAAC,WAAW,CAAC,CAwLtB;AAED;sFACsF;AACtF,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAO3E;AAED;;oDAEoD;AACpD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAM3E"}
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (v17 M0 / docs/6.1 §3.1) The `chances serve` relay — a THIN local HTTP/WS
|
|
3
|
+
* server projecting the engine onto web + mobile clients. The relay carries NO
|
|
4
|
+
* agent logic (auth, framing, replay, static serving only — all agent work
|
|
5
|
+
* stays in the engine via the `EngineHost` seam — projected onto the ACP wire by
|
|
6
|
+
* `AcpEngineDriver`/`AcpSessionHost` (M1 binding; `/rpc`→`/acp` hard cutover at M3).
|
|
7
|
+
*
|
|
8
|
+
* Runtime: the relay runs on a RUNTIME-PORTABLE stack — `node:http` for HTTP
|
|
9
|
+
* and the `ws` package for the WebSocket control channel. Both run identically
|
|
10
|
+
* under Node, Bun, and `bun --compile` binaries, so `chances serve` works from
|
|
11
|
+
* the npm `chances` (Node) bin as well as the compiled binary. (Earlier M0/M1
|
|
12
|
+
* used `Bun.serve`/`Bun.file`, which threw `Bun is not defined` under the Node
|
|
13
|
+
* entry — v18 fix.) The transport adapters in `ws-transport.ts` were already
|
|
14
|
+
* runtime-agnostic; only this file changed.
|
|
15
|
+
*
|
|
16
|
+
* The bind default is `127.0.0.1` and NEVER `0.0.0.0` (R1 N1 / §7): LAN exposure
|
|
17
|
+
* is an explicit, loud opt-in that lands together with auth in M3 — there is no
|
|
18
|
+
* auth yet, so binding the wider interface would be a footgun.
|
|
19
|
+
*
|
|
20
|
+
* Pure pieces (`resolveBindAddress`, `handleRequest`, `serveStatic`) stay
|
|
21
|
+
* `(Request) → Response`/`Promise<Response | null>`, so routing + static
|
|
22
|
+
* resolution are unit-tested without a real socket (§11 — no timing flakiness).
|
|
23
|
+
* `startRelay` bridges them onto `node:http` (`IncomingMessage` ⇆ `Request`,
|
|
24
|
+
* `Response` ⇆ `ServerResponse`).
|
|
25
|
+
*
|
|
26
|
+
* M2 (docs/6.2) wires the WS `/acp` control channel onto ONE persistent
|
|
27
|
+
* {@link AcpSessionHost} (built once when `control` is present), NOT a per-socket
|
|
28
|
+
* session. Each accepted socket *attaches* to that session — replaying the gap
|
|
29
|
+
* since its `Last-Event-ID` cursor (read from the `?last_event_id=N` query
|
|
30
|
+
* param), then joining the fan-out. The M1 single-controller `409` is GONE:
|
|
31
|
+
* every socket may drive, the engine serializes turns (`AGENT_BUSY`), and a
|
|
32
|
+
* half-dead socket is a harmless dead fan-out target (dropped on a guarded send)
|
|
33
|
+
* rather than a lockout. The relay still carries no agent logic of its own.
|
|
34
|
+
*/
|
|
35
|
+
import { createServer } from "node:http";
|
|
36
|
+
import { createServer as createSecureServer } from "node:https";
|
|
37
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
38
|
+
import { extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
39
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
40
|
+
import { perConnectionHost } from "./ws-transport.js";
|
|
41
|
+
import { AcpSessionHost } from "./acp-session-host.js";
|
|
42
|
+
import { parseClientId, parseLastEventId } from "./relay-frames.js";
|
|
43
|
+
import { checkToken, extractToken } from "./auth.js";
|
|
44
|
+
/** Loopback — the only safe default before auth (M3). */
|
|
45
|
+
export const LOOPBACK = "127.0.0.1";
|
|
46
|
+
/** Default relay port. Arbitrary high port; overridable via `--port`. */
|
|
47
|
+
export const DEFAULT_PORT = 4517;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the bind address. Defaults to {@link LOOPBACK} (unchanged); a
|
|
50
|
+
* non-loopback `host` is the v21 M4 opt-in for LAN exposure — {@link startRelay}
|
|
51
|
+
* then REQUIRES TLS + a pairing token (fail-closed), so a wider bind can never run
|
|
52
|
+
* unencrypted/unauthenticated.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveBindAddress(opts = {}) {
|
|
55
|
+
return {
|
|
56
|
+
hostname: opts.host ?? LOOPBACK,
|
|
57
|
+
port: opts.port ?? DEFAULT_PORT,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Loopback aliases that need no TLS/auth — the trust boundary is the machine
|
|
61
|
+
* itself (any local process is already you). */
|
|
62
|
+
export function isLoopbackHost(host) {
|
|
63
|
+
if (host === "::1" || host === "localhost")
|
|
64
|
+
return true;
|
|
65
|
+
// The WHOLE 127.0.0.0/8 block is loopback (127.0.0.1 … 127.255.255.255), not
|
|
66
|
+
// just 127.0.0.1 — else 127.0.0.2 would be treated as an exposed bind and
|
|
67
|
+
// forced through TLS+token (codex M4-review SHOULD).
|
|
68
|
+
const m = /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
69
|
+
return m !== null && m.slice(1).every((o) => Number(o) <= 255);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The relay's HTTP request handler. Routes only `GET /health`; the WS `/acp`
|
|
73
|
+
* upgrade is handled by `startRelay`'s `upgrade` listener (it needs the raw
|
|
74
|
+
* socket) and static SPA assets are tried before this fallback. Pure
|
|
75
|
+
* `(Request) → Response`, so routing is unit-testable without a socket.
|
|
76
|
+
*/
|
|
77
|
+
export function handleRequest(req, deps) {
|
|
78
|
+
const url = new URL(req.url);
|
|
79
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
80
|
+
return Response.json({ status: "ok", agent: "chances", version: deps.version });
|
|
81
|
+
}
|
|
82
|
+
// The WS `/acp` upgrade (web/desktop control channel) is intercepted in
|
|
83
|
+
// `startRelay`'s `upgrade` listener; static SPA assets are tried in the
|
|
84
|
+
// request listener before falling through here. Everything else is Not Found.
|
|
85
|
+
return new Response("Not Found", { status: 404 });
|
|
86
|
+
}
|
|
87
|
+
/** Extension → `Content-Type`. `Bun.file`/`new Response(file)` set this for us;
|
|
88
|
+
* with `node:fs` we must map it ourselves. Unknown ⇒ a safe binary default. */
|
|
89
|
+
const CONTENT_TYPES = {
|
|
90
|
+
".html": "text/html; charset=utf-8",
|
|
91
|
+
".js": "text/javascript; charset=utf-8",
|
|
92
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
93
|
+
".css": "text/css; charset=utf-8",
|
|
94
|
+
".json": "application/json; charset=utf-8",
|
|
95
|
+
".map": "application/json; charset=utf-8",
|
|
96
|
+
".svg": "image/svg+xml",
|
|
97
|
+
".png": "image/png",
|
|
98
|
+
".jpg": "image/jpeg",
|
|
99
|
+
".jpeg": "image/jpeg",
|
|
100
|
+
".gif": "image/gif",
|
|
101
|
+
".webp": "image/webp",
|
|
102
|
+
".ico": "image/x-icon",
|
|
103
|
+
".woff": "font/woff",
|
|
104
|
+
".woff2": "font/woff2",
|
|
105
|
+
".ttf": "font/ttf",
|
|
106
|
+
".wasm": "application/wasm",
|
|
107
|
+
".txt": "text/plain; charset=utf-8",
|
|
108
|
+
};
|
|
109
|
+
function contentTypeFor(filePath) {
|
|
110
|
+
return CONTENT_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Serve a built SPA from `staticDir` (the web-ui `vite build` output): an exact
|
|
114
|
+
* file match, else the SPA `index.html` fallback for extension-less client
|
|
115
|
+
* routes (so a deep link / refresh still loads the app). Returns `null` when
|
|
116
|
+
* nothing matches, so the caller can fall through to {@link handleRequest}.
|
|
117
|
+
*
|
|
118
|
+
* Path traversal is rejected by resolving the candidate and requiring it to
|
|
119
|
+
* stay within `staticDir`. Async (file existence) but called only on the
|
|
120
|
+
* non-upgrade path, so it never blocks the WS handshake.
|
|
121
|
+
*/
|
|
122
|
+
export async function serveStatic(req, staticDir) {
|
|
123
|
+
if (req.method !== "GET" && req.method !== "HEAD")
|
|
124
|
+
return null;
|
|
125
|
+
const base = resolve(staticDir);
|
|
126
|
+
// The realpath of the root anchors the symlink-escape check below. If the
|
|
127
|
+
// staticDir itself doesn't exist, serve nothing.
|
|
128
|
+
const realBase = await realpath(base).catch(() => null);
|
|
129
|
+
if (realBase === null)
|
|
130
|
+
return null;
|
|
131
|
+
const pathname = new URL(req.url).pathname;
|
|
132
|
+
const candidate = pathname === "/" ? "index.html" : pathname.replace(/^\/+/, "");
|
|
133
|
+
const filePath = resolve(join(base, candidate));
|
|
134
|
+
// Lexical guard: reject anything resolving outside the root (`../../etc/...`).
|
|
135
|
+
if (!isWithin(base, filePath))
|
|
136
|
+
return null;
|
|
137
|
+
const direct = await servableFile(filePath, realBase);
|
|
138
|
+
if (direct)
|
|
139
|
+
return direct;
|
|
140
|
+
// SPA fallback: an extension-less route (client-side path) → index.html.
|
|
141
|
+
if (extname(pathname) === "") {
|
|
142
|
+
const index = await servableFile(join(base, "index.html"), realBase);
|
|
143
|
+
if (index)
|
|
144
|
+
return index;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Serve `filePath` only if it exists AND its REAL path stays within `realBase`
|
|
150
|
+
* — a symlink inside the static root that points outside it (codex R2 SHOULD-1)
|
|
151
|
+
* is rejected, which the lexical resolve check alone can't catch.
|
|
152
|
+
*/
|
|
153
|
+
async function servableFile(filePath, realBase) {
|
|
154
|
+
const st = await stat(filePath).catch(() => null);
|
|
155
|
+
if (!st || !st.isFile())
|
|
156
|
+
return null;
|
|
157
|
+
const real = await realpath(filePath).catch(() => null);
|
|
158
|
+
if (real === null || !isWithin(realBase, real))
|
|
159
|
+
return null;
|
|
160
|
+
const data = await readFile(filePath).catch(() => null);
|
|
161
|
+
if (data === null)
|
|
162
|
+
return null;
|
|
163
|
+
// `Uint8Array` body keeps the Response Web-API portable across Node/Bun.
|
|
164
|
+
return new Response(new Uint8Array(data), { headers: { "content-type": contentTypeFor(filePath) } });
|
|
165
|
+
}
|
|
166
|
+
/** Platform-correct containment: is `target` `base` itself or strictly inside it?
|
|
167
|
+
* Uses `path.relative` (not a hardcoded `/` prefix, which breaks on Windows
|
|
168
|
+
* back-slash paths — codex v17 holistic SHOULD-FIX). */
|
|
169
|
+
function isWithin(base, target) {
|
|
170
|
+
const rel = relative(base, target);
|
|
171
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
172
|
+
}
|
|
173
|
+
/** Reconstruct an absolute URL from a `node:http` request (its `url` is the
|
|
174
|
+
* path+query only) by resolving `req.url` against the relay's OWN bound base.
|
|
175
|
+
* The `Host` header is deliberately NOT trusted (codex v18 SHOULD-FIX) — only
|
|
176
|
+
* the pathname/method are read downstream, and the relay is loopback-bound.
|
|
177
|
+
* May throw on a malformed `req.url`; callers parse inside a guard. */
|
|
178
|
+
function requestUrl(req, baseUrl) {
|
|
179
|
+
return new URL(req.url ?? "/", baseUrl).toString();
|
|
180
|
+
}
|
|
181
|
+
/** `node:http` headers → Web `Headers` so the pure handlers see one shape. */
|
|
182
|
+
function toWebHeaders(headers) {
|
|
183
|
+
const out = new Headers();
|
|
184
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
185
|
+
if (Array.isArray(value))
|
|
186
|
+
for (const v of value)
|
|
187
|
+
out.append(key, v);
|
|
188
|
+
else if (value !== undefined)
|
|
189
|
+
out.set(key, value);
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
/** Pipe a Web `Response` (from the pure handlers) onto a `node:http` response. */
|
|
194
|
+
async function writeWebResponse(res, response) {
|
|
195
|
+
res.statusCode = response.status;
|
|
196
|
+
response.headers.forEach((value, key) => res.setHeader(key, value));
|
|
197
|
+
const body = Buffer.from(await response.arrayBuffer());
|
|
198
|
+
res.end(body);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Adapt a `ws` WebSocket to the {@link ServerSocket} tri-state `send` the
|
|
202
|
+
* {@link ReplayHub} fan-out uses: `>0` sent, `-1` enqueued-but-backpressured,
|
|
203
|
+
* `0` dropped/dead. `ws` has no `drain` event, so once buffered bytes cross the
|
|
204
|
+
* high-water mark we poll `bufferedAmount` and fire `onWritable` when it falls
|
|
205
|
+
* below the low-water mark — faithful backpressure without an unbounded in-memory
|
|
206
|
+
* `ws` send queue. At loopback (single user) this path is essentially never hit.
|
|
207
|
+
*/
|
|
208
|
+
function nodeServerSocket(ws, onWritable) {
|
|
209
|
+
const HIGH_WATER = 1 << 20; // 1 MiB queued ⇒ report backpressure
|
|
210
|
+
const LOW_WATER = 1 << 16; // drained below 64 KiB ⇒ resume writers
|
|
211
|
+
let pollTimer;
|
|
212
|
+
const stopPoll = () => {
|
|
213
|
+
if (pollTimer) {
|
|
214
|
+
clearInterval(pollTimer);
|
|
215
|
+
pollTimer = undefined;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
return {
|
|
219
|
+
get bufferedAmount() {
|
|
220
|
+
return ws.bufferedAmount;
|
|
221
|
+
},
|
|
222
|
+
send(data) {
|
|
223
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
224
|
+
return 0;
|
|
225
|
+
try {
|
|
226
|
+
ws.send(data);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return 0; // socket closing mid-write — treat as dropped
|
|
230
|
+
}
|
|
231
|
+
if (ws.bufferedAmount >= HIGH_WATER) {
|
|
232
|
+
if (!pollTimer) {
|
|
233
|
+
pollTimer = setInterval(() => {
|
|
234
|
+
if (ws.readyState !== WebSocket.OPEN || ws.bufferedAmount <= LOW_WATER) {
|
|
235
|
+
stopPoll();
|
|
236
|
+
onWritable();
|
|
237
|
+
}
|
|
238
|
+
}, 10);
|
|
239
|
+
pollTimer.unref?.();
|
|
240
|
+
}
|
|
241
|
+
return -1;
|
|
242
|
+
}
|
|
243
|
+
return 1;
|
|
244
|
+
},
|
|
245
|
+
close(code, reason) {
|
|
246
|
+
stopPoll();
|
|
247
|
+
try {
|
|
248
|
+
ws.close(code, reason);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
/* already closing */
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Start the relay on `node:http` + `ws`. Thin wrapper over {@link handleRequest}
|
|
258
|
+
* + {@link serveStatic} for HTTP routes; when `control` is supplied it also
|
|
259
|
+
* serves the WS `/acp` control channel (M1). `async` because `node:http`'s
|
|
260
|
+
* `listen` is asynchronous — the returned handle's `port`/`url` reflect the
|
|
261
|
+
* ACTUALLY-bound port (resolving `port: 0` → a random free port). Returns a
|
|
262
|
+
* handle the CLI keeps alive until SIGINT.
|
|
263
|
+
*/
|
|
264
|
+
export async function startRelay(opts) {
|
|
265
|
+
const { hostname, port } = resolveBindAddress(opts);
|
|
266
|
+
const control = opts.control;
|
|
267
|
+
const staticDir = opts.staticDir;
|
|
268
|
+
const tls = opts.tls;
|
|
269
|
+
// (v21 M4) Fail-closed: a non-loopback bind MUST be both encrypted (TLS) and
|
|
270
|
+
// authenticated (a pairing token), or the agent would be exposed to the LAN in
|
|
271
|
+
// cleartext with no auth. Refuse to start otherwise. Loopback is unchanged
|
|
272
|
+
// (plain HTTP, no token — the trust boundary is the local machine).
|
|
273
|
+
if (!isLoopbackHost(hostname)) {
|
|
274
|
+
if (!tls)
|
|
275
|
+
throw new Error(`serve: refusing to bind ${hostname} without TLS — a non-loopback bind requires a self-signed cert (loopback stays plain HTTP)`);
|
|
276
|
+
if (!control?.pairingToken)
|
|
277
|
+
throw new Error(`serve: refusing to bind ${hostname} without a pairing token — run \`chances serve --new-token\` first`);
|
|
278
|
+
}
|
|
279
|
+
const deps = { version: opts.version };
|
|
280
|
+
const scheme = tls ? "https" : "http";
|
|
281
|
+
// The relay's own base — `req.url` is resolved against this (never the Host
|
|
282
|
+
// header) when bridging requests onto the pure handlers.
|
|
283
|
+
const baseUrl = `${scheme}://${hostname}:${port}`;
|
|
284
|
+
const httpServer = tls
|
|
285
|
+
? createSecureServer({ cert: tls.cert, key: tls.key }, (req, res) => {
|
|
286
|
+
void serveHttp(req, res);
|
|
287
|
+
})
|
|
288
|
+
: createServer((req, res) => {
|
|
289
|
+
void serveHttp(req, res);
|
|
290
|
+
});
|
|
291
|
+
async function serveHttp(req, res) {
|
|
292
|
+
try {
|
|
293
|
+
const request = new Request(requestUrl(req, baseUrl), {
|
|
294
|
+
method: req.method ?? "GET",
|
|
295
|
+
headers: toWebHeaders(req.headers),
|
|
296
|
+
});
|
|
297
|
+
const pathname = new URL(request.url).pathname;
|
|
298
|
+
// API routes win over the SPA: `serveStatic`'s extension-less index
|
|
299
|
+
// fallback would otherwise shadow `/health` (it has no extension).
|
|
300
|
+
if (pathname === "/health") {
|
|
301
|
+
await writeWebResponse(res, handleRequest(request, deps));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
// A non-upgrade hit on the control endpoint (a real upgrade goes to the
|
|
305
|
+
// `upgrade` listener, never here) — tell the caller to upgrade.
|
|
306
|
+
if (control && pathname === "/acp") {
|
|
307
|
+
await writeWebResponse(res, new Response("Expected a WebSocket upgrade", { status: 426 }));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (staticDir) {
|
|
311
|
+
const asset = await serveStatic(request, staticDir);
|
|
312
|
+
if (asset) {
|
|
313
|
+
await writeWebResponse(res, asset);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
await writeWebResponse(res, handleRequest(request, deps));
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
if (!res.headersSent) {
|
|
321
|
+
res.statusCode = 500;
|
|
322
|
+
res.end("Internal Server Error");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// ONE persistent session for the whole `serve` process (M2). Built once when
|
|
327
|
+
// a control host is present; sockets attach/detach over it. `perConnectionHost`
|
|
328
|
+
// makes the AcpEngineDriver's own `host.dispose()` a NO-OP so the runtime is
|
|
329
|
+
// torn down EXACTLY ONCE by `main()`'s `disposeRuntime` on exit (the session is
|
|
330
|
+
// persistent, so per-session dispose would double-tear-down the shared
|
|
331
|
+
// runtime). `AcpSessionHost.shutdown()` still ends the queue + runs the engine's
|
|
332
|
+
// graceful turn/permission shutdown.
|
|
333
|
+
let sessionHost;
|
|
334
|
+
// The WS control channel rides `noServer` mode: we own the `upgrade` event so
|
|
335
|
+
// we can validate the path before `ws` takes the socket.
|
|
336
|
+
let wss;
|
|
337
|
+
if (control) {
|
|
338
|
+
sessionHost = new AcpSessionHost({
|
|
339
|
+
host: perConnectionHost(control.host),
|
|
340
|
+
agent: control.agent,
|
|
341
|
+
...(control.autoApprove !== undefined ? { autoApprove: control.autoApprove } : {}),
|
|
342
|
+
...(control.logSink ? { logSink: control.logSink } : {}),
|
|
343
|
+
...(control.controllerLease !== undefined ? { controllerLease: control.controllerLease } : {}),
|
|
344
|
+
});
|
|
345
|
+
const host = sessionHost;
|
|
346
|
+
wss = new WebSocketServer({ noServer: true });
|
|
347
|
+
wss.on("connection", (ws, req) => {
|
|
348
|
+
// Drain is dormant in M2 — the ReplayHub never parks the writer (the ring
|
|
349
|
+
// is the durable buffer), so `onWritable` is a no-op.
|
|
350
|
+
const socket = nodeServerSocket(ws, () => { });
|
|
351
|
+
// Cursor for the replay/reconnect: `?last_event_id=N` query param (a
|
|
352
|
+
// browser WebSocket cannot set headers), header fallback for non-browser.
|
|
353
|
+
const lastSeq = parseLastEventId(req.url ?? "/acp", req.headers);
|
|
354
|
+
const clientId = parseClientId(req.url ?? "/acp");
|
|
355
|
+
const attachment = host.attach(socket, lastSeq, clientId);
|
|
356
|
+
ws.on("message", (data, isBinary) => {
|
|
357
|
+
// The ACP wire is JSON-RPC NDJSON text; ignore binary frames (and empty
|
|
358
|
+
// text frames) rather than feeding them to the line parser (codex v18
|
|
359
|
+
// SHOULD-FIX — the prior `!isBinary || text` leaked binary frames in).
|
|
360
|
+
if (isBinary)
|
|
361
|
+
return;
|
|
362
|
+
const text = Array.isArray(data) ? Buffer.concat(data).toString() : data.toString();
|
|
363
|
+
if (text)
|
|
364
|
+
host.onMessage(text, clientId);
|
|
365
|
+
});
|
|
366
|
+
// Without an `error` listener, a transport/protocol error on the socket
|
|
367
|
+
// throws as an UNHANDLED EventEmitter error and crashes the relay (codex
|
|
368
|
+
// v18 MUST-FIX). Just DETACH — the session + any in-flight turn survive a
|
|
369
|
+
// socket drop (M2); a `close` event usually follows, and `detach` is
|
|
370
|
+
// idempotent.
|
|
371
|
+
ws.on("error", (err) => {
|
|
372
|
+
control.logSink?.(`relay: /acp socket error: ${err.message}\n`);
|
|
373
|
+
attachment.detach();
|
|
374
|
+
});
|
|
375
|
+
ws.on("close", () => attachment.detach());
|
|
376
|
+
});
|
|
377
|
+
httpServer.on("upgrade", (req, socket, head) => {
|
|
378
|
+
// Never let a malformed upgrade request throw OUT of this event handler
|
|
379
|
+
// (it would be an unhandled exception) — guard the URL parse and drop the
|
|
380
|
+
// socket on failure (codex v18 SHOULD-FIX).
|
|
381
|
+
let pathname;
|
|
382
|
+
try {
|
|
383
|
+
pathname = new URL(requestUrl(req, baseUrl)).pathname;
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
socket.destroy();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (pathname !== "/acp") {
|
|
390
|
+
socket.destroy();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
// (v21 M4) Pairing-token auth at the edge: when a token is configured (any
|
|
394
|
+
// non-loopback bind requires one — Stage 2), reject an absent/wrong token
|
|
395
|
+
// with a 401 BEFORE the upgrade, so it never reaches the session. Loopback
|
|
396
|
+
// runs with `pairingToken` undefined, so this is a no-op there.
|
|
397
|
+
if (control.pairingToken !== undefined && !checkToken(extractToken(req.url, req.headers, baseUrl), control.pairingToken)) {
|
|
398
|
+
socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
|
|
399
|
+
socket.destroy();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
// No 409: every socket attaches + fans out (M2). A reconnecting or 2nd
|
|
403
|
+
// client just joins; the engine serializes turns via AGENT_BUSY.
|
|
404
|
+
wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
408
|
+
const onError = (err) => rejectListen(err);
|
|
409
|
+
httpServer.once("error", onError);
|
|
410
|
+
httpServer.listen(port, hostname, () => {
|
|
411
|
+
httpServer.removeListener("error", onError);
|
|
412
|
+
resolveListen();
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
const addr = httpServer.address();
|
|
416
|
+
const boundPort = addr && typeof addr === "object" ? addr.port : port;
|
|
417
|
+
return {
|
|
418
|
+
hostname,
|
|
419
|
+
port: boundPort,
|
|
420
|
+
url: `${scheme}://${hostname}:${boundPort}`,
|
|
421
|
+
stop: async () => {
|
|
422
|
+
// AWAIT the engine's graceful shutdown FIRST (end the persistent queue →
|
|
423
|
+
// cancel the turn, deny pending, flush the terminal `result` to the
|
|
424
|
+
// still-open sockets) so an in-flight browser-driven turn settles before
|
|
425
|
+
// the caller lets `main()`'s `disposeRuntime` tear the runtime down (codex
|
|
426
|
+
// M2 SHOULD — the prior fire-and-forget raced disposal). `host.dispose`
|
|
427
|
+
// inside this is a no-op via perConnectionHost, so the runtime is still
|
|
428
|
+
// disposed exactly once, by `main()`.
|
|
429
|
+
try {
|
|
430
|
+
await sessionHost?.shutdown();
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
/* best-effort — never block the socket/server close on it */
|
|
434
|
+
}
|
|
435
|
+
for (const client of wss?.clients ?? []) {
|
|
436
|
+
try {
|
|
437
|
+
client.terminate();
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
/* already gone */
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
wss?.close();
|
|
444
|
+
// Force-close lingering keep-alive sockets so the port frees promptly
|
|
445
|
+
// (node 18.2+; absent under some runtimes — guard with `?.`).
|
|
446
|
+
httpServer.closeAllConnections?.();
|
|
447
|
+
httpServer.close();
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/** Parse a `--port <n>` flag out of a positional token list (CLI helper). Returns
|
|
452
|
+
* undefined when absent or malformed (caller falls back to {@link DEFAULT_PORT}). */
|
|
453
|
+
export function parsePortFlag(tokens) {
|
|
454
|
+
const i = tokens.indexOf("--port");
|
|
455
|
+
if (i < 0)
|
|
456
|
+
return undefined;
|
|
457
|
+
const raw = tokens[i + 1];
|
|
458
|
+
if (raw === undefined)
|
|
459
|
+
return undefined;
|
|
460
|
+
const n = Number.parseInt(raw, 10);
|
|
461
|
+
return Number.isInteger(n) && n > 0 && n < 65536 ? n : undefined;
|
|
462
|
+
}
|
|
463
|
+
/** Parse a `--host <addr>` flag (CLI helper, v21 M4). Returns undefined when
|
|
464
|
+
* absent (caller stays on {@link LOOPBACK}); a value beginning with `-` is the
|
|
465
|
+
* next flag, not an address, so it is rejected. */
|
|
466
|
+
export function parseHostFlag(tokens) {
|
|
467
|
+
const i = tokens.indexOf("--host");
|
|
468
|
+
if (i < 0)
|
|
469
|
+
return undefined;
|
|
470
|
+
const raw = tokens[i + 1];
|
|
471
|
+
if (raw === undefined || raw.startsWith("-"))
|
|
472
|
+
return undefined;
|
|
473
|
+
return raw;
|
|
474
|
+
}
|
|
475
|
+
//# sourceMappingURL=relay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay.js","sourceRoot":"","sources":["../../src/serve/relay.ts"],"names":[],"mappings":"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"}
|