@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/README.md +14 -13
- package/dist/gateway/src/agent-permission-http.d.ts +10 -46
- package/dist/gateway/src/agent-permission-http.js +12 -60
- package/dist/gateway/src/agent-permission-http.js.map +1 -1
- package/dist/gateway/src/embedded.js +4 -4
- package/dist/gateway/src/embedded.js.map +1 -1
- package/dist/gateway/src/index.js +5 -5
- package/dist/gateway/src/index.js.map +1 -1
- package/dist/gateway/src/relay.d.ts +2 -2
- package/dist/gateway/src/relay.js +7 -23
- package/dist/gateway/src/relay.js.map +1 -1
- package/dist/gateway/src/sessions.d.ts +0 -4
- package/dist/gateway/src/sessions.js +0 -2
- package/dist/gateway/src/sessions.js.map +1 -1
- package/dist/gateway/src/tokens.d.ts +1 -1
- package/dist/gateway/src/tokens.js +1 -1
- package/dist/gateway/src/tokens.js.map +1 -1
- package/dist/gateway/src/tunnel.d.ts +4 -7
- package/dist/gateway/src/tunnel.js +3 -14
- package/dist/gateway/src/tunnel.js.map +1 -1
- package/dist/gateway/tsconfig.tsbuildinfo +1 -1
- package/dist/shared-protocol/src/index.d.ts +2558 -4433
- package/dist/shared-protocol/src/index.js +2 -83
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/package.json +10 -10
- package/src/agent-permission-http.ts +16 -67
- package/src/embedded.ts +4 -4
- package/src/index.ts +5 -5
- package/src/relay.ts +15 -31
- package/src/sessions.ts +0 -5
- package/src/tokens.ts +1 -1
- package/src/tunnel.ts +9 -21
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 {
|
|
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:
|
|
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<
|
|
103
|
-
sessions:
|
|
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("
|
|
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
|
-
|
|
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
|
|
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<
|
|
204
|
+
session: ReturnType<DeviceManager["get"]> & {},
|
|
211
205
|
deviceId: string,
|
|
212
|
-
sessions:
|
|
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
|
-
|
|
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<
|
|
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<
|
|
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
|
-
|
|
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 {
|
|
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;
|
|
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,
|
|
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;
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
338
|
+
sessions: DeviceManager,
|
|
346
339
|
tokens: TokenManager,
|
|
347
340
|
): Promise<void> {
|
|
348
|
-
const hostDeviceId
|
|
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");
|