diff --git a/CHANGELOG.md b/CHANGELOG.md index e9a76df62..e68511a6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ Docs: https://docs.clawd.bot - macOS: avoid touching launchd in Remote over SSH so quitting the app no longer disables the remote gateway. (#1105) - Memory: index atomically so failed reindex preserves the previous memory database. (#1151) - Memory: avoid sqlite-vec unique constraint failures when reindexing duplicate chunk ids. (#1151) +- Skills: improve remote bin probe logging with node labels + connectivity hints. - Exec approvals: enforce allowlist when ask is off; prefer raw command for node approvals/events. - Tools: return a companion-app-required message when node exec is requested with no paired node. - Streaming: emit assistant deltas for OpenAI-compatible SSE chunks. (#1147) — thanks @alauppe. diff --git a/src/gateway/server-node-bridge.ts b/src/gateway/server-node-bridge.ts index 20f9ee78b..3312e637f 100644 --- a/src/gateway/server-node-bridge.ts +++ b/src/gateway/server-node-bridge.ts @@ -148,6 +148,7 @@ export async function startGatewayNodeBridge(params: { platform: node.platform, deviceFamily: node.deviceFamily, commands: node.commands, + remoteIp: node.remoteIp, }); bumpSkillsSnapshotVersion({ reason: "remote-node" }); await refreshRemoteNodeBins({ diff --git a/src/infra/skills-remote.ts b/src/infra/skills-remote.ts index 49cca2ee2..cf81b7657 100644 --- a/src/infra/skills-remote.ts +++ b/src/infra/skills-remote.ts @@ -14,12 +14,53 @@ type RemoteNodeRecord = { deviceFamily?: string; commands?: string[]; bins: Set; + remoteIp?: string; }; const log = createSubsystemLogger("gateway/skills-remote"); const remoteNodes = new Map(); let remoteBridge: NodeBridgeServer | null = null; +function describeNode(nodeId: string): string { + const record = remoteNodes.get(nodeId); + const name = record?.displayName?.trim(); + const base = name && name !== nodeId ? `${name} (${nodeId})` : nodeId; + const ip = record?.remoteIp?.trim(); + return ip ? `${base} @ ${ip}` : base; +} + +function extractErrorMessage(err: unknown): string | undefined { + if (!err) return undefined; + if (typeof err === "string") return err; + if (err instanceof Error) return err.message; + if (typeof err === "object" && "message" in err && typeof err.message === "string") { + return err.message; + } + return String(err); +} + +function logRemoteBinProbeFailure(nodeId: string, err: unknown) { + const message = extractErrorMessage(err); + const label = describeNode(nodeId); + if (message?.includes("UNAVAILABLE: node not connected")) { + log.info( + `remote bin probe skipped: node not connected (${label}); check nodes list/status for ${label}`, + ); + return; + } + if (message?.includes("UNAVAILABLE: invoke timeout")) { + log.warn(`remote bin probe timed out (${label}); check node connectivity for ${label}`); + return; + } + if (message?.includes("bridge connection closed")) { + log.warn( + `remote bin probe aborted: bridge connection closed (${label}); check nodes list/status for ${label}`, + ); + return; + } + log.warn(`remote bin probe error (${label}): ${message ?? "unknown"}`); +} + function isMacPlatform(platform?: string, deviceFamily?: string): boolean { const platformNorm = String(platform ?? "") .trim() @@ -47,6 +88,7 @@ function upsertNode(record: { platform?: string; deviceFamily?: string; commands?: string[]; + remoteIp?: string; bins?: string[]; }) { const existing = remoteNodes.get(record.nodeId); @@ -57,6 +99,7 @@ function upsertNode(record: { platform: record.platform ?? existing?.platform, deviceFamily: record.deviceFamily ?? existing?.deviceFamily, commands: record.commands ?? existing?.commands, + remoteIp: record.remoteIp ?? existing?.remoteIp, bins, }); } @@ -76,6 +119,7 @@ export async function primeRemoteSkillsCache() { platform: node.platform, deviceFamily: node.deviceFamily, commands: node.commands, + remoteIp: node.remoteIp, bins: node.bins, }); if (isMacPlatform(node.platform, node.deviceFamily) && supportsSystemRun(node.commands)) { @@ -96,6 +140,7 @@ export function recordRemoteNodeInfo(node: { platform?: string; deviceFamily?: string; commands?: string[]; + remoteIp?: string; }) { upsertNode(node); } @@ -203,7 +248,7 @@ export async function refreshRemoteNodeBins(params: { }, ); if (!res.ok) { - log.warn(`remote bin probe failed (${params.nodeId}): ${res.error?.message ?? "unknown"}`); + logRemoteBinProbeFailure(params.nodeId, res.error?.message ?? "unknown"); return; } const bins = parseBinProbePayload(res.payloadJSON); @@ -211,7 +256,7 @@ export async function refreshRemoteNodeBins(params: { await updatePairedNodeMetadata(params.nodeId, { bins }); bumpSkillsSnapshotVersion({ reason: "remote-node" }); } catch (err) { - log.warn(`remote bin probe error (${params.nodeId}): ${String(err)}`); + logRemoteBinProbeFailure(params.nodeId, err); } }