From 7371fff5785debcc207123d76906e5d81fc85042 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 13 Jul 2026 16:46:24 +0200 Subject: [PATCH] fix(hub): return relationship command errors --- .../session/daemon/daemon-session.test.ts | 57 +++++++++++++++++++ .../server/session/daemon/daemon-session.ts | 48 ++++++++++------ 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/packages/server/src/server/session/daemon/daemon-session.test.ts b/packages/server/src/server/session/daemon/daemon-session.test.ts index 9417a17edf..f025480035 100644 --- a/packages/server/src/server/session/daemon/daemon-session.test.ts +++ b/packages/server/src/server/session/daemon/daemon-session.test.ts @@ -10,6 +10,7 @@ import { } from "./daemon-session.js"; import type { DaemonWebSocketRuntimeDiagnosticSnapshot } from "./diagnostics.js"; import type { ProviderAvailability } from "../../agent/agent-manager.js"; +import type { HubRelationshipManagement } from "../../hub/relationship-controller.js"; import type { SessionOutboundMessage } from "../../messages.js"; const tempDirs: string[] = []; @@ -40,6 +41,7 @@ function makeSubsystem(overrides: { daemonRuntimeConfig?: DaemonRuntimeConfig; listProviderAvailability?: () => Promise; getWebSocketRuntimeMetrics?: () => DaemonWebSocketRuntimeDiagnosticSnapshot | null; + hubRelationships?: HubRelationshipManagement; }) { const emitted: SessionOutboundMessage[] = []; const restartIntents: Parameters[0][] = []; @@ -60,12 +62,67 @@ function makeSubsystem(overrides: { listWorkspaces: async () => [], listProviderAvailability: overrides.listProviderAvailability ?? (async () => []), getWebSocketRuntimeMetrics: overrides.getWebSocketRuntimeMetrics, + hubRelationships: overrides.hubRelationships, logger: pino({ level: "silent" }), }); return { subsystem, emitted, paseoHome, restartIntents }; } describe("DaemonSession", () => { + test("Hub relationship command failures return correlated RPC errors", async () => { + const { subsystem, emitted } = makeSubsystem({ + hubRelationships: { + connect: async () => { + throw new Error("Hub rejected enrollment (401)"); + }, + status: () => ({ + state: "not_connected", + relationshipId: null, + hubOrigin: null, + scopes: [], + connectedAt: null, + lastError: null, + }), + disconnect: async () => { + throw new Error("Hub revocation failed (503)"); + }, + }, + }); + + await subsystem.handleHubRelationshipRequest({ + type: "hub.relationship.connect.request", + requestId: "connect-1", + hubUrl: "https://hub.test", + token: "token", + }); + await subsystem.handleHubRelationshipRequest({ + type: "hub.relationship.disconnect.request", + requestId: "disconnect-1", + force: false, + }); + + expect(emitted).toEqual([ + { + type: "rpc_error", + payload: { + requestId: "connect-1", + requestType: "hub.relationship.connect.request", + error: "Hub rejected enrollment (401)", + code: "handler_error", + }, + }, + { + type: "rpc_error", + payload: { + requestId: "disconnect-1", + requestType: "hub.relationship.disconnect.request", + error: "Hub revocation failed (503)", + code: "handler_error", + }, + }, + ]); + }); + test("status reports identity, runtime config, and providers with errors normalized to null", async () => { const { subsystem, emitted } = makeSubsystem({ serverId: "srv-1", diff --git a/packages/server/src/server/session/daemon/daemon-session.ts b/packages/server/src/server/session/daemon/daemon-session.ts index 290e74e291..afd72b7f6e 100644 --- a/packages/server/src/server/session/daemon/daemon-session.ts +++ b/packages/server/src/server/session/daemon/daemon-session.ts @@ -107,27 +107,43 @@ export class DaemonSession { } >, ): Promise { - if (!this.hubRelationships) throw new Error("Hub relationship management is unavailable"); - if (msg.type === "hub.relationship.connect.request") { - const status = await this.hubRelationships.connect({ hubUrl: msg.hubUrl, token: msg.token }); + try { + if (!this.hubRelationships) throw new Error("Hub relationship management is unavailable"); + if (msg.type === "hub.relationship.connect.request") { + const status = await this.hubRelationships.connect({ + hubUrl: msg.hubUrl, + token: msg.token, + }); + this.host.emit({ + type: "hub.relationship.connect.response", + payload: { requestId: msg.requestId, status }, + }); + return; + } + if (msg.type === "hub.relationship.disconnect.request") { + const result = await this.hubRelationships.disconnect({ force: msg.force ?? false }); + this.host.emit({ + type: "hub.relationship.disconnect.response", + payload: { requestId: msg.requestId, ...result }, + }); + return; + } this.host.emit({ - type: "hub.relationship.connect.response", - payload: { requestId: msg.requestId, status }, + type: "hub.relationship.get_status.response", + payload: { requestId: msg.requestId, status: this.hubRelationships.status() }, }); - return; - } - if (msg.type === "hub.relationship.disconnect.request") { - const result = await this.hubRelationships.disconnect({ force: msg.force ?? false }); + } catch (error) { + this.logger.error({ err: error }, "Failed to handle Hub relationship request"); this.host.emit({ - type: "hub.relationship.disconnect.response", - payload: { requestId: msg.requestId, ...result }, + type: "rpc_error", + payload: { + requestId: msg.requestId, + requestType: msg.type, + error: error instanceof Error ? error.message : String(error), + code: "handler_error", + }, }); - return; } - this.host.emit({ - type: "hub.relationship.get_status.response", - payload: { requestId: msg.requestId, status: this.hubRelationships.status() }, - }); } async handleGetStatusRequest(