@dev-anywhere/relay 0.5.47 → 0.6.1

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.
@@ -44,8 +44,8 @@
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-BCWFUjda.js"></script>
48
- <link rel="stylesheet" crossorigin href="/assets/index-mGSY150B.css">
47
+ <script type="module" crossorigin src="/assets/index-CXT4gEaR.js"></script>
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">
51
51
  <div id="root"></div>
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 f=e=>i(e,t),c={module:{uri:t},exports:o,require:f};s[t]=Promise.all(n.map(e=>c[e]||f(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:"8e2c319f8c178f713b5779fde18a041d"},{url:"assets/index-mGSY150B.css",revision:null},{url:"assets/index-BCWFUjda.js",revision:null},{url:"assets/fvad-DYxbW2my.wasm",revision:null},{url:"assets/fvad-B51vkASP.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")});
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:"aa5f2f91104adcc66cc14f85c755800f"},{url:"assets/index-Dmm_ocq_.css",revision:null},{url:"assets/index-CXT4gEaR.js",revision:null},{url:"assets/fvad-mBAhtF0y.js",revision:null},{url:"assets/fvad-DYxbW2my.wasm",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")});
@@ -888,8 +888,13 @@ var relayControlDefinitions = [
888
888
  sessionId: IdSchema.optional()
889
889
  }, "client_to_proxy"),
890
890
  // 会话历史浏览
891
- control("session_history_request", RequestIdShape, "client_to_proxy"),
892
- control("session_history_response", { ...RequestIdShape, sessions: z9.array(HistorySessionSchema) }, "proxy_to_client"),
891
+ control("session_history_request", RequiredRequestIdShape, "client_to_proxy"),
892
+ control("session_history_response", {
893
+ ...RequiredRequestIdShape,
894
+ ...RequestErrorShape,
895
+ success: z9.boolean(),
896
+ sessions: z9.array(HistorySessionSchema)
897
+ }, "proxy_to_client"),
893
898
  // PTY 语义状态,从 Envelope 迁移到 Control 层
894
899
  control("pty_state", { sessionId: IdSchema, payload: PtyStatePayloadSchema }, "proxy_to_client"),
895
900
  // Provider 语义状态,来自 Claude/Codex hook 等结构化事件,不从 PTY 字节推断
@@ -1776,7 +1781,7 @@ function rejectNotRegistered(ws) {
1776
1781
  })
1777
1782
  );
1778
1783
  }
