mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
Presence: add device identity fields
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.steipete.clawdis.node
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.steipete.clawdis.node.bridge.BridgeDiscovery
|
||||
import com.steipete.clawdis.node.bridge.BridgeEndpoint
|
||||
import com.steipete.clawdis.node.bridge.BridgePairingClient
|
||||
@@ -182,6 +183,8 @@ class NodeRuntime(context: Context) {
|
||||
token = null,
|
||||
platform = "Android",
|
||||
version = "dev",
|
||||
deviceFamily = "Android",
|
||||
modelIdentifier = Build.MODEL,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
@@ -204,6 +207,8 @@ class NodeRuntime(context: Context) {
|
||||
token = authToken,
|
||||
platform = "Android",
|
||||
version = "dev",
|
||||
deviceFamily = "Android",
|
||||
modelIdentifier = Build.MODEL,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ class BridgePairingClient {
|
||||
val token: String?,
|
||||
val platform: String?,
|
||||
val version: String?,
|
||||
val deviceFamily: String?,
|
||||
val modelIdentifier: String?,
|
||||
)
|
||||
|
||||
data class PairResult(val ok: Boolean, val token: String?, val error: String? = null)
|
||||
@@ -55,6 +57,8 @@ class BridgePairingClient {
|
||||
hello.token?.let { put("token", JsonPrimitive(it)) }
|
||||
hello.platform?.let { put("platform", JsonPrimitive(it)) }
|
||||
hello.version?.let { put("version", JsonPrimitive(it)) }
|
||||
hello.deviceFamily?.let { put("deviceFamily", JsonPrimitive(it)) }
|
||||
hello.modelIdentifier?.let { put("modelIdentifier", JsonPrimitive(it)) }
|
||||
},
|
||||
)
|
||||
|
||||
@@ -76,6 +80,8 @@ class BridgePairingClient {
|
||||
hello.displayName?.let { put("displayName", JsonPrimitive(it)) }
|
||||
hello.platform?.let { put("platform", JsonPrimitive(it)) }
|
||||
hello.version?.let { put("version", JsonPrimitive(it)) }
|
||||
hello.deviceFamily?.let { put("deviceFamily", JsonPrimitive(it)) }
|
||||
hello.modelIdentifier?.let { put("modelIdentifier", JsonPrimitive(it)) }
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ class BridgeSession(
|
||||
val token: String?,
|
||||
val platform: String?,
|
||||
val version: String?,
|
||||
val deviceFamily: String?,
|
||||
val modelIdentifier: String?,
|
||||
)
|
||||
|
||||
data class InvokeRequest(val id: String, val command: String, val paramsJson: String?)
|
||||
@@ -191,6 +193,8 @@ class BridgeSession(
|
||||
hello.token?.let { put("token", JsonPrimitive(it)) }
|
||||
hello.platform?.let { put("platform", JsonPrimitive(it)) }
|
||||
hello.version?.let { put("version", JsonPrimitive(it)) }
|
||||
hello.deviceFamily?.let { put("deviceFamily", JsonPrimitive(it)) }
|
||||
hello.modelIdentifier?.let { put("modelIdentifier", JsonPrimitive(it)) }
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ class BridgePairingClientTest {
|
||||
token = "token-123",
|
||||
platform = "Android",
|
||||
version = "test",
|
||||
deviceFamily = "Android",
|
||||
modelIdentifier = "SM-X000",
|
||||
),
|
||||
)
|
||||
assertTrue(res.ok)
|
||||
@@ -91,6 +93,8 @@ class BridgePairingClientTest {
|
||||
token = null,
|
||||
platform = "Android",
|
||||
version = "test",
|
||||
deviceFamily = "Android",
|
||||
modelIdentifier = "SM-X000",
|
||||
),
|
||||
)
|
||||
assertTrue(res.ok)
|
||||
@@ -98,4 +102,3 @@ class BridgePairingClientTest {
|
||||
server.await()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,9 @@ actor BridgeClient {
|
||||
nodeId: hello.nodeId,
|
||||
displayName: hello.displayName,
|
||||
platform: hello.platform,
|
||||
version: hello.version),
|
||||
version: hello.version,
|
||||
deviceFamily: hello.deviceFamily,
|
||||
modelIdentifier: hello.modelIdentifier),
|
||||
over: connection)
|
||||
|
||||
onStatus?("Waiting for approval…")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import ClawdisKit
|
||||
import Darwin
|
||||
import Foundation
|
||||
import Network
|
||||
import Observation
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
@@ -131,12 +133,43 @@ final class BridgeConnectionController {
|
||||
displayName: displayName,
|
||||
token: token,
|
||||
platform: self.platformString(),
|
||||
version: self.appVersion())
|
||||
version: self.appVersion(),
|
||||
deviceFamily: self.deviceFamily(),
|
||||
modelIdentifier: self.modelIdentifier())
|
||||
}
|
||||
|
||||
private func platformString() -> String {
|
||||
let v = ProcessInfo.processInfo.operatingSystemVersion
|
||||
return "iOS \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)"
|
||||
let name: String
|
||||
switch UIDevice.current.userInterfaceIdiom {
|
||||
case .pad:
|
||||
name = "iPadOS"
|
||||
case .phone:
|
||||
name = "iOS"
|
||||
default:
|
||||
name = "iOS"
|
||||
}
|
||||
return "\(name) \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)"
|
||||
}
|
||||
|
||||
private func deviceFamily() -> String {
|
||||
switch UIDevice.current.userInterfaceIdiom {
|
||||
case .pad:
|
||||
return "iPad"
|
||||
case .phone:
|
||||
return "iPhone"
|
||||
default:
|
||||
return "iOS"
|
||||
}
|
||||
}
|
||||
|
||||
private func modelIdentifier() -> String {
|
||||
var systemInfo = utsname()
|
||||
uname(&systemInfo)
|
||||
let machine = withUnsafeBytes(of: &systemInfo.machine) { ptr in
|
||||
String(decoding: ptr.prefix { $0 != 0 }, as: UTF8.self)
|
||||
}
|
||||
return machine.isEmpty ? "unknown" : machine
|
||||
}
|
||||
|
||||
private func appVersion() -> String {
|
||||
|
||||
@@ -192,7 +192,7 @@ actor GatewayChannelActor {
|
||||
let clientName = InstanceIdentity.displayName
|
||||
|
||||
let reqId = UUID().uuidString
|
||||
let client: [String: ProtoAnyCodable] = [
|
||||
var client: [String: ProtoAnyCodable] = [
|
||||
"name": ProtoAnyCodable(clientName),
|
||||
"version": ProtoAnyCodable(
|
||||
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "dev"),
|
||||
@@ -200,6 +200,10 @@ actor GatewayChannelActor {
|
||||
"mode": ProtoAnyCodable("app"),
|
||||
"instanceId": ProtoAnyCodable(InstanceIdentity.instanceId),
|
||||
]
|
||||
client["deviceFamily"] = ProtoAnyCodable("Mac")
|
||||
if let model = InstanceIdentity.modelIdentifier {
|
||||
client["modelIdentifier"] = ProtoAnyCodable(model)
|
||||
}
|
||||
var params: [String: ProtoAnyCodable] = [
|
||||
"minProtocol": ProtoAnyCodable(GATEWAY_PROTOCOL_VERSION),
|
||||
"maxProtocol": ProtoAnyCodable(GATEWAY_PROTOCOL_VERSION),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Darwin
|
||||
import Foundation
|
||||
|
||||
enum InstanceIdentity {
|
||||
@@ -30,4 +31,15 @@ enum InstanceIdentity {
|
||||
}
|
||||
return "clawdis-mac"
|
||||
}()
|
||||
|
||||
static let modelIdentifier: String? = {
|
||||
var size = 0
|
||||
guard sysctlbyname("hw.model", nil, &size, nil, 0) == 0, size > 1 else { return nil }
|
||||
|
||||
var buffer = [CChar](repeating: 0, count: size)
|
||||
guard sysctlbyname("hw.model", &buffer, &size, nil, 0) == 0 else { return nil }
|
||||
|
||||
let s = String(cString: buffer).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return s.isEmpty ? nil : s
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ struct InstancesSettings: View {
|
||||
if let platform = inst.platform, let prettyPlatform = self.prettyPlatform(platform) {
|
||||
self.label(icon: self.platformIcon(platform), text: prettyPlatform)
|
||||
}
|
||||
if let deviceText = self.deviceDescription(inst),
|
||||
let deviceIcon = self.deviceIcon(inst)
|
||||
{
|
||||
self.label(icon: deviceIcon, text: deviceText)
|
||||
}
|
||||
self.label(icon: "clock", text: inst.lastInputDescription)
|
||||
if let mode = inst.mode { self.label(icon: "network", text: mode) }
|
||||
if let reason = inst.reason, !reason.isEmpty {
|
||||
@@ -115,6 +120,29 @@ struct InstancesSettings: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func deviceIcon(_ inst: InstanceInfo) -> String? {
|
||||
let family = inst.deviceFamily?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
if family.isEmpty { return nil }
|
||||
switch family.lowercased() {
|
||||
case "ipad":
|
||||
return "ipad"
|
||||
case "iphone":
|
||||
return "iphone"
|
||||
case "mac":
|
||||
return "laptopcomputer"
|
||||
default:
|
||||
return "cpu"
|
||||
}
|
||||
}
|
||||
|
||||
private func deviceDescription(_ inst: InstanceInfo) -> String? {
|
||||
let family = inst.deviceFamily?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
let model = inst.modelIdentifier?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
if !family.isEmpty, !model.isEmpty { return "\(family) (\(model))" }
|
||||
if !model.isEmpty { return model }
|
||||
return family.isEmpty ? nil : family
|
||||
}
|
||||
|
||||
private func prettyPlatform(_ raw: String) -> String? {
|
||||
let (prefix, version) = self.parsePlatform(raw)
|
||||
if prefix.isEmpty { return nil }
|
||||
|
||||
@@ -10,6 +10,8 @@ struct InstanceInfo: Identifiable, Codable {
|
||||
let ip: String?
|
||||
let version: String?
|
||||
let platform: String?
|
||||
let deviceFamily: String?
|
||||
let modelIdentifier: String?
|
||||
let lastInputSeconds: Int?
|
||||
let mode: String?
|
||||
let reason: String?
|
||||
@@ -284,6 +286,8 @@ final class InstancesStore {
|
||||
ip: entry.ip,
|
||||
version: entry.version,
|
||||
platform: entry.platform,
|
||||
deviceFamily: entry.devicefamily,
|
||||
modelIdentifier: entry.modelidentifier,
|
||||
lastInputSeconds: entry.lastinputseconds,
|
||||
mode: entry.mode,
|
||||
reason: entry.reason,
|
||||
@@ -308,6 +312,8 @@ extension InstancesStore {
|
||||
ip: "10.0.0.12",
|
||||
version: "1.2.3",
|
||||
platform: "macos 26.2.0",
|
||||
deviceFamily: "Mac",
|
||||
modelIdentifier: "Mac16,6",
|
||||
lastInputSeconds: 12,
|
||||
mode: "local",
|
||||
reason: "preview",
|
||||
@@ -319,6 +325,8 @@ extension InstancesStore {
|
||||
ip: "100.64.0.2",
|
||||
version: "1.2.3",
|
||||
platform: "linux 6.6.0",
|
||||
deviceFamily: "Linux",
|
||||
modelIdentifier: "x86_64",
|
||||
lastInputSeconds: 45,
|
||||
mode: "remote",
|
||||
reason: "preview",
|
||||
|
||||
@@ -45,6 +45,8 @@ final class PresenceReporter {
|
||||
"version": AnyHashable(version),
|
||||
"reason": AnyHashable(reason),
|
||||
]
|
||||
params["deviceFamily"] = AnyHashable("Mac")
|
||||
if let model = InstanceIdentity.modelIdentifier { params["modelIdentifier"] = AnyHashable(model) }
|
||||
if let lastInput { params["lastInputSeconds"] = AnyHashable(lastInput) }
|
||||
do {
|
||||
try await ControlChannel.shared.sendSystemEvent(text, params: params)
|
||||
|
||||
@@ -168,6 +168,8 @@ public struct PresenceEntry: Codable {
|
||||
public let ip: String?
|
||||
public let version: String?
|
||||
public let platform: String?
|
||||
public let devicefamily: String?
|
||||
public let modelidentifier: String?
|
||||
public let mode: String?
|
||||
public let lastinputseconds: Int?
|
||||
public let reason: String?
|
||||
@@ -181,6 +183,8 @@ public struct PresenceEntry: Codable {
|
||||
ip: String?,
|
||||
version: String?,
|
||||
platform: String?,
|
||||
devicefamily: String?,
|
||||
modelidentifier: String?,
|
||||
mode: String?,
|
||||
lastinputseconds: Int?,
|
||||
reason: String?,
|
||||
@@ -193,6 +197,8 @@ public struct PresenceEntry: Codable {
|
||||
self.ip = ip
|
||||
self.version = version
|
||||
self.platform = platform
|
||||
self.devicefamily = devicefamily
|
||||
self.modelidentifier = modelidentifier
|
||||
self.mode = mode
|
||||
self.lastinputseconds = lastinputseconds
|
||||
self.reason = reason
|
||||
@@ -206,6 +212,8 @@ public struct PresenceEntry: Codable {
|
||||
case ip
|
||||
case version
|
||||
case platform
|
||||
case devicefamily = "deviceFamily"
|
||||
case modelidentifier = "modelIdentifier"
|
||||
case mode
|
||||
case lastinputseconds = "lastInputSeconds"
|
||||
case reason
|
||||
|
||||
@@ -63,6 +63,8 @@ public struct BridgeHello: Codable, Sendable {
|
||||
public let token: String?
|
||||
public let platform: String?
|
||||
public let version: String?
|
||||
public let deviceFamily: String?
|
||||
public let modelIdentifier: String?
|
||||
|
||||
public init(
|
||||
type: String = "hello",
|
||||
@@ -70,7 +72,9 @@ public struct BridgeHello: Codable, Sendable {
|
||||
displayName: String?,
|
||||
token: String?,
|
||||
platform: String?,
|
||||
version: String?)
|
||||
version: String?,
|
||||
deviceFamily: String? = nil,
|
||||
modelIdentifier: String? = nil)
|
||||
{
|
||||
self.type = type
|
||||
self.nodeId = nodeId
|
||||
@@ -78,6 +82,8 @@ public struct BridgeHello: Codable, Sendable {
|
||||
self.token = token
|
||||
self.platform = platform
|
||||
self.version = version
|
||||
self.deviceFamily = deviceFamily
|
||||
self.modelIdentifier = modelIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +103,8 @@ public struct BridgePairRequest: Codable, Sendable {
|
||||
public let displayName: String?
|
||||
public let platform: String?
|
||||
public let version: String?
|
||||
public let deviceFamily: String?
|
||||
public let modelIdentifier: String?
|
||||
public let remoteAddress: String?
|
||||
|
||||
public init(
|
||||
@@ -105,6 +113,8 @@ public struct BridgePairRequest: Codable, Sendable {
|
||||
displayName: String?,
|
||||
platform: String?,
|
||||
version: String?,
|
||||
deviceFamily: String? = nil,
|
||||
modelIdentifier: String? = nil,
|
||||
remoteAddress: String? = nil)
|
||||
{
|
||||
self.type = type
|
||||
@@ -112,6 +122,8 @@ public struct BridgePairRequest: Codable, Sendable {
|
||||
self.displayName = displayName
|
||||
self.platform = platform
|
||||
self.version = version
|
||||
self.deviceFamily = deviceFamily
|
||||
self.modelIdentifier = modelIdentifier
|
||||
self.remoteAddress = remoteAddress
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ export const PresenceEntrySchema = Type.Object(
|
||||
ip: Type.Optional(NonEmptyString),
|
||||
version: Type.Optional(NonEmptyString),
|
||||
platform: Type.Optional(NonEmptyString),
|
||||
deviceFamily: Type.Optional(NonEmptyString),
|
||||
modelIdentifier: Type.Optional(NonEmptyString),
|
||||
mode: Type.Optional(NonEmptyString),
|
||||
lastInputSeconds: Type.Optional(Type.Integer({ minimum: 0 })),
|
||||
reason: Type.Optional(NonEmptyString),
|
||||
@@ -65,6 +67,8 @@ export const ConnectParamsSchema = Type.Object(
|
||||
platform: NonEmptyString,
|
||||
mode: NonEmptyString,
|
||||
instanceId: Type.Optional(NonEmptyString),
|
||||
deviceFamily: Type.Optional(NonEmptyString),
|
||||
modelIdentifier: Type.Optional(NonEmptyString),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
|
||||
@@ -2111,6 +2111,8 @@ describe("gateway server", () => {
|
||||
platform: "test",
|
||||
mode: "ui",
|
||||
instanceId: "abc",
|
||||
deviceFamily: "Mac",
|
||||
modelIdentifier: "Mac16,6",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2133,6 +2135,8 @@ describe("gateway server", () => {
|
||||
expect(clientEntry?.host).toBe("fingerprint");
|
||||
expect(clientEntry?.version).toBe("9.9.9");
|
||||
expect(clientEntry?.mode).toBe("ui");
|
||||
expect(clientEntry?.deviceFamily).toBe("Mac");
|
||||
expect(clientEntry?.modelIdentifier).toBe("Mac16,6");
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
|
||||
@@ -1300,12 +1300,16 @@ export async function startGatewayServer(
|
||||
const ip = node.remoteIp?.trim();
|
||||
const version = node.version?.trim() || "unknown";
|
||||
const platform = node.platform?.trim() || undefined;
|
||||
const deviceFamily = node.deviceFamily?.trim() || undefined;
|
||||
const modelIdentifier = node.modelIdentifier?.trim() || undefined;
|
||||
const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason iris-connected`;
|
||||
upsertPresence(node.nodeId, {
|
||||
host,
|
||||
ip,
|
||||
version,
|
||||
platform,
|
||||
deviceFamily,
|
||||
modelIdentifier,
|
||||
mode: "remote",
|
||||
reason: "iris-connected",
|
||||
lastInputSeconds: 0,
|
||||
@@ -1342,12 +1346,16 @@ export async function startGatewayServer(
|
||||
const ip = node.remoteIp?.trim();
|
||||
const version = node.version?.trim() || "unknown";
|
||||
const platform = node.platform?.trim() || undefined;
|
||||
const deviceFamily = node.deviceFamily?.trim() || undefined;
|
||||
const modelIdentifier = node.modelIdentifier?.trim() || undefined;
|
||||
const text = `Node: ${host}${ip ? ` (${ip})` : ""} · app ${version} · last input 0s ago · mode remote · reason iris-disconnected`;
|
||||
upsertPresence(node.nodeId, {
|
||||
host,
|
||||
ip,
|
||||
version,
|
||||
platform,
|
||||
deviceFamily,
|
||||
modelIdentifier,
|
||||
mode: "remote",
|
||||
reason: "iris-disconnected",
|
||||
lastInputSeconds: 0,
|
||||
@@ -1743,6 +1751,8 @@ export async function startGatewayServer(
|
||||
ip: isLoopbackAddress(remoteAddr) ? undefined : remoteAddr,
|
||||
version: connectParams.client.version,
|
||||
platform: connectParams.client.platform,
|
||||
deviceFamily: connectParams.client.deviceFamily,
|
||||
modelIdentifier: connectParams.client.modelIdentifier,
|
||||
mode: connectParams.client.mode,
|
||||
instanceId: connectParams.client.instanceId,
|
||||
reason: "connect",
|
||||
@@ -2424,6 +2434,14 @@ export async function startGatewayServer(
|
||||
typeof params.version === "string" ? params.version : undefined;
|
||||
const platform =
|
||||
typeof params.platform === "string" ? params.platform : undefined;
|
||||
const deviceFamily =
|
||||
typeof params.deviceFamily === "string"
|
||||
? params.deviceFamily
|
||||
: undefined;
|
||||
const modelIdentifier =
|
||||
typeof params.modelIdentifier === "string"
|
||||
? params.modelIdentifier
|
||||
: undefined;
|
||||
const lastInputSeconds =
|
||||
typeof params.lastInputSeconds === "number" &&
|
||||
Number.isFinite(params.lastInputSeconds)
|
||||
@@ -2444,6 +2462,8 @@ export async function startGatewayServer(
|
||||
mode,
|
||||
version,
|
||||
platform,
|
||||
deviceFamily,
|
||||
modelIdentifier,
|
||||
lastInputSeconds,
|
||||
reason,
|
||||
tags,
|
||||
|
||||
@@ -17,6 +17,8 @@ type BridgeHelloFrame = {
|
||||
token?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
};
|
||||
|
||||
type BridgePairRequestFrame = {
|
||||
@@ -25,6 +27,8 @@ type BridgePairRequestFrame = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
remoteAddress?: string;
|
||||
};
|
||||
|
||||
@@ -108,6 +112,8 @@ export type NodeBridgeClientInfo = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
remoteIp?: string;
|
||||
};
|
||||
|
||||
@@ -263,6 +269,8 @@ export async function startNodeBridgeServer(
|
||||
displayName: verified.node.displayName ?? hello.displayName,
|
||||
platform: verified.node.platform ?? hello.platform,
|
||||
version: verified.node.version ?? hello.version,
|
||||
deviceFamily: verified.node.deviceFamily ?? hello.deviceFamily,
|
||||
modelIdentifier: verified.node.modelIdentifier ?? hello.modelIdentifier,
|
||||
remoteIp: remoteAddress,
|
||||
};
|
||||
connections.set(nodeId, { socket, nodeInfo, invokeWaiters });
|
||||
@@ -319,6 +327,8 @@ export async function startNodeBridgeServer(
|
||||
displayName: req.displayName,
|
||||
platform: req.platform,
|
||||
version: req.version,
|
||||
deviceFamily: req.deviceFamily,
|
||||
modelIdentifier: req.modelIdentifier,
|
||||
remoteIp: remoteAddress,
|
||||
},
|
||||
opts.pairingBaseDir,
|
||||
|
||||
@@ -9,6 +9,8 @@ export type NodePairingPendingRequest = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
remoteIp?: string;
|
||||
isRepair?: boolean;
|
||||
ts: number;
|
||||
@@ -20,6 +22,8 @@ export type NodePairingPairedNode = {
|
||||
displayName?: string;
|
||||
platform?: string;
|
||||
version?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
remoteIp?: string;
|
||||
createdAtMs: number;
|
||||
approvedAtMs: number;
|
||||
@@ -172,6 +176,8 @@ export async function requestNodePairing(
|
||||
displayName: req.displayName,
|
||||
platform: req.platform,
|
||||
version: req.version,
|
||||
deviceFamily: req.deviceFamily,
|
||||
modelIdentifier: req.modelIdentifier,
|
||||
remoteIp: req.remoteIp,
|
||||
isRepair,
|
||||
ts: Date.now(),
|
||||
@@ -199,6 +205,8 @@ export async function approveNodePairing(
|
||||
displayName: pending.displayName,
|
||||
platform: pending.platform,
|
||||
version: pending.version,
|
||||
deviceFamily: pending.deviceFamily,
|
||||
modelIdentifier: pending.modelIdentifier,
|
||||
remoteIp: pending.remoteIp,
|
||||
createdAtMs: existing?.createdAtMs ?? now,
|
||||
approvedAtMs: now,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import os from "node:os";
|
||||
|
||||
export type SystemPresence = {
|
||||
@@ -5,6 +6,8 @@ export type SystemPresence = {
|
||||
ip?: string;
|
||||
version?: string;
|
||||
platform?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
lastInputSeconds?: number;
|
||||
mode?: string;
|
||||
reason?: string;
|
||||
@@ -47,6 +50,17 @@ function initSelfPresence() {
|
||||
const ip = resolvePrimaryIPv4() ?? undefined;
|
||||
const version =
|
||||
process.env.CLAWDIS_VERSION ?? process.env.npm_package_version ?? "unknown";
|
||||
const modelIdentifier = (() => {
|
||||
const p = os.platform();
|
||||
if (p === "darwin") {
|
||||
const res = spawnSync("sysctl", ["-n", "hw.model"], {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
const out = typeof res.stdout === "string" ? res.stdout.trim() : "";
|
||||
return out.length > 0 ? out : undefined;
|
||||
}
|
||||
return os.arch();
|
||||
})();
|
||||
const platform = (() => {
|
||||
const p = os.platform();
|
||||
const rel = os.release();
|
||||
@@ -54,12 +68,21 @@ function initSelfPresence() {
|
||||
if (p === "win32") return `windows ${rel}`;
|
||||
return `${p} ${rel}`;
|
||||
})();
|
||||
const deviceFamily = (() => {
|
||||
const p = os.platform();
|
||||
if (p === "darwin") return "Mac";
|
||||
if (p === "win32") return "Windows";
|
||||
if (p === "linux") return "Linux";
|
||||
return p;
|
||||
})();
|
||||
const text = `Gateway: ${host}${ip ? ` (${ip})` : ""} · app ${version} · mode gateway · reason self`;
|
||||
const selfEntry: SystemPresence = {
|
||||
host,
|
||||
ip,
|
||||
version,
|
||||
platform,
|
||||
deviceFamily,
|
||||
modelIdentifier,
|
||||
mode: "gateway",
|
||||
reason: "self",
|
||||
text,
|
||||
@@ -123,6 +146,8 @@ type SystemPresencePayload = {
|
||||
ip?: string;
|
||||
version?: string;
|
||||
platform?: string;
|
||||
deviceFamily?: string;
|
||||
modelIdentifier?: string;
|
||||
lastInputSeconds?: number;
|
||||
mode?: string;
|
||||
reason?: string;
|
||||
@@ -147,6 +172,8 @@ export function updateSystemPresence(payload: SystemPresencePayload) {
|
||||
ip: payload.ip ?? parsed.ip ?? existing.ip,
|
||||
version: payload.version ?? parsed.version ?? existing.version,
|
||||
platform: payload.platform ?? existing.platform,
|
||||
deviceFamily: payload.deviceFamily ?? existing.deviceFamily,
|
||||
modelIdentifier: payload.modelIdentifier ?? existing.modelIdentifier,
|
||||
mode: payload.mode ?? parsed.mode ?? existing.mode,
|
||||
lastInputSeconds:
|
||||
payload.lastInputSeconds ??
|
||||
|
||||
Reference in New Issue
Block a user