@dev-anywhere/relay 0.4.64 → 0.4.65

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.
@@ -62,6 +62,7 @@ import { z as z4 } from "zod";
62
62
  var providerValues = ["claude", "codex"];
63
63
  var ptyOwnerValues = ["local-terminal", "proxy-hosted"];
64
64
  var sessionModeValues = ["pty", "json"];
65
+ var sessionKindValues = ["agent", "terminal"];
65
66
 
66
67
  // ../../packages/shared/dist/constants/pty.js
67
68
  var PtySemanticState = {
@@ -94,6 +95,7 @@ var agentStatusPhaseValues = [
94
95
  ];
95
96
  var SessionInfoSchema = z4.object({
96
97
  sessionId: IdSchema,
98
+ kind: z4.enum(sessionKindValues).optional(),
97
99
  name: z4.string().optional(),
98
100
  // cwd 只用于展示完整路径/tooltip,不作为前端路由或权限判断来源。
99
101
  cwd: z4.string().optional(),
@@ -505,6 +507,14 @@ var ProxyInfoSchema = z8.object({
505
507
  online: z8.boolean(),
506
508
  sessions: z8.array(z8.string()).optional()
507
509
  });
510
+ var RelayClientInfoSchema = z8.object({
511
+ clientId: IdSchema,
512
+ proxyId: IdSchema.optional(),
513
+ connectedAt: z8.number().int().nonnegative(),
514
+ current: z8.boolean().optional(),
515
+ userAgent: z8.string().optional(),
516
+ remoteAddress: z8.string().optional()
517
+ });
508
518
  var AgentCliAvailabilitySchema = z8.object({
509
519
  available: z8.boolean(),
510
520
  command: z8.string().optional(),
@@ -571,6 +581,21 @@ var relayControlDefinitions = [
571
581
  ...RequestIdShape,
572
582
  proxies: z8.array(ProxyInfoSchema)
573
583
  }),
584
+ control("relay_client_list_request", RequestIdShape),
585
+ control("relay_client_list_response", {
586
+ ...RequestIdShape,
587
+ clients: z8.array(RelayClientInfoSchema)
588
+ }),
589
+ control("relay_client_kick", { ...RequiredRequestIdShape, clientId: IdSchema }),
590
+ control("relay_client_kick_response", {
591
+ ...RequiredRequestIdShape,
592
+ clientId: IdSchema,
593
+ success: z8.boolean(),
594
+ ...RequestErrorShape
595
+ }),
596
+ control("relay_client_kicked", {
597
+ reason: z8.string().optional()
598
+ }),
574
599
  control("proxy_select", { ...RequestIdShape, proxyId: IdSchema }),
575
600
  control("proxy_select_response", {
576
601
  ...RequestIdShape,
@@ -810,9 +835,10 @@ var relayControlDefinitions = [
810
835
  // 远程创建 JSON 会话,client -> proxy -> response
811
836
  control("session_create", {
812
837
  ...RequestIdShape,
813
- cwd: z8.string(),
838
+ kind: z8.enum(sessionKindValues).optional(),
839
+ cwd: z8.string().optional(),
814
840
  name: z8.string().optional(),
815
- provider: z8.enum(providerValues),
841
+ provider: z8.enum(providerValues).optional(),
816
842
  mode: z8.enum(sessionModeValues).optional(),
817
843
  resumeSessionId: z8.string().optional(),
818
844
  // 透传给 claude CLI 的 --permission-mode, undefined 时 proxy 兜底为 "default"
@@ -824,6 +850,7 @@ var relayControlDefinitions = [
824
850
  sessionId: IdSchema.optional(),
825
851
  name: z8.string().optional(),
826
852
  nameLocked: z8.boolean().optional(),
853
+ kind: z8.enum(sessionKindValues).optional(),
827
854
  mode: z8.enum(sessionModeValues).optional(),
828
855
  provider: z8.enum(providerValues).optional(),
829
856
  ptyOwner: z8.enum(ptyOwnerValues).optional(),
@@ -902,6 +929,7 @@ var relayControlDefinitions = [
902
929
  control("session_sync", {
903
930
  sessions: z8.array(z8.object({
904
931
  id: z8.string(),
932
+ kind: z8.enum(sessionKindValues).optional(),
905
933
  mode: z8.enum(sessionModeValues),
906
934
  provider: z8.enum(providerValues),
907
935
  ptyOwner: z8.enum(ptyOwnerValues).optional(),
@@ -934,6 +962,11 @@ function isClientToProxyRelayControlType(type) {
934
962
  return ClientToProxyRelayControlTypes.has(type);
935
963
  }
936
964
 
965
+ // ../../packages/shared/dist/constants/relay-close-codes.js
966
+ var RelayCloseCode = {
967
+ CLIENT_KICKED: 4401
968
+ };
969
+
937
970
  // ../../packages/shared/dist/binary-frame.js
938
971
  var SID_LEN_BYTES = 1;
939
972
  var SEQ_BYTES = 4;
@@ -977,7 +1010,7 @@ var proxyConnectionFSM = defineFSM({
977
1010
  var RelayRegistry = class {
978
1011
  proxyStates = /* @__PURE__ */ new Map();
979
1012
  clientBindings = /* @__PURE__ */ new Map();
980
- connectedClients = /* @__PURE__ */ new Set();
1013
+ connectedClients = /* @__PURE__ */ new Map();
981
1014
  registerProxy(proxyId, ws, name) {
982
1015
  const existing = this.proxyStates.get(proxyId);
983
1016
  if (existing) {
@@ -1135,21 +1168,43 @@ var RelayRegistry = class {
1135
1168
  }
1136
1169
  return count;
1137
1170
  }
1138
- addClientWs(ws) {
1139
- this.connectedClients.add(ws);
1171
+ addClientWs(ws, metadata = {}) {
1172
+ this.connectedClients.set(ws, {
1173
+ connectedAt: metadata.connectedAt ?? Date.now(),
1174
+ ...metadata.clientId !== void 0 ? { clientId: metadata.clientId } : {},
1175
+ ...metadata.userAgent !== void 0 ? { userAgent: metadata.userAgent } : {},
1176
+ ...metadata.remoteAddress !== void 0 ? { remoteAddress: metadata.remoteAddress } : {}
1177
+ });
1178
+ }
1179
+ updateConnectedClientId(ws, clientId) {
1180
+ const metadata = this.connectedClients.get(ws);
1181
+ if (metadata) {
1182
+ metadata.clientId = clientId;
1183
+ return;
1184
+ }
1185
+ this.addClientWs(ws, { clientId });
1140
1186
  }
1141
1187
  removeClientWs(ws) {
1142
1188
  this.connectedClients.delete(ws);
1143
1189
  }
1144
1190
  getAllClientWs() {
1145
1191
  const clients = [];
1146
- for (const ws of this.connectedClients) {
1192
+ for (const ws of this.connectedClients.keys()) {
1147
1193
  if (ws.readyState === WebSocket.OPEN) {
1148
1194
  clients.push(ws);
1149
1195
  }
1150
1196
  }
1151
1197
  return clients;
1152
1198
  }
1199
+ getConnectedClientSockets(clientId) {
1200
+ const sockets = [];
1201
+ for (const [ws, metadata] of this.connectedClients) {
1202
+ if (metadata.clientId === clientId && ws.readyState === WebSocket.OPEN) {
1203
+ sockets.push(ws);
1204
+ }
1205
+ }
1206
+ return sockets;
1207
+ }
1153
1208
  // 获取单个 proxy 的详细状态信息
1154
1209
  getProxyDetail(proxyId) {
1155
1210
  const state = this.proxyStates.get(proxyId);
@@ -1176,6 +1231,22 @@ var RelayRegistry = class {
1176
1231
  }
1177
1232
  return details;
1178
1233
  }
1234
+ getConnectedClientDetails(currentClientId) {
1235
+ const details = [];
1236
+ for (const [ws, metadata] of this.connectedClients) {
1237
+ if (ws.readyState !== WebSocket.OPEN || !metadata.clientId) continue;
1238
+ const binding = this.clientBindings.get(metadata.clientId);
1239
+ details.push({
1240
+ clientId: metadata.clientId,
1241
+ ...binding?.proxyId !== void 0 ? { proxyId: binding.proxyId } : {},
1242
+ connectedAt: metadata.connectedAt,
1243
+ ...metadata.clientId === currentClientId ? { current: true } : {},
1244
+ ...metadata.userAgent !== void 0 ? { userAgent: metadata.userAgent } : {},
1245
+ ...metadata.remoteAddress !== void 0 ? { remoteAddress: metadata.remoteAddress } : {}
1246
+ });
1247
+ }
1248
+ return details;
1249
+ }
1179
1250
  };
1180
1251
 
1181
1252
  // src/health.ts
@@ -2335,6 +2406,7 @@ function handleVoiceConfigControl(msg, ws, store, logger, providers) {
2335
2406
  var MAX_JSON_MESSAGE_SIZE2 = 1 * 1024 * 1024;
2336
2407
  function handleClientRegister(clientId, clientWs, registry, logger) {
2337
2408
  clientWs.clientId = clientId;
2409
+ registry.updateConnectedClientId(clientWs, clientId);
2338
2410
  const binding = registry.getClientBinding(clientId);
2339
2411
  if (!binding) {
2340
2412
  clientWs.send(
@@ -2369,6 +2441,72 @@ function handleClientRegister(clientId, clientWs, registry, logger) {
2369
2441
  );
2370
2442
  logger.info({ clientId, proxyId, status: "restored" }, "Client registered");
2371
2443
  }
2444
+ function handleRelayClientListRequest(clientWs, registry, requestId) {
2445
+ clientWs.send(
2446
+ JSON.stringify({
2447
+ type: "relay_client_list_response",
2448
+ requestId,
2449
+ clients: registry.getConnectedClientDetails(clientWs.clientId)
2450
+ })
2451
+ );
2452
+ }
2453
+ function handleRelayClientKick(clientWs, registry, logger, requestId, targetClientId) {
2454
+ if (targetClientId === clientWs.clientId) {
2455
+ clientWs.send(
2456
+ JSON.stringify({
2457
+ type: "relay_client_kick_response",
2458
+ requestId,
2459
+ clientId: targetClientId,
2460
+ success: false,
2461
+ errorCode: ControlErrorCode.UNKNOWN,
2462
+ error: "\u4E0D\u80FD\u65AD\u5F00\u5F53\u524D\u5BA2\u6237\u7AEF"
2463
+ })
2464
+ );
2465
+ return;
2466
+ }
2467
+ const targets = registry.getConnectedClientSockets(targetClientId);
2468
+ if (targets.length === 0) {
2469
+ clientWs.send(
2470
+ JSON.stringify({
2471
+ type: "relay_client_kick_response",
2472
+ requestId,
2473
+ clientId: targetClientId,
2474
+ success: false,
2475
+ errorCode: ControlErrorCode.UNKNOWN,
2476
+ error: "\u5BA2\u6237\u7AEF\u4E0D\u5728\u7EBF"
2477
+ })
2478
+ );
2479
+ return;
2480
+ }
2481
+ const kickedMessage = JSON.stringify({
2482
+ type: "relay_client_kicked",
2483
+ reason: "\u7531\u5BA2\u6237\u7AEF\u7BA1\u7406\u65AD\u5F00"
2484
+ });
2485
+ for (const target of targets) {
2486
+ try {
2487
+ target.send(kickedMessage);
2488
+ target.close(RelayCloseCode.CLIENT_KICKED, "client kicked");
2489
+ } catch (err) {
2490
+ logger.warn({ err, clientId: targetClientId }, "Failed to close kicked client");
2491
+ target.terminate();
2492
+ } finally {
2493
+ registry.removeClientWs(target);
2494
+ registry.unbindClientById(targetClientId);
2495
+ }
2496
+ }
2497
+ clientWs.send(
2498
+ JSON.stringify({
2499
+ type: "relay_client_kick_response",
2500
+ requestId,
2501
+ clientId: targetClientId,
2502
+ success: true
2503
+ })
2504
+ );
2505
+ logger.info(
2506
+ { byClientId: clientWs.clientId, targetClientId, targetCount: targets.length },
2507
+ "Relay client kicked"
2508
+ );
2509
+ }
2372
2510
  function rejectNotBound(ws) {
2373
2511
  ws.send(
2374
2512
  JSON.stringify({
@@ -2405,10 +2543,10 @@ function sendRelayProxyProbeFailure(ws, requestId, error, chaos) {
2405
2543
  }
2406
2544
  ws.send(response);
2407
2545
  }
2408
- function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, voiceProviders) {
2546
+ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, voiceProviders, connectionInfo = {}) {
2409
2547
  const clientWs = ws;
2410
2548
  clientWs.isAlive = true;
2411
- registry.addClientWs(clientWs);
2549
+ registry.addClientWs(clientWs, connectionInfo);
2412
2550
  clientWs.on("pong", () => {
2413
2551
  clientWs.isAlive = true;
2414
2552
  });
@@ -2435,6 +2573,14 @@ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, v
2435
2573
  handleClientRegister(msg.clientId, clientWs, registry, logger);
2436
2574
  return;
2437
2575
  }
2576
+ if (msg.type === "relay_client_list_request") {
2577
+ handleRelayClientListRequest(clientWs, registry, msg.requestId);
2578
+ return;
2579
+ }
2580
+ if (msg.type === "relay_client_kick") {
2581
+ handleRelayClientKick(clientWs, registry, logger, msg.requestId, msg.clientId);
2582
+ return;
2583
+ }
2438
2584
  if (msg.type === "proxy_list_request") {
2439
2585
  const proxies = registry.listProxiesWithName().map((p) => ({
2440
2586
  ...p,
@@ -3152,6 +3298,15 @@ function createVoiceProviderRegistry(adapters) {
3152
3298
  // src/server.ts
3153
3299
  var MODULE_DIR = dirname3(fileURLToPath2(import.meta.url));
3154
3300
  var PACKAGED_FONTS_DIR = resolve(MODULE_DIR, "../assets/fonts");
3301
+ function firstHeaderValue(value) {
3302
+ if (Array.isArray(value)) return value[0];
3303
+ return value;
3304
+ }
3305
+ function requestRemoteAddress(request) {
3306
+ const forwardedFor = firstHeaderValue(request.headers["x-forwarded-for"]);
3307
+ const firstForwarded = forwardedFor?.split(",")[0]?.trim();
3308
+ return firstForwarded || request.socket.remoteAddress;
3309
+ }
3155
3310
  function createRelayServer(options) {
3156
3311
  const { heartbeatInterval = 3e4, logger, dataDir, proxyToken, clientToken, chaos } = options;
3157
3312
  const proxyTokenRequired = typeof proxyToken === "string" && proxyToken.length > 0;
@@ -3297,8 +3452,11 @@ function createRelayServer(options) {
3297
3452
  proxyWss.on("connection", (ws) => {
3298
3453
  handleProxyConnection(ws, registry, logger, relayChaos);
3299
3454
  });
3300
- clientWss.on("connection", (ws) => {
3301
- handleClientConnection(ws, registry, logger, relayChaos, voiceConfigStore, voiceProviders);
3455
+ clientWss.on("connection", (ws, request) => {
3456
+ handleClientConnection(ws, registry, logger, relayChaos, voiceConfigStore, voiceProviders, {
3457
+ userAgent: firstHeaderValue(request.headers["user-agent"]),
3458
+ remoteAddress: requestRemoteAddress(request)
3459
+ });
3302
3460
  });
3303
3461
  voiceAsrWss.on("connection", (ws) => {
3304
3462
  handleVoiceAsrConnection(ws, voiceConfigStore, logger, voiceProviders);
@@ -3368,4 +3526,4 @@ export {
3368
3526
  parseRelayChaosFromEnv,
3369
3527
  createRelayServer
3370
3528
  };
3371
- //# sourceMappingURL=chunk-PM4ZJUGS.js.map
3529
+ //# sourceMappingURL=chunk-H7OL7UK7.js.map