diff --git a/CHANGELOG.md b/CHANGELOG.md index 8348921a5..e9a76df62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,44 @@ Docs: https://docs.clawd.bot - Memory: add native Gemini embeddings provider for memory search. (#1151) - Agents: add local docs path resolution and include docs/mirror/source/community pointers in the system prompt. - Slack: add HTTP webhook mode via Bolt HTTP receiver for Events API deployments. (#1143) — thanks @jdrhyne. +- Nodes: report core/ui versions in node list + presence; surface both in CLI + macOS UI. +<<<<<<< HEAD +- Agents: add local docs path resolution and include docs/mirror/source/community pointers in the system prompt. +- Slack: add HTTP webhook mode via Bolt HTTP receiver for Events API deployments. (#1143) — thanks @jdrhyne. +||||||| parent of 903e9be49 (feat: surface node core/ui versions in macOS) + +### Fixes +- Auth profiles: keep auto-pinned preference while allowing rotation on failover; user pins stay locked. (#1138) — thanks @cheeeee. +- 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) + +## 2026.1.18-5 + +### Changes +- Dependencies: update core + plugin deps (grammy, vitest, openai, Microsoft agents hosting, etc.). + +## 2026.1.18-3 + +### Changes +======= +- Nodes: report core/ui versions in node list + presence; surface both in CLI + macOS UI. + +### Fixes +- Auth profiles: keep auto-pinned preference while allowing rotation on failover; user pins stay locked. (#1138) — thanks @cheeeee. +- 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) + +## 2026.1.18-5 + +### Changes +- Dependencies: update core + plugin deps (grammy, vitest, openai, Microsoft agents hosting, etc.). + +## 2026.1.18-3 + +### Changes +>>>>>>> 903e9be49 (feat: surface node core/ui versions in macOS) - Exec: add host/security/ask routing for gateway + node exec. - Exec: add `/exec` directive for per-session exec defaults (host/security/ask/node). - macOS: migrate exec approvals to `~/.clawdbot/exec-approvals.json` with per-agent allowlists and skill auto-allow toggle. diff --git a/apps/macos/Sources/Clawdbot/Bridge/BridgeConnectionHandler.swift b/apps/macos/Sources/Clawdbot/Bridge/BridgeConnectionHandler.swift index ebe753e5d..ea38c3ee1 100644 --- a/apps/macos/Sources/Clawdbot/Bridge/BridgeConnectionHandler.swift +++ b/apps/macos/Sources/Clawdbot/Bridge/BridgeConnectionHandler.swift @@ -8,6 +8,8 @@ struct BridgeNodeInfo: Sendable { var displayName: String? var platform: String? var version: String? + var coreVersion: String? + var uiVersion: String? var deviceFamily: String? var modelIdentifier: String? var remoteAddress: String? @@ -147,6 +149,8 @@ actor BridgeConnectionHandler { displayName: hello.displayName, platform: hello.platform, version: hello.version, + coreVersion: hello.coreVersion, + uiVersion: hello.uiVersion, deviceFamily: hello.deviceFamily, modelIdentifier: hello.modelIdentifier, remoteAddress: self.remoteAddressString(), @@ -171,6 +175,8 @@ actor BridgeConnectionHandler { displayName: req.displayName, platform: req.platform, version: req.version, + coreVersion: req.coreVersion, + uiVersion: req.uiVersion, deviceFamily: req.deviceFamily, modelIdentifier: req.modelIdentifier, caps: req.caps, @@ -186,6 +192,8 @@ actor BridgeConnectionHandler { displayName: enriched.displayName, platform: enriched.platform, version: enriched.version, + coreVersion: enriched.coreVersion, + uiVersion: enriched.uiVersion, deviceFamily: enriched.deviceFamily, modelIdentifier: enriched.modelIdentifier, remoteAddress: enriched.remoteAddress, diff --git a/apps/macos/Sources/Clawdbot/GatewayConnection.swift b/apps/macos/Sources/Clawdbot/GatewayConnection.swift index bf569213e..477a65954 100644 --- a/apps/macos/Sources/Clawdbot/GatewayConnection.swift +++ b/apps/macos/Sources/Clawdbot/GatewayConnection.swift @@ -249,6 +249,13 @@ actor GatewayConnection { return trimmed.isEmpty ? nil : trimmed } + func cachedGatewayVersion() -> String? { + guard let snapshot = self.lastSnapshot else { return nil } + let raw = snapshot.server["version"]?.value as? String + let trimmed = raw?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) ?? "" + return trimmed.isEmpty ? nil : trimmed + } + func snapshotPaths() -> (configPath: String?, stateDir: String?) { guard let snapshot = self.lastSnapshot else { return (nil, nil) } let configPath = snapshot.snapshot.configpath?.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/apps/macos/Sources/Clawdbot/MenuSessionsInjector.swift b/apps/macos/Sources/Clawdbot/MenuSessionsInjector.swift index 7092598fa..c1617d17e 100644 --- a/apps/macos/Sources/Clawdbot/MenuSessionsInjector.swift +++ b/apps/macos/Sources/Clawdbot/MenuSessionsInjector.swift @@ -747,8 +747,8 @@ extension MenuSessionsInjector { menu.addItem(self.makeNodeCopyItem(label: "Platform", value: platform)) } - if let version = entry.version?.nonEmpty { - menu.addItem(self.makeNodeCopyItem(label: "Version", value: self.formatVersionLabel(version))) + if let version = NodeMenuEntryFormatter.detailRightVersion(entry)?.nonEmpty { + menu.addItem(self.makeNodeCopyItem(label: "Version", value: version)) } menu.addItem(self.makeNodeDetailItem(label: "Connected", value: entry.isConnected ? "Yes" : "No")) diff --git a/apps/macos/Sources/Clawdbot/NodeMode/MacNodeBridgePairingClient.swift b/apps/macos/Sources/Clawdbot/NodeMode/MacNodeBridgePairingClient.swift index 38b4877bc..22341d902 100644 --- a/apps/macos/Sources/Clawdbot/NodeMode/MacNodeBridgePairingClient.swift +++ b/apps/macos/Sources/Clawdbot/NodeMode/MacNodeBridgePairingClient.swift @@ -95,6 +95,8 @@ actor MacNodeBridgePairingClient { displayName: hello.displayName, platform: hello.platform, version: hello.version, + coreVersion: hello.coreVersion, + uiVersion: hello.uiVersion, deviceFamily: hello.deviceFamily, modelIdentifier: hello.modelIdentifier, caps: hello.caps, diff --git a/apps/macos/Sources/Clawdbot/NodeMode/MacNodeModeCoordinator.swift b/apps/macos/Sources/Clawdbot/NodeMode/MacNodeModeCoordinator.swift index 06187f973..7217d76a0 100644 --- a/apps/macos/Sources/Clawdbot/NodeMode/MacNodeModeCoordinator.swift +++ b/apps/macos/Sources/Clawdbot/NodeMode/MacNodeModeCoordinator.swift @@ -114,12 +114,19 @@ final class MacNodeModeCoordinator { let caps = self.currentCaps() let commands = self.currentCommands(caps: caps) let permissions = await self.currentPermissions() + let uiVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String + let liveGatewayVersion = await GatewayConnection.shared.cachedGatewayVersion() + let fallbackGatewayVersion = GatewayProcessManager.shared.environmentStatus.gatewayVersion + let coreVersion = (liveGatewayVersion ?? fallbackGatewayVersion)? + .trimmingCharacters(in: .whitespacesAndNewlines) return BridgeHello( nodeId: Self.nodeId(), displayName: InstanceIdentity.displayName, token: token, platform: "macos", - version: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String, + version: uiVersion, + coreVersion: coreVersion?.isEmpty == false ? coreVersion : nil, + uiVersion: uiVersion, deviceFamily: "Mac", modelIdentifier: InstanceIdentity.modelIdentifier, caps: caps, diff --git a/apps/macos/Sources/Clawdbot/NodesMenu.swift b/apps/macos/Sources/Clawdbot/NodesMenu.swift index 3ba560aaf..f88177d8d 100644 --- a/apps/macos/Sources/Clawdbot/NodesMenu.swift +++ b/apps/macos/Sources/Clawdbot/NodesMenu.swift @@ -35,8 +35,9 @@ struct NodeMenuEntryFormatter { if let platform = self.platformText(entry) { parts.append("platform \(platform)") } - if let version = entry.version?.nonEmpty { - parts.append("app \(self.compactVersion(version))") + let versionLabels = self.versionLabels(entry) + if !versionLabels.isEmpty { + parts.append(versionLabels.joined(separator: " · ")) } parts.append("status \(self.roleText(entry))") return parts.joined(separator: " · ") @@ -60,8 +61,9 @@ struct NodeMenuEntryFormatter { } static func detailRightVersion(_ entry: NodeInfo) -> String? { - guard let version = entry.version?.nonEmpty else { return nil } - return self.shortVersionLabel(version) + let labels = self.versionLabels(entry, compact: false) + if labels.isEmpty { return nil } + return labels.joined(separator: " · ") } static func platformText(_ entry: NodeInfo) -> String? { @@ -127,6 +129,39 @@ struct NodeMenuEntryFormatter { return compact } + private static func versionLabels(_ entry: NodeInfo, compact: Bool = true) -> [String] { + let (core, ui) = self.resolveVersions(entry) + var labels: [String] = [] + if let core { + let label = compact ? self.compactVersion(core) : self.shortVersionLabel(core) + labels.append("core \(label)") + } + if let ui { + let label = compact ? self.compactVersion(ui) : self.shortVersionLabel(ui) + labels.append("ui \(label)") + } + return labels + } + + private static func resolveVersions(_ entry: NodeInfo) -> (core: String?, ui: String?) { + let core = entry.coreVersion?.nonEmpty + let ui = entry.uiVersion?.nonEmpty + if core != nil || ui != nil { + return (core, ui) + } + guard let legacy = entry.version?.nonEmpty else { return (nil, nil) } + if self.isHeadlessPlatform(entry) { + return (legacy, nil) + } + return (nil, legacy) + } + + private static func isHeadlessPlatform(_ entry: NodeInfo) -> Bool { + let raw = entry.platform?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() ?? "" + if raw == "darwin" || raw == "linux" || raw == "win32" || raw == "windows" { return true } + return false + } + static func leadingSymbol(_ entry: NodeInfo) -> String { if self.isGateway(entry) { return self.safeSystemSymbol( diff --git a/apps/macos/Sources/Clawdbot/NodesStore.swift b/apps/macos/Sources/Clawdbot/NodesStore.swift index 24501bc6f..51d43336d 100644 --- a/apps/macos/Sources/Clawdbot/NodesStore.swift +++ b/apps/macos/Sources/Clawdbot/NodesStore.swift @@ -7,6 +7,8 @@ struct NodeInfo: Identifiable, Codable { let displayName: String? let platform: String? let version: String? + let coreVersion: String? + let uiVersion: String? let deviceFamily: String? let modelIdentifier: String? let remoteIp: String? diff --git a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift index ec6637e22..b135261d2 100644 --- a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift +++ b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift @@ -530,6 +530,8 @@ public struct NodePairRequestParams: Codable, Sendable { public let displayname: String? public let platform: String? public let version: String? + public let coreversion: String? + public let uiversion: String? public let devicefamily: String? public let modelidentifier: String? public let caps: [String]? @@ -542,6 +544,8 @@ public struct NodePairRequestParams: Codable, Sendable { displayname: String?, platform: String?, version: String?, + coreversion: String?, + uiversion: String?, devicefamily: String?, modelidentifier: String?, caps: [String]?, @@ -553,6 +557,8 @@ public struct NodePairRequestParams: Codable, Sendable { self.displayname = displayname self.platform = platform self.version = version + self.coreversion = coreversion + self.uiversion = uiversion self.devicefamily = devicefamily self.modelidentifier = modelidentifier self.caps = caps @@ -565,6 +571,8 @@ public struct NodePairRequestParams: Codable, Sendable { case displayname = "displayName" case platform case version + case coreversion = "coreVersion" + case uiversion = "uiVersion" case devicefamily = "deviceFamily" case modelidentifier = "modelIdentifier" case caps diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/BridgeFrames.swift b/apps/shared/ClawdbotKit/Sources/ClawdbotKit/BridgeFrames.swift index 833a3c96b..5d45ab269 100644 --- a/apps/shared/ClawdbotKit/Sources/ClawdbotKit/BridgeFrames.swift +++ b/apps/shared/ClawdbotKit/Sources/ClawdbotKit/BridgeFrames.swift @@ -63,6 +63,8 @@ public struct BridgeHello: Codable, Sendable { public let token: String? public let platform: String? public let version: String? + public let coreVersion: String? + public let uiVersion: String? public let deviceFamily: String? public let modelIdentifier: String? public let caps: [String]? @@ -76,6 +78,8 @@ public struct BridgeHello: Codable, Sendable { token: String?, platform: String?, version: String?, + coreVersion: String? = nil, + uiVersion: String? = nil, deviceFamily: String? = nil, modelIdentifier: String? = nil, caps: [String]? = nil, @@ -88,6 +92,8 @@ public struct BridgeHello: Codable, Sendable { self.token = token self.platform = platform self.version = version + self.coreVersion = coreVersion + self.uiVersion = uiVersion self.deviceFamily = deviceFamily self.modelIdentifier = modelIdentifier self.caps = caps @@ -121,6 +127,8 @@ public struct BridgePairRequest: Codable, Sendable { public let displayName: String? public let platform: String? public let version: String? + public let coreVersion: String? + public let uiVersion: String? public let deviceFamily: String? public let modelIdentifier: String? public let caps: [String]? @@ -135,6 +143,8 @@ public struct BridgePairRequest: Codable, Sendable { displayName: String?, platform: String?, version: String?, + coreVersion: String? = nil, + uiVersion: String? = nil, deviceFamily: String? = nil, modelIdentifier: String? = nil, caps: [String]? = nil, @@ -148,6 +158,8 @@ public struct BridgePairRequest: Codable, Sendable { self.displayName = displayName self.platform = platform self.version = version + self.coreVersion = coreVersion + self.uiVersion = uiVersion self.deviceFamily = deviceFamily self.modelIdentifier = modelIdentifier self.caps = caps