@dev-anywhere/relay 0.6.0 → 0.6.2
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/assets/web/assets/{fvad-BVUkJ2pD.js → fvad-CTkpWO2a.js} +1 -1
- package/assets/web/assets/{index-CUkAA_Bz.js → index-Bz4c0NSL.js} +175 -175
- package/assets/web/index.html +1 -1
- package/assets/web/sw.js +1 -1
- package/dist/{chunk-7YWKWUNZ.js → chunk-SDL4V2RE.js} +352 -8
- package/dist/chunk-SDL4V2RE.js.map +1 -0
- package/dist/handlers/client.d.ts +2 -1
- package/dist/handlers/client.d.ts.map +1 -1
- package/dist/handlers/proxy.d.ts +2 -1
- package/dist/handlers/proxy.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/session-history-route-registry.d.ts +69 -0
- package/dist/session-history-route-registry.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-7YWKWUNZ.js.map +0 -1
package/assets/web/index.html
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
<link rel="icon" type="image/svg+xml" href="/brand-icon.svg" />
|
|
45
45
|
<link rel="icon" type="image/png" sizes="64x64" href="/pwa-64x64.png" />
|
|
46
46
|
<link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" />
|
|
47
|
-
<script type="module" crossorigin src="/assets/index-
|
|
47
|
+
<script type="module" crossorigin src="/assets/index-Bz4c0NSL.js"></script>
|
|
48
48
|
<link rel="stylesheet" crossorigin href="/assets/index-Dmm_ocq_.css">
|
|
49
49
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
|
50
50
|
<body class="bg-background text-foreground">
|
package/assets/web/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let o={};const c=e=>i(e,t),f={module:{uri:t},exports:o,require:c};s[t]=Promise.all(n.map(e=>f[e]||c(e))).then(e=>(r(...e),o))}}define(["./workbox-10a3128e"],function(e){"use strict";importScripts("notification-sw.js"),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"notification-sw.js",revision:"ee0be62f4c1f36625b92680b3060becb"},{url:"index.html",revision:"
|
|
1
|
+
if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let o={};const c=e=>i(e,t),f={module:{uri:t},exports:o,require:c};s[t]=Promise.all(n.map(e=>f[e]||c(e))).then(e=>(r(...e),o))}}define(["./workbox-10a3128e"],function(e){"use strict";importScripts("notification-sw.js"),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"1872c500de691dce40960bb85481de07"},{url:"notification-sw.js",revision:"ee0be62f4c1f36625b92680b3060becb"},{url:"index.html",revision:"0a117ca9dcfb6e34e8c330ca09a8f33c"},{url:"assets/index-Dmm_ocq_.css",revision:null},{url:"assets/index-Bz4c0NSL.js",revision:null},{url:"assets/fvad-DYxbW2my.wasm",revision:null},{url:"assets/fvad-CTkpWO2a.js",revision:null},{url:"assets/__vite-browser-external-BIHI7g3E.js",revision:null},{url:"brand-icon.svg",revision:"efe8505a7b7ef12cbb3a4bcd286254bd"},{url:"maskable-icon-512x512.png",revision:"e762ff07cdedcd78f8650f59c6881727"},{url:"pwa-192x192.png",revision:"4e4603ce0f22528eded553ba7f2ffbc5"},{url:"pwa-512x512.png",revision:"e762ff07cdedcd78f8650f59c6881727"},{url:"pwa-64x64.png",revision:"d322160ad1dc08ba9e133cb3d296ae63"},{url:"manifest.webmanifest",revision:"4daa295c44052f4ce59a1fcfd6f06ab8"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/proxy(\/|$|\?)/,/^\/client(\/|$|\?)/,/^\/voice(\/|$|\?)/,/^\/fonts\//,/^\/health/,/^\/status/,/^\/api\//]})),e.registerRoute(/^\/fonts\/.*\.(woff2|woff|ttf|otf|css)$/,new e.CacheFirst({cacheName:"dev-anywhere-fonts",plugins:[new e.ExpirationPlugin({maxEntries:40,maxAgeSeconds:15552e3})]}),"GET")});
|
|
@@ -542,7 +542,9 @@ var ControlErrorCode = {
|
|
|
542
542
|
PATH_ACCESS_DENIED: "PATH_ACCESS_DENIED",
|
|
543
543
|
PROXY_OFFLINE: "PROXY_OFFLINE",
|
|
544
544
|
SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
|
|
545
|
+
SESSION_ALREADY_ACTIVE: "SESSION_ALREADY_ACTIVE",
|
|
545
546
|
PROVIDER_UNSUPPORTED: "PROVIDER_UNSUPPORTED",
|
|
547
|
+
APPROVAL_POLICY_UNSUPPORTED: "APPROVAL_POLICY_UNSUPPORTED",
|
|
546
548
|
WORKER_START_FAILED: "WORKER_START_FAILED",
|
|
547
549
|
PROCESS_START_FAILED: "PROCESS_START_FAILED",
|
|
548
550
|
UNKNOWN: "UNKNOWN"
|
|
@@ -888,9 +890,9 @@ var relayControlDefinitions = [
|
|
|
888
890
|
sessionId: IdSchema.optional()
|
|
889
891
|
}, "client_to_proxy"),
|
|
890
892
|
// 会话历史浏览
|
|
891
|
-
control("session_history_request",
|
|
893
|
+
control("session_history_request", RequiredRequestIdShape, "client_to_proxy"),
|
|
892
894
|
control("session_history_response", {
|
|
893
|
-
...
|
|
895
|
+
...RequiredRequestIdShape,
|
|
894
896
|
...RequestErrorShape,
|
|
895
897
|
success: z9.boolean(),
|
|
896
898
|
sessions: z9.array(HistorySessionSchema)
|
|
@@ -951,7 +953,8 @@ var relayControlDefinitions = [
|
|
|
951
953
|
// 重连或换设备不会隐式重排既有终端内容。
|
|
952
954
|
cols: z9.number().int().positive().max(PTY_INITIAL_MAX_COLS).optional(),
|
|
953
955
|
rows: z9.number().int().positive().max(PTY_INITIAL_MAX_ROWS).optional(),
|
|
954
|
-
//
|
|
956
|
+
// Provider 级审批策略。Claude 未提供时仍使用其 default;Codex 未提供时尊重
|
|
957
|
+
// 用户本机配置,不由 DEV Anywhere 注入覆盖值。
|
|
955
958
|
permissionMode: z9.enum(["default", "auto", "acceptEdits", "plan", "bypassPermissions", "dontAsk"]).optional()
|
|
956
959
|
}, "client_to_proxy"),
|
|
957
960
|
control("session_create_response", {
|
|
@@ -964,8 +967,18 @@ var relayControlDefinitions = [
|
|
|
964
967
|
mode: z9.enum(sessionModeValues).optional(),
|
|
965
968
|
provider: z9.enum(providerValues).optional(),
|
|
966
969
|
ptyOwner: z9.enum(ptyOwnerValues).optional(),
|
|
970
|
+
// Codex 原生 thread 被其他进程持有时返回真实锁持有 PID;无法探测时省略。
|
|
971
|
+
activeWriterPid: z9.number().int().positive().optional(),
|
|
967
972
|
...RequestErrorShape
|
|
968
973
|
}, "proxy_to_client"),
|
|
974
|
+
// 会话创建响应已经成功返回、页面已进入终端后,Provider 仍可能在 bootstrap 阶段
|
|
975
|
+
// 非零退出。把可识别的运行时错误结构化推给 Web,避免关键错误只留在终端闪屏里。
|
|
976
|
+
control("session_runtime_error", {
|
|
977
|
+
sessionId: IdSchema,
|
|
978
|
+
error: z9.string(),
|
|
979
|
+
errorCode: ControlErrorCodeSchema,
|
|
980
|
+
activeWriterPid: z9.number().int().positive().optional()
|
|
981
|
+
}, "proxy_to_client"),
|
|
969
982
|
// 客户端请求会话历史消息,client -> proxy
|
|
970
983
|
control("session_messages_request", {
|
|
971
984
|
...RequestIdShape,
|
|
@@ -1781,7 +1794,7 @@ function rejectNotRegistered(ws) {
|
|
|
1781
1794
|
})
|
|
1782
1795
|
);
|
|
1783
1796
|
}
|
|
1784
|
-
function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, chaos, remoteFileBridge) {
|
|
1797
|
+
function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, sessionHistoryRoutes, chaos, remoteFileBridge) {
|
|
1785
1798
|
const proxyWs = ws;
|
|
1786
1799
|
proxyWs.isAlive = true;
|
|
1787
1800
|
proxyWs.on("pong", () => {
|
|
@@ -1829,7 +1842,10 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, chaos, r
|
|
|
1829
1842
|
const { proxyId, name } = result.message;
|
|
1830
1843
|
const status = registry.registerProxy(proxyId, proxyWs, name);
|
|
1831
1844
|
proxyWs.proxyId = proxyId;
|
|
1832
|
-
if (status === "reconnected")
|
|
1845
|
+
if (status === "reconnected") {
|
|
1846
|
+
ptySnapshotRoutes.clearProxy(proxyId);
|
|
1847
|
+
sessionHistoryRoutes.clearProxy(proxyId);
|
|
1848
|
+
}
|
|
1833
1849
|
logger.info({ proxyId, status }, "Proxy registered");
|
|
1834
1850
|
proxyWs.send(
|
|
1835
1851
|
serializeControl({
|
|
@@ -1846,6 +1862,7 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, chaos, r
|
|
|
1846
1862
|
if (result.kind === "control" && result.message.type === "proxy_disconnect") {
|
|
1847
1863
|
if (proxyWs.proxyId) {
|
|
1848
1864
|
ptySnapshotRoutes.clearProxy(proxyWs.proxyId);
|
|
1865
|
+
sessionHistoryRoutes.clearProxy(proxyWs.proxyId);
|
|
1849
1866
|
notifyClientsProxyOffline(proxyWs.proxyId, registry, logger, chaos);
|
|
1850
1867
|
registry.unregisterProxy(proxyWs.proxyId);
|
|
1851
1868
|
logger.info(
|
|
@@ -1931,6 +1948,56 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, chaos, r
|
|
|
1931
1948
|
);
|
|
1932
1949
|
return;
|
|
1933
1950
|
}
|
|
1951
|
+
if (result.message.type === "session_history_response") {
|
|
1952
|
+
if (!proxyWs.proxyId) {
|
|
1953
|
+
rejectNotRegistered(proxyWs);
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
const route = sessionHistoryRoutes.resolve(
|
|
1957
|
+
proxyWs.proxyId,
|
|
1958
|
+
result.message.requestId,
|
|
1959
|
+
proxyWs
|
|
1960
|
+
);
|
|
1961
|
+
if (route.kind === "matched") {
|
|
1962
|
+
let delivered = 0;
|
|
1963
|
+
for (const target of route.targets) {
|
|
1964
|
+
if (target.clientWs.readyState !== WebSocket4.OPEN) continue;
|
|
1965
|
+
const response = serializeControl({
|
|
1966
|
+
...result.message,
|
|
1967
|
+
requestId: target.requestId
|
|
1968
|
+
});
|
|
1969
|
+
if (chaos) {
|
|
1970
|
+
chaos.send(target.clientWs, response, {
|
|
1971
|
+
direction: "proxy_to_client",
|
|
1972
|
+
type: result.message.type
|
|
1973
|
+
});
|
|
1974
|
+
} else {
|
|
1975
|
+
target.clientWs.send(response);
|
|
1976
|
+
}
|
|
1977
|
+
delivered += 1;
|
|
1978
|
+
}
|
|
1979
|
+
logger.debug(
|
|
1980
|
+
{
|
|
1981
|
+
proxyId: proxyWs.proxyId,
|
|
1982
|
+
upstreamRequestId: result.message.requestId,
|
|
1983
|
+
waiterCount: route.targets.length,
|
|
1984
|
+
delivered,
|
|
1985
|
+
success: result.message.success
|
|
1986
|
+
},
|
|
1987
|
+
"Session history response fanned out"
|
|
1988
|
+
);
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
logger.debug(
|
|
1992
|
+
{
|
|
1993
|
+
proxyId: proxyWs.proxyId,
|
|
1994
|
+
requestId: result.message.requestId,
|
|
1995
|
+
route: route.kind
|
|
1996
|
+
},
|
|
1997
|
+
"Unmatched session history response dropped"
|
|
1998
|
+
);
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
1934
2001
|
if (isProxyToClientRelayControlType(result.message.type)) {
|
|
1935
2002
|
if (!proxyWs.proxyId) {
|
|
1936
2003
|
rejectNotRegistered(proxyWs);
|
|
@@ -1990,6 +2057,7 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, chaos, r
|
|
|
1990
2057
|
return;
|
|
1991
2058
|
}
|
|
1992
2059
|
ptySnapshotRoutes.clearProxy(proxyWs.proxyId);
|
|
2060
|
+
sessionHistoryRoutes.clearProxy(proxyWs.proxyId);
|
|
1993
2061
|
notifyClientsProxyOffline(proxyWs.proxyId, registry, logger, chaos);
|
|
1994
2062
|
try {
|
|
1995
2063
|
registry.transitionProxy(proxyWs.proxyId, "online", "offline");
|
|
@@ -2910,7 +2978,7 @@ function sendRemoteFileUploadUrlFailure(ws, requestId, sessionId, error, errorCo
|
|
|
2910
2978
|
})
|
|
2911
2979
|
);
|
|
2912
2980
|
}
|
|
2913
|
-
function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, chaos, voiceConfigStore, voiceProviders, remoteFileBridge, connectionInfo = {}) {
|
|
2981
|
+
function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, sessionHistoryRoutes, chaos, voiceConfigStore, voiceProviders, remoteFileBridge, connectionInfo = {}) {
|
|
2914
2982
|
const clientWs = ws;
|
|
2915
2983
|
clientWs.isAlive = true;
|
|
2916
2984
|
registry.addClientWs(clientWs, connectionInfo);
|
|
@@ -3150,6 +3218,60 @@ function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, chaos,
|
|
|
3150
3218
|
}
|
|
3151
3219
|
const proxyWs = registry.getProxy(targetProxyId);
|
|
3152
3220
|
if (proxyWs && proxyWs.readyState === WebSocket7.OPEN) {
|
|
3221
|
+
if (msg.type === "session_history_request") {
|
|
3222
|
+
const registration = sessionHistoryRoutes.register(
|
|
3223
|
+
targetProxyId,
|
|
3224
|
+
msg.requestId,
|
|
3225
|
+
clientWs,
|
|
3226
|
+
proxyWs
|
|
3227
|
+
);
|
|
3228
|
+
if (registration.kind === "duplicate") {
|
|
3229
|
+
logger.debug(
|
|
3230
|
+
{ proxyId: targetProxyId, requestId: msg.requestId },
|
|
3231
|
+
"Duplicate session history request suppressed"
|
|
3232
|
+
);
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
if (registration.kind === "joined") {
|
|
3236
|
+
logger.debug(
|
|
3237
|
+
{
|
|
3238
|
+
proxyId: targetProxyId,
|
|
3239
|
+
requestId: msg.requestId,
|
|
3240
|
+
upstreamRequestId: registration.upstreamRequestId
|
|
3241
|
+
},
|
|
3242
|
+
"Session history request joined active upstream flight"
|
|
3243
|
+
);
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
3246
|
+
if (registration.kind !== "leader") {
|
|
3247
|
+
logger.warn(
|
|
3248
|
+
{ proxyId: targetProxyId, requestId: msg.requestId, registration },
|
|
3249
|
+
"Session history response route rejected"
|
|
3250
|
+
);
|
|
3251
|
+
clientWs.send(
|
|
3252
|
+
JSON.stringify({
|
|
3253
|
+
type: "relay_error",
|
|
3254
|
+
requestId: msg.requestId,
|
|
3255
|
+
code: RelayErrorCode.INVALID_MESSAGE,
|
|
3256
|
+
message: registration.kind === "collision" ? "Session history requestId is already in use" : "Too many pending session history requests"
|
|
3257
|
+
})
|
|
3258
|
+
);
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
3261
|
+
const upstreamRequest = serializeControl({
|
|
3262
|
+
type: "session_history_request",
|
|
3263
|
+
requestId: registration.upstreamRequestId
|
|
3264
|
+
});
|
|
3265
|
+
if (chaos) {
|
|
3266
|
+
chaos.send(proxyWs, upstreamRequest, {
|
|
3267
|
+
direction: "client_to_proxy",
|
|
3268
|
+
type: msg.type
|
|
3269
|
+
});
|
|
3270
|
+
} else {
|
|
3271
|
+
proxyWs.send(upstreamRequest);
|
|
3272
|
+
}
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3153
3275
|
if (msg.type === "session_subscribe") {
|
|
3154
3276
|
const registration = ptySnapshotRoutes.register(
|
|
3155
3277
|
targetProxyId,
|
|
@@ -3269,6 +3391,7 @@ function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, chaos,
|
|
|
3269
3391
|
});
|
|
3270
3392
|
clientWs.on("close", (code, reason) => {
|
|
3271
3393
|
ptySnapshotRoutes.abandonSocket(clientWs);
|
|
3394
|
+
sessionHistoryRoutes.abandonSocket(clientWs);
|
|
3272
3395
|
registry.removeClientWs(clientWs);
|
|
3273
3396
|
if (clientWs.clientId) {
|
|
3274
3397
|
registry.unbindClientSocket(clientWs.clientId, clientWs);
|
|
@@ -4608,6 +4731,217 @@ var PtySnapshotRouteRegistry = class {
|
|
|
4608
4731
|
}
|
|
4609
4732
|
};
|
|
4610
4733
|
|
|
4734
|
+
// src/session-history-route-registry.ts
|
|
4735
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
4736
|
+
var DEFAULT_MAX_ENTRIES2 = 4096;
|
|
4737
|
+
var DEFAULT_MAX_PENDING_PER_CLIENT2 = 64;
|
|
4738
|
+
var DEFAULT_PENDING_TTL_MS2 = 2e4;
|
|
4739
|
+
var DEFAULT_JOIN_WINDOW_MS = 8e3;
|
|
4740
|
+
var DEFAULT_TOMBSTONE_TTL_MS2 = 6e4;
|
|
4741
|
+
function routeKey2(proxyId, requestId) {
|
|
4742
|
+
return JSON.stringify([proxyId, requestId]);
|
|
4743
|
+
}
|
|
4744
|
+
var SessionHistoryRouteRegistry = class {
|
|
4745
|
+
routes = /* @__PURE__ */ new Map();
|
|
4746
|
+
flights = /* @__PURE__ */ new Map();
|
|
4747
|
+
flightTombstones = /* @__PURE__ */ new Map();
|
|
4748
|
+
maxEntries;
|
|
4749
|
+
maxPendingPerClient;
|
|
4750
|
+
pendingTtlMs;
|
|
4751
|
+
joinWindowMs;
|
|
4752
|
+
tombstoneTtlMs;
|
|
4753
|
+
now;
|
|
4754
|
+
upstreamRequestIdFactory;
|
|
4755
|
+
upstreamRequestSequence = 0;
|
|
4756
|
+
constructor(options = {}) {
|
|
4757
|
+
this.maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_MAX_ENTRIES2);
|
|
4758
|
+
this.maxPendingPerClient = Math.max(
|
|
4759
|
+
1,
|
|
4760
|
+
options.maxPendingPerClient ?? DEFAULT_MAX_PENDING_PER_CLIENT2
|
|
4761
|
+
);
|
|
4762
|
+
this.pendingTtlMs = Math.max(1, options.pendingTtlMs ?? DEFAULT_PENDING_TTL_MS2);
|
|
4763
|
+
this.joinWindowMs = Math.max(
|
|
4764
|
+
1,
|
|
4765
|
+
Math.min(options.joinWindowMs ?? DEFAULT_JOIN_WINDOW_MS, this.pendingTtlMs)
|
|
4766
|
+
);
|
|
4767
|
+
this.tombstoneTtlMs = Math.max(1, options.tombstoneTtlMs ?? DEFAULT_TOMBSTONE_TTL_MS2);
|
|
4768
|
+
this.now = options.now ?? Date.now;
|
|
4769
|
+
this.upstreamRequestIdFactory = options.upstreamRequestIdFactory ?? (() => {
|
|
4770
|
+
this.upstreamRequestSequence += 1;
|
|
4771
|
+
return `relay-history-${this.upstreamRequestSequence.toString(36)}-${nanoid4(12)}`;
|
|
4772
|
+
});
|
|
4773
|
+
}
|
|
4774
|
+
register(proxyId, requestId, clientWs, proxyWs) {
|
|
4775
|
+
const now = this.now();
|
|
4776
|
+
this.pruneExpired(now);
|
|
4777
|
+
const key = routeKey2(proxyId, requestId);
|
|
4778
|
+
const existing = this.routes.get(key);
|
|
4779
|
+
if (existing) {
|
|
4780
|
+
if (existing.state === "pending" && existing.clientWs === clientWs && existing.proxyWs === proxyWs) {
|
|
4781
|
+
return { kind: "duplicate" };
|
|
4782
|
+
}
|
|
4783
|
+
return { kind: "collision" };
|
|
4784
|
+
}
|
|
4785
|
+
let clientPendingCount = 0;
|
|
4786
|
+
for (const route of this.routes.values()) {
|
|
4787
|
+
if (route.state === "pending" && route.clientWs === clientWs) {
|
|
4788
|
+
clientPendingCount += 1;
|
|
4789
|
+
if (clientPendingCount >= this.maxPendingPerClient) {
|
|
4790
|
+
return { kind: "client_capacity_exceeded" };
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
if (this.routes.size >= this.maxEntries && !this.evictOldestRouteTombstone()) {
|
|
4795
|
+
return { kind: "capacity_exceeded" };
|
|
4796
|
+
}
|
|
4797
|
+
let flight = this.flights.get(proxyId);
|
|
4798
|
+
if (flight && flight.proxyWs !== proxyWs) {
|
|
4799
|
+
this.expireFlight(flight, now);
|
|
4800
|
+
flight = void 0;
|
|
4801
|
+
}
|
|
4802
|
+
let kind;
|
|
4803
|
+
if (!flight || flight.joinUntil <= now) {
|
|
4804
|
+
const replacement = this.createFlight(proxyId, proxyWs, now);
|
|
4805
|
+
if (flight) this.migrateFlight(flight, replacement, now);
|
|
4806
|
+
this.flights.set(proxyId, replacement);
|
|
4807
|
+
flight = replacement;
|
|
4808
|
+
kind = "leader";
|
|
4809
|
+
} else {
|
|
4810
|
+
kind = "joined";
|
|
4811
|
+
}
|
|
4812
|
+
const routeExpiresAt = now + this.pendingTtlMs;
|
|
4813
|
+
flight.requestIds.add(requestId);
|
|
4814
|
+
flight.expiresAt = Math.max(flight.expiresAt, routeExpiresAt);
|
|
4815
|
+
this.routes.set(key, {
|
|
4816
|
+
state: "pending",
|
|
4817
|
+
proxyId,
|
|
4818
|
+
proxyWs,
|
|
4819
|
+
clientWs,
|
|
4820
|
+
upstreamRequestId: flight.upstreamRequestId,
|
|
4821
|
+
expiresAt: routeExpiresAt
|
|
4822
|
+
});
|
|
4823
|
+
return { kind, upstreamRequestId: flight.upstreamRequestId };
|
|
4824
|
+
}
|
|
4825
|
+
resolve(proxyId, requestId, proxyWs) {
|
|
4826
|
+
const now = this.now();
|
|
4827
|
+
this.pruneExpired(now);
|
|
4828
|
+
const flight = this.flights.get(proxyId);
|
|
4829
|
+
if (!flight || flight.upstreamRequestId !== requestId) {
|
|
4830
|
+
return this.flightTombstones.has(routeKey2(proxyId, requestId)) ? { kind: "tombstone" } : { kind: "unmatched" };
|
|
4831
|
+
}
|
|
4832
|
+
if (flight.proxyWs !== proxyWs) return { kind: "stale_proxy" };
|
|
4833
|
+
this.flights.delete(proxyId);
|
|
4834
|
+
this.setFlightTombstone(flight, now);
|
|
4835
|
+
const targets = [];
|
|
4836
|
+
for (const externalRequestId of flight.requestIds) {
|
|
4837
|
+
const key = routeKey2(proxyId, externalRequestId);
|
|
4838
|
+
const route = this.routes.get(key);
|
|
4839
|
+
if (!route || route.state !== "pending" || route.proxyWs !== proxyWs || route.upstreamRequestId !== requestId) {
|
|
4840
|
+
continue;
|
|
4841
|
+
}
|
|
4842
|
+
this.routes.set(key, {
|
|
4843
|
+
state: "tombstone",
|
|
4844
|
+
proxyId,
|
|
4845
|
+
expiresAt: now + this.tombstoneTtlMs
|
|
4846
|
+
});
|
|
4847
|
+
targets.push({ clientWs: route.clientWs, requestId: externalRequestId });
|
|
4848
|
+
}
|
|
4849
|
+
return { kind: "matched", targets };
|
|
4850
|
+
}
|
|
4851
|
+
abandonSocket(clientWs) {
|
|
4852
|
+
const now = this.now();
|
|
4853
|
+
this.pruneExpired(now);
|
|
4854
|
+
for (const [key, route] of this.routes) {
|
|
4855
|
+
if (route.state === "pending" && route.clientWs === clientWs) {
|
|
4856
|
+
this.routes.set(key, {
|
|
4857
|
+
state: "tombstone",
|
|
4858
|
+
proxyId: route.proxyId,
|
|
4859
|
+
expiresAt: now + this.tombstoneTtlMs
|
|
4860
|
+
});
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
clearProxy(proxyId) {
|
|
4865
|
+
for (const [key, route] of this.routes) {
|
|
4866
|
+
if (route.proxyId === proxyId) this.routes.delete(key);
|
|
4867
|
+
}
|
|
4868
|
+
this.flights.delete(proxyId);
|
|
4869
|
+
for (const [key, tombstone] of this.flightTombstones) {
|
|
4870
|
+
if (tombstone.proxyId === proxyId) this.flightTombstones.delete(key);
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
createFlight(proxyId, proxyWs, now) {
|
|
4874
|
+
return {
|
|
4875
|
+
proxyId,
|
|
4876
|
+
proxyWs,
|
|
4877
|
+
upstreamRequestId: this.upstreamRequestIdFactory(),
|
|
4878
|
+
requestIds: /* @__PURE__ */ new Set(),
|
|
4879
|
+
joinUntil: now + this.joinWindowMs,
|
|
4880
|
+
expiresAt: now + this.pendingTtlMs
|
|
4881
|
+
};
|
|
4882
|
+
}
|
|
4883
|
+
migrateFlight(previous, replacement, now) {
|
|
4884
|
+
if (this.flights.get(previous.proxyId) === previous) this.flights.delete(previous.proxyId);
|
|
4885
|
+
this.setFlightTombstone(previous, now);
|
|
4886
|
+
for (const externalRequestId of previous.requestIds) {
|
|
4887
|
+
const key = routeKey2(previous.proxyId, externalRequestId);
|
|
4888
|
+
const route = this.routes.get(key);
|
|
4889
|
+
if (!route || route.state !== "pending" || route.proxyWs !== previous.proxyWs || route.upstreamRequestId !== previous.upstreamRequestId) {
|
|
4890
|
+
continue;
|
|
4891
|
+
}
|
|
4892
|
+
route.upstreamRequestId = replacement.upstreamRequestId;
|
|
4893
|
+
replacement.requestIds.add(externalRequestId);
|
|
4894
|
+
replacement.expiresAt = Math.max(replacement.expiresAt, route.expiresAt);
|
|
4895
|
+
}
|
|
4896
|
+
}
|
|
4897
|
+
expireFlight(flight, now) {
|
|
4898
|
+
if (this.flights.get(flight.proxyId) === flight) this.flights.delete(flight.proxyId);
|
|
4899
|
+
this.setFlightTombstone(flight, now);
|
|
4900
|
+
for (const externalRequestId of flight.requestIds) {
|
|
4901
|
+
const key = routeKey2(flight.proxyId, externalRequestId);
|
|
4902
|
+
const route = this.routes.get(key);
|
|
4903
|
+
if (route?.state === "pending" && route.upstreamRequestId === flight.upstreamRequestId) {
|
|
4904
|
+
this.routes.delete(key);
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
}
|
|
4908
|
+
setFlightTombstone(flight, now) {
|
|
4909
|
+
const key = routeKey2(flight.proxyId, flight.upstreamRequestId);
|
|
4910
|
+
if (!this.flightTombstones.has(key) && this.flightTombstones.size >= this.maxEntries) {
|
|
4911
|
+
const oldest = this.flightTombstones.keys().next().value;
|
|
4912
|
+
if (oldest !== void 0) this.flightTombstones.delete(oldest);
|
|
4913
|
+
}
|
|
4914
|
+
this.flightTombstones.set(key, {
|
|
4915
|
+
proxyId: flight.proxyId,
|
|
4916
|
+
expiresAt: now + this.tombstoneTtlMs
|
|
4917
|
+
});
|
|
4918
|
+
}
|
|
4919
|
+
evictOldestRouteTombstone() {
|
|
4920
|
+
let oldestTombstoneKey;
|
|
4921
|
+
let oldestTombstoneExpiry = Number.POSITIVE_INFINITY;
|
|
4922
|
+
for (const [candidateKey, route] of this.routes) {
|
|
4923
|
+
if (route.state === "tombstone" && route.expiresAt < oldestTombstoneExpiry) {
|
|
4924
|
+
oldestTombstoneKey = candidateKey;
|
|
4925
|
+
oldestTombstoneExpiry = route.expiresAt;
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
if (!oldestTombstoneKey) return false;
|
|
4929
|
+
this.routes.delete(oldestTombstoneKey);
|
|
4930
|
+
return true;
|
|
4931
|
+
}
|
|
4932
|
+
pruneExpired(now) {
|
|
4933
|
+
for (const flight of this.flights.values()) {
|
|
4934
|
+
if (flight.expiresAt <= now) this.expireFlight(flight, now);
|
|
4935
|
+
}
|
|
4936
|
+
for (const [key, route] of this.routes) {
|
|
4937
|
+
if (route.expiresAt <= now) this.routes.delete(key);
|
|
4938
|
+
}
|
|
4939
|
+
for (const [key, tombstone] of this.flightTombstones) {
|
|
4940
|
+
if (tombstone.expiresAt <= now) this.flightTombstones.delete(key);
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
};
|
|
4944
|
+
|
|
4611
4945
|
// src/server.ts
|
|
4612
4946
|
var MODULE_DIR = dirname3(fileURLToPath2(import.meta.url));
|
|
4613
4947
|
var PACKAGED_FONTS_DIR = resolve(MODULE_DIR, "../assets/fonts");
|
|
@@ -4653,6 +4987,7 @@ function createRelayServer(options) {
|
|
|
4653
4987
|
}
|
|
4654
4988
|
const registry = new RelayRegistry();
|
|
4655
4989
|
const ptySnapshotRoutes = new PtySnapshotRouteRegistry();
|
|
4990
|
+
const sessionHistoryRoutes = new SessionHistoryRouteRegistry();
|
|
4656
4991
|
const remoteFileBridge = new RemoteFileBridge({ registry, logger });
|
|
4657
4992
|
const voiceConfigStore = createVoiceConfigStore({
|
|
4658
4993
|
dataDir,
|
|
@@ -4796,7 +5131,15 @@ function createRelayServer(options) {
|
|
|
4796
5131
|
socket.destroy();
|
|
4797
5132
|
});
|
|
4798
5133
|
proxyWss.on("connection", (ws) => {
|
|
4799
|
-
handleProxyConnection(
|
|
5134
|
+
handleProxyConnection(
|
|
5135
|
+
ws,
|
|
5136
|
+
registry,
|
|
5137
|
+
logger,
|
|
5138
|
+
ptySnapshotRoutes,
|
|
5139
|
+
sessionHistoryRoutes,
|
|
5140
|
+
relayChaos,
|
|
5141
|
+
remoteFileBridge
|
|
5142
|
+
);
|
|
4800
5143
|
});
|
|
4801
5144
|
clientWss.on("connection", (ws, request) => {
|
|
4802
5145
|
handleClientConnection(
|
|
@@ -4804,6 +5147,7 @@ function createRelayServer(options) {
|
|
|
4804
5147
|
registry,
|
|
4805
5148
|
logger,
|
|
4806
5149
|
ptySnapshotRoutes,
|
|
5150
|
+
sessionHistoryRoutes,
|
|
4807
5151
|
relayChaos,
|
|
4808
5152
|
voiceConfigStore,
|
|
4809
5153
|
voiceProviders,
|
|
@@ -4899,4 +5243,4 @@ export {
|
|
|
4899
5243
|
parseRelayChaosFromEnv,
|
|
4900
5244
|
createRelayServer
|
|
4901
5245
|
};
|
|
4902
|
-
//# sourceMappingURL=chunk-
|
|
5246
|
+
//# sourceMappingURL=chunk-SDL4V2RE.js.map
|