refactor: remove bridge protocol

This commit is contained in:
Peter Steinberger
2026-01-19 04:50:07 +00:00
parent b347d5d9cc
commit 2f8206862a
118 changed files with 1560 additions and 8087 deletions

View File

@@ -113,7 +113,7 @@ export const chatHandlers: GatewayRequestHandlers = {
removeChatRun: context.removeChatRun,
agentRunSeq: context.agentRunSeq,
broadcast: context.broadcast,
bridgeSendToSession: context.bridgeSendToSession,
nodeSendToSession: context.nodeSendToSession,
};
if (!runId) {
@@ -250,7 +250,7 @@ export const chatHandlers: GatewayRequestHandlers = {
removeChatRun: context.removeChatRun,
agentRunSeq: context.agentRunSeq,
broadcast: context.broadcast,
bridgeSendToSession: context.bridgeSendToSession,
nodeSendToSession: context.nodeSendToSession,
},
{ sessionKey: p.sessionKey, stopReason: "stop" },
);
@@ -451,7 +451,7 @@ export const chatHandlers: GatewayRequestHandlers = {
message: transcriptEntry.message,
};
context.broadcast("chat", chatPayload);
context.bridgeSendToSession(p.sessionKey, "chat", chatPayload);
context.nodeSendToSession(p.sessionKey, "chat", chatPayload);
respond(true, { ok: true, messageId });
},

View File

