From 8d888b426f6dfe4d3de58a6d80a4745123158db2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 9 Dec 2025 17:11:25 +0000 Subject: [PATCH] chore: format swift/ts and fix gateway lint --- apps/macos/Sources/Clawdis/AppState.swift | 31 ++++-- .../Sources/Clawdis/ControlChannel.swift | 7 +- apps/macos/Sources/Clawdis/DebugActions.swift | 3 +- .../Sources/Clawdis/GatewayChannel.swift | 7 +- .../Sources/Clawdis/GeneralSettings.swift | 4 +- apps/macos/Sources/Clawdis/HealthStore.swift | 18 +-- .../Sources/Clawdis/InstancesStore.swift | 10 +- apps/macos/Sources/Clawdis/Onboarding.swift | 3 +- .../Sources/Clawdis/PermissionsSettings.swift | 5 +- .../Sources/Clawdis/RelayEnvironment.swift | 2 +- apps/macos/Sources/Clawdis/SessionData.swift | 2 +- .../macos/Sources/Clawdis/WebChatWindow.swift | 40 ++++++- .../ClawdisProtocol/GatewayModels.swift | 103 ++++++++++-------- src/auto-reply/reply.ts | 2 +- src/cli/program.force.test.ts | 11 +- src/cli/program.ts | 5 +- src/gateway/server.ts | 69 +++++++----- src/infra/provider-summary.ts | 4 +- 18 files changed, 205 insertions(+), 121 deletions(-) diff --git a/apps/macos/Sources/Clawdis/AppState.swift b/apps/macos/Sources/Clawdis/AppState.swift index 2a0ff705d..90ac6f746 100644 --- a/apps/macos/Sources/Clawdis/AppState.swift +++ b/apps/macos/Sources/Clawdis/AppState.swift @@ -26,11 +26,14 @@ final class AppState: ObservableObject { } @Published var onboardingSeen: Bool { - didSet { self.ifNotPreview { UserDefaults.standard.set(self.onboardingSeen, forKey: "clawdis.onboardingSeen") } } + didSet { self.ifNotPreview { UserDefaults.standard.set(self.onboardingSeen, forKey: "clawdis.onboardingSeen") } + } } @Published var debugPaneEnabled: Bool { - didSet { self.ifNotPreview { UserDefaults.standard.set(self.debugPaneEnabled, forKey: "clawdis.debugPaneEnabled") } } + didSet { + self.ifNotPreview { UserDefaults.standard.set(self.debugPaneEnabled, forKey: "clawdis.debugPaneEnabled") } + } } @Published var swabbleEnabled: Bool { @@ -63,7 +66,9 @@ final class AppState: ObservableObject { } @Published var iconAnimationsEnabled: Bool { - didSet { self.ifNotPreview { UserDefaults.standard.set(self.iconAnimationsEnabled, forKey: iconAnimationsEnabledKey) } } + didSet { self.ifNotPreview { UserDefaults.standard.set( + self.iconAnimationsEnabled, + forKey: iconAnimationsEnabledKey) } } } @Published var showDockIcon: Bool { @@ -98,19 +103,27 @@ final class AppState: ObservableObject { } @Published var voiceWakeAdditionalLocaleIDs: [String] { - didSet { self.ifNotPreview { UserDefaults.standard.set(self.voiceWakeAdditionalLocaleIDs, forKey: voiceWakeAdditionalLocalesKey) } } + didSet { self.ifNotPreview { UserDefaults.standard.set( + self.voiceWakeAdditionalLocaleIDs, + forKey: voiceWakeAdditionalLocalesKey) } } } @Published var voiceWakeForwardEnabled: Bool { - didSet { self.ifNotPreview { UserDefaults.standard.set(self.voiceWakeForwardEnabled, forKey: voiceWakeForwardEnabledKey) } } + didSet { self.ifNotPreview { UserDefaults.standard.set( + self.voiceWakeForwardEnabled, + forKey: voiceWakeForwardEnabledKey) } } } @Published var voiceWakeForwardCommand: String { - didSet { self.ifNotPreview { UserDefaults.standard.set(self.voiceWakeForwardCommand, forKey: voiceWakeForwardCommandKey) } } + didSet { self.ifNotPreview { UserDefaults.standard.set( + self.voiceWakeForwardCommand, + forKey: voiceWakeForwardCommandKey) } } } @Published var voicePushToTalkEnabled: Bool { - didSet { self.ifNotPreview { UserDefaults.standard.set(self.voicePushToTalkEnabled, forKey: voicePushToTalkEnabledKey) } } + didSet { self.ifNotPreview { UserDefaults.standard.set( + self.voicePushToTalkEnabled, + forKey: voicePushToTalkEnabledKey) } } } @Published var iconOverride: IconOverrideSelection { @@ -131,7 +144,9 @@ final class AppState: ObservableObject { } @Published var connectionMode: ConnectionMode { - didSet { self.ifNotPreview { UserDefaults.standard.set(self.connectionMode.rawValue, forKey: connectionModeKey) } } + didSet { + self.ifNotPreview { UserDefaults.standard.set(self.connectionMode.rawValue, forKey: connectionModeKey) } + } } @Published var webChatEnabled: Bool { diff --git a/apps/macos/Sources/Clawdis/ControlChannel.swift b/apps/macos/Sources/Clawdis/ControlChannel.swift index 7b66afce4..bcd1e5546 100644 --- a/apps/macos/Sources/Clawdis/ControlChannel.swift +++ b/apps/macos/Sources/Clawdis/ControlChannel.swift @@ -1,7 +1,7 @@ +import ClawdisProtocol import Foundation import OSLog import SwiftUI -import ClawdisProtocol struct ControlHeartbeatEvent: Codable { let ts: Double @@ -14,7 +14,7 @@ struct ControlHeartbeatEvent: Codable { } struct ControlAgentEvent: Codable, Sendable, Identifiable { - var id: String { "\(runId)-\(seq)" } + var id: String { "\(self.runId)-\(self.seq)" } let runId: String let seq: Int let stream: String @@ -173,7 +173,8 @@ final class ControlChannel: ObservableObject { if let data = evt.payload?.value, JSONSerialization.isValidJSONObject(data), let blob = try? JSONSerialization.data(withJSONObject: data), - let agent = try? JSONDecoder().decode(AgentEvent.self, from: blob) { + let agent = try? JSONDecoder().decode(AgentEvent.self, from: blob) + { Task { @MainActor in AgentEventStore.shared.append(ControlAgentEvent( runId: agent.runid, diff --git a/apps/macos/Sources/Clawdis/DebugActions.swift b/apps/macos/Sources/Clawdis/DebugActions.swift index fdb0b29e1..33643e248 100644 --- a/apps/macos/Sources/Clawdis/DebugActions.swift +++ b/apps/macos/Sources/Clawdis/DebugActions.swift @@ -108,7 +108,8 @@ enum DebugActionError: LocalizedError { var errorDescription: String? { switch self { - case let .message(text): text + case let .message(text): + text } } } diff --git a/apps/macos/Sources/Clawdis/GatewayChannel.swift b/apps/macos/Sources/Clawdis/GatewayChannel.swift index 373f0b38e..34be3a323 100644 --- a/apps/macos/Sources/Clawdis/GatewayChannel.swift +++ b/apps/macos/Sources/Clawdis/GatewayChannel.swift @@ -1,6 +1,6 @@ +import ClawdisProtocol import Foundation import OSLog -import ClawdisProtocol struct GatewayEvent: Codable { let type: String @@ -27,7 +27,7 @@ private actor GatewayChannelActor { private var shouldReconnect = true private var lastSeq: Int? private var lastTick: Date? - private var tickIntervalMs: Double = 30_000 + private var tickIntervalMs: Double = 30000 private let decoder = JSONDecoder() private let encoder = JSONEncoder() @@ -88,7 +88,8 @@ private actor GatewayChannelActor { let type = obj["type"] as? String else { return false } if type == "hello-ok" { if let policy = obj["policy"] as? [String: Any], - let tick = policy["tickIntervalMs"] as? Double { + let tick = policy["tickIntervalMs"] as? Double + { self.tickIntervalMs = tick } self.lastTick = Date() diff --git a/apps/macos/Sources/Clawdis/GeneralSettings.swift b/apps/macos/Sources/Clawdis/GeneralSettings.swift index fbe2074e8..e4c1e7873 100644 --- a/apps/macos/Sources/Clawdis/GeneralSettings.swift +++ b/apps/macos/Sources/Clawdis/GeneralSettings.swift @@ -296,7 +296,9 @@ struct GeneralSettings: View { .disabled(self.relayInstalling) } - Text(self.relayInstallMessage ?? "Installs the global \"clawdis\" package and expects the gateway on port 18789.") + Text(self + .relayInstallMessage ?? + "Installs the global \"clawdis\" package and expects the gateway on port 18789.") .font(.caption) .foregroundStyle(.secondary) .lineLimit(2) diff --git a/apps/macos/Sources/Clawdis/HealthStore.swift b/apps/macos/Sources/Clawdis/HealthStore.swift index 9aad27013..c0d89400f 100644 --- a/apps/macos/Sources/Clawdis/HealthStore.swift +++ b/apps/macos/Sources/Clawdis/HealthStore.swift @@ -151,15 +151,15 @@ final class HealthStore: ObservableObject { /// Short, human-friendly detail for the last failure, used in the UI. var detailLine: String? { - if let error = self.lastError, !error.isEmpty { - let lower = error.lowercased() - if lower.contains("connection refused") { - return "The gateway control port (127.0.0.1:18789) isn’t listening — restart Clawdis to bring it back." - } - if lower.contains("timeout") { - return "Timed out waiting for the control server; the relay may be crashed or still starting." - } - return error + if let error = self.lastError, !error.isEmpty { + let lower = error.lowercased() + if lower.contains("connection refused") { + return "The gateway control port (127.0.0.1:18789) isn’t listening — restart Clawdis to bring it back." + } + if lower.contains("timeout") { + return "Timed out waiting for the control server; the relay may be crashed or still starting." + } + return error } return nil } diff --git a/apps/macos/Sources/Clawdis/InstancesStore.swift b/apps/macos/Sources/Clawdis/InstancesStore.swift index 9abb7a156..562720a12 100644 --- a/apps/macos/Sources/Clawdis/InstancesStore.swift +++ b/apps/macos/Sources/Clawdis/InstancesStore.swift @@ -1,7 +1,7 @@ +import ClawdisProtocol import Cocoa import Foundation import OSLog -import ClawdisProtocol struct InstanceInfo: Identifiable, Codable { let id: String @@ -79,7 +79,8 @@ final class InstancesStore: ObservableObject { case let .event(evt) where evt.event == "presence": if let payload = evt.payload?.value as? [String: Any], let presence = payload["presence"], - let presenceData = try? JSONSerialization.data(withJSONObject: presence) { + let presenceData = try? JSONSerialization.data(withJSONObject: presence) + { Task { @MainActor [weak self] in self?.decodeAndApplyPresenceData(presenceData) } } default: @@ -104,7 +105,8 @@ final class InstancesStore: ObservableObject { switch frame { case let .helloOk(hello): if JSONSerialization.isValidJSONObject(hello.snapshot.presence), - let data = try? JSONEncoder().encode(hello.snapshot.presence) { + let data = try? JSONEncoder().encode(hello.snapshot.presence) + { Task { @MainActor [weak self] in self?.decodeAndApplyPresenceData(data) } } default: @@ -323,7 +325,7 @@ extension InstancesStore { mode: "remote", reason: "preview", text: "Relay node · tunnel ok", - ts: Date().timeIntervalSince1970 * 1000 - 45_000), + ts: Date().timeIntervalSince1970 * 1000 - 45000), ]) -> InstancesStore { let store = InstancesStore(isPreview: true) store.instances = instances diff --git a/apps/macos/Sources/Clawdis/Onboarding.swift b/apps/macos/Sources/Clawdis/Onboarding.swift index f2b60adab..cb00ba8aa 100644 --- a/apps/macos/Sources/Clawdis/Onboarding.swift +++ b/apps/macos/Sources/Clawdis/Onboarding.swift @@ -181,7 +181,8 @@ struct OnboardingView: View { self.onboardingPage { Text("Install the relay") .font(.largeTitle.weight(.semibold)) - Text("Clawdis now runs the WebSocket gateway from the global \"clawdis\" package. Install/update it here and we’ll check Node for you.") + Text( + "Clawdis now runs the WebSocket gateway from the global \"clawdis\" package. Install/update it here and we’ll check Node for you.") .font(.body) .foregroundStyle(.secondary) .multilineTextAlignment(.center) diff --git a/apps/macos/Sources/Clawdis/PermissionsSettings.swift b/apps/macos/Sources/Clawdis/PermissionsSettings.swift index 599b9f317..4418ac3f2 100644 --- a/apps/macos/Sources/Clawdis/PermissionsSettings.swift +++ b/apps/macos/Sources/Clawdis/PermissionsSettings.swift @@ -132,9 +132,8 @@ struct PermissionsSettings_Previews: PreviewProvider { .speechRecognition: false, ], refresh: {}, - showOnboarding: {} - ) - .frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight) + showOnboarding: {}) + .frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight) } } #endif diff --git a/apps/macos/Sources/Clawdis/RelayEnvironment.swift b/apps/macos/Sources/Clawdis/RelayEnvironment.swift index 6e7e03f72..8967b89bd 100644 --- a/apps/macos/Sources/Clawdis/RelayEnvironment.swift +++ b/apps/macos/Sources/Clawdis/RelayEnvironment.swift @@ -1,5 +1,5 @@ -import Foundation import ClawdisIPC +import Foundation // Lightweight SemVer helper (major.minor.patch only) for relay compatibility checks. struct Semver: Comparable, CustomStringConvertible, Sendable { diff --git a/apps/macos/Sources/Clawdis/SessionData.swift b/apps/macos/Sources/Clawdis/SessionData.swift index 1546b0ff4..900765e33 100644 --- a/apps/macos/Sources/Clawdis/SessionData.swift +++ b/apps/macos/Sources/Clawdis/SessionData.swift @@ -126,7 +126,7 @@ extension SessionRow { id: "global", key: "global", kind: .global, - updatedAt: Date().addingTimeInterval(-86_400), + updatedAt: Date().addingTimeInterval(-86400), sessionId: nil, thinkingLevel: nil, verboseLevel: nil, diff --git a/apps/macos/Sources/Clawdis/WebChatWindow.swift b/apps/macos/Sources/Clawdis/WebChatWindow.swift index 82f626e17..bb7114085 100644 --- a/apps/macos/Sources/Clawdis/WebChatWindow.swift +++ b/apps/macos/Sources/Clawdis/WebChatWindow.swift @@ -101,7 +101,14 @@ final class WebChatWindowController: NSWindowController, WKNavigationDelegate { private func loadWebChat(baseEndpoint: URL) { var comps = URLComponents(url: baseEndpoint.appendingPathComponent("webchat/"), resolvingAgainstBaseURL: false) - comps?.queryItems = [URLQueryItem(name: "session", value: self.sessionKey)] + var items = [URLQueryItem(name: "session", value: self.sessionKey)] + if let hostName = Host.current().localizedName ?? Host.current().name { + items.append(URLQueryItem(name: "host", value: hostName)) + } + if let ip = Self.primaryIPv4Address() { + items.append(URLQueryItem(name: "ip", value: ip)) + } + comps?.queryItems = items guard let url = comps?.url else { self.showError("invalid webchat url") return @@ -197,6 +204,37 @@ final class WebChatWindowController: NSWindowController, WKNavigationDelegate { } } +extension WebChatWindowController { + /// Returns the first non-loopback IPv4 address, skipping link-local (169.254.x.x). + fileprivate static func primaryIPv4Address() -> String? { + var ifaddr: UnsafeMutablePointer? + guard getifaddrs(&ifaddr) == 0, let first = ifaddr else { return nil } + defer { freeifaddrs(ifaddr) } + + for ptr in sequence(first: first, next: { $0.pointee.ifa_next }) { + let flags = Int32(ptr.pointee.ifa_flags) + let addrFamily = ptr.pointee.ifa_addr.pointee.sa_family + if (flags & IFF_UP) == 0 || (flags & IFF_LOOPBACK) != 0 { continue } + if addrFamily == UInt8(AF_INET) { + var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + if getnameinfo( + ptr.pointee.ifa_addr, + socklen_t(ptr.pointee.ifa_addr.pointee.sa_len), + &hostname, + socklen_t(hostname.count), + nil, + 0, + NI_NUMERICHOST) == 0 + { + let ip = String(cString: hostname) + if !ip.hasPrefix("169.254") { return ip } + } + } + } + return nil + } +} + // MARK: - Manager @MainActor diff --git a/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift index ed9796e93..a9651511f 100644 --- a/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift +++ b/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift @@ -28,8 +28,8 @@ public struct Hello: Codable { caps: [String]?, auth: [String: AnyCodable]?, locale: String?, - useragent: String? - ) { + useragent: String?) + { self.type = type self.minprotocol = minprotocol self.maxprotocol = maxprotocol @@ -39,6 +39,7 @@ public struct Hello: Codable { self.locale = locale self.useragent = useragent } + private enum CodingKeys: String, CodingKey { case type case minprotocol = "minProtocol" @@ -65,8 +66,8 @@ public struct HelloOk: Codable { server: [String: AnyCodable], features: [String: AnyCodable], snapshot: Snapshot, - policy: [String: AnyCodable] - ) { + policy: [String: AnyCodable]) + { self.type = type self._protocol = _protocol self.server = server @@ -74,6 +75,7 @@ public struct HelloOk: Codable { self.snapshot = snapshot self.policy = policy } + private enum CodingKeys: String, CodingKey { case type case _protocol = "protocol" @@ -94,13 +96,14 @@ public struct HelloError: Codable { type: String, reason: String, expectedprotocol: Int?, - minclient: String? - ) { + minclient: String?) + { self.type = type self.reason = reason self.expectedprotocol = expectedprotocol self.minclient = minclient } + private enum CodingKeys: String, CodingKey { case type case reason @@ -119,13 +122,14 @@ public struct RequestFrame: Codable { type: String, id: String, method: String, - params: AnyCodable? - ) { + params: AnyCodable?) + { self.type = type self.id = id self.method = method self.params = params } + private enum CodingKeys: String, CodingKey { case type case id @@ -146,14 +150,15 @@ public struct ResponseFrame: Codable { id: String, ok: Bool, payload: AnyCodable?, - error: [String: AnyCodable]? - ) { + error: [String: AnyCodable]?) + { self.type = type self.id = id self.ok = ok self.payload = payload self.error = error } + private enum CodingKeys: String, CodingKey { case type case id @@ -175,14 +180,15 @@ public struct EventFrame: Codable { event: String, payload: AnyCodable?, seq: Int?, - stateversion: [String: AnyCodable]? - ) { + stateversion: [String: AnyCodable]?) + { self.type = type self.event = event self.payload = payload self.seq = seq self.stateversion = stateversion } + private enum CodingKeys: String, CodingKey { case type case event @@ -214,8 +220,8 @@ public struct PresenceEntry: Codable { tags: [String]?, text: String?, ts: Int, - instanceid: String? - ) { + instanceid: String?) + { self.host = host self.ip = ip self.version = version @@ -227,6 +233,7 @@ public struct PresenceEntry: Codable { self.ts = ts self.instanceid = instanceid } + private enum CodingKeys: String, CodingKey { case host case ip @@ -247,11 +254,12 @@ public struct StateVersion: Codable { public init( presence: Int, - health: Int - ) { + health: Int) + { self.presence = presence self.health = health } + private enum CodingKeys: String, CodingKey { case presence case health @@ -268,13 +276,14 @@ public struct Snapshot: Codable { presence: [PresenceEntry], health: AnyCodable, stateversion: StateVersion, - uptimems: Int - ) { + uptimems: Int) + { self.presence = presence self.health = health self.stateversion = stateversion self.uptimems = uptimems } + private enum CodingKeys: String, CodingKey { case presence case health @@ -295,14 +304,15 @@ public struct ErrorShape: Codable { message: String, details: AnyCodable?, retryable: Bool?, - retryafterms: Int? - ) { + retryafterms: Int?) + { self.code = code self.message = message self.details = details self.retryable = retryable self.retryafterms = retryafterms } + private enum CodingKeys: String, CodingKey { case code case message @@ -324,14 +334,15 @@ public struct AgentEvent: Codable { seq: Int, stream: String, ts: Int, - data: [String: AnyCodable] - ) { + data: [String: AnyCodable]) + { self.runid = runid self.seq = seq self.stream = stream self.ts = ts self.data = data } + private enum CodingKeys: String, CodingKey { case runid = "runId" case seq @@ -353,14 +364,15 @@ public struct SendParams: Codable { message: String, mediaurl: String?, provider: String?, - idempotencykey: String - ) { + idempotencykey: String) + { self.to = to self.message = message self.mediaurl = mediaurl self.provider = provider self.idempotencykey = idempotencykey } + private enum CodingKeys: String, CodingKey { case to case message @@ -386,8 +398,8 @@ public struct AgentParams: Codable { thinking: String?, deliver: Bool?, timeout: Int?, - idempotencykey: String - ) { + idempotencykey: String) + { self.message = message self.to = to self.sessionid = sessionid @@ -396,6 +408,7 @@ public struct AgentParams: Codable { self.timeout = timeout self.idempotencykey = idempotencykey } + private enum CodingKeys: String, CodingKey { case message case to @@ -411,10 +424,11 @@ public struct TickEvent: Codable { public let ts: Int public init( - ts: Int - ) { + ts: Int) + { self.ts = ts } + private enum CodingKeys: String, CodingKey { case ts } @@ -426,11 +440,12 @@ public struct ShutdownEvent: Codable { public init( reason: String, - restartexpectedms: Int? - ) { + restartexpectedms: Int?) + { self.reason = reason self.restartexpectedms = restartexpectedms } + private enum CodingKeys: String, CodingKey { case reason case restartexpectedms = "restartExpectedMs" @@ -454,17 +469,17 @@ public enum GatewayFrame: Codable { } switch type { case "hello": - self = .hello(try Self.decodePayload(Hello.self, from: raw)) + self = try .hello(Self.decodePayload(Hello.self, from: raw)) case "hello-ok": - self = .helloOk(try Self.decodePayload(HelloOk.self, from: raw)) + self = try .helloOk(Self.decodePayload(HelloOk.self, from: raw)) case "hello-error": - self = .helloError(try Self.decodePayload(HelloError.self, from: raw)) + self = try .helloError(Self.decodePayload(HelloError.self, from: raw)) case "req": - self = .req(try Self.decodePayload(RequestFrame.self, from: raw)) + self = try .req(Self.decodePayload(RequestFrame.self, from: raw)) case "res": - self = .res(try Self.decodePayload(ResponseFrame.self, from: raw)) + self = try .res(Self.decodePayload(ResponseFrame.self, from: raw)) case "event": - self = .event(try Self.decodePayload(EventFrame.self, from: raw)) + self = try .event(Self.decodePayload(EventFrame.self, from: raw)) default: self = .unknown(type: type, raw: raw) } @@ -472,23 +487,21 @@ public enum GatewayFrame: Codable { public func encode(to encoder: Encoder) throws { switch self { - case .hello(let v): try v.encode(to: encoder) - case .helloOk(let v): try v.encode(to: encoder) - case .helloError(let v): try v.encode(to: encoder) - case .req(let v): try v.encode(to: encoder) - case .res(let v): try v.encode(to: encoder) - case .event(let v): try v.encode(to: encoder) - case .unknown(_, let raw): + case let .hello(v): try v.encode(to: encoder) + case let .helloOk(v): try v.encode(to: encoder) + case let .helloError(v): try v.encode(to: encoder) + case let .req(v): try v.encode(to: encoder) + case let .res(v): try v.encode(to: encoder) + case let .event(v): try v.encode(to: encoder) + case let .unknown(_, raw): var container = encoder.singleValueContainer() try container.encode(raw) } } - private static func decodePayload(_ type: T.Type, from raw: [String: AnyCodable]) throws -> T { let data = try JSONSerialization.data(withJSONObject: raw) let decoder = JSONDecoder() return try decoder.decode(T.self, from: data) } - } diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index e97a1fb67..c30d285a5 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -624,7 +624,7 @@ export async function getReplyFromConfig( systemLines.push(...queued); if (isNewSession) { const summary = await buildProviderSummary(cfg); - if (summary) systemLines.unshift(summary); + if (summary.length > 0) systemLines.unshift(...summary); } if (systemLines.length > 0) { const block = systemLines.map((l) => `System: ${l}`).join("\n"); diff --git a/src/cli/program.force.test.ts b/src/cli/program.force.test.ts index 331c9d3af..f988993c1 100644 --- a/src/cli/program.force.test.ts +++ b/src/cli/program.force.test.ts @@ -1,9 +1,10 @@ -import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("node:child_process", async () => { - const actual = await vi.importActual( - "node:child_process", - ); + const actual = + await vi.importActual( + "node:child_process", + ); return { ...actual, execFileSync: vi.fn(), @@ -14,8 +15,8 @@ import { execFileSync } from "node:child_process"; import { forceFreePort, listPortListeners, - parseLsofOutput, type PortProcess, + parseLsofOutput, } from "./program.js"; describe("gateway --force helpers", () => { diff --git a/src/cli/program.ts b/src/cli/program.ts index 8df7ebbf3..c64adaa91 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -116,10 +116,7 @@ export function buildProgram() { 'clawdis send --to +15555550123 --message "Hi" --json', "Send via your web session and print JSON result.", ], - [ - "clawdis gateway --port 18789", - "Run the WebSocket Gateway locally.", - ], + ["clawdis gateway --port 18789", "Run the WebSocket Gateway locally."], [ "clawdis gateway --force", "Kill anything bound to the default gateway port, then start it.", diff --git a/src/gateway/server.ts b/src/gateway/server.ts index 48785c010..7faa85c97 100644 --- a/src/gateway/server.ts +++ b/src/gateway/server.ts @@ -15,11 +15,11 @@ import { } from "../infra/system-presence.js"; import { logError } from "../logger.js"; import { getResolvedLoggerSettings } from "../logging.js"; -import { defaultRuntime } from "../runtime.js"; import { monitorWebProvider, webAuthExists } from "../providers/web/index.js"; -import { sendMessageWhatsApp } from "../web/outbound.js"; +import { defaultRuntime } from "../runtime.js"; import { monitorTelegramProvider } from "../telegram/monitor.js"; import { sendMessageTelegram } from "../telegram/send.js"; +import { sendMessageWhatsApp } from "../web/outbound.js"; import { ErrorCodes, type ErrorShape, @@ -450,32 +450,41 @@ export async function startGatewayServer(port = 18789): Promise { const message = params.message.trim(); const provider = (params.provider ?? "whatsapp").toLowerCase(); try { - const result = - provider === "telegram" - ? await sendMessageTelegram(to, message, { - mediaUrl: params.mediaUrl, - verbose: isVerbose(), - }) - : await sendMessageWhatsApp(to, message, { - mediaUrl: params.mediaUrl, - verbose: isVerbose(), - }); - const payload = - provider === "telegram" - ? { - runId: idem, - messageId: result.messageId, - chatId: result.chatId, - provider, - } - : { - runId: idem, - messageId: result.messageId, - toJid: result.toJid ?? `${to}@s.whatsapp.net`, - provider, - }; - dedupe.set(`send:${idem}`, { ts: Date.now(), ok: true, payload }); - respond(true, payload, undefined); + if (provider === "telegram") { + const result = await sendMessageTelegram(to, message, { + mediaUrl: params.mediaUrl, + verbose: isVerbose(), + }); + const payload = { + runId: idem, + messageId: result.messageId, + chatId: result.chatId, + provider, + }; + dedupe.set(`send:${idem}`, { + ts: Date.now(), + ok: true, + payload, + }); + respond(true, payload, undefined); + } else { + const result = await sendMessageWhatsApp(to, message, { + mediaUrl: params.mediaUrl, + verbose: isVerbose(), + }); + const payload = { + runId: idem, + messageId: result.messageId, + toJid: result.toJid ?? `${to}@s.whatsapp.net`, + provider, + }; + dedupe.set(`send:${idem}`, { + ts: Date.now(), + ok: true, + payload, + }); + respond(true, payload, undefined); + } } catch (err) { const error = errorShape(ErrorCodes.UNAVAILABLE, String(err)); dedupe.set(`send:${idem}`, { ts: Date.now(), ok: false, error }); @@ -594,7 +603,9 @@ export async function startGatewayServer(port = 18789): Promise { if (process.env.CLAWDIS_SKIP_PROVIDERS !== "1") { void startProviders(); } else { - defaultRuntime.log("gateway: skipping provider start (CLAWDIS_SKIP_PROVIDERS=1)"); + defaultRuntime.log( + "gateway: skipping provider start (CLAWDIS_SKIP_PROVIDERS=1)", + ); } return { diff --git a/src/infra/provider-summary.ts b/src/infra/provider-summary.ts index b8d743c9b..82bda46aa 100644 --- a/src/infra/provider-summary.ts +++ b/src/infra/provider-summary.ts @@ -21,7 +21,9 @@ export async function buildProviderSummary( const { e164 } = readWebSelfId(); lines.push( webLinked - ? chalk.green(`WhatsApp: linked${e164 ? ` as ${e164}` : ""} (auth ${authAge})`) + ? chalk.green( + `WhatsApp: linked${e164 ? ` as ${e164}` : ""} (auth ${authAge})`, + ) : chalk.red("WhatsApp: not linked"), );