@dev-anywhere/relay 0.4.63 → 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(),
@@ -129,6 +131,9 @@ var SessionStatusPayloadSchema = z4.object({
129
131
  });
130
132
  var PtyStatePayloadSchema = z4.object({
131
133
  state: z4.enum(ptySemanticStateValues),
134
+ // Semantic event sequence. Present for runtime PTY semantic events; optional for
135
+ // legacy cleanup messages from older proxies.
136
+ seq: z4.number().int().nonnegative().optional(),
132
137
  title: z4.string().optional(),
133
138
  tool: z4.string().optional()
134
139
  });
@@ -502,6 +507,14 @@ var ProxyInfoSchema = z8.object({
502
507
  online: z8.boolean(),
503
508
  sessions: z8.array(z8.string()).optional()
504
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
+ });
505
518
  var AgentCliAvailabilitySchema = z8.object({
506
519
  available: z8.boolean(),
507
520
  command: z8.string().optional(),
@@ -568,6 +581,21 @@ var relayControlDefinitions = [
568
581
  ...RequestIdShape,
569
582
  proxies: z8.array(ProxyInfoSchema)
570
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
+ }),
571
599
  control("proxy_select", { ...RequestIdShape, proxyId: IdSchema }),
572
600
  control("proxy_select_response", {
573
601
  ...RequestIdShape,
@@ -807,9 +835,10 @@ var relayControlDefinitions = [
807
835
  // 远程创建 JSON 会话,client -> proxy -> response
808
836
  control("session_create", {
809
837
  ...RequestIdShape,
810
- cwd: z8.string(),
838
+ kind: z8.enum(sessionKindValues).optional(),
839
+ cwd: z8.string().optional(),
811
840
  name: z8.string().optional(),
812
- provider: z8.enum(providerValues),
841
+ provider: z8.enum(providerValues).optional(),
813
842
  mode: z8.enum(sessionModeValues).optional(),
814
843
  resumeSessionId: z8.string().optional(),
815
844
  // 透传给 claude CLI 的 --permission-mode, undefined 时 proxy 兜底为 "default"
@@ -821,6 +850,7 @@ var relayControlDefinitions = [
821
850
  sessionId: IdSchema.optional(),
822
851
  name: z8.string().optional(),
823
852
  nameLocked: z8.boolean().optional(),
853
+ kind: z8.enum(sessionKindValues).optional(),
824
854
  mode: z8.enum(sessionModeValues).optional(),
825
855
  provider: z8.enum(providerValues).optional(),
826
856
  ptyOwner: z8.enum(ptyOwnerValues).optional(),
@@ -899,6 +929,7 @@ var relayControlDefinitions = [
899
929
  control("session_sync", {
900
930
  sessions: z8.array(z8.object({
901
931
  id: z8.string(),
932
+ kind: z8.enum(sessionKindValues).optional(),
902
933
  mode: z8.enum(sessionModeValues),
903
934
  provider: z8.enum(providerValues),
904
935
  ptyOwner: z8.enum(ptyOwnerValues).optional(),
@@ -931,6 +962,11 @@ function isClientToProxyRelayControlType(type) {
931
962
  return ClientToProxyRelayControlTypes.has(type);
932
963
  }
933
964
 
965
+ // ../../packages/shared/dist/constants/relay-close-codes.js
966
+ var RelayCloseCode = {
967
+ CLIENT_KICKED: 4401
968
+ };
969
+
934
970
  // ../../packages/shared/dist/binary-frame.js
935
971
  var SID_LEN_BYTES = 1;
936
972
  var SEQ_BYTES = 4;
@@ -974,7 +1010,7 @@ var proxyConnectionFSM = defineFSM({
974
1010
  var RelayRegistry = class {
975
1011
  proxyStates = /* @__PURE__ */ new Map();
976
1012
  clientBindings = /* @__PURE__ */ new Map();
977
- connectedClients = /* @__PURE__ */ new Set();
1013
+ connectedClients = /* @__PURE__ */ new Map();
978
1014
  registerProxy(proxyId, ws, name) {
979
1015
  const existing = this.proxyStates.get(proxyId);
980
1016
  if (existing) {
@@ -1132,21 +1168,43 @@ var RelayRegistry = class {
1132
1168
  }
1133
1169
  return count;
1134
1170
  }
1135
- addClientWs(ws) {
1136
- 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 });
1137
1186
  }
1138
1187
  removeClientWs(ws) {
1139
1188
  this.connectedClients.delete(ws);
1140
1189
  }
1141
1190
  getAllClientWs() {
1142
1191
  const clients = [];
1143
- for (const ws of this.connectedClients) {
1192
+ for (const ws of this.connectedClients.keys()) {
1144
1193
  if (ws.readyState === WebSocket.OPEN) {
1145
1194
  clients.push(ws);
1146
1195
  }
1147
1196
  }
1148
1197
  return clients;
1149
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
+ }
1150
1208
  // 获取单个 proxy 的详细状态信息
1151
1209
  getProxyDetail(proxyId) {
1152
1210
  const state = this.proxyStates.get(proxyId);
@@ -1173,6 +1231,22 @@ var RelayRegistry = class {
1173
1231
  }
1174
1232
  return details;
1175
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
+ }
1176
1250
  };
1177
1251
 
1178
1252
  // src/health.ts
@@ -2332,6 +2406,7 @@ function handleVoiceConfigControl(msg, ws, store, logger, providers) {
2332
2406
  var MAX_JSON_MESSAGE_SIZE2 = 1 * 1024 * 1024;
2333
2407
  function handleClientRegister(clientId, clientWs, registry, logger) {
2334
2408
  clientWs.clientId = clientId;
2409
+ registry.updateConnectedClientId(clientWs, clientId);
2335
2410
  const binding = registry.getClientBinding(clientId);
2336
2411
  if (!binding) {
2337
2412
  clientWs.send(
@@ -2366,6 +2441,72 @@ function handleClientRegister(clientId, clientWs, registry, logger) {
2366
2441
  );
2367
2442
  logger.info({ clientId, proxyId, status: "restored" }, "Client registered");
2368
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
+ }
2369
2510
  function rejectNotBound(ws) {
2370
2511
  ws.send(
2371
2512
  JSON.stringify({
@@ -2402,10 +2543,10 @@ function sendRelayProxyProbeFailure(ws, requestId, error, chaos) {
2402
2543
  }
2403
2544
  ws.send(response);
2404
2545
  }
2405
- function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, voiceProviders) {
2546
+ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, voiceProviders, connectionInfo = {}) {
2406
2547
  const clientWs = ws;
2407
2548
  clientWs.isAlive = true;
2408
- registry.addClientWs(clientWs);
2549
+ registry.addClientWs(clientWs, connectionInfo);
2409
2550
  clientWs.on("pong", () => {
2410
2551
  clientWs.isAlive = true;
2411
2552
  });
@@ -2432,6 +2573,14 @@ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, v
2432
2573
  handleClientRegister(msg.clientId, clientWs, registry, logger);
2433
2574
  return;
2434
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
+ }
2435
2584
  if (msg.type === "proxy_list_request") {
2436
2585
  const proxies = registry.listProxiesWithName().map((p) => ({
2437
2586
  ...p,
@@ -3149,6 +3298,15 @@ function createVoiceProviderRegistry(adapters) {
3149
3298
  // src/server.ts
3150
3299
  var MODULE_DIR = dirname3(fileURLToPath2(import.meta.url));
3151
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
+ }
3152
3310
  function createRelayServer(options) {
3153
3311
  const { heartbeatInterval = 3e4, logger, dataDir, proxyToken, clientToken, chaos } = options;
3154
3312
  const proxyTokenRequired = typeof proxyToken === "string" && proxyToken.length > 0;
@@ -3294,8 +3452,11 @@ function createRelayServer(options) {
3294
3452
  proxyWss.on("connection", (ws) => {
3295
3453
  handleProxyConnection(ws, registry, logger, relayChaos);
3296
3454
  });
3297
- clientWss.on("connection", (ws) => {
3298
- 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
+ });
3299
3460
  });
3300
3461
  voiceAsrWss.on("connection", (ws) => {
3301
3462
  handleVoiceAsrConnection(ws, voiceConfigStore, logger, voiceProviders);
@@ -3365,4 +3526,4 @@ export {
3365
3526
  parseRelayChaosFromEnv,
3366
3527
  createRelayServer
3367
3528
  };
3368
- //# sourceMappingURL=chunk-4V3WSB2Y.js.map
3529
+ //# sourceMappingURL=chunk-H7OL7UK7.js.map