@@ -167,11 +167,6 @@ export const execApprovalsHandlers: GatewayRequestHandlers = {
);
return;
}
const bridge = context.bridge;
if (!bridge) {
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "bridge not running"));
return;
}
const { nodeId } = params as { nodeId: string };
const id = nodeId.trim();
if (!id) {
@@ -179,10 +174,10 @@ export const execApprovalsHandlers: GatewayRequestHandlers = {
return;
}
await respondUnavailableOnThrow(respond, async () => {
const res = await bridge.invoke({
const res = await context.nodeRegistry.invoke({
nodeId: id,
command: "system.execApprovals.get",
paramsJSON: "{}",
params: {},
});
if (!res.ok) {
respond(
@@ -194,7 +189,7 @@ export const execApprovalsHandlers: GatewayRequestHandlers = {
);
return;
}
const payload = safeParseJson(res.payloadJSON ?? null);
const payload = res.payloadJSON ? safeParseJson(res.payloadJSON) : res.payload;
respond(true, payload, undefined);
});
},
@@ -210,11 +205,6 @@ export const execApprovalsHandlers: GatewayRequestHandlers = {
);
return;
}
const bridge = context.bridge;
if (!bridge) {
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "bridge not running"));
return;
}
const { nodeId, file, baseHash } = params as {
nodeId: string;
file: ExecApprovalsFile;
@@ -226,10 +216,10 @@ export const execApprovalsHandlers: GatewayRequestHandlers = {
return;
}
await respondUnavailableOnThrow(respond, async () => {
const res = await bridge.invoke({
const res = await context.nodeRegistry.invoke({
nodeId: id,
command: "system.execApprovals.set",
paramsJSON: JSON.stringify({ file, baseHash }),
params: { file, baseHash },
});
if (!res.ok) {
respond(

View File

@@ -6,11 +6,14 @@ import {
requestNodePairing,
verifyNodeToken,
} from "../../infra/node-pairing.js";
import { listDevicePairing } from "../../infra/device-pairing.js";
import {
ErrorCodes,
errorShape,
validateNodeDescribeParams,
validateNodeEventParams,
validateNodeInvokeParams,
validateNodeInvokeResultParams,
validateNodeListParams,
validateNodePairApproveParams,
validateNodePairListParams,
@@ -201,9 +204,29 @@ export const nodeHandlers: GatewayRequestHandlers = {
return;
}
await respondUnavailableOnThrow(respond, async () => {
const list = await listNodePairing();
const pairedById = new Map(list.paired.map((n) => [n.nodeId, n]));
const connected = context.bridge?.listConnected?.() ?? [];
const list = await listDevicePairing();
const pairedById = new Map(
list.paired
.filter((entry) => entry.role === "node")
.map((entry) => [
entry.deviceId,
{
nodeId: entry.deviceId,
displayName: entry.displayName,
platform: entry.platform,
version: undefined,
coreVersion: undefined,
uiVersion: undefined,
deviceFamily: undefined,
modelIdentifier: undefined,
remoteIp: entry.remoteIp,
caps: [],
commands: [],
permissions: undefined,
},
]),
);
const connected = context.nodeRegistry.listConnected();
const connectedById = new Map(connected.map((n) => [n.nodeId, n]));
const nodeIds = new Set<string>([...pairedById.keys(), ...connectedById.keys()]);
@@ -260,9 +283,9 @@ export const nodeHandlers: GatewayRequestHandlers = {
return;
}
await respondUnavailableOnThrow(respond, async () => {
const list = await listNodePairing();
const paired = list.paired.find((n) => n.nodeId === id);
const connected = context.bridge?.listConnected?.() ?? [];
const list = await listDevicePairing();
const paired = list.paired.find((n) => n.deviceId === id && n.role === "node");
const connected = context.nodeRegistry.listConnected();
const live = connected.find((n) => n.nodeId === id);
if (!paired && !live) {
@@ -270,8 +293,8 @@ export const nodeHandlers: GatewayRequestHandlers = {
return;
}
const caps = uniqueSortedStrings([...(live?.caps ?? paired?.caps ?? [])]);
const commands = uniqueSortedStrings([...(live?.commands ?? paired?.commands ?? [])]);
const caps = uniqueSortedStrings([...(live?.caps ?? [])]);
const commands = uniqueSortedStrings([...(live?.commands ?? [])]);
respond(
true,
@@ -280,15 +303,15 @@ export const nodeHandlers: GatewayRequestHandlers = {
nodeId: id,
displayName: live?.displayName ?? paired?.displayName,
platform: live?.platform ?? paired?.platform,
version: live?.version ?? paired?.version,
coreVersion: live?.coreVersion ?? paired?.coreVersion,
uiVersion: live?.uiVersion ?? paired?.uiVersion,
deviceFamily: live?.deviceFamily ?? paired?.deviceFamily,
modelIdentifier: live?.modelIdentifier ?? paired?.modelIdentifier,
version: live?.version,
coreVersion: live?.coreVersion,
uiVersion: live?.uiVersion,
deviceFamily: live?.deviceFamily,
modelIdentifier: live?.modelIdentifier,
remoteIp: live?.remoteIp ?? paired?.remoteIp,
caps,
commands,
permissions: live?.permissions ?? paired?.permissions,
permissions: live?.permissions,
paired: Boolean(paired),
connected: Boolean(live),
},
@@ -305,11 +328,6 @@ export const nodeHandlers: GatewayRequestHandlers = {
});
return;
}
const bridge = context.bridge;
if (!bridge) {
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "bridge not running"));
return;
}
const p = params as {
nodeId: string;
command: string;
@@ -329,12 +347,12 @@ export const nodeHandlers: GatewayRequestHandlers = {
}
await respondUnavailableOnThrow(respond, async () => {
const paramsJSON = "params" in p && p.params !== undefined ? JSON.stringify(p.params) : null;
const res = await bridge.invoke({
const res = await context.nodeRegistry.invoke({
nodeId,
command,
paramsJSON,
params: p.params,
timeoutMs: p.timeoutMs,
idempotencyKey: p.idempotencyKey,
});
if (!res.ok) {
respond(
@@ -346,7 +364,7 @@ export const nodeHandlers: GatewayRequestHandlers = {
);
return;
}
const payload = safeParseJson(res.payloadJSON ?? null);
const payload = res.payloadJSON ? safeParseJson(res.payloadJSON) : res.payload;
respond(
true,
{
@@ -360,4 +378,85 @@ export const nodeHandlers: GatewayRequestHandlers = {
);
});
},
"node.invoke.result": async ({ params, respond, context }) => {
if (!validateNodeInvokeResultParams(params)) {
respondInvalidParams({
respond,
method: "node.invoke.result",
validator: validateNodeInvokeResultParams,
});
return;
}
const p = params as {
id: string;
nodeId: string;
ok: boolean;
payload?: unknown;
payloadJSON?: string | null;
error?: { code?: string; message?: string } | null;
};
const ok = context.nodeRegistry.handleInvokeResult({
id: p.id,
nodeId: p.nodeId,
ok: p.ok,
payload: p.payload,
payloadJSON: p.payloadJSON ?? null,
error: p.error ?? null,
});
if (!ok) {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "unknown invoke id"));
return;
}
respond(true, { ok: true }, undefined);
},
"node.event": async ({ params, respond, context }) => {
if (!validateNodeEventParams(params)) {
respondInvalidParams({
respond,
method: "node.event",
validator: validateNodeEventParams,
});
return;
}
const p = params as { event: string; payload?: unknown; payloadJSON?: string | null };
const payloadJSON =
typeof p.payloadJSON === "string"
? p.payloadJSON
: p.payload !== undefined
? JSON.stringify(p.payload)
: null;
await respondUnavailableOnThrow(respond, async () => {
const { handleNodeEvent } = await import("../server-node-events.js");
const nodeContext = {
deps: context.deps,
broadcast: context.broadcast,
nodeSendToSession: context.nodeSendToSession,
nodeSubscribe: context.nodeSubscribe,
nodeUnsubscribe: context.nodeUnsubscribe,
broadcastVoiceWakeChanged: context.broadcastVoiceWakeChanged,
addChatRun: context.addChatRun,
removeChatRun: context.removeChatRun,
chatAbortControllers: context.chatAbortControllers,
chatAbortedRuns: context.chatAbortedRuns,
chatRunBuffers: context.chatRunBuffers,
chatDeltaSentAt: context.chatDeltaSentAt,
dedupe: context.dedupe,
agentRunSeq: context.agentRunSeq,
getHealthCache: context.getHealthCache,
refreshHealthSnapshot: context.refreshHealthSnapshot,
loadGatewayModelCatalog: context.loadGatewayModelCatalog,
logGateway: { warn: context.logGateway.warn },
};
await handleNodeEvent(
nodeContext,
"node",
{
type: "event",
event: p.event,
payloadJSON,
},
);
respond(true, { ok: true }, undefined);
});
},
};

View File

@@ -2,9 +2,9 @@ import type { ModelCatalogEntry } from "../../agents/model-catalog.js";
import type { createDefaultDeps } from "../../cli/deps.js";
import type { HealthSummary } from "../../commands/health.js";
import type { CronService } from "../../cron/service.js";
import type { startNodeBridgeServer } from "../../infra/bridge/server.js";
import type { WizardSession } from "../../wizard/session.js";
import type { ChatAbortControllerEntry } from "../chat-abort.js";
import type { NodeRegistry } from "../node-registry.js";
import type { ConnectParams, ErrorShape, RequestFrame } from "../protocol/index.js";
import type { ChannelRuntimeSnapshot } from "../server-channels.js";
import type { DedupeEntry } from "../server-shared.js";
@@ -39,9 +39,13 @@ export type GatewayRequestContext = {
stateVersion?: { presence?: number; health?: number };
},
) => void;
bridge: Awaited<ReturnType<typeof startNodeBridgeServer>> | null;
bridgeSendToSession: (sessionKey: string, event: string, payload: unknown) => void;
nodeSendToSession: (sessionKey: string, event: string, payload: unknown) => void;
nodeSendToAllSubscribed: (event: string, payload: unknown) => void;
nodeSubscribe: (nodeId: string, sessionKey: string) => void;
nodeUnsubscribe: (nodeId: string, sessionKey: string) => void;
nodeUnsubscribeAll: (nodeId: string) => void;
hasConnectedMobileNode: () => boolean;
nodeRegistry: NodeRegistry;
agentRunSeq: Map<string, number>;
chatAbortControllers: Map<string, ChatAbortControllerEntry>;
chatAbortedRuns: Map<string, number>;