refactor: rename to openclaw

This commit is contained in:
Peter Steinberger
2026-01-30 03:15:10 +01:00
parent 4583f88626
commit 9a7160786a
2357 changed files with 16688 additions and 16788 deletions
@@ -1,27 +0,0 @@
import Foundation
public enum MoltbotBonjour {
// v0: internal-only, subject to rename.
public static let gatewayServiceType = "_moltbot-gw._tcp"
public static let gatewayServiceDomain = "local."
public static let wideAreaGatewayServiceDomain = "moltbot.internal."
public static let gatewayServiceDomains = [
gatewayServiceDomain,
wideAreaGatewayServiceDomain,
]
public static func normalizeServiceDomain(_ raw: String?) -> String {
let trimmed = (raw ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty {
return self.gatewayServiceDomain
}
let lower = trimmed.lowercased()
if lower == "local" || lower == "local." {
return self.gatewayServiceDomain
}
return lower.hasSuffix(".") ? lower : (lower + ".")
}
}
@@ -1,7 +0,0 @@
import Foundation
public enum MoltbotLocationMode: String, Codable, Sendable, CaseIterable {
case off
case whileUsing
case always
}
@@ -3,15 +3,15 @@
import PackageDescription
let package = Package(
name: "MoltbotKit",
name: "OpenClawKit",
platforms: [
.iOS(.v18),
.macOS(.v15),
],
products: [
.library(name: "MoltbotProtocol", targets: ["MoltbotProtocol"]),
.library(name: "MoltbotKit", targets: ["MoltbotKit"]),
.library(name: "MoltbotChatUI", targets: ["MoltbotChatUI"]),
.library(name: "OpenClawProtocol", targets: ["OpenClawProtocol"]),
.library(name: "OpenClawKit", targets: ["OpenClawKit"]),
.library(name: "OpenClawChatUI", targets: ["OpenClawChatUI"]),
],
dependencies: [
.package(url: "https://github.com/steipete/ElevenLabsKit", exact: "0.1.0"),
@@ -19,18 +19,18 @@ let package = Package(
],
targets: [
.target(
name: "MoltbotProtocol",
path: "Sources/MoltbotProtocol",
name: "OpenClawProtocol",
path: "Sources/OpenClawProtocol",
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
.target(
name: "MoltbotKit",
name: "OpenClawKit",
dependencies: [
"MoltbotProtocol",
"OpenClawProtocol",
.product(name: "ElevenLabsKit", package: "ElevenLabsKit"),
],
path: "Sources/MoltbotKit",
path: "Sources/OpenClawKit",
resources: [
.process("Resources"),
],
@@ -38,22 +38,22 @@ let package = Package(
.enableUpcomingFeature("StrictConcurrency"),
]),
.target(
name: "MoltbotChatUI",
name: "OpenClawChatUI",
dependencies: [
"MoltbotKit",
"OpenClawKit",
.product(
name: "Textual",
package: "textual",
condition: .when(platforms: [.macOS, .iOS])),
],
path: "Sources/MoltbotChatUI",
path: "Sources/OpenClawChatUI",
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
.testTarget(
name: "MoltbotKitTests",
dependencies: ["MoltbotKit", "MoltbotChatUI"],
path: "Tests/MoltbotKitTests",
name: "OpenClawKitTests",
dependencies: ["OpenClawKit", "OpenClawChatUI"],
path: "Tests/OpenClawKitTests",
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
.enableExperimentalFeature("SwiftTesting"),
@@ -8,9 +8,9 @@ import UniformTypeIdentifiers
#endif
@MainActor
struct MoltbotChatComposer: View {
@Bindable var viewModel: MoltbotChatViewModel
let style: MoltbotChatView.Style
struct OpenClawChatComposer: View {
@Bindable var viewModel: OpenClawChatViewModel
let style: OpenClawChatView.Style
let showsSessionSwitcher: Bool
#if !os(macOS)
@@ -54,21 +54,21 @@ struct MoltbotChatComposer: View {
topTrailing: 0),
style: .continuous)
shape
.fill(MoltbotChatTheme.composerBackground)
.overlay(shape.strokeBorder(MoltbotChatTheme.composerBorder, lineWidth: 1))
.fill(OpenClawChatTheme.composerBackground)
.overlay(shape.strokeBorder(OpenClawChatTheme.composerBorder, lineWidth: 1))
.shadow(color: .black.opacity(0.12), radius: 12, y: 6)
} else {
let shape = RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
shape
.fill(MoltbotChatTheme.composerBackground)
.overlay(shape.strokeBorder(MoltbotChatTheme.composerBorder, lineWidth: 1))
.fill(OpenClawChatTheme.composerBackground)
.overlay(shape.strokeBorder(OpenClawChatTheme.composerBorder, lineWidth: 1))
.shadow(color: .black.opacity(0.12), radius: 12, y: 6)
}
#else
let shape = RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
shape
.fill(MoltbotChatTheme.composerBackground)
.overlay(shape.strokeBorder(MoltbotChatTheme.composerBorder, lineWidth: 1))
.fill(OpenClawChatTheme.composerBackground)
.overlay(shape.strokeBorder(OpenClawChatTheme.composerBorder, lineWidth: 1))
.shadow(color: .black.opacity(0.12), radius: 12, y: 6)
#endif
}
@@ -144,11 +144,11 @@ struct MoltbotChatComposer: View {
HStack(spacing: 6) {
ForEach(
self.viewModel.attachments,
id: \MoltbotPendingAttachment.id)
{ (att: MoltbotPendingAttachment) in
id: \OpenClawPendingAttachment.id)
{ (att: OpenClawPendingAttachment) in
HStack(spacing: 6) {
if let img = att.preview {
MoltbotPlatformImageFactory.image(img)
OpenClawPlatformImageFactory.image(img)
.resizable()
.scaledToFill()
.frame(width: 22, height: 22)
@@ -181,7 +181,7 @@ struct MoltbotChatComposer: View {
self.editorOverlay
Rectangle()
.fill(MoltbotChatTheme.divider)
.fill(OpenClawChatTheme.divider)
.frame(height: 1)
.padding(.horizontal, 2)
@@ -197,10 +197,10 @@ struct MoltbotChatComposer: View {
.padding(.vertical, 8)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(MoltbotChatTheme.composerField)
.fill(OpenClawChatTheme.composerField)
.overlay(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.strokeBorder(MoltbotChatTheme.composerBorder)))
.strokeBorder(OpenClawChatTheme.composerBorder)))
.padding(self.editorPadding)
}
@@ -217,7 +217,7 @@ struct MoltbotChatComposer: View {
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(MoltbotChatTheme.subtleCard)
.background(OpenClawChatTheme.subtleCard)
.clipShape(Capsule())
}
@@ -230,7 +230,7 @@ struct MoltbotChatComposer: View {
private var editorOverlay: some View {
ZStack(alignment: .topLeading) {
if self.viewModel.input.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text("Message Clawd")
Text("Message OpenClaw…")
.foregroundStyle(.tertiary)
.padding(.horizontal, 4)
.padding(.vertical, 4)
@@ -4,7 +4,7 @@ enum ChatMarkdownPreprocessor {
struct InlineImage: Identifiable {
let id = UUID()
let label: String
let image: MoltbotPlatformImage?
let image: OpenClawPlatformImage?
}
struct Result {
@@ -30,11 +30,11 @@ enum ChatMarkdownPreprocessor {
let label = ns.substring(with: match.range(at: 1))
let dataURL = ns.substring(with: match.range(at: 2))
let image: MoltbotPlatformImage? = {
let image: OpenClawPlatformImage? = {
guard let comma = dataURL.firstIndex(of: ",") else { return nil }
let b64 = String(dataURL[dataURL.index(after: comma)...])
guard let data = Data(base64Encoded: b64) else { return nil }
return MoltbotPlatformImage(data: data)
return OpenClawPlatformImage(data: data)
}()
images.append(InlineImage(label: label, image: image))
@@ -72,7 +72,7 @@ private struct InlineImageList: View {
var body: some View {
ForEach(images, id: \.id) { item in
if let img = item.image {
MoltbotPlatformImageFactory.image(img)
OpenClawPlatformImageFactory.image(img)
.resizable()
.scaledToFit()
.frame(maxHeight: 260)
@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import Foundation
import SwiftUI
@@ -135,8 +135,8 @@ private struct ChatBubbleShape: InsettableShape {
@MainActor
struct ChatMessageBubble: View {
let message: MoltbotChatMessage
let style: MoltbotChatView.Style
let message: OpenClawChatMessage
let style: OpenClawChatView.Style
let markdownVariant: ChatMarkdownVariant
let userAccent: Color?
@@ -157,15 +157,15 @@ struct ChatMessageBubble: View {
@MainActor
private struct ChatMessageBody: View {
let message: MoltbotChatMessage
let message: OpenClawChatMessage
let isUser: Bool
let style: MoltbotChatView.Style
let style: OpenClawChatView.Style
let markdownVariant: ChatMarkdownVariant
let userAccent: Color?
var body: some View {
let text = self.primaryText
let textColor = self.isUser ? MoltbotChatTheme.userText : MoltbotChatTheme.assistantText
let textColor = self.isUser ? OpenClawChatTheme.userText : OpenClawChatTheme.assistantText
VStack(alignment: .leading, spacing: 10) {
if self.isToolResultMessage {
@@ -232,7 +232,7 @@ private struct ChatMessageBody: View {
return parts.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
}
private var inlineAttachments: [MoltbotChatMessageContent] {
private var inlineAttachments: [OpenClawChatMessageContent] {
self.message.content.filter { content in
switch content.type ?? "text" {
case "file", "attachment":
@@ -243,7 +243,7 @@ private struct ChatMessageBody: View {
}
}
private var toolCalls: [MoltbotChatMessageContent] {
private var toolCalls: [OpenClawChatMessageContent] {
self.message.content.filter { content in
let kind = (content.type ?? "").lowercased()
if ["toolcall", "tool_call", "tooluse", "tool_use"].contains(kind) {
@@ -253,7 +253,7 @@ private struct ChatMessageBody: View {
}
}
private var inlineToolResults: [MoltbotChatMessageContent] {
private var inlineToolResults: [OpenClawChatMessageContent] {
self.message.content.filter { content in
let kind = (content.type ?? "").lowercased()
return kind == "toolresult" || kind == "tool_result"
@@ -276,12 +276,12 @@ private struct ChatMessageBody: View {
private var bubbleFillColor: Color {
if self.isUser {
return self.userAccent ?? MoltbotChatTheme.userBubble
return self.userAccent ?? OpenClawChatTheme.userBubble
}
if self.style == .onboarding {
return MoltbotChatTheme.onboardingAssistantBubble
return OpenClawChatTheme.onboardingAssistantBubble
}
return MoltbotChatTheme.assistantBubble
return OpenClawChatTheme.assistantBubble
}
private var bubbleBackground: AnyShapeStyle {
@@ -293,7 +293,7 @@ private struct ChatMessageBody: View {
return Color.white.opacity(0.12)
}
if self.style == .onboarding {
return MoltbotChatTheme.onboardingAssistantBorder
return OpenClawChatTheme.onboardingAssistantBorder
}
return Color.white.opacity(0.08)
}
@@ -339,7 +339,7 @@ private struct ChatMessageBody: View {
}
private struct AttachmentRow: View {
let att: MoltbotChatMessageContent
let att: OpenClawChatMessageContent
let isUser: Bool
var body: some View {
@@ -348,7 +348,7 @@ private struct AttachmentRow: View {
Text(self.att.fileName ?? "Attachment")
.font(.footnote)
.lineLimit(1)
.foregroundStyle(self.isUser ? MoltbotChatTheme.userText : MoltbotChatTheme.assistantText)
.foregroundStyle(self.isUser ? OpenClawChatTheme.userText : OpenClawChatTheme.assistantText)
Spacer()
}
.padding(10)
@@ -358,7 +358,7 @@ private struct AttachmentRow: View {
}
private struct ToolCallCard: View {
let content: MoltbotChatMessageContent
let content: OpenClawChatMessageContent
let isUser: Bool
var body: some View {
@@ -379,7 +379,7 @@ private struct ToolCallCard: View {
.padding(10)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(MoltbotChatTheme.subtleCard)
.fill(OpenClawChatTheme.subtleCard)
.overlay(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1)))
@@ -414,7 +414,7 @@ private struct ToolResultCard: View {
Text(self.displayText)
.font(.footnote.monospaced())
.foregroundStyle(self.isUser ? MoltbotChatTheme.userText : MoltbotChatTheme.assistantText)
.foregroundStyle(self.isUser ? OpenClawChatTheme.userText : OpenClawChatTheme.assistantText)
.lineLimit(self.expanded ? nil : Self.previewLineLimit)
if self.shouldShowToggle {
@@ -429,7 +429,7 @@ private struct ToolResultCard: View {
.padding(10)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(MoltbotChatTheme.subtleCard)
.fill(OpenClawChatTheme.subtleCard)
.overlay(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1)))
@@ -453,13 +453,13 @@ private struct ToolResultCard: View {
@MainActor
struct ChatTypingIndicatorBubble: View {
let style: MoltbotChatView.Style
let style: OpenClawChatView.Style
var body: some View {
HStack(spacing: 10) {
TypingDots()
if self.style == .standard {
Text("Clawd is thinking…")
Text("OpenClaw is thinking…")
.font(.subheadline)
.foregroundStyle(.secondary)
Spacer()
@@ -469,7 +469,7 @@ struct ChatTypingIndicatorBubble: View {
.padding(.horizontal, self.style == .standard ? 12 : 14)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(MoltbotChatTheme.assistantBubble))
.fill(OpenClawChatTheme.assistantBubble))
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1))
@@ -496,7 +496,7 @@ struct ChatStreamingAssistantBubble: View {
.padding(12)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(MoltbotChatTheme.assistantBubble))
.fill(OpenClawChatTheme.assistantBubble))
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1))
@@ -507,7 +507,7 @@ struct ChatStreamingAssistantBubble: View {
@MainActor
struct ChatPendingToolsBubble: View {
let toolCalls: [MoltbotChatPendingToolCall]
let toolCalls: [OpenClawChatPendingToolCall]
var body: some View {
VStack(alignment: .leading, spacing: 8) {
@@ -540,7 +540,7 @@ struct ChatPendingToolsBubble: View {
.padding(12)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(MoltbotChatTheme.assistantBubble))
.fill(OpenClawChatTheme.assistantBubble))
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1))
@@ -609,7 +609,7 @@ private struct ChatAssistantTextBody: View {
context: .assistant,
variant: self.markdownVariant,
font: font,
textColor: MoltbotChatTheme.assistantText)
textColor: OpenClawChatTheme.assistantText)
}
}
}
@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import Foundation
// NOTE: keep this file lightweight; decode must be resilient to varying transcript formats.
@@ -6,14 +6,14 @@ import Foundation
#if canImport(AppKit)
import AppKit
public typealias MoltbotPlatformImage = NSImage
public typealias OpenClawPlatformImage = NSImage
#elseif canImport(UIKit)
import UIKit
public typealias MoltbotPlatformImage = UIImage
public typealias OpenClawPlatformImage = UIImage
#endif
public struct MoltbotChatUsageCost: Codable, Hashable, Sendable {
public struct OpenClawChatUsageCost: Codable, Hashable, Sendable {
public let input: Double?
public let output: Double?
public let cacheRead: Double?
@@ -21,12 +21,12 @@ public struct MoltbotChatUsageCost: Codable, Hashable, Sendable {
public let total: Double?
}
public struct MoltbotChatUsage: Codable, Hashable, Sendable {
public struct OpenClawChatUsage: Codable, Hashable, Sendable {
public let input: Int?
public let output: Int?
public let cacheRead: Int?
public let cacheWrite: Int?
public let cost: MoltbotChatUsageCost?
public let cost: OpenClawChatUsageCost?
public let total: Int?
enum CodingKeys: String, CodingKey {
@@ -45,7 +45,7 @@ public struct MoltbotChatUsage: Codable, Hashable, Sendable {
self.output = try container.decodeIfPresent(Int.self, forKey: .output)
self.cacheRead = try container.decodeIfPresent(Int.self, forKey: .cacheRead)
self.cacheWrite = try container.decodeIfPresent(Int.self, forKey: .cacheWrite)
self.cost = try container.decodeIfPresent(MoltbotChatUsageCost.self, forKey: .cost)
self.cost = try container.decodeIfPresent(OpenClawChatUsageCost.self, forKey: .cost)
self.total =
try container.decodeIfPresent(Int.self, forKey: .total) ??
container.decodeIfPresent(Int.self, forKey: .totalTokens)
@@ -62,7 +62,7 @@ public struct MoltbotChatUsage: Codable, Hashable, Sendable {
}
}
public struct MoltbotChatMessageContent: Codable, Hashable, Sendable {
public struct OpenClawChatMessageContent: Codable, Hashable, Sendable {
public let type: String?
public let text: String?
public let thinking: String?
@@ -135,14 +135,14 @@ public struct MoltbotChatMessageContent: Codable, Hashable, Sendable {
}
}
public struct MoltbotChatMessage: Codable, Identifiable, Sendable {
public struct OpenClawChatMessage: Codable, Identifiable, Sendable {
public var id: UUID = .init()
public let role: String
public let content: [MoltbotChatMessageContent]
public let content: [OpenClawChatMessageContent]
public let timestamp: Double?
public let toolCallId: String?
public let toolName: String?
public let usage: MoltbotChatUsage?
public let usage: OpenClawChatUsage?
public let stopReason: String?
enum CodingKeys: String, CodingKey {
@@ -160,11 +160,11 @@ public struct MoltbotChatMessage: Codable, Identifiable, Sendable {
public init(
id: UUID = .init(),
role: String,
content: [MoltbotChatMessageContent],
content: [OpenClawChatMessageContent],
timestamp: Double?,
toolCallId: String? = nil,
toolName: String? = nil,
usage: MoltbotChatUsage? = nil,
usage: OpenClawChatUsage? = nil,
stopReason: String? = nil)
{
self.id = id
@@ -187,10 +187,10 @@ public struct MoltbotChatMessage: Codable, Identifiable, Sendable {
self.toolName =
try container.decodeIfPresent(String.self, forKey: .toolName) ??
container.decodeIfPresent(String.self, forKey: .tool_name)
self.usage = try container.decodeIfPresent(MoltbotChatUsage.self, forKey: .usage)
self.usage = try container.decodeIfPresent(OpenClawChatUsage.self, forKey: .usage)
self.stopReason = try container.decodeIfPresent(String.self, forKey: .stopReason)
if let decoded = try? container.decode([MoltbotChatMessageContent].self, forKey: .content) {
if let decoded = try? container.decode([OpenClawChatMessageContent].self, forKey: .content) {
self.content = decoded
return
}
@@ -198,7 +198,7 @@ public struct MoltbotChatMessage: Codable, Identifiable, Sendable {
// Some session log formats store `content` as a plain string.
if let text = try? container.decode(String.self, forKey: .content) {
self.content = [
MoltbotChatMessageContent(
OpenClawChatMessageContent(
type: "text",
text: text,
thinking: nil,
@@ -228,40 +228,40 @@ public struct MoltbotChatMessage: Codable, Identifiable, Sendable {
}
}
public struct MoltbotChatHistoryPayload: Codable, Sendable {
public struct OpenClawChatHistoryPayload: Codable, Sendable {
public let sessionKey: String
public let sessionId: String?
public let messages: [AnyCodable]?
public let thinkingLevel: String?
}
public struct MoltbotSessionPreviewItem: Codable, Hashable, Sendable {
public struct OpenClawSessionPreviewItem: Codable, Hashable, Sendable {
public let role: String
public let text: String
}
public struct MoltbotSessionPreviewEntry: Codable, Sendable {
public struct OpenClawSessionPreviewEntry: Codable, Sendable {
public let key: String
public let status: String
public let items: [MoltbotSessionPreviewItem]
public let items: [OpenClawSessionPreviewItem]
}
public struct MoltbotSessionsPreviewPayload: Codable, Sendable {
public struct OpenClawSessionsPreviewPayload: Codable, Sendable {
public let ts: Int
public let previews: [MoltbotSessionPreviewEntry]
public let previews: [OpenClawSessionPreviewEntry]
public init(ts: Int, previews: [MoltbotSessionPreviewEntry]) {
public init(ts: Int, previews: [OpenClawSessionPreviewEntry]) {
self.ts = ts
self.previews = previews
}
}
public struct MoltbotChatSendResponse: Codable, Sendable {
public struct OpenClawChatSendResponse: Codable, Sendable {
public let runId: String
public let status: String
}
public struct MoltbotChatEventPayload: Codable, Sendable {
public struct OpenClawChatEventPayload: Codable, Sendable {
public let runId: String?
public let sessionKey: String?
public let state: String?
@@ -269,7 +269,7 @@ public struct MoltbotChatEventPayload: Codable, Sendable {
public let errorMessage: String?
}
public struct MoltbotAgentEventPayload: Codable, Sendable, Identifiable {
public struct OpenClawAgentEventPayload: Codable, Sendable, Identifiable {
public var id: String { "\(self.runId)-\(self.seq ?? -1)" }
public let runId: String
public let seq: Int?
@@ -278,7 +278,7 @@ public struct MoltbotAgentEventPayload: Codable, Sendable, Identifiable {
public let data: [String: AnyCodable]
}
public struct MoltbotChatPendingToolCall: Identifiable, Hashable, Sendable {
public struct OpenClawChatPendingToolCall: Identifiable, Hashable, Sendable {
public var id: String { self.toolCallId }
public let toolCallId: String
public let name: String
@@ -287,18 +287,18 @@ public struct MoltbotChatPendingToolCall: Identifiable, Hashable, Sendable {
public let isError: Bool?
}
public struct MoltbotGatewayHealthOK: Codable, Sendable {
public struct OpenClawGatewayHealthOK: Codable, Sendable {
public let ok: Bool?
}
public struct MoltbotPendingAttachment: Identifiable {
public struct OpenClawPendingAttachment: Identifiable {
public let id = UUID()
public let url: URL?
public let data: Data
public let fileName: String
public let mimeType: String
public let type: String
public let preview: MoltbotPlatformImage?
public let preview: OpenClawPlatformImage?
public init(
url: URL?,
@@ -306,7 +306,7 @@ public struct MoltbotPendingAttachment: Identifiable {
fileName: String,
mimeType: String,
type: String = "file",
preview: MoltbotPlatformImage?)
preview: OpenClawPlatformImage?)
{
self.url = url
self.data = data
@@ -317,7 +317,7 @@ public struct MoltbotPendingAttachment: Identifiable {
}
}
public struct MoltbotChatAttachmentPayload: Codable, Sendable, Hashable {
public struct OpenClawChatAttachmentPayload: Codable, Sendable, Hashable {
public let type: String
public let mimeType: String
public let fileName: String
@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import Foundation
enum ChatPayloadDecoding {
@@ -1,11 +1,11 @@
import Foundation
public struct MoltbotChatSessionsDefaults: Codable, Sendable {
public struct OpenClawChatSessionsDefaults: Codable, Sendable {
public let model: String?
public let contextTokens: Int?
}
public struct MoltbotChatSessionEntry: Codable, Identifiable, Sendable, Hashable {
public struct OpenClawChatSessionEntry: Codable, Identifiable, Sendable, Hashable {
public var id: String { self.key }
public let key: String
@@ -31,10 +31,10 @@ public struct MoltbotChatSessionEntry: Codable, Identifiable, Sendable, Hashable
public let contextTokens: Int?
}
public struct MoltbotChatSessionsListResponse: Codable, Sendable {
public struct OpenClawChatSessionsListResponse: Codable, Sendable {
public let ts: Double?
public let path: String?
public let count: Int?
public let defaults: MoltbotChatSessionsDefaults?
public let sessions: [MoltbotChatSessionEntry]
public let defaults: OpenClawChatSessionsDefaults?
public let sessions: [OpenClawChatSessionEntry]
}
@@ -3,7 +3,7 @@ import SwiftUI
@MainActor
struct ChatSessionsSheet: View {
@Bindable var viewModel: MoltbotChatViewModel
@Bindable var viewModel: OpenClawChatViewModel
@Environment(\.dismiss) private var dismiss
var body: some View {
@@ -14,7 +14,7 @@ extension NSAppearance {
}
#endif
enum MoltbotChatTheme {
enum OpenClawChatTheme {
#if os(macOS)
static func resolvedAssistantBubbleColor(for appearance: NSAppearance) -> NSColor {
// NSColor semantic colors don't reliably resolve for arbitrary NSAppearance in SwiftPM.
@@ -31,11 +31,11 @@ enum MoltbotChatTheme {
}
static let assistantBubbleDynamicNSColor = NSColor(
name: NSColor.Name("MoltbotChatTheme.assistantBubble"),
name: NSColor.Name("OpenClawChatTheme.assistantBubble"),
dynamicProvider: resolvedAssistantBubbleColor(for:))
static let onboardingAssistantBubbleDynamicNSColor = NSColor(
name: NSColor.Name("MoltbotChatTheme.onboardingAssistantBubble"),
name: NSColor.Name("OpenClawChatTheme.onboardingAssistantBubble"),
dynamicProvider: resolvedOnboardingAssistantBubbleColor(for:))
#endif
@@ -163,8 +163,8 @@ enum MoltbotChatTheme {
}
}
enum MoltbotPlatformImageFactory {
static func image(_ image: MoltbotPlatformImage) -> Image {
enum OpenClawPlatformImageFactory {
static func image(_ image: OpenClawPlatformImage) -> Image {
#if os(macOS)
Image(nsImage: image)
#else
@@ -1,44 +1,44 @@
import Foundation
public enum MoltbotChatTransportEvent: Sendable {
public enum OpenClawChatTransportEvent: Sendable {
case health(ok: Bool)
case tick
case chat(MoltbotChatEventPayload)
case agent(MoltbotAgentEventPayload)
case chat(OpenClawChatEventPayload)
case agent(OpenClawAgentEventPayload)
case seqGap
}
public protocol MoltbotChatTransport: Sendable {
func requestHistory(sessionKey: String) async throws -> MoltbotChatHistoryPayload
public protocol OpenClawChatTransport: Sendable {
func requestHistory(sessionKey: String) async throws -> OpenClawChatHistoryPayload
func sendMessage(
sessionKey: String,
message: String,
thinking: String,
idempotencyKey: String,
attachments: [MoltbotChatAttachmentPayload]) async throws -> MoltbotChatSendResponse
attachments: [OpenClawChatAttachmentPayload]) async throws -> OpenClawChatSendResponse
func abortRun(sessionKey: String, runId: String) async throws
func listSessions(limit: Int?) async throws -> MoltbotChatSessionsListResponse
func listSessions(limit: Int?) async throws -> OpenClawChatSessionsListResponse
func requestHealth(timeoutMs: Int) async throws -> Bool
func events() -> AsyncStream<MoltbotChatTransportEvent>
func events() -> AsyncStream<OpenClawChatTransportEvent>
func setActiveSessionKey(_ sessionKey: String) async throws
}
extension MoltbotChatTransport {
extension OpenClawChatTransport {
public func setActiveSessionKey(_: String) async throws {}
public func abortRun(sessionKey _: String, runId _: String) async throws {
throw NSError(
domain: "MoltbotChatTransport",
domain: "OpenClawChatTransport",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "chat.abort not supported by this transport"])
}
public func listSessions(limit _: Int?) async throws -> MoltbotChatSessionsListResponse {
public func listSessions(limit _: Int?) async throws -> OpenClawChatSessionsListResponse {
throw NSError(
domain: "MoltbotChatTransport",
domain: "OpenClawChatTransport",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "sessions.list not supported by this transport"])
}
@@ -1,13 +1,13 @@
import SwiftUI
@MainActor
public struct MoltbotChatView: View {
public struct OpenClawChatView: View {
public enum Style {
case standard
case onboarding
}
@State private var viewModel: MoltbotChatViewModel
@State private var viewModel: OpenClawChatViewModel
@State private var scrollerBottomID = UUID()
@State private var scrollPosition: UUID?
@State private var showSessions = false
@@ -42,7 +42,7 @@ public struct MoltbotChatView: View {
}
public init(
viewModel: MoltbotChatViewModel,
viewModel: OpenClawChatViewModel,
showsSessionSwitcher: Bool = false,
style: Style = .standard,
markdownVariant: ChatMarkdownVariant = .standard,
@@ -58,14 +58,14 @@ public struct MoltbotChatView: View {
public var body: some View {
ZStack {
if self.style == .standard {
MoltbotChatTheme.background
OpenClawChatTheme.background
.ignoresSafeArea()
}
VStack(spacing: Layout.stackSpacing) {
self.messageList
.padding(.horizontal, Layout.outerPaddingHorizontal)
MoltbotChatComposer(
OpenClawChatComposer(
viewModel: self.viewModel,
style: self.style,
showsSessionSwitcher: self.showsSessionSwitcher)
@@ -206,8 +206,8 @@ public struct MoltbotChatView: View {
}
}
private var visibleMessages: [MoltbotChatMessage] {
let base: [MoltbotChatMessage]
private var visibleMessages: [OpenClawChatMessage] {
let base: [OpenClawChatMessage]
if self.style == .onboarding {
guard let first = self.viewModel.messages.first else { return [] }
base = first.role.lowercased() == "user" ? Array(self.viewModel.messages.dropFirst()) : self.viewModel
@@ -324,8 +324,8 @@ public struct MoltbotChatView: View {
return ("Error", "exclamationmark.triangle.fill", .orange)
}
private func mergeToolResults(in messages: [MoltbotChatMessage]) -> [MoltbotChatMessage] {
var result: [MoltbotChatMessage] = []
private func mergeToolResults(in messages: [OpenClawChatMessage]) -> [OpenClawChatMessage] {
var result: [OpenClawChatMessage] = []
result.reserveCapacity(messages.count)
for message in messages {
@@ -349,7 +349,7 @@ public struct MoltbotChatView: View {
var content = last.content
content.append(
MoltbotChatMessageContent(
OpenClawChatMessageContent(
type: "tool_result",
text: toolText,
thinking: nil,
@@ -361,7 +361,7 @@ public struct MoltbotChatView: View {
name: message.toolName,
arguments: nil))
let merged = MoltbotChatMessage(
let merged = OpenClawChatMessage(
id: last.id,
role: last.role,
content: content,
@@ -376,12 +376,12 @@ public struct MoltbotChatView: View {
return result
}
private func isToolResultMessage(_ message: MoltbotChatMessage) -> Bool {
private func isToolResultMessage(_ message: OpenClawChatMessage) -> Bool {
let role = message.role.lowercased()
return role == "toolresult" || role == "tool_result"
}
private func toolCallIds(in message: MoltbotChatMessage) -> Set<String> {
private func toolCallIds(in message: OpenClawChatMessage) -> Set<String> {
var ids = Set<String>()
for content in message.content {
let kind = (content.type ?? "").lowercased()
@@ -398,7 +398,7 @@ public struct MoltbotChatView: View {
return ids
}
private func toolResultText(from message: MoltbotChatMessage) -> String {
private func toolResultText(from message: OpenClawChatMessage) -> String {
let parts = message.content.compactMap { content -> String? in
let kind = (content.type ?? "text").lowercased()
guard kind == "text" || kind.isEmpty else { return nil }
@@ -446,7 +446,7 @@ private struct ChatNoticeCard: View {
.padding(18)
.background(
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(MoltbotChatTheme.subtleCard)
.fill(OpenClawChatTheme.subtleCard)
.overlay(
RoundedRectangle(cornerRadius: 18, style: .continuous)
.strokeBorder(Color.white.opacity(0.12), lineWidth: 1)))
@@ -499,7 +499,7 @@ private struct ChatNoticeBanner: View {
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(MoltbotChatTheme.subtleCard)
.fill(OpenClawChatTheme.subtleCard)
.overlay(
RoundedRectangle(cornerRadius: 14, style: .continuous)
.strokeBorder(Color.white.opacity(0.12), lineWidth: 1)))
@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import Foundation
import Observation
import OSLog
@@ -10,28 +10,28 @@ import AppKit
import UIKit
#endif
private let chatUILogger = Logger(subsystem: "bot.molt", category: "MoltbotChatUI")
private let chatUILogger = Logger(subsystem: "ai.openclaw", category: "OpenClawChatUI")
@MainActor
@Observable
public final class MoltbotChatViewModel {
public private(set) var messages: [MoltbotChatMessage] = []
public final class OpenClawChatViewModel {
public private(set) var messages: [OpenClawChatMessage] = []
public var input: String = ""
public var thinkingLevel: String = "off"
public private(set) var isLoading = false
public private(set) var isSending = false
public private(set) var isAborting = false
public var errorText: String?
public var attachments: [MoltbotPendingAttachment] = []
public var attachments: [OpenClawPendingAttachment] = []
public private(set) var healthOK: Bool = false
public private(set) var pendingRunCount: Int = 0
public private(set) var sessionKey: String
public private(set) var sessionId: String?
public private(set) var streamingAssistantText: String?
public private(set) var pendingToolCalls: [MoltbotChatPendingToolCall] = []
public private(set) var sessions: [MoltbotChatSessionEntry] = []
private let transport: any MoltbotChatTransport
public private(set) var pendingToolCalls: [OpenClawChatPendingToolCall] = []
public private(set) var sessions: [OpenClawChatSessionEntry] = []
private let transport: any OpenClawChatTransport
@ObservationIgnored
private nonisolated(unsafe) var eventTask: Task<Void, Never>?
@@ -43,7 +43,7 @@ public final class MoltbotChatViewModel {
private nonisolated(unsafe) var pendingRunTimeoutTasks: [String: Task<Void, Never>] = [:]
private let pendingRunTimeoutMs: UInt64 = 120_000
private var pendingToolCallsById: [String: MoltbotChatPendingToolCall] = [:] {
private var pendingToolCallsById: [String: OpenClawChatPendingToolCall] = [:] {
didSet {
self.pendingToolCalls = self.pendingToolCallsById.values
.sorted { ($0.startedAt ?? 0) < ($1.startedAt ?? 0) }
@@ -52,7 +52,7 @@ public final class MoltbotChatViewModel {
private var lastHealthPollAt: Date?
public init(sessionKey: String, transport: any MoltbotChatTransport) {
public init(sessionKey: String, transport: any OpenClawChatTransport) {
self.sessionKey = sessionKey
self.transport = transport
@@ -99,12 +99,12 @@ public final class MoltbotChatViewModel {
Task { await self.performSwitchSession(to: sessionKey) }
}
public var sessionChoices: [MoltbotChatSessionEntry] {
public var sessionChoices: [OpenClawChatSessionEntry] {
let now = Date().timeIntervalSince1970 * 1000
let cutoff = now - (24 * 60 * 60 * 1000)
let sorted = self.sessions.sorted { ($0.updatedAt ?? 0) > ($1.updatedAt ?? 0) }
var seen = Set<String>()
var recent: [MoltbotChatSessionEntry] = []
var recent: [OpenClawChatSessionEntry] = []
for entry in sorted {
guard !seen.contains(entry.key) else { continue }
seen.insert(entry.key)
@@ -112,7 +112,7 @@ public final class MoltbotChatViewModel {
recent.append(entry)
}
var result: [MoltbotChatSessionEntry] = []
var result: [OpenClawChatSessionEntry] = []
var included = Set<String>()
for entry in recent where !included.contains(entry.key) {
result.append(entry)
@@ -138,7 +138,7 @@ public final class MoltbotChatViewModel {
Task { await self.addImageAttachment(url: nil, data: data, fileName: fileName, mimeType: mimeType) }
}
public func removeAttachment(_ id: MoltbotPendingAttachment.ID) {
public func removeAttachment(_ id: OpenClawPendingAttachment.ID) {
self.attachments.removeAll { $0.id == id }
}
@@ -180,15 +180,15 @@ public final class MoltbotChatViewModel {
}
}
private static func decodeMessages(_ raw: [AnyCodable]) -> [MoltbotChatMessage] {
private static func decodeMessages(_ raw: [AnyCodable]) -> [OpenClawChatMessage] {
let decoded = raw.compactMap { item in
(try? ChatPayloadDecoding.decode(item, as: MoltbotChatMessage.self))
(try? ChatPayloadDecoding.decode(item, as: OpenClawChatMessage.self))
}
return Self.dedupeMessages(decoded)
}
private static func dedupeMessages(_ messages: [MoltbotChatMessage]) -> [MoltbotChatMessage] {
var result: [MoltbotChatMessage] = []
private static func dedupeMessages(_ messages: [OpenClawChatMessage]) -> [OpenClawChatMessage] {
var result: [OpenClawChatMessage] = []
result.reserveCapacity(messages.count)
var seen = Set<String>()
@@ -205,7 +205,7 @@ public final class MoltbotChatViewModel {
return result
}
private static func dedupeKey(for message: MoltbotChatMessage) -> String? {
private static func dedupeKey(for message: OpenClawChatMessage) -> String? {
guard let timestamp = message.timestamp else { return nil }
let text = message.content.compactMap(\.text).joined(separator: "\n")
.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -233,8 +233,8 @@ public final class MoltbotChatViewModel {
self.streamingAssistantText = nil
// Optimistically append user message to UI.
var userContent: [MoltbotChatMessageContent] = [
MoltbotChatMessageContent(
var userContent: [OpenClawChatMessageContent] = [
OpenClawChatMessageContent(
type: "text",
text: messageText,
thinking: nil,
@@ -246,8 +246,8 @@ public final class MoltbotChatViewModel {
name: nil,
arguments: nil),
]
let encodedAttachments = self.attachments.map { att -> MoltbotChatAttachmentPayload in
MoltbotChatAttachmentPayload(
let encodedAttachments = self.attachments.map { att -> OpenClawChatAttachmentPayload in
OpenClawChatAttachmentPayload(
type: att.type,
mimeType: att.mimeType,
fileName: att.fileName,
@@ -255,7 +255,7 @@ public final class MoltbotChatViewModel {
}
for att in encodedAttachments {
userContent.append(
MoltbotChatMessageContent(
OpenClawChatMessageContent(
type: att.type,
text: nil,
thinking: nil,
@@ -268,7 +268,7 @@ public final class MoltbotChatViewModel {
arguments: nil))
}
self.messages.append(
MoltbotChatMessage(
OpenClawChatMessage(
id: UUID(),
role: "user",
content: userContent,
@@ -332,8 +332,8 @@ public final class MoltbotChatViewModel {
await self.bootstrap()
}
private func placeholderSession(key: String) -> MoltbotChatSessionEntry {
MoltbotChatSessionEntry(
private func placeholderSession(key: String) -> OpenClawChatSessionEntry {
OpenClawChatSessionEntry(
key: key,
kind: nil,
displayName: nil,
@@ -354,7 +354,7 @@ public final class MoltbotChatViewModel {
contextTokens: nil)
}
private func handleTransportEvent(_ evt: MoltbotChatTransportEvent) {
private func handleTransportEvent(_ evt: OpenClawChatTransportEvent) {
switch evt {
case let .health(ok):
self.healthOK = ok
@@ -370,7 +370,7 @@ public final class MoltbotChatViewModel {
}
}
private func handleChatEvent(_ chat: MoltbotChatEventPayload) {
private func handleChatEvent(_ chat: OpenClawChatEventPayload) {
if let sessionKey = chat.sessionKey, sessionKey != self.sessionKey {
return
}
@@ -407,7 +407,7 @@ public final class MoltbotChatViewModel {
}
}
private func handleAgentEvent(_ evt: MoltbotAgentEventPayload) {
private func handleAgentEvent(_ evt: OpenClawAgentEventPayload) {
if let sessionId, evt.runId != sessionId {
return
}
@@ -423,7 +423,7 @@ public final class MoltbotChatViewModel {
guard let toolCallId = evt.data["toolCallId"]?.value as? String else { return }
if phase == "start" {
let args = evt.data["args"]
self.pendingToolCallsById[toolCallId] = MoltbotChatPendingToolCall(
self.pendingToolCallsById[toolCallId] = OpenClawChatPendingToolCall(
toolCallId: toolCallId,
name: name,
args: args,
@@ -534,7 +534,7 @@ public final class MoltbotChatViewModel {
let preview = Self.previewImage(data: data)
self.attachments.append(
MoltbotPendingAttachment(
OpenClawPendingAttachment(
url: url,
data: data,
fileName: fileName,
@@ -542,7 +542,7 @@ public final class MoltbotChatViewModel {
preview: preview))
}
private static func previewImage(data: Data) -> MoltbotPlatformImage? {
private static func previewImage(data: Data) -> OpenClawPlatformImage? {
#if canImport(AppKit)
NSImage(data: data)
#elseif canImport(UIKit)
@@ -0,0 +1,40 @@
import Foundation
public enum OpenClawBonjour {
// v0: internal-only, subject to rename.
public static let gatewayServiceType = "_openclaw-gw._tcp"
public static let gatewayServiceDomain = "local."
public static var wideAreaGatewayServiceDomain: String? {
let env = ProcessInfo.processInfo.environment
return resolveWideAreaDomain(env["OPENCLAW_WIDE_AREA_DOMAIN"])
}
public static var gatewayServiceDomains: [String] {
var domains = [gatewayServiceDomain]
if let wideArea = wideAreaGatewayServiceDomain {
domains.append(wideArea)
}
return domains
}
private static func resolveWideAreaDomain(_ raw: String?) -> String? {
let trimmed = (raw ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty { return nil }
let normalized = normalizeServiceDomain(trimmed)
return normalized == gatewayServiceDomain ? nil : normalized
}
public static func normalizeServiceDomain(_ raw: String?) -> String {
let trimmed = (raw ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty {
return self.gatewayServiceDomain
}
let lower = trimmed.lowercased()
if lower == "local" || lower == "local." {
return self.gatewayServiceDomain
}
return lower.hasSuffix(".") ? lower : (lower + ".")
}
}
@@ -27,14 +27,14 @@ public struct BridgeInvokeResponse: Codable, Sendable {
public let id: String
public let ok: Bool
public let payloadJSON: String?
public let error: MoltbotNodeError?
public let error: OpenClawNodeError?
public init(
type: String = "invoke-res",
id: String,
ok: Bool,
payloadJSON: String? = nil,
error: MoltbotNodeError? = nil)
error: OpenClawNodeError? = nil)
{
self.type = type
self.id = id
@@ -1,38 +1,38 @@
import Foundation
public enum MoltbotCameraCommand: String, Codable, Sendable {
public enum OpenClawCameraCommand: String, Codable, Sendable {
case list = "camera.list"
case snap = "camera.snap"
case clip = "camera.clip"
}
public enum MoltbotCameraFacing: String, Codable, Sendable {
public enum OpenClawCameraFacing: String, Codable, Sendable {
case back
case front
}
public enum MoltbotCameraImageFormat: String, Codable, Sendable {
public enum OpenClawCameraImageFormat: String, Codable, Sendable {
case jpg
case jpeg
}
public enum MoltbotCameraVideoFormat: String, Codable, Sendable {
public enum OpenClawCameraVideoFormat: String, Codable, Sendable {
case mp4
}
public struct MoltbotCameraSnapParams: Codable, Sendable, Equatable {
public var facing: MoltbotCameraFacing?
public struct OpenClawCameraSnapParams: Codable, Sendable, Equatable {
public var facing: OpenClawCameraFacing?
public var maxWidth: Int?
public var quality: Double?
public var format: MoltbotCameraImageFormat?
public var format: OpenClawCameraImageFormat?
public var deviceId: String?
public var delayMs: Int?
public init(
facing: MoltbotCameraFacing? = nil,
facing: OpenClawCameraFacing? = nil,
maxWidth: Int? = nil,
quality: Double? = nil,
format: MoltbotCameraImageFormat? = nil,
format: OpenClawCameraImageFormat? = nil,
deviceId: String? = nil,
delayMs: Int? = nil)
{
@@ -45,18 +45,18 @@ public struct MoltbotCameraSnapParams: Codable, Sendable, Equatable {
}
}
public struct MoltbotCameraClipParams: Codable, Sendable, Equatable {
public var facing: MoltbotCameraFacing?
public struct OpenClawCameraClipParams: Codable, Sendable, Equatable {
public var facing: OpenClawCameraFacing?
public var durationMs: Int?
public var includeAudio: Bool?
public var format: MoltbotCameraVideoFormat?
public var format: OpenClawCameraVideoFormat?
public var deviceId: String?
public init(
facing: MoltbotCameraFacing? = nil,
facing: OpenClawCameraFacing? = nil,
durationMs: Int? = nil,
includeAudio: Bool? = nil,
format: MoltbotCameraVideoFormat? = nil,
format: OpenClawCameraVideoFormat? = nil,
deviceId: String? = nil)
{
self.facing = facing
@@ -1,6 +1,6 @@
import Foundation
public enum MoltbotCanvasA2UIAction: Sendable {
public enum OpenClawCanvasA2UIAction: Sendable {
public struct AgentMessageContext: Sendable {
public struct Session: Sendable {
public var key: String
@@ -94,6 +94,11 @@ public enum MoltbotCanvasA2UIAction: Sendable {
}
return "{\"id\":\"\(actionId)\",\"ok\":\(ok ? "true" : "false"),\"error\":\"\"}"
}()
return "window.dispatchEvent(new CustomEvent('moltbot:a2ui-action-status', { detail: \(json) }));"
return """
(() => {
const detail = \(json);
window.dispatchEvent(new CustomEvent('openclaw:a2ui-action-status', { detail }));
})();
"""
}
}
@@ -1,6 +1,6 @@
import Foundation
public enum MoltbotCanvasA2UICommand: String, Codable, Sendable {
public enum OpenClawCanvasA2UICommand: String, Codable, Sendable {
/// Render A2UI content on the device canvas.
case push = "canvas.a2ui.push"
/// Legacy alias for `push` when sending JSONL.
@@ -9,7 +9,7 @@ public enum MoltbotCanvasA2UICommand: String, Codable, Sendable {
case reset = "canvas.a2ui.reset"
}
public struct MoltbotCanvasA2UIPushParams: Codable, Sendable, Equatable {
public struct OpenClawCanvasA2UIPushParams: Codable, Sendable, Equatable {
public var messages: [AnyCodable]
public init(messages: [AnyCodable]) {
@@ -17,7 +17,7 @@ public struct MoltbotCanvasA2UIPushParams: Codable, Sendable, Equatable {
}
}
public struct MoltbotCanvasA2UIPushJSONLParams: Codable, Sendable, Equatable {
public struct OpenClawCanvasA2UIPushJSONLParams: Codable, Sendable, Equatable {
public var jsonl: String
public init(jsonl: String) {
@@ -1,6 +1,6 @@
import Foundation
public enum MoltbotCanvasA2UIJSONL: Sendable {
public enum OpenClawCanvasA2UIJSONL: Sendable {
public struct ParsedItem: Sendable {
public var lineNumber: Int
public var message: AnyCodable
@@ -1,6 +1,6 @@
import Foundation
public struct MoltbotCanvasNavigateParams: Codable, Sendable, Equatable {
public struct OpenClawCanvasNavigateParams: Codable, Sendable, Equatable {
public var url: String
public init(url: String) {
@@ -8,7 +8,7 @@ public struct MoltbotCanvasNavigateParams: Codable, Sendable, Equatable {
}
}
public struct MoltbotCanvasPlacement: Codable, Sendable, Equatable {
public struct OpenClawCanvasPlacement: Codable, Sendable, Equatable {
public var x: Double?
public var y: Double?
public var width: Double?
@@ -22,17 +22,17 @@ public struct MoltbotCanvasPlacement: Codable, Sendable, Equatable {
}
}
public struct MoltbotCanvasPresentParams: Codable, Sendable, Equatable {
public struct OpenClawCanvasPresentParams: Codable, Sendable, Equatable {
public var url: String?
public var placement: MoltbotCanvasPlacement?
public var placement: OpenClawCanvasPlacement?
public init(url: String? = nil, placement: MoltbotCanvasPlacement? = nil) {
public init(url: String? = nil, placement: OpenClawCanvasPlacement? = nil) {
self.url = url
self.placement = placement
}
}
public struct MoltbotCanvasEvalParams: Codable, Sendable, Equatable {
public struct OpenClawCanvasEvalParams: Codable, Sendable, Equatable {
public var javaScript: String
public init(javaScript: String) {
@@ -40,7 +40,7 @@ public struct MoltbotCanvasEvalParams: Codable, Sendable, Equatable {
}
}
public enum MoltbotCanvasSnapshotFormat: String, Codable, Sendable {
public enum OpenClawCanvasSnapshotFormat: String, Codable, Sendable {
case png
case jpeg
@@ -63,12 +63,12 @@ public enum MoltbotCanvasSnapshotFormat: String, Codable, Sendable {
}
}
public struct MoltbotCanvasSnapshotParams: Codable, Sendable, Equatable {
public struct OpenClawCanvasSnapshotParams: Codable, Sendable, Equatable {
public var maxWidth: Int?
public var quality: Double?
public var format: MoltbotCanvasSnapshotFormat?
public var format: OpenClawCanvasSnapshotFormat?
public init(maxWidth: Int? = nil, quality: Double? = nil, format: MoltbotCanvasSnapshotFormat? = nil) {
public init(maxWidth: Int? = nil, quality: Double? = nil, format: OpenClawCanvasSnapshotFormat? = nil) {
self.maxWidth = maxWidth
self.quality = quality
self.format = format
@@ -1,6 +1,6 @@
import Foundation
public enum MoltbotCanvasCommand: String, Codable, Sendable {
public enum OpenClawCanvasCommand: String, Codable, Sendable {
case present = "canvas.present"
case hide = "canvas.hide"
case navigate = "canvas.navigate"
@@ -1,6 +1,6 @@
import Foundation
public enum MoltbotCapability: String, Codable, Sendable {
public enum OpenClawCapability: String, Codable, Sendable {
case canvas
case camera
case screen
@@ -37,7 +37,11 @@ public struct AgentDeepLink: Codable, Sendable, Equatable {
public enum DeepLinkParser {
public static func parse(_ url: URL) -> DeepLinkRoute? {
guard url.scheme?.lowercased() == "moltbot" else { return nil }
guard let scheme = url.scheme?.lowercased(),
scheme == "openclaw"
else {
return nil
}
guard let host = url.host?.lowercased(), !host.isEmpty else { return nil }
guard let comps = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return nil }
@@ -16,21 +16,23 @@ public struct DeviceIdentity: Codable, Sendable {
}
enum DeviceIdentityPaths {
private static let stateDirEnv = "CLAWDBOT_STATE_DIR"
private static let stateDirEnv = ["OPENCLAW_STATE_DIR"]
static func stateDirURL() -> URL {
if let raw = getenv(self.stateDirEnv) {
let value = String(cString: raw).trimmingCharacters(in: .whitespacesAndNewlines)
if !value.isEmpty {
return URL(fileURLWithPath: value, isDirectory: true)
for key in self.stateDirEnv {
if let raw = getenv(key) {
let value = String(cString: raw).trimmingCharacters(in: .whitespacesAndNewlines)
if !value.isEmpty {
return URL(fileURLWithPath: value, isDirectory: true)
}
}
}
if let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
return appSupport.appendingPathComponent("moltbot", isDirectory: true)
return appSupport.appendingPathComponent("OpenClaw", isDirectory: true)
}
return FileManager.default.temporaryDirectory.appendingPathComponent("moltbot", isDirectory: true)
return FileManager.default.temporaryDirectory.appendingPathComponent("openclaw", isDirectory: true)
}
}
@@ -1,4 +1,4 @@
import MoltbotProtocol
import OpenClawProtocol
import Foundation
import OSLog
@@ -102,14 +102,14 @@ public enum GatewayAuthSource: String, Sendable {
}
// Avoid ambiguity with the app's own AnyCodable type.
private typealias ProtoAnyCodable = MoltbotProtocol.AnyCodable
private typealias ProtoAnyCodable = OpenClawProtocol.AnyCodable
private enum ConnectChallengeError: Error {
case timeout
}
public actor GatewayChannelActor {
private let logger = Logger(subsystem: "bot.molt", category: "gateway")
private let logger = Logger(subsystem: "ai.openclaw", category: "gateway")
private var task: WebSocketTaskBox?
private var pending: [String: CheckedContinuation<GatewayFrame, Error>] = [:]
private var connected = false
@@ -268,7 +268,7 @@ public actor GatewayChannelActor {
caps: [],
commands: [],
permissions: [:],
clientId: "moltbot-macos",
clientId: "openclaw-macos",
clientMode: "ui",
clientDisplayName: InstanceIdentity.displayName)
let clientDisplayName = options.clientDisplayName ?? InstanceIdentity.displayName
@@ -1,4 +1,4 @@
import MoltbotProtocol
import OpenClawProtocol
import Foundation
/// Structured error surfaced when the gateway responds with `{ ok: false }`.
@@ -1,4 +1,4 @@
import MoltbotProtocol
import OpenClawProtocol
import Foundation
import OSLog
@@ -12,7 +12,7 @@ private struct NodeInvokeRequestPayload: Codable, Sendable {
}
public actor GatewayNodeSession {
private let logger = Logger(subsystem: "bot.molt", category: "node.gateway")
private let logger = Logger(subsystem: "ai.openclaw", category: "node.gateway")
private let decoder = JSONDecoder()
private let encoder = JSONEncoder()
private var channel: GatewayChannelActor?
@@ -41,7 +41,7 @@ public actor GatewayNodeSession {
return BridgeInvokeResponse(
id: request.id,
ok: false,
error: MoltbotNodeError(
error: OpenClawNodeError(
code: .unavailable,
message: "node invoke timed out")
)
@@ -1,9 +1,9 @@
import MoltbotProtocol
import OpenClawProtocol
import Foundation
public enum GatewayPayloadDecoding {
public static func decode<T: Decodable>(
_ payload: MoltbotProtocol.AnyCodable,
_ payload: OpenClawProtocol.AnyCodable,
as _: T.Type = T.self) throws -> T
{
let data = try JSONEncoder().encode(payload)
@@ -19,7 +19,7 @@ public enum GatewayPayloadDecoding {
}
public static func decodeIfPresent<T: Decodable>(
_ payload: MoltbotProtocol.AnyCodable?,
_ payload: OpenClawProtocol.AnyCodable?,
as _: T.Type = T.self) throws -> T?
{
guard let payload else { return nil }
@@ -1,4 +1,4 @@
import MoltbotProtocol
import OpenClawProtocol
/// Server-push messages from the gateway websocket.
///
@@ -17,29 +17,17 @@ public struct GatewayTLSParams: Sendable {
}
public enum GatewayTLSStore {
private static let suiteName = "bot.molt.shared"
private static let legacySuiteName = "com.clawdbot.shared"
private static let suiteName = "ai.openclaw.shared"
private static let keyPrefix = "gateway.tls."
private static var defaults: UserDefaults {
UserDefaults(suiteName: suiteName) ?? .standard
}
private static var legacyDefaults: UserDefaults? {
UserDefaults(suiteName: legacySuiteName)
}
public static func loadFingerprint(stableID: String) -> String? {
let key = self.keyPrefix + stableID
let raw = self.defaults.string(forKey: key)?.trimmingCharacters(in: .whitespacesAndNewlines)
if raw?.isEmpty == false { return raw }
let legacy = self.legacyDefaults?.string(forKey: key)?.trimmingCharacters(in: .whitespacesAndNewlines)
if legacy?.isEmpty == false {
self.defaults.set(legacy, forKey: key)
return legacy
}
return nil
}
@@ -5,18 +5,13 @@ import UIKit
#endif
public enum InstanceIdentity {
private static let suiteName = "bot.molt.shared"
private static let legacySuiteName = "com.clawdbot.shared"
private static let suiteName = "ai.openclaw.shared"
private static let instanceIdKey = "instanceId"
private static var defaults: UserDefaults {
UserDefaults(suiteName: suiteName) ?? .standard
}
private static var legacyDefaults: UserDefaults? {
UserDefaults(suiteName: legacySuiteName)
}
#if canImport(UIKit)
private static func readMainActor<T: Sendable>(_ body: @MainActor () -> T) -> T {
if Thread.isMainThread {
@@ -37,14 +32,6 @@ public enum InstanceIdentity {
return existing
}
if let legacy = Self.legacyDefaults?.string(forKey: instanceIdKey)?
.trimmingCharacters(in: .whitespacesAndNewlines),
!legacy.isEmpty
{
defaults.set(legacy, forKey: instanceIdKey)
return legacy
}
let id = UUID().uuidString.lowercased()
defaults.set(id, forKey: instanceIdKey)
return id
@@ -55,14 +42,14 @@ public enum InstanceIdentity {
let name = Self.readMainActor {
UIDevice.current.name.trimmingCharacters(in: .whitespacesAndNewlines)
}
return name.isEmpty ? "moltbot" : name
return name.isEmpty ? "openclaw" : name
#else
if let name = Host.current().localizedName?.trimmingCharacters(in: .whitespacesAndNewlines),
!name.isEmpty
{
return name
}
return "moltbot"
return "openclaw"
#endif
}()
@@ -1,28 +1,28 @@
import Foundation
public enum MoltbotLocationCommand: String, Codable, Sendable {
public enum OpenClawLocationCommand: String, Codable, Sendable {
case get = "location.get"
}
public enum MoltbotLocationAccuracy: String, Codable, Sendable {
public enum OpenClawLocationAccuracy: String, Codable, Sendable {
case coarse
case balanced
case precise
}
public struct MoltbotLocationGetParams: Codable, Sendable, Equatable {
public struct OpenClawLocationGetParams: Codable, Sendable, Equatable {
public var timeoutMs: Int?
public var maxAgeMs: Int?
public var desiredAccuracy: MoltbotLocationAccuracy?
public var desiredAccuracy: OpenClawLocationAccuracy?
public init(timeoutMs: Int? = nil, maxAgeMs: Int? = nil, desiredAccuracy: MoltbotLocationAccuracy? = nil) {
public init(timeoutMs: Int? = nil, maxAgeMs: Int? = nil, desiredAccuracy: OpenClawLocationAccuracy? = nil) {
self.timeoutMs = timeoutMs
self.maxAgeMs = maxAgeMs
self.desiredAccuracy = desiredAccuracy
}
}
public struct MoltbotLocationPayload: Codable, Sendable, Equatable {
public struct OpenClawLocationPayload: Codable, Sendable, Equatable {
public var lat: Double
public var lon: Double
public var accuracyMeters: Double
@@ -0,0 +1,7 @@
import Foundation
public enum OpenClawLocationMode: String, Codable, Sendable, CaseIterable {
case off
case whileUsing
case always
}
@@ -1,6 +1,6 @@
import Foundation
public enum MoltbotNodeErrorCode: String, Codable, Sendable {
public enum OpenClawNodeErrorCode: String, Codable, Sendable {
case notPaired = "NOT_PAIRED"
case unauthorized = "UNAUTHORIZED"
case backgroundUnavailable = "NODE_BACKGROUND_UNAVAILABLE"
@@ -8,14 +8,14 @@ public enum MoltbotNodeErrorCode: String, Codable, Sendable {
case unavailable = "UNAVAILABLE"
}
public struct MoltbotNodeError: Error, Codable, Sendable, Equatable {
public var code: MoltbotNodeErrorCode
public struct OpenClawNodeError: Error, Codable, Sendable, Equatable {
public var code: OpenClawNodeErrorCode
public var message: String
public var retryable: Bool?
public var retryAfterMs: Int?
public init(
code: MoltbotNodeErrorCode,
code: OpenClawNodeErrorCode,
message: String,
retryable: Bool? = nil,
retryAfterMs: Int? = nil)
@@ -1,7 +1,7 @@
import Foundation
public enum MoltbotKitResources {
/// Resource bundle for MoltbotKit.
public enum OpenClawKitResources {
/// Resource bundle for OpenClawKit.
///
/// Locates the SwiftPM-generated resource bundle, checking multiple locations:
/// 1. Inside Bundle.main (packaged apps)
@@ -13,7 +13,7 @@ public enum MoltbotKitResources {
/// SwiftPM's expectations.
public static let bundle: Bundle = locateBundle()
private static let bundleName = "MoltbotKit_MoltbotKit"
private static let bundleName = "OpenClawKit_OpenClawKit"
private static func locateBundle() -> Bundle {
// 1. Check inside Bundle.main (packaged apps copy resources here)
@@ -55,7 +55,7 @@
backface-visibility: hidden;
opacity: 0.45;
pointer-events: none;
animation: moltbot-grid-drift 140s ease-in-out infinite alternate;
animation: openclaw-grid-drift 140s ease-in-out infinite alternate;
}
:root[data-platform="android"] body::before { opacity: 0.80; }
body::after {
@@ -73,7 +73,7 @@
backface-visibility: hidden;
transform: translate3d(0,0,0);
pointer-events: none;
animation: moltbot-glow-drift 110s ease-in-out infinite alternate;
animation: openclaw-glow-drift 110s ease-in-out infinite alternate;
}
:root[data-platform="android"] body::after { opacity: 0.85; }
@supports (mix-blend-mode: screen) {
@@ -82,12 +82,12 @@
@supports not (mix-blend-mode: screen) {
body::after { opacity: 0.70; }
}
@keyframes moltbot-grid-drift {
@keyframes openclaw-grid-drift {
0% { transform: translate3d(-12px, 8px, 0) rotate(-7deg); opacity: 0.40; }
50% { transform: translate3d( 10px,-7px, 0) rotate(-6.6deg); opacity: 0.56; }
100% { transform: translate3d(-8px, 6px, 0) rotate(-7.2deg); opacity: 0.42; }
}
@keyframes moltbot-glow-drift {
@keyframes openclaw-glow-drift {
0% { transform: translate3d(-18px, 12px, 0) scale(1.02); opacity: 0.40; }
50% { transform: translate3d( 14px,-10px, 0) scale(1.05); opacity: 0.52; }
100% { transform: translate3d(-10px, 8px, 0) scale(1.03); opacity: 0.43; }
@@ -101,14 +101,14 @@
touch-action: none;
z-index: 1;
}
:root[data-platform="android"] #moltbot-canvas {
:root[data-platform="android"] #openclaw-canvas {
background:
radial-gradient(1100px 800px at 20% 15%, rgba(42, 113, 255, 0.78), rgba(0,0,0,0) 58%),
radial-gradient(900px 650px at 82% 28%, rgba(255, 0, 138, 0.66), rgba(0,0,0,0) 62%),
radial-gradient(1000px 900px at 60% 88%, rgba(0, 209, 255, 0.58), rgba(0,0,0,0) 62%),
#141c33;
}
#moltbot-status {
#openclaw-status {
position: fixed;
inset: 0;
display: none;
@@ -119,7 +119,7 @@
pointer-events: none;
z-index: 3;
}
#moltbot-status .card {
#openclaw-status .card {
text-align: center;
padding: 16px 18px;
border-radius: 14px;
@@ -129,13 +129,13 @@
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
}
#moltbot-status .title {
#openclaw-status .title {
font: 600 20px -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", system-ui, sans-serif;
letter-spacing: 0.2px;
color: rgba(255,255,255,0.92);
text-shadow: 0 0 22px rgba(42, 113, 255, 0.35);
}
#moltbot-status .subtitle {
#openclaw-status .subtitle {
margin-top: 6px;
font: 500 12px -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
color: rgba(255,255,255,0.58);
@@ -143,20 +143,20 @@
</style>
</head>
<body>
<canvas id="moltbot-canvas"></canvas>
<div id="moltbot-status">
<canvas id="openclaw-canvas"></canvas>
<div id="openclaw-status">
<div class="card">
<div class="title" id="moltbot-status-title">Ready</div>
<div class="subtitle" id="moltbot-status-subtitle">Waiting for agent</div>
<div class="title" id="openclaw-status-title">Ready</div>
<div class="subtitle" id="openclaw-status-subtitle">Waiting for agent</div>
</div>
</div>
<script>
(() => {
const canvas = document.getElementById('moltbot-canvas');
const canvas = document.getElementById('openclaw-canvas');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('moltbot-status');
const titleEl = document.getElementById('moltbot-status-title');
const subtitleEl = document.getElementById('moltbot-status-subtitle');
const statusEl = document.getElementById('openclaw-status');
const titleEl = document.getElementById('openclaw-status-title');
const subtitleEl = document.getElementById('openclaw-status-subtitle');
const debugStatusEnabledByQuery = (() => {
try {
const params = new URLSearchParams(window.location.search);
@@ -194,7 +194,7 @@
statusEl.style.display = 'none';
}
window.__moltbot = {
const api = {
canvas,
ctx,
setDebugStatusEnabled,
@@ -217,6 +217,7 @@
}
}
};
window.__openclaw = api;
})();
</script>
@@ -1,10 +1,10 @@
import Foundation
public enum MoltbotScreenCommand: String, Codable, Sendable {
public enum OpenClawScreenCommand: String, Codable, Sendable {
case record = "screen.record"
}
public struct MoltbotScreenRecordParams: Codable, Sendable, Equatable {
public struct OpenClawScreenRecordParams: Codable, Sendable, Equatable {
public var screenIndex: Int?
public var durationMs: Int?
public var fps: Double?
@@ -1,14 +1,14 @@
import Foundation
public enum MoltbotNodeStorage {
public enum OpenClawNodeStorage {
public static func appSupportDir() throws -> URL {
let base = FileManager().urls(for: .applicationSupportDirectory, in: .userDomainMask).first
guard let base else {
throw NSError(domain: "MoltbotNodeStorage", code: 1, userInfo: [
throw NSError(domain: "OpenClawNodeStorage", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Application Support directory unavailable",
])
}
return base.appendingPathComponent("Moltbot", isDirectory: true)
return base.appendingPathComponent("OpenClaw", isDirectory: true)
}
public static func canvasRoot(sessionKey: String) throws -> URL {
@@ -21,11 +21,11 @@ public enum MoltbotNodeStorage {
public static func cachesDir() throws -> URL {
let base = FileManager().urls(for: .cachesDirectory, in: .userDomainMask).first
guard let base else {
throw NSError(domain: "MoltbotNodeStorage", code: 2, userInfo: [
throw NSError(domain: "OpenClawNodeStorage", code: 2, userInfo: [
NSLocalizedDescriptionKey: "Caches directory unavailable",
])
}
return base.appendingPathComponent("Moltbot", isDirectory: true)
return base.appendingPathComponent("OpenClaw", isDirectory: true)
}
public static func canvasSnapshotsRoot(sessionKey: String) throws -> URL {
@@ -1,6 +1,6 @@
import Foundation
public enum MoltbotSystemCommand: String, Codable, Sendable {
public enum OpenClawSystemCommand: String, Codable, Sendable {
case run = "system.run"
case which = "system.which"
case notify = "system.notify"
@@ -8,19 +8,19 @@ public enum MoltbotSystemCommand: String, Codable, Sendable {
case execApprovalsSet = "system.execApprovals.set"
}
public enum MoltbotNotificationPriority: String, Codable, Sendable {
public enum OpenClawNotificationPriority: String, Codable, Sendable {
case passive
case active
case timeSensitive
}
public enum MoltbotNotificationDelivery: String, Codable, Sendable {
public enum OpenClawNotificationDelivery: String, Codable, Sendable {
case system
case overlay
case auto
}
public struct MoltbotSystemRunParams: Codable, Sendable, Equatable {
public struct OpenClawSystemRunParams: Codable, Sendable, Equatable {
public var command: [String]
public var rawCommand: String?
public var cwd: String?
@@ -57,7 +57,7 @@ public struct MoltbotSystemRunParams: Codable, Sendable, Equatable {
}
}
public struct MoltbotSystemWhichParams: Codable, Sendable, Equatable {
public struct OpenClawSystemWhichParams: Codable, Sendable, Equatable {
public var bins: [String]
public init(bins: [String]) {
@@ -65,19 +65,19 @@ public struct MoltbotSystemWhichParams: Codable, Sendable, Equatable {
}
}
public struct MoltbotSystemNotifyParams: Codable, Sendable, Equatable {
public struct OpenClawSystemNotifyParams: Codable, Sendable, Equatable {
public var title: String
public var body: String
public var sound: String?
public var priority: MoltbotNotificationPriority?
public var delivery: MoltbotNotificationDelivery?
public var priority: OpenClawNotificationPriority?
public var delivery: OpenClawNotificationDelivery?
public init(
title: String,
body: String,
sound: String? = nil,
priority: MoltbotNotificationPriority? = nil,
delivery: MoltbotNotificationDelivery? = nil)
priority: OpenClawNotificationPriority? = nil,
delivery: OpenClawNotificationDelivery? = nil)
{
self.title = title
self.body = body
@@ -90,7 +90,7 @@ public enum ToolDisplayRegistry {
}
private static func loadConfig() -> ToolDisplayConfig {
guard let url = MoltbotKitResources.bundle.url(forResource: "tool-display", withExtension: "json") else {
guard let url = OpenClawKitResources.bundle.url(forResource: "tool-display", withExtension: "json") else {
return self.defaultConfig()
}
do {
@@ -1,5 +1,5 @@
import Testing
@testable import MoltbotChatUI
@testable import OpenClawChatUI
@Suite struct AssistantTextParserTests {
@Test func splitsThinkAndFinalSegments() {
@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import Testing
@Suite struct BonjourEscapesTests {
@@ -8,7 +8,7 @@ import Testing
}
@Test func decodeSpaces() {
#expect(BonjourEscapes.decode("Moltbot\\032Gateway") == "Moltbot Gateway")
#expect(BonjourEscapes.decode("OpenClaw\\032Gateway") == "OpenClaw Gateway")
}
@Test func decodeMultipleEscapes() {
@@ -1,28 +1,28 @@
import MoltbotKit
import OpenClawKit
import Foundation
import Testing
@Suite struct CanvasA2UIActionTests {
@Test func sanitizeTagValueIsStable() {
#expect(MoltbotCanvasA2UIAction.sanitizeTagValue("Hello World!") == "Hello_World_")
#expect(MoltbotCanvasA2UIAction.sanitizeTagValue(" ") == "-")
#expect(MoltbotCanvasA2UIAction.sanitizeTagValue("macOS 26.2") == "macOS_26.2")
#expect(OpenClawCanvasA2UIAction.sanitizeTagValue("Hello World!") == "Hello_World_")
#expect(OpenClawCanvasA2UIAction.sanitizeTagValue(" ") == "-")
#expect(OpenClawCanvasA2UIAction.sanitizeTagValue("macOS 26.2") == "macOS_26.2")
}
@Test func extractActionNameAcceptsNameOrAction() {
#expect(MoltbotCanvasA2UIAction.extractActionName(["name": "Hello"]) == "Hello")
#expect(MoltbotCanvasA2UIAction.extractActionName(["action": "Wave"]) == "Wave")
#expect(MoltbotCanvasA2UIAction.extractActionName(["name": " ", "action": "Fallback"]) == "Fallback")
#expect(MoltbotCanvasA2UIAction.extractActionName(["action": " "]) == nil)
#expect(OpenClawCanvasA2UIAction.extractActionName(["name": "Hello"]) == "Hello")
#expect(OpenClawCanvasA2UIAction.extractActionName(["action": "Wave"]) == "Wave")
#expect(OpenClawCanvasA2UIAction.extractActionName(["name": " ", "action": "Fallback"]) == "Fallback")
#expect(OpenClawCanvasA2UIAction.extractActionName(["action": " "]) == nil)
}
@Test func formatAgentMessageIsTokenEfficientAndUnambiguous() {
let messageContext = MoltbotCanvasA2UIAction.AgentMessageContext(
let messageContext = OpenClawCanvasA2UIAction.AgentMessageContext(
actionName: "Get Weather",
session: .init(key: "main", surfaceId: "main"),
component: .init(id: "btnWeather", host: "Peters iPad", instanceId: "ipad16,6"),
contextJSON: "{\"city\":\"Vienna\"}")
let msg = MoltbotCanvasA2UIAction.formatAgentMessage(messageContext)
let msg = OpenClawCanvasA2UIAction.formatAgentMessage(messageContext)
#expect(msg.contains("CANVAS_A2UI "))
#expect(msg.contains("action=Get_Weather"))
@@ -1,11 +1,11 @@
import MoltbotKit
import OpenClawKit
import Testing
@Suite struct CanvasA2UITests {
@Test func commandStringsAreStable() {
#expect(MoltbotCanvasA2UICommand.push.rawValue == "canvas.a2ui.push")
#expect(MoltbotCanvasA2UICommand.pushJSONL.rawValue == "canvas.a2ui.pushJSONL")
#expect(MoltbotCanvasA2UICommand.reset.rawValue == "canvas.a2ui.reset")
#expect(OpenClawCanvasA2UICommand.push.rawValue == "canvas.a2ui.push")
#expect(OpenClawCanvasA2UICommand.pushJSONL.rawValue == "canvas.a2ui.pushJSONL")
#expect(OpenClawCanvasA2UICommand.reset.rawValue == "canvas.a2ui.reset")
}
@Test func jsonlDecodesAndValidatesV0_8() throws {
@@ -16,7 +16,7 @@ import Testing
{"deleteSurface":{"surfaceId":"main"}}
"""
let messages = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
let messages = try OpenClawCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
#expect(messages.count == 4)
}
@@ -26,7 +26,7 @@ import Testing
"""
#expect(throws: Error.self) {
_ = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
_ = try OpenClawCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
}
}
@@ -36,7 +36,7 @@ import Testing
"""
#expect(throws: Error.self) {
_ = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
_ = try OpenClawCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
}
}
}
@@ -1,11 +1,11 @@
import MoltbotKit
import OpenClawKit
import Foundation
import Testing
@Suite struct CanvasSnapshotFormatTests {
@Test func acceptsJpgAlias() throws {
struct Wrapper: Codable {
var format: MoltbotCanvasSnapshotFormat
var format: OpenClawCanvasSnapshotFormat
}
let data = try #require("{\"format\":\"jpg\"}".data(using: .utf8))
@@ -1,5 +1,5 @@
import Testing
@testable import MoltbotChatUI
@testable import OpenClawChatUI
@Suite("ChatMarkdownPreprocessor")
struct ChatMarkdownPreprocessorTests {
@@ -1,6 +1,6 @@
import Foundation
import Testing
@testable import MoltbotChatUI
@testable import OpenClawChatUI
#if os(macOS)
import AppKit
@@ -19,8 +19,8 @@ private func luminance(_ color: NSColor) throws -> CGFloat {
let lightAppearance = try #require(NSAppearance(named: .aqua))
let darkAppearance = try #require(NSAppearance(named: .darkAqua))
let lightResolved = MoltbotChatTheme.resolvedAssistantBubbleColor(for: lightAppearance)
let darkResolved = MoltbotChatTheme.resolvedAssistantBubbleColor(for: darkAppearance)
let lightResolved = OpenClawChatTheme.resolvedAssistantBubbleColor(for: lightAppearance)
let darkResolved = OpenClawChatTheme.resolvedAssistantBubbleColor(for: darkAppearance)
#expect(try luminance(lightResolved) > luminance(darkResolved))
#else
#expect(Bool(true))
@@ -1,7 +1,7 @@
import MoltbotKit
import OpenClawKit
import Foundation
import Testing
@testable import MoltbotChatUI
@testable import OpenClawChatUI
private struct TimeoutError: Error, CustomStringConvertible {
let label: String
@@ -31,40 +31,40 @@ private actor TestChatTransportState {
var abortedRunIds: [String] = []
}
private final class TestChatTransport: @unchecked Sendable, MoltbotChatTransport {
private final class TestChatTransport: @unchecked Sendable, OpenClawChatTransport {
private let state = TestChatTransportState()
private let historyResponses: [MoltbotChatHistoryPayload]
private let sessionsResponses: [MoltbotChatSessionsListResponse]
private let historyResponses: [OpenClawChatHistoryPayload]
private let sessionsResponses: [OpenClawChatSessionsListResponse]
private let stream: AsyncStream<MoltbotChatTransportEvent>
private let continuation: AsyncStream<MoltbotChatTransportEvent>.Continuation
private let stream: AsyncStream<OpenClawChatTransportEvent>
private let continuation: AsyncStream<OpenClawChatTransportEvent>.Continuation
init(
historyResponses: [MoltbotChatHistoryPayload],
sessionsResponses: [MoltbotChatSessionsListResponse] = [])
historyResponses: [OpenClawChatHistoryPayload],
sessionsResponses: [OpenClawChatSessionsListResponse] = [])
{
self.historyResponses = historyResponses
self.sessionsResponses = sessionsResponses
var cont: AsyncStream<MoltbotChatTransportEvent>.Continuation!
var cont: AsyncStream<OpenClawChatTransportEvent>.Continuation!
self.stream = AsyncStream { c in
cont = c
}
self.continuation = cont
}
func events() -> AsyncStream<MoltbotChatTransportEvent> {
func events() -> AsyncStream<OpenClawChatTransportEvent> {
self.stream
}
func setActiveSessionKey(_: String) async throws {}
func requestHistory(sessionKey: String) async throws -> MoltbotChatHistoryPayload {
func requestHistory(sessionKey: String) async throws -> OpenClawChatHistoryPayload {
let idx = await self.state.historyCallCount
await self.state.setHistoryCallCount(idx + 1)
if idx < self.historyResponses.count {
return self.historyResponses[idx]
}
return self.historyResponses.last ?? MoltbotChatHistoryPayload(
return self.historyResponses.last ?? OpenClawChatHistoryPayload(
sessionKey: sessionKey,
sessionId: nil,
messages: [],
@@ -76,23 +76,23 @@ private final class TestChatTransport: @unchecked Sendable, MoltbotChatTransport
message _: String,
thinking _: String,
idempotencyKey: String,
attachments _: [MoltbotChatAttachmentPayload]) async throws -> MoltbotChatSendResponse
attachments _: [OpenClawChatAttachmentPayload]) async throws -> OpenClawChatSendResponse
{
await self.state.sentRunIdsAppend(idempotencyKey)
return MoltbotChatSendResponse(runId: idempotencyKey, status: "ok")
return OpenClawChatSendResponse(runId: idempotencyKey, status: "ok")
}
func abortRun(sessionKey _: String, runId: String) async throws {
await self.state.abortedRunIdsAppend(runId)
}
func listSessions(limit _: Int?) async throws -> MoltbotChatSessionsListResponse {
func listSessions(limit _: Int?) async throws -> OpenClawChatSessionsListResponse {
let idx = await self.state.sessionsCallCount
await self.state.setSessionsCallCount(idx + 1)
if idx < self.sessionsResponses.count {
return self.sessionsResponses[idx]
}
return self.sessionsResponses.last ?? MoltbotChatSessionsListResponse(
return self.sessionsResponses.last ?? OpenClawChatSessionsListResponse(
ts: nil,
path: nil,
count: 0,
@@ -104,7 +104,7 @@ private final class TestChatTransport: @unchecked Sendable, MoltbotChatTransport
true
}
func emit(_ evt: MoltbotChatTransportEvent) {
func emit(_ evt: OpenClawChatTransportEvent) {
self.continuation.yield(evt)
}
@@ -139,12 +139,12 @@ extension TestChatTransportState {
@Suite struct ChatViewModelTests {
@Test func streamsAssistantAndClearsOnFinal() async throws {
let sessionId = "sess-main"
let history1 = MoltbotChatHistoryPayload(
let history1 = OpenClawChatHistoryPayload(
sessionKey: "main",
sessionId: sessionId,
messages: [],
thinkingLevel: "off")
let history2 = MoltbotChatHistoryPayload(
let history2 = OpenClawChatHistoryPayload(
sessionKey: "main",
sessionId: sessionId,
messages: [
@@ -157,7 +157,7 @@ extension TestChatTransportState {
thinkingLevel: "off")
let transport = TestChatTransport(historyResponses: [history1, history2])
let vm = await MainActor.run { MoltbotChatViewModel(sessionKey: "main", transport: transport) }
let vm = await MainActor.run { OpenClawChatViewModel(sessionKey: "main", transport: transport) }
await MainActor.run { vm.load() }
try await waitUntil("bootstrap") { await MainActor.run { vm.healthOK && vm.sessionId == sessionId } }
@@ -170,7 +170,7 @@ extension TestChatTransportState {
transport.emit(
.agent(
MoltbotAgentEventPayload(
OpenClawAgentEventPayload(
runId: sessionId,
seq: 1,
stream: "assistant",
@@ -183,7 +183,7 @@ extension TestChatTransportState {
transport.emit(
.agent(
MoltbotAgentEventPayload(
OpenClawAgentEventPayload(
runId: sessionId,
seq: 2,
stream: "tool",
@@ -200,7 +200,7 @@ extension TestChatTransportState {
let runId = try #require(await transport.lastSentRunId())
transport.emit(
.chat(
MoltbotChatEventPayload(
OpenClawChatEventPayload(
runId: runId,
sessionKey: "main",
state: "final",
@@ -217,20 +217,20 @@ extension TestChatTransportState {
@Test func clearsStreamingOnExternalFinalEvent() async throws {
let sessionId = "sess-main"
let history = MoltbotChatHistoryPayload(
let history = OpenClawChatHistoryPayload(
sessionKey: "main",
sessionId: sessionId,
messages: [],
thinkingLevel: "off")
let transport = TestChatTransport(historyResponses: [history, history])
let vm = await MainActor.run { MoltbotChatViewModel(sessionKey: "main", transport: transport) }
let vm = await MainActor.run { OpenClawChatViewModel(sessionKey: "main", transport: transport) }
await MainActor.run { vm.load() }
try await waitUntil("bootstrap") { await MainActor.run { vm.healthOK && vm.sessionId == sessionId } }
transport.emit(
.agent(
MoltbotAgentEventPayload(
OpenClawAgentEventPayload(
runId: sessionId,
seq: 1,
stream: "assistant",
@@ -239,7 +239,7 @@ extension TestChatTransportState {
transport.emit(
.agent(
MoltbotAgentEventPayload(
OpenClawAgentEventPayload(
runId: sessionId,
seq: 2,
stream: "tool",
@@ -258,7 +258,7 @@ extension TestChatTransportState {
transport.emit(
.chat(
MoltbotChatEventPayload(
OpenClawChatEventPayload(
runId: "other-run",
sessionKey: "main",
state: "final",
@@ -274,18 +274,18 @@ extension TestChatTransportState {
let recent = now - (2 * 60 * 60 * 1000)
let recentOlder = now - (5 * 60 * 60 * 1000)
let stale = now - (26 * 60 * 60 * 1000)
let history = MoltbotChatHistoryPayload(
let history = OpenClawChatHistoryPayload(
sessionKey: "main",
sessionId: "sess-main",
messages: [],
thinkingLevel: "off")
let sessions = MoltbotChatSessionsListResponse(
let sessions = OpenClawChatSessionsListResponse(
ts: now,
path: nil,
count: 4,
defaults: nil,
sessions: [
MoltbotChatSessionEntry(
OpenClawChatSessionEntry(
key: "recent-1",
kind: nil,
displayName: nil,
@@ -304,7 +304,7 @@ extension TestChatTransportState {
totalTokens: nil,
model: nil,
contextTokens: nil),
MoltbotChatSessionEntry(
OpenClawChatSessionEntry(
key: "main",
kind: nil,
displayName: nil,
@@ -323,7 +323,7 @@ extension TestChatTransportState {
totalTokens: nil,
model: nil,
contextTokens: nil),
MoltbotChatSessionEntry(
OpenClawChatSessionEntry(
key: "recent-2",
kind: nil,
displayName: nil,
@@ -342,7 +342,7 @@ extension TestChatTransportState {
totalTokens: nil,
model: nil,
contextTokens: nil),
MoltbotChatSessionEntry(
OpenClawChatSessionEntry(
key: "old-1",
kind: nil,
displayName: nil,
@@ -366,7 +366,7 @@ extension TestChatTransportState {
let transport = TestChatTransport(
historyResponses: [history],
sessionsResponses: [sessions])
let vm = await MainActor.run { MoltbotChatViewModel(sessionKey: "main", transport: transport) }
let vm = await MainActor.run { OpenClawChatViewModel(sessionKey: "main", transport: transport) }
await MainActor.run { vm.load() }
try await waitUntil("sessions loaded") { await MainActor.run { !vm.sessions.isEmpty } }
@@ -377,18 +377,18 @@ extension TestChatTransportState {
@Test func sessionChoicesIncludeCurrentWhenMissing() async throws {
let now = Date().timeIntervalSince1970 * 1000
let recent = now - (30 * 60 * 1000)
let history = MoltbotChatHistoryPayload(
let history = OpenClawChatHistoryPayload(
sessionKey: "custom",
sessionId: "sess-custom",
messages: [],
thinkingLevel: "off")
let sessions = MoltbotChatSessionsListResponse(
let sessions = OpenClawChatSessionsListResponse(
ts: now,
path: nil,
count: 1,
defaults: nil,
sessions: [
MoltbotChatSessionEntry(
OpenClawChatSessionEntry(
key: "main",
kind: nil,
displayName: nil,
@@ -412,7 +412,7 @@ extension TestChatTransportState {
let transport = TestChatTransport(
historyResponses: [history],
sessionsResponses: [sessions])
let vm = await MainActor.run { MoltbotChatViewModel(sessionKey: "custom", transport: transport) }
let vm = await MainActor.run { OpenClawChatViewModel(sessionKey: "custom", transport: transport) }
await MainActor.run { vm.load() }
try await waitUntil("sessions loaded") { await MainActor.run { !vm.sessions.isEmpty } }
@@ -422,20 +422,20 @@ extension TestChatTransportState {
@Test func clearsStreamingOnExternalErrorEvent() async throws {
let sessionId = "sess-main"
let history = MoltbotChatHistoryPayload(
let history = OpenClawChatHistoryPayload(
sessionKey: "main",
sessionId: sessionId,
messages: [],
thinkingLevel: "off")
let transport = TestChatTransport(historyResponses: [history, history])
let vm = await MainActor.run { MoltbotChatViewModel(sessionKey: "main", transport: transport) }
let vm = await MainActor.run { OpenClawChatViewModel(sessionKey: "main", transport: transport) }
await MainActor.run { vm.load() }
try await waitUntil("bootstrap") { await MainActor.run { vm.healthOK && vm.sessionId == sessionId } }
transport.emit(
.agent(
MoltbotAgentEventPayload(
OpenClawAgentEventPayload(
runId: sessionId,
seq: 1,
stream: "assistant",
@@ -448,7 +448,7 @@ extension TestChatTransportState {
transport.emit(
.chat(
MoltbotChatEventPayload(
OpenClawChatEventPayload(
runId: "other-run",
sessionKey: "main",
state: "error",
@@ -460,13 +460,13 @@ extension TestChatTransportState {
@Test func abortRequestsDoNotClearPendingUntilAbortedEvent() async throws {
let sessionId = "sess-main"
let history = MoltbotChatHistoryPayload(
let history = OpenClawChatHistoryPayload(
sessionKey: "main",
sessionId: sessionId,
messages: [],
thinkingLevel: "off")
let transport = TestChatTransport(historyResponses: [history, history])
let vm = await MainActor.run { MoltbotChatViewModel(sessionKey: "main", transport: transport) }
let vm = await MainActor.run { OpenClawChatViewModel(sessionKey: "main", transport: transport) }
await MainActor.run { vm.load() }
try await waitUntil("bootstrap") { await MainActor.run { vm.healthOK && vm.sessionId == sessionId } }
@@ -490,7 +490,7 @@ extension TestChatTransportState {
transport.emit(
.chat(
MoltbotChatEventPayload(
OpenClawChatEventPayload(
runId: runId,
sessionKey: "main",
state: "aborted",
@@ -1,5 +1,5 @@
import XCTest
@testable import MoltbotKit
@testable import OpenClawKit
final class ElevenLabsTTSValidationTests: XCTestCase {
func testValidatedOutputFormatAllowsOnlyMp3Presets() {
@@ -1,7 +1,7 @@
import Foundation
import Testing
@testable import MoltbotKit
import MoltbotProtocol
@testable import OpenClawKit
import OpenClawProtocol
struct GatewayNodeSessionTests {
@Test
@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import CoreGraphics
import ImageIO
import Testing
@@ -1,5 +1,5 @@
import XCTest
@testable import MoltbotKit
@testable import OpenClawKit
final class TalkDirectiveTests: XCTestCase {
func testParsesDirectiveAndStripsLine() {
@@ -1,5 +1,5 @@
import XCTest
@testable import MoltbotKit
@testable import OpenClawKit
final class TalkHistoryTimestampTests: XCTestCase {
func testSecondsTimestampsAreAcceptedWithSmallTolerance() {
@@ -1,5 +1,5 @@
import XCTest
@testable import MoltbotKit
@testable import OpenClawKit
final class TalkPromptBuilderTests: XCTestCase {
func testBuildIncludesTranscript() {
@@ -1,10 +1,10 @@
import MoltbotKit
import OpenClawKit
import Foundation
import Testing
@Suite struct ToolDisplayRegistryTests {
@Test func loadsToolDisplayConfigFromBundle() {
let url = MoltbotKitResources.bundle.url(forResource: "tool-display", withExtension: "json")
let url = OpenClawKitResources.bundle.url(forResource: "tool-display", withExtension: "json")
#expect(url != nil)
}
@@ -4,7 +4,7 @@ import { ContextProvider } from "@lit/context";
import { v0_8 } from "@a2ui/lit";
import "@a2ui/lit/ui";
import { themeContext } from "@moltbot/a2ui-theme-context";
import { themeContext } from "@openclaw/a2ui-theme-context";
const modalStyles = css`
dialog {
@@ -42,7 +42,7 @@ const buttonShadow = isAndroid ? "0 2px 10px rgba(6, 182, 212, 0.14)" : "0 10px
const statusShadow = isAndroid ? "0 2px 10px rgba(0, 0, 0, 0.18)" : "0 10px 24px rgba(0, 0, 0, 0.25)";
const statusBlur = isAndroid ? "10px" : "14px";
const moltbotTheme = {
const openclawTheme = {
components: {
AudioPlayer: emptyClasses(),
Button: emptyClasses(),
@@ -152,7 +152,7 @@ const moltbotTheme = {
},
};
class MoltbotA2UIHost extends LitElement {
class OpenClawA2UIHost extends LitElement {
static properties = {
surfaces: { state: true },
pendingAction: { state: true },
@@ -162,7 +162,7 @@ class MoltbotA2UIHost extends LitElement {
#processor = v0_8.Data.createSignalA2uiMessageProcessor();
#themeProvider = new ContextProvider(this, {
context: themeContext,
initialValue: moltbotTheme,
initialValue: openclawTheme,
});
surfaces = [];
@@ -177,10 +177,10 @@ class MoltbotA2UIHost extends LitElement {
position: relative;
box-sizing: border-box;
padding:
var(--moltbot-a2ui-inset-top, 0px)
var(--moltbot-a2ui-inset-right, 0px)
var(--moltbot-a2ui-inset-bottom, 0px)
var(--moltbot-a2ui-inset-left, 0px);
var(--openclaw-a2ui-inset-top, 0px)
var(--openclaw-a2ui-inset-right, 0px)
var(--openclaw-a2ui-inset-bottom, 0px)
var(--openclaw-a2ui-inset-left, 0px);
}
#surfaces {
@@ -189,14 +189,14 @@ class MoltbotA2UIHost extends LitElement {
gap: 12px;
height: 100%;
overflow: auto;
padding-bottom: var(--moltbot-a2ui-scroll-pad-bottom, 0px);
padding-bottom: var(--openclaw-a2ui-scroll-pad-bottom, 0px);
}
.status {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: var(--moltbot-a2ui-status-top, 12px);
top: var(--openclaw-a2ui-status-top, 12px);
display: inline-flex;
align-items: center;
gap: 8px;
@@ -217,7 +217,7 @@ class MoltbotA2UIHost extends LitElement {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: var(--moltbot-a2ui-toast-bottom, 12px);
bottom: var(--openclaw-a2ui-toast-bottom, 12px);
display: inline-flex;
align-items: center;
gap: 8px;
@@ -243,7 +243,7 @@ class MoltbotA2UIHost extends LitElement {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: var(--moltbot-a2ui-empty-top, var(--moltbot-a2ui-status-top, 12px));
top: var(--openclaw-a2ui-empty-top, var(--openclaw-a2ui-status-top, 12px));
text-align: center;
opacity: 0.8;
padding: 10px 12px;
@@ -281,18 +281,21 @@ class MoltbotA2UIHost extends LitElement {
reset: () => this.reset(),
getSurfaces: () => Array.from(this.#processor.getSurfaces().keys()),
};
globalThis.moltbotA2UI = api;
globalThis.clawdbotA2UI = api;
globalThis.openclawA2UI = api;
this.addEventListener("a2uiaction", (evt) => this.#handleA2UIAction(evt));
this.#statusListener = (evt) => this.#handleActionStatus(evt);
globalThis.addEventListener("moltbot:a2ui-action-status", this.#statusListener);
for (const eventName of ["openclaw:a2ui-action-status"]) {
globalThis.addEventListener(eventName, this.#statusListener);
}
this.#syncSurfaces();
}
disconnectedCallback() {
super.disconnectedCallback();
if (this.#statusListener) {
globalThis.removeEventListener("moltbot:a2ui-action-status", this.#statusListener);
for (const eventName of ["openclaw:a2ui-action-status"]) {
globalThis.removeEventListener(eventName, this.#statusListener);
}
this.#statusListener = null;
}
}
@@ -395,20 +398,15 @@ class MoltbotA2UIHost extends LitElement {
...(Object.keys(context).length ? { context } : {}),
};
globalThis.__moltbotLastA2UIAction = userAction;
globalThis.__openclawLastA2UIAction = userAction;
const handler =
globalThis.webkit?.messageHandlers?.moltbotCanvasA2UIAction ??
globalThis.webkit?.messageHandlers?.clawdbotCanvasA2UIAction ??
globalThis.moltbotCanvasA2UIAction ??
globalThis.clawdbotCanvasA2UIAction;
globalThis.webkit?.messageHandlers?.openclawCanvasA2UIAction ??
globalThis.openclawCanvasA2UIAction;
if (handler?.postMessage) {
try {
// WebKit message handlers support structured objects; Android's JS interface expects strings.
if (
handler === globalThis.moltbotCanvasA2UIAction ||
handler === globalThis.clawdbotCanvasA2UIAction
) {
if (handler === globalThis.openclawCanvasA2UIAction) {
handler.postMessage(JSON.stringify({ userAction }));
} else {
handler.postMessage({ userAction });
@@ -488,4 +486,6 @@ class MoltbotA2UIHost extends LitElement {
}
}
customElements.define("moltbot-a2ui-host", MoltbotA2UIHost);
if (!customElements.get("openclaw-a2ui-host")) {
customElements.define("openclaw-a2ui-host", OpenClawA2UIHost);
}
@@ -27,7 +27,7 @@ export default defineConfig({
alias: {
"@a2ui/lit": path.resolve(a2uiLitDist, "index.js"),
"@a2ui/lit/ui": path.resolve(a2uiLitDist, "0.8/ui/ui.js"),
"@moltbot/a2ui-theme-context": a2uiThemeContext,
"@openclaw/a2ui-theme-context": a2uiThemeContext,
"@lit/context": path.resolve(repoRoot, "node_modules/@lit/context/index.js"),
"@lit/context/": path.resolve(repoRoot, "node_modules/@lit/context/"),
"@lit-labs/signals": path.resolve(repoRoot, "node_modules/@lit-labs/signals/index.js"),