feat: add node core/ui versions in bridge
This commit is contained in:
@@ -4,6 +4,9 @@ export type NodeListNode = {
|
||||
nodeId: string;
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
remoteIp?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
@@ -20,6 +23,8 @@ type PendingRequest = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
remoteIp?: string;
|
||||
isRepair?: boolean;
|
||||
ts: number;
|
||||
@@ -31,6 +36,8 @@ type PairedNode = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
remoteIp?: string;
|
||||
permissions?: Record<string, boolean>;
|
||||
createdAtMs?: number;
|
||||
|
||||
@@ -4,6 +4,43 @@ import { formatAge, formatPermissions, parseNodeList, parsePairingList } from ".
|
||||
import { callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js";
|
||||
import type { NodesRpcOpts } from "./types.js";
|
||||
|
||||
function formatVersionLabel(raw: string) {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return raw;
|
||||
if (trimmed.toLowerCase().startsWith("v")) return trimmed;
|
||||
return /^\d/.test(trimmed) ? `v${trimmed}` : trimmed;
|
||||
}
|
||||
|
||||
function resolveNodeVersions(node: {
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
}) {
|
||||
const core = node.coreVersion?.trim() || undefined;
|
||||
const ui = node.uiVersion?.trim() || undefined;
|
||||
if (core || ui) return { core, ui };
|
||||
const legacy = node.version?.trim();
|
||||
if (!legacy) return { core: undefined, ui: undefined };
|
||||
const platform = node.platform?.trim().toLowerCase() ?? "";
|
||||
const headless =
|
||||
platform === "darwin" || platform === "linux" || platform === "win32" || platform === "windows";
|
||||
return headless ? { core: legacy, ui: undefined } : { core: undefined, ui: legacy };
|
||||
}
|
||||
|
||||
function formatNodeVersions(node: {
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
}) {
|
||||
const { core, ui } = resolveNodeVersions(node);
|
||||
const parts: string[] = [];
|
||||
if (core) parts.push(`core ${formatVersionLabel(core)}`);
|
||||
if (ui) parts.push(`ui ${formatVersionLabel(ui)}`);
|
||||
return parts.length > 0 ? parts.join(" · ") : null;
|
||||
}
|
||||
|
||||
export function registerNodesStatusCommands(nodes: Command) {
|
||||
nodesCallOpts(
|
||||
nodes
|
||||
@@ -29,6 +66,8 @@ export function registerNodesStatusCommands(nodes: Command) {
|
||||
const hw = n.modelIdentifier ? ` · hw: ${n.modelIdentifier}` : "";
|
||||
const perms = formatPermissions(n.permissions);
|
||||
const permsText = perms ? ` · perms: ${perms}` : "";
|
||||
const versions = formatNodeVersions(n);
|
||||
const versionText = versions ? ` · ${versions}` : "";
|
||||
const caps =
|
||||
Array.isArray(n.caps) && n.caps.length > 0
|
||||
? `[${n.caps.map(String).filter(Boolean).sort().join(",")}]`
|
||||
@@ -37,7 +76,7 @@ export function registerNodesStatusCommands(nodes: Command) {
|
||||
: "?";
|
||||
const pairing = n.paired ? "paired" : "unpaired";
|
||||
defaultRuntime.log(
|
||||
`- ${name} · ${n.nodeId}${ip}${device}${hw}${permsText} · ${pairing} · ${n.connected ? "connected" : "disconnected"} · caps: ${caps}`,
|
||||
`- ${name} · ${n.nodeId}${ip}${device}${hw}${permsText}${versionText} · ${pairing} · ${n.connected ? "connected" : "disconnected"} · caps: ${caps}`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -77,12 +116,19 @@ export function registerNodesStatusCommands(nodes: Command) {
|
||||
const family = typeof obj.deviceFamily === "string" ? obj.deviceFamily : null;
|
||||
const model = typeof obj.modelIdentifier === "string" ? obj.modelIdentifier : null;
|
||||
const ip = typeof obj.remoteIp === "string" ? obj.remoteIp : null;
|
||||
const versions = formatNodeVersions(obj as {
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
});
|
||||
|
||||
const parts: string[] = ["Node:", displayName, nodeId];
|
||||
if (ip) parts.push(ip);
|
||||
if (family) parts.push(`device: ${family}`);
|
||||
if (model) parts.push(`hw: ${model}`);
|
||||
if (perms) parts.push(`perms: ${perms}`);
|
||||
if (versions) parts.push(versions);
|
||||
parts.push(connected ? "connected" : "disconnected");
|
||||
parts.push(`caps: ${caps ? `[${caps.join(",")}]` : "?"}`);
|
||||
defaultRuntime.log(parts.join(" · "));
|
||||
|
||||
@@ -46,6 +46,8 @@ export type NodeListNode = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
remoteIp?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
@@ -62,6 +64,8 @@ export type PendingRequest = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
remoteIp?: string;
|
||||
isRepair?: boolean;
|
||||
ts: number;
|
||||
@@ -73,6 +77,8 @@ export type PairedNode = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
remoteIp?: string;
|
||||
permissions?: Record<string, boolean>;
|
||||
createdAtMs?: number;
|
||||
|
||||
@@ -8,6 +8,8 @@ export const NodePairRequestParamsSchema = Type.Object(
|
||||
displayName: Type.Optional(NonEmptyString),
|
||||
platform: Type.Optional(NonEmptyString),
|
||||
version: Type.Optional(NonEmptyString),
|
||||
coreVersion: Type.Optional(NonEmptyString),
|
||||
uiVersion: Type.Optional(NonEmptyString),
|
||||
deviceFamily: Type.Optional(NonEmptyString),
|
||||
modelIdentifier: Type.Optional(NonEmptyString),
|
||||
caps: Type.Optional(Type.Array(NonEmptyString)),
|
||||
|
||||
@@ -42,6 +42,8 @@ export const nodeHandlers: GatewayRequestHandlers = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
caps?: string[];
|
||||
@@ -55,6 +57,8 @@ export const nodeHandlers: GatewayRequestHandlers = {
|
||||
displayName: p.displayName,
|
||||
platform: p.platform,
|
||||
version: p.version,
|
||||
coreVersion: p.coreVersion,
|
||||
uiVersion: p.uiVersion,
|
||||
deviceFamily: p.deviceFamily,
|
||||
modelIdentifier: p.modelIdentifier,
|
||||
caps: p.caps,
|
||||
@@ -215,6 +219,8 @@ export const nodeHandlers: GatewayRequestHandlers = {
|
||||
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,
|
||||
remoteIp: live?.remoteIp ?? paired?.remoteIp,
|
||||
@@ -275,6 +281,8 @@ export const nodeHandlers: GatewayRequestHandlers = {
|
||||
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,
|
||||
remoteIp: live?.remoteIp ?? paired?.remoteIp,
|
||||
|
||||
@@ -43,6 +43,25 @@ export async function startGatewayNodeBridge(params: {
|
||||
}): Promise<GatewayNodeBridgeRuntime> {
|
||||
const nodePresenceTimers = new Map<string, ReturnType<typeof setInterval>>();
|
||||
|
||||
const formatVersionLabel = (raw: string): string => {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return raw;
|
||||
if (trimmed.toLowerCase().startsWith("v")) return trimmed;
|
||||
return /^\d/.test(trimmed) ? `v${trimmed}` : trimmed;
|
||||
};
|
||||
|
||||
const resolveNodeVersionLabel = (node: {
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
}): string | null => {
|
||||
const core = node.coreVersion?.trim();
|
||||
const ui = node.uiVersion?.trim();
|
||||
const parts: string[] = [];
|
||||
if (core) parts.push(`core ${formatVersionLabel(core)}`);
|
||||
if (ui) parts.push(`ui ${formatVersionLabel(ui)}`);
|
||||
return parts.length > 0 ? parts.join(" · ") : null;
|
||||
};
|
||||
|
||||
const stopNodePresenceTimer = (nodeId: string) => {
|
||||
const timer = nodePresenceTimers.get(nodeId);
|
||||
if (timer) {
|
||||
@@ -57,6 +76,8 @@ export async function startGatewayNodeBridge(params: {
|
||||
displayName?: string;
|
||||
remoteIp?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
platform?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
@@ -66,7 +87,7 @@ export async function startGatewayNodeBridge(params: {
|
||||
const host = node.displayName?.trim() || node.nodeId;
|
||||
const rawIp = node.remoteIp?.trim();
|
||||
const ip = rawIp && !isLoopbackAddress(rawIp) ? rawIp : undefined;
|
||||
const version = node.version?.trim() || "unknown";
|
||||
const version = resolveNodeVersionLabel(node) ?? node.version?.trim() ?? "unknown";
|
||||
const platform = node.platform?.trim() || undefined;
|
||||
const deviceFamily = node.deviceFamily?.trim() || undefined;
|
||||
const modelIdentifier = node.modelIdentifier?.trim() || undefined;
|
||||
|
||||
@@ -168,6 +168,8 @@ export function createNodeBridgeConnectionHandler(params: {
|
||||
displayName: verified.node.displayName ?? hello.displayName,
|
||||
platform: verified.node.platform ?? hello.platform,
|
||||
version: verified.node.version ?? hello.version,
|
||||
coreVersion: verified.node.coreVersion ?? hello.coreVersion,
|
||||
uiVersion: verified.node.uiVersion ?? hello.uiVersion,
|
||||
deviceFamily: verified.node.deviceFamily ?? hello.deviceFamily,
|
||||
modelIdentifier: verified.node.modelIdentifier ?? hello.modelIdentifier,
|
||||
caps,
|
||||
@@ -181,6 +183,8 @@ export function createNodeBridgeConnectionHandler(params: {
|
||||
displayName: nodeInfo.displayName,
|
||||
platform: nodeInfo.platform,
|
||||
version: nodeInfo.version,
|
||||
coreVersion: nodeInfo.coreVersion,
|
||||
uiVersion: nodeInfo.uiVersion,
|
||||
deviceFamily: nodeInfo.deviceFamily,
|
||||
modelIdentifier: nodeInfo.modelIdentifier,
|
||||
remoteIp: nodeInfo.remoteIp,
|
||||
@@ -243,6 +247,8 @@ export function createNodeBridgeConnectionHandler(params: {
|
||||
displayName: req.displayName,
|
||||
platform: req.platform,
|
||||
version: req.version,
|
||||
coreVersion: req.coreVersion,
|
||||
uiVersion: req.uiVersion,
|
||||
deviceFamily: req.deviceFamily,
|
||||
modelIdentifier: req.modelIdentifier,
|
||||
caps: Array.isArray(req.caps)
|
||||
@@ -286,6 +292,8 @@ export function createNodeBridgeConnectionHandler(params: {
|
||||
displayName: req.displayName,
|
||||
platform: req.platform,
|
||||
version: req.version,
|
||||
coreVersion: req.coreVersion,
|
||||
uiVersion: req.uiVersion,
|
||||
deviceFamily: req.deviceFamily,
|
||||
modelIdentifier: req.modelIdentifier,
|
||||
caps: Array.isArray(req.caps) ? req.caps.map((c) => String(c)).filter(Boolean) : undefined,
|
||||
|
||||
@@ -9,6 +9,8 @@ export type BridgeHelloFrame = {
|
||||
token?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
caps?: string[];
|
||||
@@ -22,6 +24,8 @@ export type BridgePairRequestFrame = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
caps?: string[];
|
||||
@@ -113,6 +117,8 @@ export type NodeBridgeClientInfo = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
remoteIp?: string;
|
||||
|
||||
@@ -9,6 +9,8 @@ export type NodePairingPendingRequest = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
caps?: string[];
|
||||
@@ -26,6 +28,8 @@ export type NodePairingPairedNode = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
caps?: string[];
|
||||
@@ -186,6 +190,8 @@ export async function requestNodePairing(
|
||||
displayName: req.displayName,
|
||||
platform: req.platform,
|
||||
version: req.version,
|
||||
coreVersion: req.coreVersion,
|
||||
uiVersion: req.uiVersion,
|
||||
deviceFamily: req.deviceFamily,
|
||||
modelIdentifier: req.modelIdentifier,
|
||||
caps: req.caps,
|
||||
@@ -219,6 +225,8 @@ export async function approveNodePairing(
|
||||
displayName: pending.displayName,
|
||||
platform: pending.platform,
|
||||
version: pending.version,
|
||||
coreVersion: pending.coreVersion,
|
||||
uiVersion: pending.uiVersion,
|
||||
deviceFamily: pending.deviceFamily,
|
||||
modelIdentifier: pending.modelIdentifier,
|
||||
caps: pending.caps,
|
||||
@@ -278,6 +286,8 @@ export async function updatePairedNodeMetadata(
|
||||
displayName: patch.displayName ?? existing.displayName,
|
||||
platform: patch.platform ?? existing.platform,
|
||||
version: patch.version ?? existing.version,
|
||||
coreVersion: patch.coreVersion ?? existing.coreVersion,
|
||||
uiVersion: patch.uiVersion ?? existing.uiVersion,
|
||||
deviceFamily: patch.deviceFamily ?? existing.deviceFamily,
|
||||
modelIdentifier: patch.modelIdentifier ?? existing.modelIdentifier,
|
||||
remoteIp: patch.remoteIp ?? existing.remoteIp,
|
||||
|
||||
@@ -27,6 +27,8 @@ export type BridgeClientOptions = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
coreVersion?: string;
|
||||
uiVersion?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
caps?: string[];
|
||||
@@ -172,6 +174,8 @@ export class BridgeClient {
|
||||
displayName: this.opts.displayName,
|
||||
platform: this.opts.platform,
|
||||
version: this.opts.version,
|
||||
coreVersion: this.opts.coreVersion,
|
||||
uiVersion: this.opts.uiVersion,
|
||||
deviceFamily: this.opts.deviceFamily,
|
||||
modelIdentifier: this.opts.modelIdentifier,
|
||||
caps: this.opts.caps,
|
||||
@@ -188,6 +192,8 @@ export class BridgeClient {
|
||||
displayName: this.opts.displayName,
|
||||
platform: this.opts.platform,
|
||||
version: this.opts.version,
|
||||
coreVersion: this.opts.coreVersion,
|
||||
uiVersion: this.opts.uiVersion,
|
||||
deviceFamily: this.opts.deviceFamily,
|
||||
modelIdentifier: this.opts.modelIdentifier,
|
||||
caps: this.opts.caps,
|
||||
|
||||
@@ -346,6 +346,7 @@ export async function runNodeHost(opts: NodeHostRunOptions): Promise<void> {
|
||||
displayName,
|
||||
platform: process.platform,
|
||||
version: VERSION,
|
||||
coreVersion: VERSION,
|
||||
deviceFamily: os.platform(),
|
||||
modelIdentifier: os.hostname(),
|
||||
caps: ["system"],
|
||||
|
||||
Reference in New Issue
Block a user