@linkshell/gateway 0.2.48 → 0.3.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/src/relay.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  } from "@linkshell/protocol";
9
9
  import type { Envelope, ProtocolMessageType } from "@linkshell/protocol";
10
10
  import { ZodError } from "zod";
11
- import type { SessionManager, ConnectedDevice } from "./sessions.js";
11
+ import type { DeviceManager, ConnectedDevice } from "./sessions.js";
12
12
  import {
13
13
  handleTunnelResponse,
14
14
  handleTunnelWsData,
@@ -22,7 +22,7 @@ export function handleSocketMessage(
22
22
  role: "host" | "client",
23
23
  hostDeviceId: string,
24
24
  deviceId: string,
25
- sessions: SessionManager,
25
+ sessions: DeviceManager,
26
26
  ): void {
27
27
  let envelope: Envelope;
28
28
  try {
@@ -99,14 +99,13 @@ function sendSessionError(
99
99
 
100
100
  function handleHostMessage(
101
101
  envelope: Envelope,
102
- session: ReturnType<SessionManager["get"]> & {},
103
- sessions: SessionManager,
102
+ session: ReturnType<DeviceManager["get"]> & {},
103
+ sessions: DeviceManager,
104
104
  ): void {
105
105
  switch (envelope.type) {
106
- case "device.connect":
107
- case "session.connect": {
106
+ case "device.connect": {
108
107
  // Extract metadata from host's connect message
109
- const p = parseTypedPayload("session.connect", envelope.payload);
108
+ const p = parseTypedPayload("device.connect", envelope.payload);
110
109
  if (p.machineId || p.hostname || p.platform || p.cwd || p.capabilities) {
111
110
  sessions.setMetadata(
112
111
  session.id,
@@ -132,12 +131,11 @@ function handleHostMessage(
132
131
  break;
133
132
  }
134
133
  case "device.heartbeat":
135
- case "session.heartbeat":
136
134
  break;
137
135
  case "permission.decision.result": {
138
136
  const p = parseTypedPayload("permission.decision.result", envelope.payload);
139
137
  resolveAgentPermissionHttpAck({
140
- sessionId: session.id,
138
+ hostDeviceId: session.id,
141
139
  ack: {
142
140
  requestId: p.requestId,
143
141
  decision: p.decision,
@@ -175,11 +173,7 @@ function handleHostMessage(
175
173
  case "screen.status":
176
174
  case "screen.offer":
177
175
  case "screen.ice":
178
- // Agent GUI: host → clients
179
- case "agent.capabilities":
180
- case "agent.update":
181
- case "agent.permission.request":
182
- case "agent.snapshot":
176
+ // Agent Workspace: host → clients
183
177
  case "agent.v2.capabilities":
184
178
  case "agent.v2.conversation.opened":
185
179
  case "agent.v2.conversation.list.result":
@@ -207,9 +201,9 @@ function handleHostMessage(
207
201
  function handleClientMessage(
208
202
  envelope: Envelope,
209
203
  socket: WebSocket,
210
- session: ReturnType<SessionManager["get"]> & {},
204
+ session: ReturnType<DeviceManager["get"]> & {},
211
205
  deviceId: string,
212
- sessions: SessionManager,
206
+ sessions: DeviceManager,
213
207
  ): void {
214
208
  const requireController = (): boolean => {
215
209
  if (session.controllerId === deviceId) return true;
@@ -239,15 +233,13 @@ function handleClientMessage(
239
233
  sendToHost(session, envelope);
240
234
  break;
241
235
  }
242
- case "device.ack":
243
- case "session.ack": {
236
+ case "device.ack": {
244
237
  // Forward ACK to host
245
238
  sendToHost(session, envelope);
246
239
  break;
247
240
  }
248
- case "device.resume":
249
- case "session.resume": {
250
- const p = parseTypedPayload("session.resume", envelope.payload);
241
+ case "device.resume": {
242
+ const p = parseTypedPayload("device.resume", envelope.payload);
251
243
  // Replay from gateway buffer first
252
244
  const replay = sessions.getReplayFrom(
253
245
  session.id,
@@ -309,18 +301,12 @@ function handleClientMessage(
309
301
  break;
310
302
  }
311
303
  case "device.heartbeat":
312
- case "session.heartbeat":
313
304
  break;
314
305
  // Screen sharing: client → host
315
306
  case "screen.start":
316
307
  case "screen.stop":
317
308
  case "screen.answer":
318
309
  case "screen.ice":
319
- case "agent.session.new":
320
- case "agent.session.load":
321
- case "agent.prompt":
322
- case "agent.cancel":
323
- case "agent.permission.response":
324
310
  case "agent.v2.conversation.open":
325
311
  case "agent.v2.prompt":
326
312
  case "agent.v2.command.execute":
@@ -340,8 +326,6 @@ function handleClientMessage(
340
326
  if (!requireController()) return;
341
327
  sendToHost(session, envelope);
342
328
  break;
343
- case "agent.initialize":
344
- case "agent.session.list":
345
329
  case "agent.v2.capabilities.request":
346
330
  case "agent.v2.conversation.list":
347
331
  case "agent.v2.snapshot.request":
@@ -354,7 +338,7 @@ function handleClientMessage(
354
338
  }
355
339
 
356
340
  function broadcastToClients(
357
- session: ReturnType<SessionManager["get"]> & {},
341
+ session: ReturnType<DeviceManager["get"]> & {},
358
342
  envelope: Envelope,
359
343
  ): void {
360
344
  const data = serializeEnvelope(envelope);
@@ -366,7 +350,7 @@ function broadcastToClients(
366
350
  }
367
351
 
368
352
  function sendToHost(
369
- session: ReturnType<SessionManager["get"]> & {},
353
+ session: ReturnType<DeviceManager["get"]> & {},
370
354
  envelope: Envelope,
371
355
  ): void {
372
356
  if (
package/src/sessions.ts CHANGED
@@ -2,7 +2,6 @@ import type WebSocket from "ws";
2
2
  import type { Envelope } from "@linkshell/protocol";
3
3
 
4
4
  export type DeviceState = "active" | "host_disconnected" | "terminated";
5
- export type SessionState = DeviceState;
6
5
 
7
6
  export interface ConnectedDevice {
8
7
  socket: WebSocket;
@@ -33,8 +32,6 @@ export interface HostDevice {
33
32
  userId: string | undefined;
34
33
  }
35
34
 
36
- export type Session = HostDevice;
37
-
38
35
  const OUTPUT_BUFFER_CAPACITY = 200;
39
36
  const HOST_RECONNECT_WINDOW = 60_000;
40
37
  const CLEANUP_INTERVAL = 30_000;
@@ -311,5 +308,3 @@ export class DeviceManager {
311
308
  clearInterval(this.cleanupTimer);
312
309
  }
313
310
  }
314
-
315
- export class SessionManager extends DeviceManager {}
package/src/tokens.ts CHANGED
@@ -149,7 +149,7 @@ export class AuthorizationManager {
149
149
  return this.tokens.get(token)?.authorizations.get(hostDeviceId)?.authorizationId;
150
150
  }
151
151
 
152
- getTokenForSession(hostDeviceId: string): string | undefined {
152
+ getTokenForHostDevice(hostDeviceId: string): string | undefined {
153
153
  return this.hostDeviceToTokens.get(hostDeviceId)?.values().next().value;
154
154
  }
155
155
 
package/src/tunnel.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  createEnvelope,
6
6
  serializeEnvelope,
7
7
  } from "@linkshell/protocol";
8
- import type { SessionManager } from "./sessions.js";
8
+ import type { DeviceManager } from "./sessions.js";
9
9
  import type { TokenManager } from "./tokens.js";
10
10
  import { AUTH_REQUIRED } from "./auth-middleware.js";
11
11
 
@@ -66,7 +66,7 @@ function extractToken(req: IncomingMessage, url: URL): string | null {
66
66
  }
67
67
 
68
68
  /** Parse lsh_tunnel cookie: "hostDeviceId:port:token" */
69
- export function parseTunnelCookie(req: IncomingMessage): { hostDeviceId: string; sessionId: string; port: number; token: string } | null {
69
+ export function parseTunnelCookie(req: IncomingMessage): { hostDeviceId: string; port: number; token: string } | null {
70
70
  const cookie = req.headers.cookie;
71
71
  if (!cookie) return null;
72
72
  const match = cookie.match(/lsh_tunnel=([^;]+)/);
@@ -77,7 +77,7 @@ export function parseTunnelCookie(req: IncomingMessage): { hostDeviceId: string;
77
77
  const port = Number(parts[1]);
78
78
  const token = parts.slice(2).join(":"); // token may contain colons
79
79
  if (!hostDeviceId || isNaN(port) || port < 1 || port > 65535 || !token) return null;
80
- return { hostDeviceId, sessionId: hostDeviceId, port, token };
80
+ return { hostDeviceId, port, token };
81
81
  }
82
82
 
83
83
  function errorResponse(res: ServerResponse, status: number, message: string): void {
@@ -89,22 +89,20 @@ function errorResponse(res: ServerResponse, status: number, message: string): vo
89
89
  res.end(message);
90
90
  }
91
91
 
92
- export function parseTunnelPath(pathname: string): { hostDeviceId: string; sessionId: string; port: number; path: string } | null {
92
+ export function parseTunnelPath(pathname: string): { hostDeviceId: string; port: number; path: string } | null {
93
93
  const match = pathname.match(/^\/tunnel\/([^/]+)\/(\d+)(\/.*)?$/);
94
94
  if (!match) return null;
95
95
  const port = Number(match[2]);
96
96
  if (port < 1 || port > 65535) return null;
97
97
  return {
98
98
  hostDeviceId: match[1]!,
99
- sessionId: match[1]!,
100
99
  port,
101
100
  path: match[3] || "/",
102
101
  };
103
102
  }
104
103
 
105
104
  type ParsedTunnelTarget = {
106
- hostDeviceId?: string;
107
- sessionId?: string;
105
+ hostDeviceId: string;
108
106
  port: number;
109
107
  path: string;
110
108
  };
@@ -112,18 +110,13 @@ type ParsedTunnelTarget = {
112
110
  export async function handleTunnelRequest(
113
111
  req: IncomingMessage,
114
112
  res: ServerResponse,
115
- sessions: SessionManager,
113
+ sessions: DeviceManager,
116
114
  tokens: TokenManager,
117
115
  parsed: ParsedTunnelTarget,
118
116
  url: URL,
119
117
  preAuthToken?: string,
120
118
  ): Promise<void> {
121
- const hostDeviceId = parsed.hostDeviceId ?? parsed.sessionId;
122
- const { port, path } = parsed;
123
- if (!hostDeviceId) {
124
- errorResponse(res, 400, "Missing host device id");
125
- return;
126
- }
119
+ const { hostDeviceId, port, path } = parsed;
127
120
 
128
121
  // Auth: device token OR Supabase JWT (userId owns session)
129
122
  const token = preAuthToken || extractToken(req, url);
@@ -342,15 +335,10 @@ export async function handleTunnelWsUpgrade(
342
335
  ws: WebSocket,
343
336
  parsed: ParsedTunnelTarget,
344
337
  url: URL,
345
- sessions: SessionManager,
338
+ sessions: DeviceManager,
346
339
  tokens: TokenManager,
347
340
  ): Promise<void> {
348
- const hostDeviceId = parsed.hostDeviceId ?? parsed.sessionId;
349
- const { port, path } = parsed;
350
- if (!hostDeviceId) {
351
- ws.close(1008, "Missing host device id");
352
- return;
353
- }
341
+ const { hostDeviceId, port, path } = parsed;
354
342
 
355
343
  // Auth: device token OR Supabase JWT (userId owns session)
356
344
  const token = url.searchParams.get("token");