From 9292ec9880fd19ef5568a1af7e8254af91c3ba06 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 19 Jan 2026 09:23:40 +0000 Subject: [PATCH] chore: clean artifacts and fix device roles --- .gitignore | 3 +++ src/gateway/protocol/schema/devices.ts | 1 + src/gateway/server-methods/nodes.ts | 10 ++++++++-- src/infra/device-pairing.ts | 26 ++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4c5494e19..efeb989d3 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,8 @@ vendor/ apps/ios/Clawdbot.xcodeproj/ apps/ios/Clawdbot.xcodeproj/** apps/macos/.build/** +**/*.bun-build +apps/ios/*.xcfilelist # Vendor build artifacts vendor/a2ui/renderers/lit/dist/ @@ -48,6 +50,7 @@ apps/ios/fastlane/screenshots/ apps/ios/fastlane/test_output/ apps/ios/fastlane/logs/ apps/ios/fastlane/.env +apps/ios/fastlane/report.xml # fastlane build artifacts (local) apps/ios/*.ipa diff --git a/src/gateway/protocol/schema/devices.ts b/src/gateway/protocol/schema/devices.ts index a7633abdd..1a5fa3840 100644 --- a/src/gateway/protocol/schema/devices.ts +++ b/src/gateway/protocol/schema/devices.ts @@ -24,6 +24,7 @@ export const DevicePairRequestedEventSchema = Type.Object( clientId: Type.Optional(NonEmptyString), clientMode: Type.Optional(NonEmptyString), role: Type.Optional(NonEmptyString), + roles: Type.Optional(Type.Array(NonEmptyString)), scopes: Type.Optional(Type.Array(NonEmptyString)), remoteIp: Type.Optional(NonEmptyString), silent: Type.Optional(Type.Boolean()), diff --git a/src/gateway/server-methods/nodes.ts b/src/gateway/server-methods/nodes.ts index a4e033a7d..d040b6163 100644 --- a/src/gateway/server-methods/nodes.ts +++ b/src/gateway/server-methods/nodes.ts @@ -30,6 +30,12 @@ import { } from "./nodes.helpers.js"; import type { GatewayRequestHandlers } from "./types.js"; +function isNodeEntry(entry: { role?: string; roles?: string[] }) { + if (entry.role === "node") return true; + if (Array.isArray(entry.roles) && entry.roles.includes("node")) return true; + return false; +} + export const nodeHandlers: GatewayRequestHandlers = { "node.pair.request": async ({ params, respond, context }) => { if (!validateNodePairRequestParams(params)) { @@ -207,7 +213,7 @@ export const nodeHandlers: GatewayRequestHandlers = { const list = await listDevicePairing(); const pairedById = new Map( list.paired - .filter((entry) => entry.role === "node") + .filter((entry) => isNodeEntry(entry)) .map((entry) => [ entry.deviceId, { @@ -284,7 +290,7 @@ export const nodeHandlers: GatewayRequestHandlers = { } await respondUnavailableOnThrow(respond, async () => { const list = await listDevicePairing(); - const paired = list.paired.find((n) => n.deviceId === id && n.role === "node"); + const paired = list.paired.find((n) => n.deviceId === id && isNodeEntry(n)); const connected = context.nodeRegistry.listConnected(); const live = connected.find((n) => n.nodeId === id); diff --git a/src/infra/device-pairing.ts b/src/infra/device-pairing.ts index 893412757..8c1a9d166 100644 --- a/src/infra/device-pairing.ts +++ b/src/infra/device-pairing.ts @@ -12,6 +12,7 @@ export type DevicePairingPendingRequest = { clientId?: string; clientMode?: string; role?: string; + roles?: string[]; scopes?: string[]; remoteIp?: string; silent?: boolean; @@ -27,6 +28,7 @@ export type PairedDevice = { clientId?: string; clientMode?: string; role?: string; + roles?: string[]; scopes?: string[]; remoteIp?: string; createdAtMs: number; @@ -134,6 +136,24 @@ function normalizeDeviceId(deviceId: string) { return deviceId.trim(); } +function mergeRoles(...items: Array): string[] | undefined { + const roles = new Set(); + for (const item of items) { + if (!item) continue; + if (Array.isArray(item)) { + for (const role of item) { + const trimmed = role.trim(); + if (trimmed) roles.add(trimmed); + } + } else { + const trimmed = item.trim(); + if (trimmed) roles.add(trimmed); + } + } + if (roles.size === 0) return undefined; + return [...roles]; +} + export async function listDevicePairing(baseDir?: string): Promise { const state = await loadState(baseDir); const pending = Object.values(state.pendingById).sort((a, b) => b.ts - a.ts); @@ -179,6 +199,7 @@ export async function requestDevicePairing( clientId: req.clientId, clientMode: req.clientMode, role: req.role, + roles: req.role ? [req.role] : undefined, scopes: req.scopes, remoteIp: req.remoteIp, silent: req.silent, @@ -201,6 +222,7 @@ export async function approveDevicePairing( if (!pending) return null; const now = Date.now(); const existing = state.pairedByDeviceId[pending.deviceId]; + const roles = mergeRoles(existing?.roles, existing?.role, pending.roles, pending.role); const device: PairedDevice = { deviceId: pending.deviceId, publicKey: pending.publicKey, @@ -209,6 +231,7 @@ export async function approveDevicePairing( clientId: pending.clientId, clientMode: pending.clientMode, role: pending.role, + roles, scopes: pending.scopes, remoteIp: pending.remoteIp, createdAtMs: existing?.createdAtMs ?? now, @@ -244,12 +267,15 @@ export async function updatePairedDeviceMetadata( const state = await loadState(baseDir); const existing = state.pairedByDeviceId[normalizeDeviceId(deviceId)]; if (!existing) return; + const roles = mergeRoles(existing.roles, existing.role, patch.role); state.pairedByDeviceId[deviceId] = { ...existing, ...patch, deviceId: existing.deviceId, createdAtMs: existing.createdAtMs, approvedAtMs: existing.approvedAtMs, + role: patch.role ?? existing.role, + roles, }; await persistState(state, baseDir); });