1779
- function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, chaos, remoteFileBridge) {
1784
+ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, sessionHistoryRoutes, chaos, remoteFileBridge) {
1780
1785
  const proxyWs = ws;
1781
1786
  proxyWs.isAlive = true;
1782
1787
  proxyWs.on("pong", () => {
@@ -1824,7 +1829,10 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, chaos, r
1824
1829
  const { proxyId, name } = result.message;
1825
1830
  const status = registry.registerProxy(proxyId, proxyWs, name);
1826
1831
  proxyWs.proxyId = proxyId;
1827
- if (status === "reconnected") ptySnapshotRoutes.clearProxy(proxyId);
1832
+ if (status === "reconnected") {
1833
+ ptySnapshotRoutes.clearProxy(proxyId);
1834
+ sessionHistoryRoutes.clearProxy(proxyId);
1835
+ }
1828
1836
  logger.info({ proxyId, status }, "Proxy registered");
1829
1837
  proxyWs.send(
1830
1838
  serializeControl({
@@ -1841,6 +1849,7 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, chaos, r
1841
1849
  if (result.kind === "control" && result.message.type === "proxy_disconnect") {
1842
1850
  if (proxyWs.proxyId) {
1843
1851
  ptySnapshotRoutes.clearProxy(proxyWs.proxyId);
1852
+ sessionHistoryRoutes.clearProxy(proxyWs.proxyId);
1844
1853
  notifyClientsProxyOffline(proxyWs.proxyId, registry, logger, chaos);
1845
1854
  registry.unregisterProxy(proxyWs.proxyId);
1846
1855
  logger.info(
@@ -1926,6 +1935,56 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, chaos, r
1926
1935
  );
1927
1936
  return;
1928
1937
  }
1938
+ if (result.message.type === "session_history_response") {
1939
+ if (!proxyWs.proxyId) {
1940
+ rejectNotRegistered(proxyWs);
1941
+ return;
1942
+ }
1943
+ const route = sessionHistoryRoutes.resolve(
1944
+ proxyWs.proxyId,
1945
+ result.message.requestId,
1946
+ proxyWs
1947
+ );
1948
+ if (route.kind === "matched") {
1949
+ let delivered = 0;
1950
+ for (const target of route.targets) {
1951
+ if (target.clientWs.readyState !== WebSocket4.OPEN) continue;
1952
+ const response = serializeControl({
1953
+ ...result.message,
1954
+ requestId: target.requestId
1955
+ });
1956
+ if (chaos) {
1957
+ chaos.send(target.clientWs, response, {
1958
+ direction: "proxy_to_client",
1959
+ type: result.message.type
1960
+ });
1961
+ } else {
1962
+ target.clientWs.send(response);
1963
+ }
1964
+ delivered += 1;
1965
+ }
1966
+ logger.debug(
1967
+ {
1968
+ proxyId: proxyWs.proxyId,
1969
+ upstreamRequestId: result.message.requestId,
1970
+ waiterCount: route.targets.length,
1971
+ delivered,
1972
+ success: result.message.success
1973
+ },
1974
+ "Session history response fanned out"
1975
+ );
1976
+ return;
1977
+ }
1978
+ logger.debug(
1979
+ {
1980
+ proxyId: proxyWs.proxyId,
1981
+ requestId: result.message.requestId,
1982
+ route: route.kind
1983
+ },
1984
+ "Unmatched session history response dropped"
1985
+ );
1986
+ return;
1987
+ }
1929
1988
  if (isProxyToClientRelayControlType(result.message.type)) {
1930
1989
  if (!proxyWs.proxyId) {
1931
1990
  rejectNotRegistered(proxyWs);
@@ -1985,6 +2044,7 @@ function handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, chaos, r
1985
2044
  return;
1986
2045
  }
1987
2046
  ptySnapshotRoutes.clearProxy(proxyWs.proxyId);
2047
+ sessionHistoryRoutes.clearProxy(proxyWs.proxyId);
1988
2048
  notifyClientsProxyOffline(proxyWs.proxyId, registry, logger, chaos);
1989
2049
  try {
1990
2050
  registry.transitionProxy(proxyWs.proxyId, "online", "offline");
@@ -2905,7 +2965,7 @@ function sendRemoteFileUploadUrlFailure(ws, requestId, sessionId, error, errorCo
2905
2965
  })
2906
2966
  );
2907
2967
  }
2908
- function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, chaos, voiceConfigStore, voiceProviders, remoteFileBridge, connectionInfo = {}) {
2968
+ function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, sessionHistoryRoutes, chaos, voiceConfigStore, voiceProviders, remoteFileBridge, connectionInfo = {}) {
2909
2969
  const clientWs = ws;
2910
2970
  clientWs.isAlive = true;
2911
2971
  registry.addClientWs(clientWs, connectionInfo);
@@ -3145,6 +3205,60 @@ function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, chaos,
3145
3205
  }
3146
3206
  const proxyWs = registry.getProxy(targetProxyId);
3147
3207
  if (proxyWs && proxyWs.readyState === WebSocket7.OPEN) {
3208
+ if (msg.type === "session_history_request") {
3209
+ const registration = sessionHistoryRoutes.register(
3210
+ targetProxyId,
3211
+ msg.requestId,
3212
+ clientWs,
3213
+ proxyWs
3214
+ );
3215
+ if (registration.kind === "duplicate") {
3216
+ logger.debug(
3217
+ { proxyId: targetProxyId, requestId: msg.requestId },
3218
+ "Duplicate session history request suppressed"
3219
+ );
3220
+ return;
3221
+ }
3222
+ if (registration.kind === "joined") {
3223
+ logger.debug(
3224
+ {
3225
+ proxyId: targetProxyId,
3226
+ requestId: msg.requestId,
3227
+ upstreamRequestId: registration.upstreamRequestId
3228
+ },
3229
+ "Session history request joined active upstream flight"
3230
+ );
3231
+ return;
3232
+ }
3233
+ if (registration.kind !== "leader") {
3234
+ logger.warn(
3235
+ { proxyId: targetProxyId, requestId: msg.requestId, registration },
3236
+ "Session history response route rejected"
3237
+ );
3238
+ clientWs.send(
3239
+ JSON.stringify({
3240
+ type: "relay_error",
3241
+ requestId: msg.requestId,
3242
+ code: RelayErrorCode.INVALID_MESSAGE,
3243
+ message: registration.kind === "collision" ? "Session history requestId is already in use" : "Too many pending session history requests"
3244
+ })
3245
+ );
3246
+ return;
3247
+ }
3248
+ const upstreamRequest = serializeControl({
3249
+ type: "session_history_request",
3250
+ requestId: registration.upstreamRequestId
3251
+ });
3252
+ if (chaos) {
3253
+ chaos.send(proxyWs, upstreamRequest, {
3254
+ direction: "client_to_proxy",
3255
+ type: msg.type
3256
+ });
3257
+ } else {
3258
+ proxyWs.send(upstreamRequest);
3259
+ }
3260
+ return;
3261
+ }
3148
3262
  if (msg.type === "session_subscribe") {
3149
3263
  const registration = ptySnapshotRoutes.register(
3150
3264
  targetProxyId,
@@ -3264,6 +3378,7 @@ function handleClientConnection(ws, registry, logger, ptySnapshotRoutes, chaos,
3264
3378
  });
3265
3379
  clientWs.on("close", (code, reason) => {
3266
3380
  ptySnapshotRoutes.abandonSocket(clientWs);
3381
+ sessionHistoryRoutes.abandonSocket(clientWs);
3267
3382
  registry.removeClientWs(clientWs);
3268
3383
  if (clientWs.clientId) {
3269
3384
  registry.unbindClientSocket(clientWs.clientId, clientWs);
@@ -4603,6 +4718,217 @@ var PtySnapshotRouteRegistry = class {
4603
4718
  }
4604
4719
  };
4605
4720
 
4721
+ // src/session-history-route-registry.ts
4722
+ import { nanoid as nanoid4 } from "nanoid";
4723
+ var DEFAULT_MAX_ENTRIES2 = 4096;
4724
+ var DEFAULT_MAX_PENDING_PER_CLIENT2 = 64;
4725
+ var DEFAULT_PENDING_TTL_MS2 = 2e4;
4726
+ var DEFAULT_JOIN_WINDOW_MS = 8e3;
4727
+ var DEFAULT_TOMBSTONE_TTL_MS2 = 6e4;
4728
+ function routeKey2(proxyId, requestId) {
4729
+ return JSON.stringify([proxyId, requestId]);
4730
+ }
4731
+ var SessionHistoryRouteRegistry = class {
4732
+ routes = /* @__PURE__ */ new Map();
4733
+ flights = /* @__PURE__ */ new Map();
4734
+ flightTombstones = /* @__PURE__ */ new Map();
4735
+ maxEntries;
4736
+ maxPendingPerClient;
4737
+ pendingTtlMs;
4738
+ joinWindowMs;
4739
+ tombstoneTtlMs;
4740
+ now;
4741
+ upstreamRequestIdFactory;
4742
+ upstreamRequestSequence = 0;
4743
+ constructor(options = {}) {
4744
+ this.maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_MAX_ENTRIES2);
4745
+ this.maxPendingPerClient = Math.max(
4746
+ 1,
4747
+ options.maxPendingPerClient ?? DEFAULT_MAX_PENDING_PER_CLIENT2
4748
+ );
4749
+ this.pendingTtlMs = Math.max(1, options.pendingTtlMs ?? DEFAULT_PENDING_TTL_MS2);
4750
+ this.joinWindowMs = Math.max(
4751
+ 1,
4752
+ Math.min(options.joinWindowMs ?? DEFAULT_JOIN_WINDOW_MS, this.pendingTtlMs)
4753
+ );
4754
+ this.tombstoneTtlMs = Math.max(1, options.tombstoneTtlMs ?? DEFAULT_TOMBSTONE_TTL_MS2);
4755
+ this.now = options.now ?? Date.now;
4756
+ this.upstreamRequestIdFactory = options.upstreamRequestIdFactory ?? (() => {
4757
+ this.upstreamRequestSequence += 1;
4758
+ return `relay-history-${this.upstreamRequestSequence.toString(36)}-${nanoid4(12)}`;
4759
+ });
4760
+ }
4761
+ register(proxyId, requestId, clientWs, proxyWs) {
4762
+ const now = this.now();
4763
+ this.pruneExpired(now);
4764
+ const key = routeKey2(proxyId, requestId);
4765
+ const existing = this.routes.get(key);
4766
+ if (existing) {
4767
+ if (existing.state === "pending" && existing.clientWs === clientWs && existing.proxyWs === proxyWs) {
4768
+ return { kind: "duplicate" };
4769
+ }
4770
+ return { kind: "collision" };
4771
+ }
4772
+ let clientPendingCount = 0;
4773
+ for (const route of this.routes.values()) {
4774
+ if (route.state === "pending" && route.clientWs === clientWs) {
4775
+ clientPendingCount += 1;
4776
+ if (clientPendingCount >= this.maxPendingPerClient) {
4777
+ return { kind: "client_capacity_exceeded" };
4778
+ }
4779
+ }
4780
+ }
4781
+ if (this.routes.size >= this.maxEntries && !this.evictOldestRouteTombstone()) {
4782
+ return { kind: "capacity_exceeded" };
4783
+ }
4784
+ let flight = this.flights.get(proxyId);
4785
+ if (flight && flight.proxyWs !== proxyWs) {
4786
+ this.expireFlight(flight, now);
4787
+ flight = void 0;
4788
+ }
4789
+ let kind;
4790
+ if (!flight || flight.joinUntil <= now) {
4791
+ const replacement = this.createFlight(proxyId, proxyWs, now);
4792
+ if (flight) this.migrateFlight(flight, replacement, now);
4793
+ this.flights.set(proxyId, replacement);
4794
+ flight = replacement;
4795
+ kind = "leader";
4796
+ } else {
4797
+ kind = "joined";
4798
+ }
4799
+ const routeExpiresAt = now + this.pendingTtlMs;
4800
+ flight.requestIds.add(requestId);
4801
+ flight.expiresAt = Math.max(flight.expiresAt, routeExpiresAt);
4802
+ this.routes.set(key, {
4803
+ state: "pending",
4804
+ proxyId,
4805
+ proxyWs,
4806
+ clientWs,
4807
+ upstreamRequestId: flight.upstreamRequestId,
4808
+ expiresAt: routeExpiresAt
4809
+ });
4810
+ return { kind, upstreamRequestId: flight.upstreamRequestId };
4811
+ }
4812
+ resolve(proxyId, requestId, proxyWs) {
4813
+ const now = this.now();
4814
+ this.pruneExpired(now);
4815
+ const flight = this.flights.get(proxyId);
4816
+ if (!flight || flight.upstreamRequestId !== requestId) {
4817
+ return this.flightTombstones.has(routeKey2(proxyId, requestId)) ? { kind: "tombstone" } : { kind: "unmatched" };
4818
+ }
4819
+ if (flight.proxyWs !== proxyWs) return { kind: "stale_proxy" };
4820
+ this.flights.delete(proxyId);
4821
+ this.setFlightTombstone(flight, now);
4822
+ const targets = [];
4823
+ for (const externalRequestId of flight.requestIds) {
4824
+ const key = routeKey2(proxyId, externalRequestId);
4825
+ const route = this.routes.get(key);
4826
+ if (!route || route.state !== "pending" || route.proxyWs !== proxyWs || route.upstreamRequestId !== requestId) {
4827
+ continue;
4828
+ }
4829
+ this.routes.set(key, {
4830
+ state: "tombstone",
4831
+ proxyId,
4832
+ expiresAt: now + this.tombstoneTtlMs
4833
+ });
4834
+ targets.push({ clientWs: route.clientWs, requestId: externalRequestId });
4835
+ }
4836
+ return { kind: "matched", targets };
4837
+ }
4838
+ abandonSocket(clientWs) {
4839
+ const now = this.now();
4840
+ this.pruneExpired(now);
4841
+ for (const [key, route] of this.routes) {
4842
+ if (route.state === "pending" && route.clientWs === clientWs) {
4843
+ this.routes.set(key, {
4844
+ state: "tombstone",
4845
+ proxyId: route.proxyId,
4846
+ expiresAt: now + this.tombstoneTtlMs
4847
+ });
4848
+ }
4849
+ }
4850
+ }
4851
+ clearProxy(proxyId) {
4852
+ for (const [key, route] of this.routes) {
4853
+ if (route.proxyId === proxyId) this.routes.delete(key);
4854
+ }
4855
+ this.flights.delete(proxyId);
4856
+ for (const [key, tombstone] of this.flightTombstones) {
4857
+ if (tombstone.proxyId === proxyId) this.flightTombstones.delete(key);
4858
+ }
4859
+ }
4860
+ createFlight(proxyId, proxyWs, now) {
4861
+ return {
4862
+ proxyId,
4863
+ proxyWs,
4864
+ upstreamRequestId: this.upstreamRequestIdFactory(),
4865
+ requestIds: /* @__PURE__ */ new Set(),
4866
+ joinUntil: now + this.joinWindowMs,
4867
+ expiresAt: now + this.pendingTtlMs
4868
+ };
4869
+ }
4870
+ migrateFlight(previous, replacement, now) {
4871
+ if (this.flights.get(previous.proxyId) === previous) this.flights.delete(previous.proxyId);
4872
+ this.setFlightTombstone(previous, now);
4873
+ for (const externalRequestId of previous.requestIds) {
4874
+ const key = routeKey2(previous.proxyId, externalRequestId);
4875
+ const route = this.routes.get(key);
4876
+ if (!route || route.state !== "pending" || route.proxyWs !== previous.proxyWs || route.upstreamRequestId !== previous.upstreamRequestId) {
4877
+ continue;
4878
+ }
4879
+ route.upstreamRequestId = replacement.upstreamRequestId;
4880
+ replacement.requestIds.add(externalRequestId);
4881
+ replacement.expiresAt = Math.max(replacement.expiresAt, route.expiresAt);
4882
+ }
4883
+ }
4884
+ expireFlight(flight, now) {
4885
+ if (this.flights.get(flight.proxyId) === flight) this.flights.delete(flight.proxyId);
4886
+ this.setFlightTombstone(flight, now);
4887
+ for (const externalRequestId of flight.requestIds) {
4888
+ const key = routeKey2(flight.proxyId, externalRequestId);
4889
+ const route = this.routes.get(key);
4890
+ if (route?.state === "pending" && route.upstreamRequestId === flight.upstreamRequestId) {
4891
+ this.routes.delete(key);
4892
+ }
4893
+ }
4894
+ }
4895
+ setFlightTombstone(flight, now) {
4896
+ const key = routeKey2(flight.proxyId, flight.upstreamRequestId);
4897
+ if (!this.flightTombstones.has(key) && this.flightTombstones.size >= this.maxEntries) {
4898
+ const oldest = this.flightTombstones.keys().next().value;
4899
+ if (oldest !== void 0) this.flightTombstones.delete(oldest);
4900
+ }
4901
+ this.flightTombstones.set(key, {
4902
+ proxyId: flight.proxyId,
4903
+ expiresAt: now + this.tombstoneTtlMs
4904
+ });
4905
+ }
4906
+ evictOldestRouteTombstone() {
4907
+ let oldestTombstoneKey;
4908
+ let oldestTombstoneExpiry = Number.POSITIVE_INFINITY;
4909
+ for (const [candidateKey, route] of this.routes) {
4910
+ if (route.state === "tombstone" && route.expiresAt < oldestTombstoneExpiry) {
4911
+ oldestTombstoneKey = candidateKey;
4912
+ oldestTombstoneExpiry = route.expiresAt;
4913
+ }
4914
+ }
4915
+ if (!oldestTombstoneKey) return false;
4916
+ this.routes.delete(oldestTombstoneKey);
4917
+ return true;
4918
+ }
4919
+ pruneExpired(now) {
4920
+ for (const flight of this.flights.values()) {
4921
+ if (flight.expiresAt <= now) this.expireFlight(flight, now);
4922
+ }
4923
+ for (const [key, route] of this.routes) {
4924
+ if (route.expiresAt <= now) this.routes.delete(key);
4925
+ }
4926
+ for (const [key, tombstone] of this.flightTombstones) {
4927
+ if (tombstone.expiresAt <= now) this.flightTombstones.delete(key);
4928
+ }
4929
+ }
4930
+ };
4931
+
4606
4932
  // src/server.ts
4607
4933
  var MODULE_DIR = dirname3(fileURLToPath2(import.meta.url));
4608
4934
  var PACKAGED_FONTS_DIR = resolve(MODULE_DIR, "../assets/fonts");
@@ -4648,6 +4974,7 @@ function createRelayServer(options) {
4648
4974
  }
4649
4975
  const registry = new RelayRegistry();
4650
4976
  const ptySnapshotRoutes = new PtySnapshotRouteRegistry();
4977
+ const sessionHistoryRoutes = new SessionHistoryRouteRegistry();
4651
4978
  const remoteFileBridge = new RemoteFileBridge({ registry, logger });
4652
4979
  const voiceConfigStore = createVoiceConfigStore({
4653
4980
  dataDir,
@@ -4791,7 +5118,15 @@ function createRelayServer(options) {
4791
5118
  socket.destroy();
4792
5119
  });
4793
5120
  proxyWss.on("connection", (ws) => {
4794
- handleProxyConnection(ws, registry, logger, ptySnapshotRoutes, relayChaos, remoteFileBridge);
5121
+ handleProxyConnection(
5122
+ ws,
5123
+ registry,
5124
+ logger,
5125
+ ptySnapshotRoutes,
5126
+ sessionHistoryRoutes,
5127
+ relayChaos,
5128
+ remoteFileBridge
5129
+ );
4795
5130
  });
4796
5131
  clientWss.on("connection", (ws, request) => {
4797
5132
  handleClientConnection(
@@ -4799,6 +5134,7 @@ function createRelayServer(options) {
4799
5134
  registry,
4800
5135
  logger,
4801
5136
  ptySnapshotRoutes,
5137
+ sessionHistoryRoutes,
4802
5138
  relayChaos,
4803
5139
  voiceConfigStore,
4804
5140
  voiceProviders,
@@ -4894,4 +5230,4 @@ export {
4894
5230
  parseRelayChaosFromEnv,
4895
5231
  createRelayServer
4896
5232
  };
4897
- //# sourceMappingURL=chunk-4Q4EAZP6.js.map
5233
+ //# sourceMappingURL=chunk-TWQN6HNC.js.map