From 74757cd5af8c09f1fd16627e5380612e3e7e078c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 20 Jan 2026 11:00:34 +0000 Subject: [PATCH] fix: stabilize gateway defaults --- src/cli/devices-cli.ts | 7 ++-- src/config/config.identity-defaults.test.ts | 4 ++- src/gateway/client.ts | 38 ++++++++++----------- src/gateway/server.auth.test.ts | 5 ++- src/gateway/server.health.test.ts | 1 - 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/cli/devices-cli.ts b/src/cli/devices-cli.ts index d30f2967d..0df80e6e1 100644 --- a/src/cli/devices-cli.ts +++ b/src/cli/devices-cli.ts @@ -123,7 +123,8 @@ export function registerDevicesCli(program: Command) { const name = req.displayName || req.deviceId; const repair = req.isRepair ? " (repair)" : ""; const ip = req.remoteIp ? ` · ${req.remoteIp}` : ""; - const age = typeof req.ts === "number" ? ` · ${formatAge(Date.now() - req.ts)} ago` : ""; + const age = + typeof req.ts === "number" ? ` · ${formatAge(Date.now() - req.ts)} ago` : ""; const role = req.role ? ` · role: ${req.role}` : ""; defaultRuntime.log(`- ${req.requestId}: ${name}${repair}${role}${ip}${age}`); } @@ -133,7 +134,9 @@ export function registerDevicesCli(program: Command) { for (const device of list.paired) { const name = device.displayName || device.deviceId; const roles = device.roles?.length ? `roles: ${device.roles.join(", ")}` : "roles: -"; - const scopes = device.scopes?.length ? `scopes: ${device.scopes.join(", ")}` : "scopes: -"; + const scopes = device.scopes?.length + ? `scopes: ${device.scopes.join(", ")}` + : "scopes: -"; const ip = device.remoteIp ? ` · ${device.remoteIp}` : ""; const tokens = formatTokenSummary(device.tokens); defaultRuntime.log(`- ${name} · ${roles} · ${scopes} · ${tokens}${ip}`); diff --git a/src/config/config.identity-defaults.test.ts b/src/config/config.identity-defaults.test.ts index 829264a29..b1f4da64d 100644 --- a/src/config/config.identity-defaults.test.ts +++ b/src/config/config.identity-defaults.test.ts @@ -309,7 +309,9 @@ describe("config identity defaults", () => { expect(cfg.messages?.groupChat?.mentionPatterns).toBeUndefined(); expect(cfg.agents?.list).toBeUndefined(); expect(cfg.agents?.defaults?.maxConcurrent).toBe(DEFAULT_AGENT_MAX_CONCURRENT); - expect(cfg.agents?.defaults?.subagents?.maxConcurrent).toBe(DEFAULT_SUBAGENT_MAX_CONCURRENT); + expect(cfg.agents?.defaults?.subagents?.maxConcurrent).toBe( + DEFAULT_SUBAGENT_MAX_CONCURRENT, + ); expect(cfg.session).toBeUndefined(); }); }); diff --git a/src/gateway/client.ts b/src/gateway/client.ts index addd6e9fa..9c2da26b8 100644 --- a/src/gateway/client.ts +++ b/src/gateway/client.ts @@ -72,7 +72,7 @@ export function describeGatewayCloseCode(code: number): string | undefined { export class GatewayClient { private ws: WebSocket | null = null; - private opts: GatewayClientOptions; + private opts: GatewayClientOptions & { deviceIdentity: DeviceIdentity }; private pending = new Map(); private backoffMs = 1000; private closed = false; @@ -161,25 +161,23 @@ export class GatewayClient { : undefined; const signedAtMs = Date.now(); const scopes = this.opts.scopes ?? ["operator.admin"]; - const device = (() => { - if (!this.opts.deviceIdentity) return undefined; - const payload = buildDeviceAuthPayload({ - deviceId: this.opts.deviceIdentity.deviceId, - clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT, - clientMode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND, - role, - scopes, - signedAtMs, - token: authToken ?? null, - }); - const signature = signDevicePayload(this.opts.deviceIdentity.privateKeyPem, payload); - return { - id: this.opts.deviceIdentity.deviceId, - publicKey: publicKeyRawBase64UrlFromPem(this.opts.deviceIdentity.publicKeyPem), - signature, - signedAt: signedAtMs, - }; - })(); + const deviceIdentity = this.opts.deviceIdentity; + const payload = buildDeviceAuthPayload({ + deviceId: deviceIdentity.deviceId, + clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT, + clientMode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND, + role, + scopes, + signedAtMs, + token: authToken ?? null, + }); + const signature = signDevicePayload(deviceIdentity.privateKeyPem, payload); + const device = { + id: deviceIdentity.deviceId, + publicKey: publicKeyRawBase64UrlFromPem(deviceIdentity.publicKeyPem), + signature, + signedAt: signedAtMs, + }; const params: ConnectParams = { minProtocol: this.opts.minProtocol ?? PROTOCOL_VERSION, maxProtocol: this.opts.maxProtocol ?? PROTOCOL_VERSION, diff --git a/src/gateway/server.auth.test.ts b/src/gateway/server.auth.test.ts index f48043240..a6492bb47 100644 --- a/src/gateway/server.auth.test.ts +++ b/src/gateway/server.auth.test.ts @@ -102,9 +102,8 @@ describe("gateway server auth/connect", () => { test("accepts device token auth for paired device", async () => { const { loadOrCreateDeviceIdentity } = await import("../infra/device-identity.js"); - const { approveDevicePairing, getPairedDevice, listDevicePairing } = await import( - "../infra/device-pairing.js" - ); + const { approveDevicePairing, getPairedDevice, listDevicePairing } = + await import("../infra/device-pairing.js"); const { server, ws, port, prevToken } = await startServerWithClient("secret"); const res = await connectReq(ws, { token: "secret" }); if (!res.ok) { diff --git a/src/gateway/server.health.test.ts b/src/gateway/server.health.test.ts index 92f71e0e4..173e654f9 100644 --- a/src/gateway/server.health.test.ts +++ b/src/gateway/server.health.test.ts @@ -10,7 +10,6 @@ import { signDevicePayload, } from "../infra/device-identity.js"; import { emitHeartbeatEvent } from "../infra/heartbeat-events.js"; -import { loadOrCreateDeviceIdentity } from "../infra/device-identity.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; import { connectOk,