refactor: rename clawdbot to moltbot with legacy compat

This commit is contained in:
Peter Steinberger
2026-01-27 12:19:58 +00:00
parent 83460df96f
commit 6d16a658e5
1839 changed files with 11250 additions and 11199 deletions

View File

@@ -3,15 +3,15 @@
import PackageDescription
let package = Package(
name: "ClawdbotKit",
name: "MoltbotKit",
platforms: [
.iOS(.v18),
.macOS(.v15),
],
products: [
.library(name: "ClawdbotProtocol", targets: ["ClawdbotProtocol"]),
.library(name: "ClawdbotKit", targets: ["ClawdbotKit"]),
.library(name: "ClawdbotChatUI", targets: ["ClawdbotChatUI"]),
.library(name: "MoltbotProtocol", targets: ["MoltbotProtocol"]),
.library(name: "MoltbotKit", targets: ["MoltbotKit"]),
.library(name: "MoltbotChatUI", targets: ["MoltbotChatUI"]),
],
dependencies: [
.package(url: "https://github.com/steipete/ElevenLabsKit", exact: "0.1.0"),
@@ -19,14 +19,16 @@ let package = Package(
],
targets: [
.target(
name: "ClawdbotProtocol",
name: "MoltbotProtocol",
path: "Sources/ClawdbotProtocol",
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
.target(
name: "ClawdbotKit",
name: "MoltbotKit",
path: "Sources/ClawdbotKit",
dependencies: [
"ClawdbotProtocol",
"MoltbotProtocol",
.product(name: "ElevenLabsKit", package: "ElevenLabsKit"),
],
resources: [
@@ -36,9 +38,10 @@ let package = Package(
.enableUpcomingFeature("StrictConcurrency"),
]),
.target(
name: "ClawdbotChatUI",
name: "MoltbotChatUI",
path: "Sources/ClawdbotChatUI",
dependencies: [
"ClawdbotKit",
"MoltbotKit",
.product(
name: "Textual",
package: "textual",
@@ -48,8 +51,9 @@ let package = Package(
.enableUpcomingFeature("StrictConcurrency"),
]),
.testTarget(
name: "ClawdbotKitTests",
dependencies: ["ClawdbotKit", "ClawdbotChatUI"],
name: "MoltbotKitTests",
dependencies: ["MoltbotKit", "MoltbotChatUI"],
path: "Tests/ClawdbotKitTests",
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
.enableExperimentalFeature("SwiftTesting"),

View File

@@ -8,9 +8,9 @@ import UniformTypeIdentifiers
#endif
@MainActor
struct ClawdbotChatComposer: View {
@Bindable var viewModel: ClawdbotChatViewModel
let style: ClawdbotChatView.Style
struct MoltbotChatComposer: View {
@Bindable var viewModel: MoltbotChatViewModel
let style: MoltbotChatView.Style
let showsSessionSwitcher: Bool
#if !os(macOS)
@@ -54,21 +54,21 @@ struct ClawdbotChatComposer: View {
topTrailing: 0),
style: .continuous)
shape
.fill(ClawdbotChatTheme.composerBackground)
.overlay(shape.strokeBorder(ClawdbotChatTheme.composerBorder, lineWidth: 1))
.fill(MoltbotChatTheme.composerBackground)
.overlay(shape.strokeBorder(MoltbotChatTheme.composerBorder, lineWidth: 1))
.shadow(color: .black.opacity(0.12), radius: 12, y: 6)
} else {
let shape = RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
shape
.fill(ClawdbotChatTheme.composerBackground)
.overlay(shape.strokeBorder(ClawdbotChatTheme.composerBorder, lineWidth: 1))
.fill(MoltbotChatTheme.composerBackground)
.overlay(shape.strokeBorder(MoltbotChatTheme.composerBorder, lineWidth: 1))
.shadow(color: .black.opacity(0.12), radius: 12, y: 6)
}
#else
let shape = RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
shape
.fill(ClawdbotChatTheme.composerBackground)
.overlay(shape.strokeBorder(ClawdbotChatTheme.composerBorder, lineWidth: 1))
.fill(MoltbotChatTheme.composerBackground)
.overlay(shape.strokeBorder(MoltbotChatTheme.composerBorder, lineWidth: 1))
.shadow(color: .black.opacity(0.12), radius: 12, y: 6)
#endif
}
@@ -144,11 +144,11 @@ struct ClawdbotChatComposer: View {
HStack(spacing: 6) {
ForEach(
self.viewModel.attachments,
id: \ClawdbotPendingAttachment.id)
{ (att: ClawdbotPendingAttachment) in
id: \MoltbotPendingAttachment.id)
{ (att: MoltbotPendingAttachment) in
HStack(spacing: 6) {
if let img = att.preview {
ClawdbotPlatformImageFactory.image(img)
MoltbotPlatformImageFactory.image(img)
.resizable()
.scaledToFill()
.frame(width: 22, height: 22)
@@ -181,7 +181,7 @@ struct ClawdbotChatComposer: View {
self.editorOverlay
Rectangle()
.fill(ClawdbotChatTheme.divider)
.fill(MoltbotChatTheme.divider)
.frame(height: 1)
.padding(.horizontal, 2)
@@ -197,10 +197,10 @@ struct ClawdbotChatComposer: View {
.padding(.vertical, 8)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(ClawdbotChatTheme.composerField)
.fill(MoltbotChatTheme.composerField)
.overlay(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.strokeBorder(ClawdbotChatTheme.composerBorder)))
.strokeBorder(MoltbotChatTheme.composerBorder)))
.padding(self.editorPadding)
}
@@ -217,7 +217,7 @@ struct ClawdbotChatComposer: View {
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(ClawdbotChatTheme.subtleCard)
.background(MoltbotChatTheme.subtleCard)
.clipShape(Capsule())
}

View File

@@ -4,7 +4,7 @@ enum ChatMarkdownPreprocessor {
struct InlineImage: Identifiable {
let id = UUID()
let label: String
let image: ClawdbotPlatformImage?
let image: MoltbotPlatformImage?
}
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: ClawdbotPlatformImage? = {
let image: MoltbotPlatformImage? = {
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 ClawdbotPlatformImage(data: data)
return MoltbotPlatformImage(data: data)
}()
images.append(InlineImage(label: label, image: image))

View File

@@ -72,7 +72,7 @@ private struct InlineImageList: View {
var body: some View {
ForEach(images, id: \.id) { item in
if let img = item.image {
ClawdbotPlatformImageFactory.image(img)
MoltbotPlatformImageFactory.image(img)
.resizable()
.scaledToFit()
.frame(maxHeight: 260)

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import SwiftUI
@@ -135,8 +135,8 @@ private struct ChatBubbleShape: InsettableShape {
@MainActor
struct ChatMessageBubble: View {
let message: ClawdbotChatMessage
let style: ClawdbotChatView.Style
let message: MoltbotChatMessage
let style: MoltbotChatView.Style
let markdownVariant: ChatMarkdownVariant
let userAccent: Color?
@@ -157,15 +157,15 @@ struct ChatMessageBubble: View {
@MainActor
private struct ChatMessageBody: View {
let message: ClawdbotChatMessage
let message: MoltbotChatMessage
let isUser: Bool
let style: ClawdbotChatView.Style
let style: MoltbotChatView.Style
let markdownVariant: ChatMarkdownVariant
let userAccent: Color?
var body: some View {
let text = self.primaryText
let textColor = self.isUser ? ClawdbotChatTheme.userText : ClawdbotChatTheme.assistantText
let textColor = self.isUser ? MoltbotChatTheme.userText : MoltbotChatTheme.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: [ClawdbotChatMessageContent] {
private var inlineAttachments: [MoltbotChatMessageContent] {
self.message.content.filter { content in
switch content.type ?? "text" {
case "file", "attachment":
@@ -243,7 +243,7 @@ private struct ChatMessageBody: View {
}
}
private var toolCalls: [ClawdbotChatMessageContent] {
private var toolCalls: [MoltbotChatMessageContent] {
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: [ClawdbotChatMessageContent] {
private var inlineToolResults: [MoltbotChatMessageContent] {
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 ?? ClawdbotChatTheme.userBubble
return self.userAccent ?? MoltbotChatTheme.userBubble
}
if self.style == .onboarding {
return ClawdbotChatTheme.onboardingAssistantBubble
return MoltbotChatTheme.onboardingAssistantBubble
}
return ClawdbotChatTheme.assistantBubble
return MoltbotChatTheme.assistantBubble
}
private var bubbleBackground: AnyShapeStyle {
@@ -293,7 +293,7 @@ private struct ChatMessageBody: View {
return Color.white.opacity(0.12)
}
if self.style == .onboarding {
return ClawdbotChatTheme.onboardingAssistantBorder
return MoltbotChatTheme.onboardingAssistantBorder
}
return Color.white.opacity(0.08)
}
@@ -339,7 +339,7 @@ private struct ChatMessageBody: View {
}
private struct AttachmentRow: View {
let att: ClawdbotChatMessageContent
let att: MoltbotChatMessageContent
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 ? ClawdbotChatTheme.userText : ClawdbotChatTheme.assistantText)
.foregroundStyle(self.isUser ? MoltbotChatTheme.userText : MoltbotChatTheme.assistantText)
Spacer()
}
.padding(10)
@@ -358,7 +358,7 @@ private struct AttachmentRow: View {
}
private struct ToolCallCard: View {
let content: ClawdbotChatMessageContent
let content: MoltbotChatMessageContent
let isUser: Bool
var body: some View {
@@ -379,7 +379,7 @@ private struct ToolCallCard: View {
.padding(10)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(ClawdbotChatTheme.subtleCard)
.fill(MoltbotChatTheme.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 ? ClawdbotChatTheme.userText : ClawdbotChatTheme.assistantText)
.foregroundStyle(self.isUser ? MoltbotChatTheme.userText : MoltbotChatTheme.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(ClawdbotChatTheme.subtleCard)
.fill(MoltbotChatTheme.subtleCard)
.overlay(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.strokeBorder(Color.white.opacity(0.08), lineWidth: 1)))
@@ -453,7 +453,7 @@ private struct ToolResultCard: View {
@MainActor
struct ChatTypingIndicatorBubble: View {
let style: ClawdbotChatView.Style
let style: MoltbotChatView.Style
var body: some View {
HStack(spacing: 10) {
@@ -469,7 +469,7 @@ struct ChatTypingIndicatorBubble: View {
.padding(.horizontal, self.style == .standard ? 12 : 14)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(ClawdbotChatTheme.assistantBubble))
.fill(MoltbotChatTheme.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(ClawdbotChatTheme.assistantBubble))
.fill(MoltbotChatTheme.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: [ClawdbotChatPendingToolCall]
let toolCalls: [MoltbotChatPendingToolCall]
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(ClawdbotChatTheme.assistantBubble))
.fill(MoltbotChatTheme.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: ClawdbotChatTheme.assistantText)
textColor: MoltbotChatTheme.assistantText)
}
}
}

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
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 ClawdbotPlatformImage = NSImage
public typealias MoltbotPlatformImage = NSImage
#elseif canImport(UIKit)
import UIKit
public typealias ClawdbotPlatformImage = UIImage
public typealias MoltbotPlatformImage = UIImage
#endif
public struct ClawdbotChatUsageCost: Codable, Hashable, Sendable {
public struct MoltbotChatUsageCost: Codable, Hashable, Sendable {
public let input: Double?
public let output: Double?
public let cacheRead: Double?
@@ -21,12 +21,12 @@ public struct ClawdbotChatUsageCost: Codable, Hashable, Sendable {
public let total: Double?
}
public struct ClawdbotChatUsage: Codable, Hashable, Sendable {
public struct MoltbotChatUsage: Codable, Hashable, Sendable {
public let input: Int?
public let output: Int?
public let cacheRead: Int?
public let cacheWrite: Int?
public let cost: ClawdbotChatUsageCost?
public let cost: MoltbotChatUsageCost?
public let total: Int?
enum CodingKeys: String, CodingKey {
@@ -45,7 +45,7 @@ public struct ClawdbotChatUsage: 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(ClawdbotChatUsageCost.self, forKey: .cost)
self.cost = try container.decodeIfPresent(MoltbotChatUsageCost.self, forKey: .cost)
self.total =
try container.decodeIfPresent(Int.self, forKey: .total) ??
container.decodeIfPresent(Int.self, forKey: .totalTokens)
@@ -62,7 +62,7 @@ public struct ClawdbotChatUsage: Codable, Hashable, Sendable {
}
}
public struct ClawdbotChatMessageContent: Codable, Hashable, Sendable {
public struct MoltbotChatMessageContent: Codable, Hashable, Sendable {
public let type: String?
public let text: String?
public let thinking: String?
@@ -135,14 +135,14 @@ public struct ClawdbotChatMessageContent: Codable, Hashable, Sendable {
}
}
public struct ClawdbotChatMessage: Codable, Identifiable, Sendable {
public struct MoltbotChatMessage: Codable, Identifiable, Sendable {
public var id: UUID = .init()
public let role: String
public let content: [ClawdbotChatMessageContent]
public let content: [MoltbotChatMessageContent]
public let timestamp: Double?
public let toolCallId: String?
public let toolName: String?
public let usage: ClawdbotChatUsage?
public let usage: MoltbotChatUsage?
public let stopReason: String?
enum CodingKeys: String, CodingKey {
@@ -160,11 +160,11 @@ public struct ClawdbotChatMessage: Codable, Identifiable, Sendable {
public init(
id: UUID = .init(),
role: String,
content: [ClawdbotChatMessageContent],
content: [MoltbotChatMessageContent],
timestamp: Double?,
toolCallId: String? = nil,
toolName: String? = nil,
usage: ClawdbotChatUsage? = nil,
usage: MoltbotChatUsage? = nil,
stopReason: String? = nil)
{
self.id = id
@@ -187,10 +187,10 @@ public struct ClawdbotChatMessage: Codable, Identifiable, Sendable {
self.toolName =
try container.decodeIfPresent(String.self, forKey: .toolName) ??
container.decodeIfPresent(String.self, forKey: .tool_name)
self.usage = try container.decodeIfPresent(ClawdbotChatUsage.self, forKey: .usage)
self.usage = try container.decodeIfPresent(MoltbotChatUsage.self, forKey: .usage)
self.stopReason = try container.decodeIfPresent(String.self, forKey: .stopReason)
if let decoded = try? container.decode([ClawdbotChatMessageContent].self, forKey: .content) {
if let decoded = try? container.decode([MoltbotChatMessageContent].self, forKey: .content) {
self.content = decoded
return
}
@@ -198,7 +198,7 @@ public struct ClawdbotChatMessage: 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 = [
ClawdbotChatMessageContent(
MoltbotChatMessageContent(
type: "text",
text: text,
thinking: nil,
@@ -228,40 +228,40 @@ public struct ClawdbotChatMessage: Codable, Identifiable, Sendable {
}
}
public struct ClawdbotChatHistoryPayload: Codable, Sendable {
public struct MoltbotChatHistoryPayload: Codable, Sendable {
public let sessionKey: String
public let sessionId: String?
public let messages: [AnyCodable]?
public let thinkingLevel: String?
}
public struct ClawdbotSessionPreviewItem: Codable, Hashable, Sendable {
public struct MoltbotSessionPreviewItem: Codable, Hashable, Sendable {
public let role: String
public let text: String
}
public struct ClawdbotSessionPreviewEntry: Codable, Sendable {
public struct MoltbotSessionPreviewEntry: Codable, Sendable {
public let key: String
public let status: String
public let items: [ClawdbotSessionPreviewItem]
public let items: [MoltbotSessionPreviewItem]
}
public struct ClawdbotSessionsPreviewPayload: Codable, Sendable {
public struct MoltbotSessionsPreviewPayload: Codable, Sendable {
public let ts: Int
public let previews: [ClawdbotSessionPreviewEntry]
public let previews: [MoltbotSessionPreviewEntry]
public init(ts: Int, previews: [ClawdbotSessionPreviewEntry]) {
public init(ts: Int, previews: [MoltbotSessionPreviewEntry]) {
self.ts = ts
self.previews = previews
}
}
public struct ClawdbotChatSendResponse: Codable, Sendable {
public struct MoltbotChatSendResponse: Codable, Sendable {
public let runId: String
public let status: String
}
public struct ClawdbotChatEventPayload: Codable, Sendable {
public struct MoltbotChatEventPayload: Codable, Sendable {
public let runId: String?
public let sessionKey: String?
public let state: String?
@@ -269,7 +269,7 @@ public struct ClawdbotChatEventPayload: Codable, Sendable {
public let errorMessage: String?
}
public struct ClawdbotAgentEventPayload: Codable, Sendable, Identifiable {
public struct MoltbotAgentEventPayload: 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 ClawdbotAgentEventPayload: Codable, Sendable, Identifiable {
public let data: [String: AnyCodable]
}
public struct ClawdbotChatPendingToolCall: Identifiable, Hashable, Sendable {
public struct MoltbotChatPendingToolCall: Identifiable, Hashable, Sendable {
public var id: String { self.toolCallId }
public let toolCallId: String
public let name: String
@@ -287,18 +287,18 @@ public struct ClawdbotChatPendingToolCall: Identifiable, Hashable, Sendable {
public let isError: Bool?
}
public struct ClawdbotGatewayHealthOK: Codable, Sendable {
public struct MoltbotGatewayHealthOK: Codable, Sendable {
public let ok: Bool?
}
public struct ClawdbotPendingAttachment: Identifiable {
public struct MoltbotPendingAttachment: 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: ClawdbotPlatformImage?
public let preview: MoltbotPlatformImage?
public init(
url: URL?,
@@ -306,7 +306,7 @@ public struct ClawdbotPendingAttachment: Identifiable {
fileName: String,
mimeType: String,
type: String = "file",
preview: ClawdbotPlatformImage?)
preview: MoltbotPlatformImage?)
{
self.url = url
self.data = data
@@ -317,7 +317,7 @@ public struct ClawdbotPendingAttachment: Identifiable {
}
}
public struct ClawdbotChatAttachmentPayload: Codable, Sendable, Hashable {
public struct MoltbotChatAttachmentPayload: Codable, Sendable, Hashable {
public let type: String
public let mimeType: String
public let fileName: String

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import Foundation
enum ChatPayloadDecoding {

View File

@@ -1,11 +1,11 @@
import Foundation
public struct ClawdbotChatSessionsDefaults: Codable, Sendable {
public struct MoltbotChatSessionsDefaults: Codable, Sendable {
public let model: String?
public let contextTokens: Int?
}
public struct ClawdbotChatSessionEntry: Codable, Identifiable, Sendable, Hashable {
public struct MoltbotChatSessionEntry: Codable, Identifiable, Sendable, Hashable {
public var id: String { self.key }
public let key: String
@@ -31,10 +31,10 @@ public struct ClawdbotChatSessionEntry: Codable, Identifiable, Sendable, Hashabl
public let contextTokens: Int?
}
public struct ClawdbotChatSessionsListResponse: Codable, Sendable {
public struct MoltbotChatSessionsListResponse: Codable, Sendable {
public let ts: Double?
public let path: String?
public let count: Int?
public let defaults: ClawdbotChatSessionsDefaults?
public let sessions: [ClawdbotChatSessionEntry]
public let defaults: MoltbotChatSessionsDefaults?
public let sessions: [MoltbotChatSessionEntry]
}

View File

@@ -3,7 +3,7 @@ import SwiftUI
@MainActor
struct ChatSessionsSheet: View {
@Bindable var viewModel: ClawdbotChatViewModel
@Bindable var viewModel: MoltbotChatViewModel
@Environment(\.dismiss) private var dismiss
var body: some View {

View File

@@ -14,7 +14,7 @@ extension NSAppearance {
}
#endif
enum ClawdbotChatTheme {
enum MoltbotChatTheme {
#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 ClawdbotChatTheme {
}
static let assistantBubbleDynamicNSColor = NSColor(
name: NSColor.Name("ClawdbotChatTheme.assistantBubble"),
name: NSColor.Name("MoltbotChatTheme.assistantBubble"),
dynamicProvider: resolvedAssistantBubbleColor(for:))
static let onboardingAssistantBubbleDynamicNSColor = NSColor(
name: NSColor.Name("ClawdbotChatTheme.onboardingAssistantBubble"),
name: NSColor.Name("MoltbotChatTheme.onboardingAssistantBubble"),
dynamicProvider: resolvedOnboardingAssistantBubbleColor(for:))
#endif
@@ -163,8 +163,8 @@ enum ClawdbotChatTheme {
}
}
enum ClawdbotPlatformImageFactory {
static func image(_ image: ClawdbotPlatformImage) -> Image {
enum MoltbotPlatformImageFactory {
static func image(_ image: MoltbotPlatformImage) -> Image {
#if os(macOS)
Image(nsImage: image)
#else

View File

@@ -1,44 +1,44 @@
import Foundation
public enum ClawdbotChatTransportEvent: Sendable {
public enum MoltbotChatTransportEvent: Sendable {
case health(ok: Bool)
case tick
case chat(ClawdbotChatEventPayload)
case agent(ClawdbotAgentEventPayload)
case chat(MoltbotChatEventPayload)
case agent(MoltbotAgentEventPayload)
case seqGap
}
public protocol ClawdbotChatTransport: Sendable {
func requestHistory(sessionKey: String) async throws -> ClawdbotChatHistoryPayload
public protocol MoltbotChatTransport: Sendable {
func requestHistory(sessionKey: String) async throws -> MoltbotChatHistoryPayload
func sendMessage(
sessionKey: String,
message: String,
thinking: String,
idempotencyKey: String,
attachments: [ClawdbotChatAttachmentPayload]) async throws -> ClawdbotChatSendResponse
attachments: [MoltbotChatAttachmentPayload]) async throws -> MoltbotChatSendResponse
func abortRun(sessionKey: String, runId: String) async throws
func listSessions(limit: Int?) async throws -> ClawdbotChatSessionsListResponse
func listSessions(limit: Int?) async throws -> MoltbotChatSessionsListResponse
func requestHealth(timeoutMs: Int) async throws -> Bool
func events() -> AsyncStream<ClawdbotChatTransportEvent>
func events() -> AsyncStream<MoltbotChatTransportEvent>
func setActiveSessionKey(_ sessionKey: String) async throws
}
extension ClawdbotChatTransport {
extension MoltbotChatTransport {
public func setActiveSessionKey(_: String) async throws {}
public func abortRun(sessionKey _: String, runId _: String) async throws {
throw NSError(
domain: "ClawdbotChatTransport",
domain: "MoltbotChatTransport",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "chat.abort not supported by this transport"])
}
public func listSessions(limit _: Int?) async throws -> ClawdbotChatSessionsListResponse {
public func listSessions(limit _: Int?) async throws -> MoltbotChatSessionsListResponse {
throw NSError(
domain: "ClawdbotChatTransport",
domain: "MoltbotChatTransport",
code: 0,
userInfo: [NSLocalizedDescriptionKey: "sessions.list not supported by this transport"])
}

View File

@@ -1,13 +1,13 @@
import SwiftUI
@MainActor
public struct ClawdbotChatView: View {
public struct MoltbotChatView: View {
public enum Style {
case standard
case onboarding
}
@State private var viewModel: ClawdbotChatViewModel
@State private var viewModel: MoltbotChatViewModel
@State private var scrollerBottomID = UUID()
@State private var scrollPosition: UUID?
@State private var showSessions = false
@@ -42,7 +42,7 @@ public struct ClawdbotChatView: View {
}
public init(
viewModel: ClawdbotChatViewModel,
viewModel: MoltbotChatViewModel,
showsSessionSwitcher: Bool = false,
style: Style = .standard,
markdownVariant: ChatMarkdownVariant = .standard,
@@ -58,14 +58,14 @@ public struct ClawdbotChatView: View {
public var body: some View {
ZStack {
if self.style == .standard {
ClawdbotChatTheme.background
MoltbotChatTheme.background
.ignoresSafeArea()
}
VStack(spacing: Layout.stackSpacing) {
self.messageList
.padding(.horizontal, Layout.outerPaddingHorizontal)
ClawdbotChatComposer(
MoltbotChatComposer(
viewModel: self.viewModel,
style: self.style,
showsSessionSwitcher: self.showsSessionSwitcher)
@@ -206,8 +206,8 @@ public struct ClawdbotChatView: View {
}
}
private var visibleMessages: [ClawdbotChatMessage] {
let base: [ClawdbotChatMessage]
private var visibleMessages: [MoltbotChatMessage] {
let base: [MoltbotChatMessage]
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 ClawdbotChatView: View {
return ("Error", "exclamationmark.triangle.fill", .orange)
}
private func mergeToolResults(in messages: [ClawdbotChatMessage]) -> [ClawdbotChatMessage] {
var result: [ClawdbotChatMessage] = []
private func mergeToolResults(in messages: [MoltbotChatMessage]) -> [MoltbotChatMessage] {
var result: [MoltbotChatMessage] = []
result.reserveCapacity(messages.count)
for message in messages {
@@ -349,7 +349,7 @@ public struct ClawdbotChatView: View {
var content = last.content
content.append(
ClawdbotChatMessageContent(
MoltbotChatMessageContent(
type: "tool_result",
text: toolText,
thinking: nil,
@@ -361,7 +361,7 @@ public struct ClawdbotChatView: View {
name: message.toolName,
arguments: nil))
let merged = ClawdbotChatMessage(
let merged = MoltbotChatMessage(
id: last.id,
role: last.role,
content: content,
@@ -376,12 +376,12 @@ public struct ClawdbotChatView: View {
return result
}
private func isToolResultMessage(_ message: ClawdbotChatMessage) -> Bool {
private func isToolResultMessage(_ message: MoltbotChatMessage) -> Bool {
let role = message.role.lowercased()
return role == "toolresult" || role == "tool_result"
}
private func toolCallIds(in message: ClawdbotChatMessage) -> Set<String> {
private func toolCallIds(in message: MoltbotChatMessage) -> Set<String> {
var ids = Set<String>()
for content in message.content {
let kind = (content.type ?? "").lowercased()
@@ -398,7 +398,7 @@ public struct ClawdbotChatView: View {
return ids
}
private func toolResultText(from message: ClawdbotChatMessage) -> String {
private func toolResultText(from message: MoltbotChatMessage) -> 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(ClawdbotChatTheme.subtleCard)
.fill(MoltbotChatTheme.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(ClawdbotChatTheme.subtleCard)
.fill(MoltbotChatTheme.subtleCard)
.overlay(
RoundedRectangle(cornerRadius: 14, style: .continuous)
.strokeBorder(Color.white.opacity(0.12), lineWidth: 1)))

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import Observation
import OSLog
@@ -10,28 +10,28 @@ import AppKit
import UIKit
#endif
private let chatUILogger = Logger(subsystem: "com.clawdbot", category: "ClawdbotChatUI")
private let chatUILogger = Logger(subsystem: "com.clawdbot", category: "MoltbotChatUI")
@MainActor
@Observable
public final class ClawdbotChatViewModel {
public private(set) var messages: [ClawdbotChatMessage] = []
public final class MoltbotChatViewModel {
public private(set) var messages: [MoltbotChatMessage] = []
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: [ClawdbotPendingAttachment] = []
public var attachments: [MoltbotPendingAttachment] = []
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: [ClawdbotChatPendingToolCall] = []
public private(set) var sessions: [ClawdbotChatSessionEntry] = []
private let transport: any ClawdbotChatTransport
public private(set) var pendingToolCalls: [MoltbotChatPendingToolCall] = []
public private(set) var sessions: [MoltbotChatSessionEntry] = []
private let transport: any MoltbotChatTransport
@ObservationIgnored
private nonisolated(unsafe) var eventTask: Task<Void, Never>?
@@ -43,7 +43,7 @@ public final class ClawdbotChatViewModel {
private nonisolated(unsafe) var pendingRunTimeoutTasks: [String: Task<Void, Never>] = [:]
private let pendingRunTimeoutMs: UInt64 = 120_000
private var pendingToolCallsById: [String: ClawdbotChatPendingToolCall] = [:] {
private var pendingToolCallsById: [String: MoltbotChatPendingToolCall] = [:] {
didSet {
self.pendingToolCalls = self.pendingToolCallsById.values
.sorted { ($0.startedAt ?? 0) < ($1.startedAt ?? 0) }
@@ -52,7 +52,7 @@ public final class ClawdbotChatViewModel {
private var lastHealthPollAt: Date?
public init(sessionKey: String, transport: any ClawdbotChatTransport) {
public init(sessionKey: String, transport: any MoltbotChatTransport) {
self.sessionKey = sessionKey
self.transport = transport
@@ -99,12 +99,12 @@ public final class ClawdbotChatViewModel {
Task { await self.performSwitchSession(to: sessionKey) }
}
public var sessionChoices: [ClawdbotChatSessionEntry] {
public var sessionChoices: [MoltbotChatSessionEntry] {
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: [ClawdbotChatSessionEntry] = []
var recent: [MoltbotChatSessionEntry] = []
for entry in sorted {
guard !seen.contains(entry.key) else { continue }
seen.insert(entry.key)
@@ -112,7 +112,7 @@ public final class ClawdbotChatViewModel {
recent.append(entry)
}
var result: [ClawdbotChatSessionEntry] = []
var result: [MoltbotChatSessionEntry] = []
var included = Set<String>()
for entry in recent where !included.contains(entry.key) {
result.append(entry)
@@ -138,7 +138,7 @@ public final class ClawdbotChatViewModel {
Task { await self.addImageAttachment(url: nil, data: data, fileName: fileName, mimeType: mimeType) }
}
public func removeAttachment(_ id: ClawdbotPendingAttachment.ID) {
public func removeAttachment(_ id: MoltbotPendingAttachment.ID) {
self.attachments.removeAll { $0.id == id }
}
@@ -180,15 +180,15 @@ public final class ClawdbotChatViewModel {
}
}
private static func decodeMessages(_ raw: [AnyCodable]) -> [ClawdbotChatMessage] {
private static func decodeMessages(_ raw: [AnyCodable]) -> [MoltbotChatMessage] {
let decoded = raw.compactMap { item in
(try? ChatPayloadDecoding.decode(item, as: ClawdbotChatMessage.self))
(try? ChatPayloadDecoding.decode(item, as: MoltbotChatMessage.self))
}
return Self.dedupeMessages(decoded)
}
private static func dedupeMessages(_ messages: [ClawdbotChatMessage]) -> [ClawdbotChatMessage] {
var result: [ClawdbotChatMessage] = []
private static func dedupeMessages(_ messages: [MoltbotChatMessage]) -> [MoltbotChatMessage] {
var result: [MoltbotChatMessage] = []
result.reserveCapacity(messages.count)
var seen = Set<String>()
@@ -205,7 +205,7 @@ public final class ClawdbotChatViewModel {
return result
}
private static func dedupeKey(for message: ClawdbotChatMessage) -> String? {
private static func dedupeKey(for message: MoltbotChatMessage) -> 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 ClawdbotChatViewModel {
self.streamingAssistantText = nil
// Optimistically append user message to UI.
var userContent: [ClawdbotChatMessageContent] = [
ClawdbotChatMessageContent(
var userContent: [MoltbotChatMessageContent] = [
MoltbotChatMessageContent(
type: "text",
text: messageText,
thinking: nil,
@@ -246,8 +246,8 @@ public final class ClawdbotChatViewModel {
name: nil,
arguments: nil),
]
let encodedAttachments = self.attachments.map { att -> ClawdbotChatAttachmentPayload in
ClawdbotChatAttachmentPayload(
let encodedAttachments = self.attachments.map { att -> MoltbotChatAttachmentPayload in
MoltbotChatAttachmentPayload(
type: att.type,
mimeType: att.mimeType,
fileName: att.fileName,
@@ -255,7 +255,7 @@ public final class ClawdbotChatViewModel {
}
for att in encodedAttachments {
userContent.append(
ClawdbotChatMessageContent(
MoltbotChatMessageContent(
type: att.type,
text: nil,
thinking: nil,
@@ -268,7 +268,7 @@ public final class ClawdbotChatViewModel {
arguments: nil))
}
self.messages.append(
ClawdbotChatMessage(
MoltbotChatMessage(
id: UUID(),
role: "user",
content: userContent,
@@ -332,8 +332,8 @@ public final class ClawdbotChatViewModel {
await self.bootstrap()
}
private func placeholderSession(key: String) -> ClawdbotChatSessionEntry {
ClawdbotChatSessionEntry(
private func placeholderSession(key: String) -> MoltbotChatSessionEntry {
MoltbotChatSessionEntry(
key: key,
kind: nil,
displayName: nil,
@@ -354,7 +354,7 @@ public final class ClawdbotChatViewModel {
contextTokens: nil)
}
private func handleTransportEvent(_ evt: ClawdbotChatTransportEvent) {
private func handleTransportEvent(_ evt: MoltbotChatTransportEvent) {
switch evt {
case let .health(ok):
self.healthOK = ok
@@ -370,7 +370,7 @@ public final class ClawdbotChatViewModel {
}
}
private func handleChatEvent(_ chat: ClawdbotChatEventPayload) {
private func handleChatEvent(_ chat: MoltbotChatEventPayload) {
if let sessionKey = chat.sessionKey, sessionKey != self.sessionKey {
return
}
@@ -407,7 +407,7 @@ public final class ClawdbotChatViewModel {
}
}
private func handleAgentEvent(_ evt: ClawdbotAgentEventPayload) {
private func handleAgentEvent(_ evt: MoltbotAgentEventPayload) {
if let sessionId, evt.runId != sessionId {
return
}
@@ -423,7 +423,7 @@ public final class ClawdbotChatViewModel {
guard let toolCallId = evt.data["toolCallId"]?.value as? String else { return }
if phase == "start" {
let args = evt.data["args"]
self.pendingToolCallsById[toolCallId] = ClawdbotChatPendingToolCall(
self.pendingToolCallsById[toolCallId] = MoltbotChatPendingToolCall(
toolCallId: toolCallId,
name: name,
args: args,
@@ -534,7 +534,7 @@ public final class ClawdbotChatViewModel {
let preview = Self.previewImage(data: data)
self.attachments.append(
ClawdbotPendingAttachment(
MoltbotPendingAttachment(
url: url,
data: data,
fileName: fileName,
@@ -542,7 +542,7 @@ public final class ClawdbotChatViewModel {
preview: preview))
}
private static func previewImage(data: Data) -> ClawdbotPlatformImage? {
private static func previewImage(data: Data) -> MoltbotPlatformImage? {
#if canImport(AppKit)
NSImage(data: data)
#elseif canImport(UIKit)

View File

@@ -1,10 +1,10 @@
import Foundation
public enum ClawdbotBonjour {
public enum MoltbotBonjour {
// v0: internal-only, subject to rename.
public static let gatewayServiceType = "_clawdbot-gw._tcp"
public static let gatewayServiceType = "_moltbot-gw._tcp"
public static let gatewayServiceDomain = "local."
public static let wideAreaGatewayServiceDomain = "clawdbot.internal."
public static let wideAreaGatewayServiceDomain = "moltbot.internal."
public static let gatewayServiceDomains = [
gatewayServiceDomain,

View File

@@ -27,14 +27,14 @@ public struct BridgeInvokeResponse: Codable, Sendable {
public let id: String
public let ok: Bool
public let payloadJSON: String?
public let error: ClawdbotNodeError?
public let error: MoltbotNodeError?
public init(
type: String = "invoke-res",
id: String,
ok: Bool,
payloadJSON: String? = nil,
error: ClawdbotNodeError? = nil)
error: MoltbotNodeError? = nil)
{
self.type = type
self.id = id

View File

@@ -1,38 +1,38 @@
import Foundation
public enum ClawdbotCameraCommand: String, Codable, Sendable {
public enum MoltbotCameraCommand: String, Codable, Sendable {
case list = "camera.list"
case snap = "camera.snap"
case clip = "camera.clip"
}
public enum ClawdbotCameraFacing: String, Codable, Sendable {
public enum MoltbotCameraFacing: String, Codable, Sendable {
case back
case front
}
public enum ClawdbotCameraImageFormat: String, Codable, Sendable {
public enum MoltbotCameraImageFormat: String, Codable, Sendable {
case jpg
case jpeg
}
public enum ClawdbotCameraVideoFormat: String, Codable, Sendable {
public enum MoltbotCameraVideoFormat: String, Codable, Sendable {
case mp4
}
public struct ClawdbotCameraSnapParams: Codable, Sendable, Equatable {
public var facing: ClawdbotCameraFacing?
public struct MoltbotCameraSnapParams: Codable, Sendable, Equatable {
public var facing: MoltbotCameraFacing?
public var maxWidth: Int?
public var quality: Double?
public var format: ClawdbotCameraImageFormat?
public var format: MoltbotCameraImageFormat?
public var deviceId: String?
public var delayMs: Int?
public init(
facing: ClawdbotCameraFacing? = nil,
facing: MoltbotCameraFacing? = nil,
maxWidth: Int? = nil,
quality: Double? = nil,
format: ClawdbotCameraImageFormat? = nil,
format: MoltbotCameraImageFormat? = nil,
deviceId: String? = nil,
delayMs: Int? = nil)
{
@@ -45,18 +45,18 @@ public struct ClawdbotCameraSnapParams: Codable, Sendable, Equatable {
}
}
public struct ClawdbotCameraClipParams: Codable, Sendable, Equatable {
public var facing: ClawdbotCameraFacing?
public struct MoltbotCameraClipParams: Codable, Sendable, Equatable {
public var facing: MoltbotCameraFacing?
public var durationMs: Int?
public var includeAudio: Bool?
public var format: ClawdbotCameraVideoFormat?
public var format: MoltbotCameraVideoFormat?
public var deviceId: String?
public init(
facing: ClawdbotCameraFacing? = nil,
facing: MoltbotCameraFacing? = nil,
durationMs: Int? = nil,
includeAudio: Bool? = nil,
format: ClawdbotCameraVideoFormat? = nil,
format: MoltbotCameraVideoFormat? = nil,
deviceId: String? = nil)
{
self.facing = facing

View File

@@ -1,6 +1,6 @@
import Foundation
public enum ClawdbotCanvasA2UIAction: Sendable {
public enum MoltbotCanvasA2UIAction: Sendable {
public struct AgentMessageContext: Sendable {
public struct Session: Sendable {
public var key: String
@@ -94,6 +94,6 @@ public enum ClawdbotCanvasA2UIAction: Sendable {
}
return "{\"id\":\"\(actionId)\",\"ok\":\(ok ? "true" : "false"),\"error\":\"\"}"
}()
return "window.dispatchEvent(new CustomEvent('clawdbot:a2ui-action-status', { detail: \(json) }));"
return "window.dispatchEvent(new CustomEvent('moltbot:a2ui-action-status', { detail: \(json) }));"
}
}

View File

@@ -1,6 +1,6 @@
import Foundation
public enum ClawdbotCanvasA2UICommand: String, Codable, Sendable {
public enum MoltbotCanvasA2UICommand: 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 ClawdbotCanvasA2UICommand: String, Codable, Sendable {
case reset = "canvas.a2ui.reset"
}
public struct ClawdbotCanvasA2UIPushParams: Codable, Sendable, Equatable {
public struct MoltbotCanvasA2UIPushParams: Codable, Sendable, Equatable {
public var messages: [AnyCodable]
public init(messages: [AnyCodable]) {
@@ -17,7 +17,7 @@ public struct ClawdbotCanvasA2UIPushParams: Codable, Sendable, Equatable {
}
}
public struct ClawdbotCanvasA2UIPushJSONLParams: Codable, Sendable, Equatable {
public struct MoltbotCanvasA2UIPushJSONLParams: Codable, Sendable, Equatable {
public var jsonl: String
public init(jsonl: String) {

View File

@@ -1,6 +1,6 @@
import Foundation
public enum ClawdbotCanvasA2UIJSONL: Sendable {
public enum MoltbotCanvasA2UIJSONL: Sendable {
public struct ParsedItem: Sendable {
public var lineNumber: Int
public var message: AnyCodable

View File

@@ -1,6 +1,6 @@
import Foundation
public struct ClawdbotCanvasNavigateParams: Codable, Sendable, Equatable {
public struct MoltbotCanvasNavigateParams: Codable, Sendable, Equatable {
public var url: String
public init(url: String) {
@@ -8,7 +8,7 @@ public struct ClawdbotCanvasNavigateParams: Codable, Sendable, Equatable {
}
}
public struct ClawdbotCanvasPlacement: Codable, Sendable, Equatable {
public struct MoltbotCanvasPlacement: Codable, Sendable, Equatable {
public var x: Double?
public var y: Double?
public var width: Double?
@@ -22,17 +22,17 @@ public struct ClawdbotCanvasPlacement: Codable, Sendable, Equatable {
}
}
public struct ClawdbotCanvasPresentParams: Codable, Sendable, Equatable {
public struct MoltbotCanvasPresentParams: Codable, Sendable, Equatable {
public var url: String?
public var placement: ClawdbotCanvasPlacement?
public var placement: MoltbotCanvasPlacement?
public init(url: String? = nil, placement: ClawdbotCanvasPlacement? = nil) {
public init(url: String? = nil, placement: MoltbotCanvasPlacement? = nil) {
self.url = url
self.placement = placement
}
}
public struct ClawdbotCanvasEvalParams: Codable, Sendable, Equatable {
public struct MoltbotCanvasEvalParams: Codable, Sendable, Equatable {
public var javaScript: String
public init(javaScript: String) {
@@ -40,7 +40,7 @@ public struct ClawdbotCanvasEvalParams: Codable, Sendable, Equatable {
}
}
public enum ClawdbotCanvasSnapshotFormat: String, Codable, Sendable {
public enum MoltbotCanvasSnapshotFormat: String, Codable, Sendable {
case png
case jpeg
@@ -63,12 +63,12 @@ public enum ClawdbotCanvasSnapshotFormat: String, Codable, Sendable {
}
}
public struct ClawdbotCanvasSnapshotParams: Codable, Sendable, Equatable {
public struct MoltbotCanvasSnapshotParams: Codable, Sendable, Equatable {
public var maxWidth: Int?
public var quality: Double?
public var format: ClawdbotCanvasSnapshotFormat?
public var format: MoltbotCanvasSnapshotFormat?
public init(maxWidth: Int? = nil, quality: Double? = nil, format: ClawdbotCanvasSnapshotFormat? = nil) {
public init(maxWidth: Int? = nil, quality: Double? = nil, format: MoltbotCanvasSnapshotFormat? = nil) {
self.maxWidth = maxWidth
self.quality = quality
self.format = format

View File

@@ -1,6 +1,6 @@
import Foundation
public enum ClawdbotCanvasCommand: String, Codable, Sendable {
public enum MoltbotCanvasCommand: String, Codable, Sendable {
case present = "canvas.present"
case hide = "canvas.hide"
case navigate = "canvas.navigate"

View File

@@ -1,6 +1,6 @@
import Foundation
public enum ClawdbotCapability: String, Codable, Sendable {
public enum MoltbotCapability: String, Codable, Sendable {
case canvas
case camera
case screen

View File

@@ -1,7 +1,7 @@
import Foundation
public enum ClawdbotKitResources {
/// Resource bundle for ClawdbotKit.
public enum MoltbotKitResources {
/// Resource bundle for MoltbotKit.
///
/// Locates the SwiftPM-generated resource bundle, checking multiple locations:
/// 1. Inside Bundle.main (packaged apps)
@@ -13,7 +13,7 @@ public enum ClawdbotKitResources {
/// SwiftPM's expectations.
public static let bundle: Bundle = locateBundle()
private static let bundleName = "ClawdbotKit_ClawdbotKit"
private static let bundleName = "MoltbotKit_MoltbotKit"
private static func locateBundle() -> Bundle {
// 1. Check inside Bundle.main (packaged apps copy resources here)

View File

@@ -37,7 +37,7 @@ public struct AgentDeepLink: Codable, Sendable, Equatable {
public enum DeepLinkParser {
public static func parse(_ url: URL) -> DeepLinkRoute? {
guard url.scheme?.lowercased() == "clawdbot" else { return nil }
guard url.scheme?.lowercased() == "moltbot" 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 }

View File

@@ -27,10 +27,10 @@ enum DeviceIdentityPaths {
}
if let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
return appSupport.appendingPathComponent("clawdbot", isDirectory: true)
return appSupport.appendingPathComponent("moltbot", isDirectory: true)
}
return FileManager.default.temporaryDirectory.appendingPathComponent("clawdbot", isDirectory: true)
return FileManager.default.temporaryDirectory.appendingPathComponent("moltbot", isDirectory: true)
}
}

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import Foundation
import OSLog
@@ -102,7 +102,7 @@ public enum GatewayAuthSource: String, Sendable {
}
// Avoid ambiguity with the app's own AnyCodable type.
private typealias ProtoAnyCodable = ClawdbotProtocol.AnyCodable
private typealias ProtoAnyCodable = MoltbotProtocol.AnyCodable
private enum ConnectChallengeError: Error {
case timeout
@@ -268,7 +268,7 @@ public actor GatewayChannelActor {
caps: [],
commands: [],
permissions: [:],
clientId: "clawdbot-macos",
clientId: "moltbot-macos",
clientMode: "ui",
clientDisplayName: InstanceIdentity.displayName)
let clientDisplayName = options.clientDisplayName ?? InstanceIdentity.displayName

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import Foundation
/// Structured error surfaced when the gateway responds with `{ ok: false }`.

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import Foundation
import OSLog
@@ -41,7 +41,7 @@ public actor GatewayNodeSession {
return BridgeInvokeResponse(
id: request.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .unavailable,
message: "node invoke timed out")
)

View File

@@ -1,9 +1,9 @@
import ClawdbotProtocol
import MoltbotProtocol
import Foundation
public enum GatewayPayloadDecoding {
public static func decode<T: Decodable>(
_ payload: ClawdbotProtocol.AnyCodable,
_ payload: MoltbotProtocol.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: ClawdbotProtocol.AnyCodable?,
_ payload: MoltbotProtocol.AnyCodable?,
as _: T.Type = T.self) throws -> T?
{
guard let payload else { return nil }

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
/// Server-push messages from the gateway websocket.
///

View File

@@ -42,14 +42,14 @@ public enum InstanceIdentity {
let name = Self.readMainActor {
UIDevice.current.name.trimmingCharacters(in: .whitespacesAndNewlines)
}
return name.isEmpty ? "clawdbot" : name
return name.isEmpty ? "moltbot" : name
#else
if let name = Host.current().localizedName?.trimmingCharacters(in: .whitespacesAndNewlines),
!name.isEmpty
{
return name
}
return "clawdbot"
return "moltbot"
#endif
}()

View File

@@ -1,28 +1,28 @@
import Foundation
public enum ClawdbotLocationCommand: String, Codable, Sendable {
public enum MoltbotLocationCommand: String, Codable, Sendable {
case get = "location.get"
}
public enum ClawdbotLocationAccuracy: String, Codable, Sendable {
public enum MoltbotLocationAccuracy: String, Codable, Sendable {
case coarse
case balanced
case precise
}
public struct ClawdbotLocationGetParams: Codable, Sendable, Equatable {
public struct MoltbotLocationGetParams: Codable, Sendable, Equatable {
public var timeoutMs: Int?
public var maxAgeMs: Int?
public var desiredAccuracy: ClawdbotLocationAccuracy?
public var desiredAccuracy: MoltbotLocationAccuracy?
public init(timeoutMs: Int? = nil, maxAgeMs: Int? = nil, desiredAccuracy: ClawdbotLocationAccuracy? = nil) {
public init(timeoutMs: Int? = nil, maxAgeMs: Int? = nil, desiredAccuracy: MoltbotLocationAccuracy? = nil) {
self.timeoutMs = timeoutMs
self.maxAgeMs = maxAgeMs
self.desiredAccuracy = desiredAccuracy
}
}
public struct ClawdbotLocationPayload: Codable, Sendable, Equatable {
public struct MoltbotLocationPayload: Codable, Sendable, Equatable {
public var lat: Double
public var lon: Double
public var accuracyMeters: Double

View File

@@ -1,6 +1,6 @@
import Foundation
public enum ClawdbotLocationMode: String, Codable, Sendable, CaseIterable {
public enum MoltbotLocationMode: String, Codable, Sendable, CaseIterable {
case off
case whileUsing
case always

View File

@@ -1,6 +1,6 @@
import Foundation
public enum ClawdbotNodeErrorCode: String, Codable, Sendable {
public enum MoltbotNodeErrorCode: String, Codable, Sendable {
case notPaired = "NOT_PAIRED"
case unauthorized = "UNAUTHORIZED"
case backgroundUnavailable = "NODE_BACKGROUND_UNAVAILABLE"
@@ -8,14 +8,14 @@ public enum ClawdbotNodeErrorCode: String, Codable, Sendable {
case unavailable = "UNAVAILABLE"
}
public struct ClawdbotNodeError: Error, Codable, Sendable, Equatable {
public var code: ClawdbotNodeErrorCode
public struct MoltbotNodeError: Error, Codable, Sendable, Equatable {
public var code: MoltbotNodeErrorCode
public var message: String
public var retryable: Bool?
public var retryAfterMs: Int?
public init(
code: ClawdbotNodeErrorCode,
code: MoltbotNodeErrorCode,
message: String,
retryable: Bool? = nil,
retryAfterMs: Int? = nil)

View File

@@ -55,7 +55,7 @@
backface-visibility: hidden;
opacity: 0.45;
pointer-events: none;
animation: clawdbot-grid-drift 140s ease-in-out infinite alternate;
animation: moltbot-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: clawdbot-glow-drift 110s ease-in-out infinite alternate;
animation: moltbot-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 clawdbot-grid-drift {
@keyframes moltbot-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 clawdbot-glow-drift {
@keyframes moltbot-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"] #clawdbot-canvas {
:root[data-platform="android"] #moltbot-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;
}
#clawdbot-status {
#moltbot-status {
position: fixed;
inset: 0;
display: none;
@@ -119,7 +119,7 @@
pointer-events: none;
z-index: 3;
}
#clawdbot-status .card {
#moltbot-status .card {
text-align: center;
padding: 16px 18px;
border-radius: 14px;
@@ -129,13 +129,13 @@
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
}
#clawdbot-status .title {
#moltbot-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);
}
#clawdbot-status .subtitle {
#moltbot-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="clawdbot-canvas"></canvas>
<div id="clawdbot-status">
<canvas id="moltbot-canvas"></canvas>
<div id="moltbot-status">
<div class="card">
<div class="title" id="clawdbot-status-title">Ready</div>
<div class="subtitle" id="clawdbot-status-subtitle">Waiting for agent</div>
<div class="title" id="moltbot-status-title">Ready</div>
<div class="subtitle" id="moltbot-status-subtitle">Waiting for agent</div>
</div>
</div>
<script>
(() => {
const canvas = document.getElementById('clawdbot-canvas');
const canvas = document.getElementById('moltbot-canvas');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('clawdbot-status');
const titleEl = document.getElementById('clawdbot-status-title');
const subtitleEl = document.getElementById('clawdbot-status-subtitle');
const statusEl = document.getElementById('moltbot-status');
const titleEl = document.getElementById('moltbot-status-title');
const subtitleEl = document.getElementById('moltbot-status-subtitle');
const debugStatusEnabledByQuery = (() => {
try {
const params = new URLSearchParams(window.location.search);
@@ -194,7 +194,7 @@
statusEl.style.display = 'none';
}
window.__clawdbot = {
window.__moltbot = {
canvas,
ctx,
setDebugStatusEnabled,

View File

@@ -1,10 +1,10 @@
import Foundation
public enum ClawdbotScreenCommand: String, Codable, Sendable {
public enum MoltbotScreenCommand: String, Codable, Sendable {
case record = "screen.record"
}
public struct ClawdbotScreenRecordParams: Codable, Sendable, Equatable {
public struct MoltbotScreenRecordParams: Codable, Sendable, Equatable {
public var screenIndex: Int?
public var durationMs: Int?
public var fps: Double?

View File

@@ -1,14 +1,14 @@
import Foundation
public enum ClawdbotNodeStorage {
public enum MoltbotNodeStorage {
public static func appSupportDir() throws -> URL {
let base = FileManager().urls(for: .applicationSupportDirectory, in: .userDomainMask).first
guard let base else {
throw NSError(domain: "ClawdbotNodeStorage", code: 1, userInfo: [
throw NSError(domain: "MoltbotNodeStorage", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Application Support directory unavailable",
])
}
return base.appendingPathComponent("Clawdbot", isDirectory: true)
return base.appendingPathComponent("Moltbot", isDirectory: true)
}
public static func canvasRoot(sessionKey: String) throws -> URL {
@@ -21,11 +21,11 @@ public enum ClawdbotNodeStorage {
public static func cachesDir() throws -> URL {
let base = FileManager().urls(for: .cachesDirectory, in: .userDomainMask).first
guard let base else {
throw NSError(domain: "ClawdbotNodeStorage", code: 2, userInfo: [
throw NSError(domain: "MoltbotNodeStorage", code: 2, userInfo: [
NSLocalizedDescriptionKey: "Caches directory unavailable",
])
}
return base.appendingPathComponent("Clawdbot", isDirectory: true)
return base.appendingPathComponent("Moltbot", isDirectory: true)
}
public static func canvasSnapshotsRoot(sessionKey: String) throws -> URL {

View File

@@ -1,6 +1,6 @@
import Foundation
public enum ClawdbotSystemCommand: String, Codable, Sendable {
public enum MoltbotSystemCommand: String, Codable, Sendable {
case run = "system.run"
case which = "system.which"
case notify = "system.notify"
@@ -8,19 +8,19 @@ public enum ClawdbotSystemCommand: String, Codable, Sendable {
case execApprovalsSet = "system.execApprovals.set"
}
public enum ClawdbotNotificationPriority: String, Codable, Sendable {
public enum MoltbotNotificationPriority: String, Codable, Sendable {
case passive
case active
case timeSensitive
}
public enum ClawdbotNotificationDelivery: String, Codable, Sendable {
public enum MoltbotNotificationDelivery: String, Codable, Sendable {
case system
case overlay
case auto
}
public struct ClawdbotSystemRunParams: Codable, Sendable, Equatable {
public struct MoltbotSystemRunParams: Codable, Sendable, Equatable {
public var command: [String]
public var rawCommand: String?
public var cwd: String?
@@ -57,7 +57,7 @@ public struct ClawdbotSystemRunParams: Codable, Sendable, Equatable {
}
}
public struct ClawdbotSystemWhichParams: Codable, Sendable, Equatable {
public struct MoltbotSystemWhichParams: Codable, Sendable, Equatable {
public var bins: [String]
public init(bins: [String]) {
@@ -65,19 +65,19 @@ public struct ClawdbotSystemWhichParams: Codable, Sendable, Equatable {
}
}
public struct ClawdbotSystemNotifyParams: Codable, Sendable, Equatable {
public struct MoltbotSystemNotifyParams: Codable, Sendable, Equatable {
public var title: String
public var body: String
public var sound: String?
public var priority: ClawdbotNotificationPriority?
public var delivery: ClawdbotNotificationDelivery?
public var priority: MoltbotNotificationPriority?
public var delivery: MoltbotNotificationDelivery?
public init(
title: String,
body: String,
sound: String? = nil,
priority: ClawdbotNotificationPriority? = nil,
delivery: ClawdbotNotificationDelivery? = nil)
priority: MoltbotNotificationPriority? = nil,
delivery: MoltbotNotificationDelivery? = nil)
{
self.title = title
self.body = body

View File

@@ -90,7 +90,7 @@ public enum ToolDisplayRegistry {
}
private static func loadConfig() -> ToolDisplayConfig {
guard let url = ClawdbotKitResources.bundle.url(forResource: "tool-display", withExtension: "json") else {
guard let url = MoltbotKitResources.bundle.url(forResource: "tool-display", withExtension: "json") else {
return self.defaultConfig()
}
do {

View File

@@ -1,5 +1,5 @@
import Testing
@testable import ClawdbotChatUI
@testable import MoltbotChatUI
@Suite struct AssistantTextParserTests {
@Test func splitsThinkAndFinalSegments() {

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import Testing
@Suite struct BonjourEscapesTests {
@@ -8,7 +8,7 @@ import Testing
}
@Test func decodeSpaces() {
#expect(BonjourEscapes.decode("Clawdbot\\032Gateway") == "Clawdbot Gateway")
#expect(BonjourEscapes.decode("Moltbot\\032Gateway") == "Moltbot Gateway")
}
@Test func decodeMultipleEscapes() {

View File

@@ -1,28 +1,28 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import Testing
@Suite struct CanvasA2UIActionTests {
@Test func sanitizeTagValueIsStable() {
#expect(ClawdbotCanvasA2UIAction.sanitizeTagValue("Hello World!") == "Hello_World_")
#expect(ClawdbotCanvasA2UIAction.sanitizeTagValue(" ") == "-")
#expect(ClawdbotCanvasA2UIAction.sanitizeTagValue("macOS 26.2") == "macOS_26.2")
#expect(MoltbotCanvasA2UIAction.sanitizeTagValue("Hello World!") == "Hello_World_")
#expect(MoltbotCanvasA2UIAction.sanitizeTagValue(" ") == "-")
#expect(MoltbotCanvasA2UIAction.sanitizeTagValue("macOS 26.2") == "macOS_26.2")
}
@Test func extractActionNameAcceptsNameOrAction() {
#expect(ClawdbotCanvasA2UIAction.extractActionName(["name": "Hello"]) == "Hello")
#expect(ClawdbotCanvasA2UIAction.extractActionName(["action": "Wave"]) == "Wave")
#expect(ClawdbotCanvasA2UIAction.extractActionName(["name": " ", "action": "Fallback"]) == "Fallback")
#expect(ClawdbotCanvasA2UIAction.extractActionName(["action": " "]) == nil)
#expect(MoltbotCanvasA2UIAction.extractActionName(["name": "Hello"]) == "Hello")
#expect(MoltbotCanvasA2UIAction.extractActionName(["action": "Wave"]) == "Wave")
#expect(MoltbotCanvasA2UIAction.extractActionName(["name": " ", "action": "Fallback"]) == "Fallback")
#expect(MoltbotCanvasA2UIAction.extractActionName(["action": " "]) == nil)
}
@Test func formatAgentMessageIsTokenEfficientAndUnambiguous() {
let messageContext = ClawdbotCanvasA2UIAction.AgentMessageContext(
let messageContext = MoltbotCanvasA2UIAction.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 = ClawdbotCanvasA2UIAction.formatAgentMessage(messageContext)
let msg = MoltbotCanvasA2UIAction.formatAgentMessage(messageContext)
#expect(msg.contains("CANVAS_A2UI "))
#expect(msg.contains("action=Get_Weather"))

View File

@@ -1,11 +1,11 @@
import ClawdbotKit
import MoltbotKit
import Testing
@Suite struct CanvasA2UITests {
@Test func commandStringsAreStable() {
#expect(ClawdbotCanvasA2UICommand.push.rawValue == "canvas.a2ui.push")
#expect(ClawdbotCanvasA2UICommand.pushJSONL.rawValue == "canvas.a2ui.pushJSONL")
#expect(ClawdbotCanvasA2UICommand.reset.rawValue == "canvas.a2ui.reset")
#expect(MoltbotCanvasA2UICommand.push.rawValue == "canvas.a2ui.push")
#expect(MoltbotCanvasA2UICommand.pushJSONL.rawValue == "canvas.a2ui.pushJSONL")
#expect(MoltbotCanvasA2UICommand.reset.rawValue == "canvas.a2ui.reset")
}
@Test func jsonlDecodesAndValidatesV0_8() throws {
@@ -16,7 +16,7 @@ import Testing
{"deleteSurface":{"surfaceId":"main"}}
"""
let messages = try ClawdbotCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
let messages = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
#expect(messages.count == 4)
}
@@ -26,7 +26,7 @@ import Testing
"""
#expect(throws: Error.self) {
_ = try ClawdbotCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
_ = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
}
}
@@ -36,7 +36,7 @@ import Testing
"""
#expect(throws: Error.self) {
_ = try ClawdbotCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
_ = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(jsonl)
}
}
}

View File

@@ -1,11 +1,11 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import Testing
@Suite struct CanvasSnapshotFormatTests {
@Test func acceptsJpgAlias() throws {
struct Wrapper: Codable {
var format: ClawdbotCanvasSnapshotFormat
var format: MoltbotCanvasSnapshotFormat
}
let data = try #require("{\"format\":\"jpg\"}".data(using: .utf8))

View File

@@ -1,5 +1,5 @@
import Testing
@testable import ClawdbotChatUI
@testable import MoltbotChatUI
@Suite("ChatMarkdownPreprocessor")
struct ChatMarkdownPreprocessorTests {

View File

@@ -1,6 +1,6 @@
import Foundation
import Testing
@testable import ClawdbotChatUI
@testable import MoltbotChatUI
#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 = ClawdbotChatTheme.resolvedAssistantBubbleColor(for: lightAppearance)
let darkResolved = ClawdbotChatTheme.resolvedAssistantBubbleColor(for: darkAppearance)
let lightResolved = MoltbotChatTheme.resolvedAssistantBubbleColor(for: lightAppearance)
let darkResolved = MoltbotChatTheme.resolvedAssistantBubbleColor(for: darkAppearance)
#expect(try luminance(lightResolved) > luminance(darkResolved))
#else
#expect(Bool(true))

View File

@@ -1,7 +1,7 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import Testing
@testable import ClawdbotChatUI
@testable import MoltbotChatUI
private struct TimeoutError: Error, CustomStringConvertible {
let label: String
@@ -31,40 +31,40 @@ private actor TestChatTransportState {
var abortedRunIds: [String] = []
}
private final class TestChatTransport: @unchecked Sendable, ClawdbotChatTransport {
private final class TestChatTransport: @unchecked Sendable, MoltbotChatTransport {
private let state = TestChatTransportState()
private let historyResponses: [ClawdbotChatHistoryPayload]
private let sessionsResponses: [ClawdbotChatSessionsListResponse]
private let historyResponses: [MoltbotChatHistoryPayload]
private let sessionsResponses: [MoltbotChatSessionsListResponse]
private let stream: AsyncStream<ClawdbotChatTransportEvent>
private let continuation: AsyncStream<ClawdbotChatTransportEvent>.Continuation
private let stream: AsyncStream<MoltbotChatTransportEvent>
private let continuation: AsyncStream<MoltbotChatTransportEvent>.Continuation
init(
historyResponses: [ClawdbotChatHistoryPayload],
sessionsResponses: [ClawdbotChatSessionsListResponse] = [])
historyResponses: [MoltbotChatHistoryPayload],
sessionsResponses: [MoltbotChatSessionsListResponse] = [])
{
self.historyResponses = historyResponses
self.sessionsResponses = sessionsResponses
var cont: AsyncStream<ClawdbotChatTransportEvent>.Continuation!
var cont: AsyncStream<MoltbotChatTransportEvent>.Continuation!
self.stream = AsyncStream { c in
cont = c
}
self.continuation = cont
}
func events() -> AsyncStream<ClawdbotChatTransportEvent> {
func events() -> AsyncStream<MoltbotChatTransportEvent> {
self.stream
}
func setActiveSessionKey(_: String) async throws {}
func requestHistory(sessionKey: String) async throws -> ClawdbotChatHistoryPayload {
func requestHistory(sessionKey: String) async throws -> MoltbotChatHistoryPayload {
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 ?? ClawdbotChatHistoryPayload(
return self.historyResponses.last ?? MoltbotChatHistoryPayload(
sessionKey: sessionKey,
sessionId: nil,
messages: [],
@@ -76,23 +76,23 @@ private final class TestChatTransport: @unchecked Sendable, ClawdbotChatTranspor
message _: String,
thinking _: String,
idempotencyKey: String,
attachments _: [ClawdbotChatAttachmentPayload]) async throws -> ClawdbotChatSendResponse
attachments _: [MoltbotChatAttachmentPayload]) async throws -> MoltbotChatSendResponse
{
await self.state.sentRunIdsAppend(idempotencyKey)
return ClawdbotChatSendResponse(runId: idempotencyKey, status: "ok")
return MoltbotChatSendResponse(runId: idempotencyKey, status: "ok")
}
func abortRun(sessionKey _: String, runId: String) async throws {
await self.state.abortedRunIdsAppend(runId)
}
func listSessions(limit _: Int?) async throws -> ClawdbotChatSessionsListResponse {
func listSessions(limit _: Int?) async throws -> MoltbotChatSessionsListResponse {
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 ?? ClawdbotChatSessionsListResponse(
return self.sessionsResponses.last ?? MoltbotChatSessionsListResponse(
ts: nil,
path: nil,
count: 0,
@@ -104,7 +104,7 @@ private final class TestChatTransport: @unchecked Sendable, ClawdbotChatTranspor
true
}
func emit(_ evt: ClawdbotChatTransportEvent) {
func emit(_ evt: MoltbotChatTransportEvent) {
self.continuation.yield(evt)
}
@@ -139,12 +139,12 @@ extension TestChatTransportState {
@Suite struct ChatViewModelTests {
@Test func streamsAssistantAndClearsOnFinal() async throws {
let sessionId = "sess-main"
let history1 = ClawdbotChatHistoryPayload(
let history1 = MoltbotChatHistoryPayload(
sessionKey: "main",
sessionId: sessionId,
messages: [],
thinkingLevel: "off")
let history2 = ClawdbotChatHistoryPayload(
let history2 = MoltbotChatHistoryPayload(
sessionKey: "main",
sessionId: sessionId,
messages: [
@@ -157,7 +157,7 @@ extension TestChatTransportState {
thinkingLevel: "off")
let transport = TestChatTransport(historyResponses: [history1, history2])
let vm = await MainActor.run { ClawdbotChatViewModel(sessionKey: "main", transport: transport) }
let vm = await MainActor.run { MoltbotChatViewModel(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(
ClawdbotAgentEventPayload(
MoltbotAgentEventPayload(
runId: sessionId,
seq: 1,
stream: "assistant",
@@ -183,7 +183,7 @@ extension TestChatTransportState {
transport.emit(
.agent(
ClawdbotAgentEventPayload(
MoltbotAgentEventPayload(
runId: sessionId,
seq: 2,
stream: "tool",
@@ -200,7 +200,7 @@ extension TestChatTransportState {
let runId = try #require(await transport.lastSentRunId())
transport.emit(
.chat(
ClawdbotChatEventPayload(
MoltbotChatEventPayload(
runId: runId,
sessionKey: "main",
state: "final",
@@ -217,20 +217,20 @@ extension TestChatTransportState {
@Test func clearsStreamingOnExternalFinalEvent() async throws {
let sessionId = "sess-main"
let history = ClawdbotChatHistoryPayload(
let history = MoltbotChatHistoryPayload(
sessionKey: "main",
sessionId: sessionId,
messages: [],
thinkingLevel: "off")
let transport = TestChatTransport(historyResponses: [history, history])
let vm = await MainActor.run { ClawdbotChatViewModel(sessionKey: "main", transport: transport) }
let vm = await MainActor.run { MoltbotChatViewModel(sessionKey: "main", transport: transport) }
await MainActor.run { vm.load() }
try await waitUntil("bootstrap") { await MainActor.run { vm.healthOK && vm.sessionId == sessionId } }
transport.emit(
.agent(
ClawdbotAgentEventPayload(
MoltbotAgentEventPayload(
runId: sessionId,
seq: 1,
stream: "assistant",
@@ -239,7 +239,7 @@ extension TestChatTransportState {
transport.emit(
.agent(
ClawdbotAgentEventPayload(
MoltbotAgentEventPayload(
runId: sessionId,
seq: 2,
stream: "tool",
@@ -258,7 +258,7 @@ extension TestChatTransportState {
transport.emit(
.chat(
ClawdbotChatEventPayload(
MoltbotChatEventPayload(
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 = ClawdbotChatHistoryPayload(
let history = MoltbotChatHistoryPayload(
sessionKey: "main",
sessionId: "sess-main",
messages: [],
thinkingLevel: "off")
let sessions = ClawdbotChatSessionsListResponse(
let sessions = MoltbotChatSessionsListResponse(
ts: now,
path: nil,
count: 4,
defaults: nil,
sessions: [
ClawdbotChatSessionEntry(
MoltbotChatSessionEntry(
key: "recent-1",
kind: nil,
displayName: nil,
@@ -304,7 +304,7 @@ extension TestChatTransportState {
totalTokens: nil,
model: nil,
contextTokens: nil),
ClawdbotChatSessionEntry(
MoltbotChatSessionEntry(
key: "main",
kind: nil,
displayName: nil,
@@ -323,7 +323,7 @@ extension TestChatTransportState {
totalTokens: nil,
model: nil,
contextTokens: nil),
ClawdbotChatSessionEntry(
MoltbotChatSessionEntry(
key: "recent-2",
kind: nil,
displayName: nil,
@@ -342,7 +342,7 @@ extension TestChatTransportState {
totalTokens: nil,
model: nil,
contextTokens: nil),
ClawdbotChatSessionEntry(
MoltbotChatSessionEntry(
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 { ClawdbotChatViewModel(sessionKey: "main", transport: transport) }
let vm = await MainActor.run { MoltbotChatViewModel(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 = ClawdbotChatHistoryPayload(
let history = MoltbotChatHistoryPayload(
sessionKey: "custom",
sessionId: "sess-custom",
messages: [],
thinkingLevel: "off")
let sessions = ClawdbotChatSessionsListResponse(
let sessions = MoltbotChatSessionsListResponse(
ts: now,
path: nil,
count: 1,
defaults: nil,
sessions: [
ClawdbotChatSessionEntry(
MoltbotChatSessionEntry(
key: "main",
kind: nil,
displayName: nil,
@@ -412,7 +412,7 @@ extension TestChatTransportState {
let transport = TestChatTransport(
historyResponses: [history],
sessionsResponses: [sessions])
let vm = await MainActor.run { ClawdbotChatViewModel(sessionKey: "custom", transport: transport) }
let vm = await MainActor.run { MoltbotChatViewModel(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 = ClawdbotChatHistoryPayload(
let history = MoltbotChatHistoryPayload(
sessionKey: "main",
sessionId: sessionId,
messages: [],
thinkingLevel: "off")
let transport = TestChatTransport(historyResponses: [history, history])
let vm = await MainActor.run { ClawdbotChatViewModel(sessionKey: "main", transport: transport) }
let vm = await MainActor.run { MoltbotChatViewModel(sessionKey: "main", transport: transport) }
await MainActor.run { vm.load() }
try await waitUntil("bootstrap") { await MainActor.run { vm.healthOK && vm.sessionId == sessionId } }
transport.emit(
.agent(
ClawdbotAgentEventPayload(
MoltbotAgentEventPayload(
runId: sessionId,
seq: 1,
stream: "assistant",
@@ -448,7 +448,7 @@ extension TestChatTransportState {
transport.emit(
.chat(
ClawdbotChatEventPayload(
MoltbotChatEventPayload(
runId: "other-run",
sessionKey: "main",
state: "error",
@@ -460,13 +460,13 @@ extension TestChatTransportState {
@Test func abortRequestsDoNotClearPendingUntilAbortedEvent() async throws {
let sessionId = "sess-main"
let history = ClawdbotChatHistoryPayload(
let history = MoltbotChatHistoryPayload(
sessionKey: "main",
sessionId: sessionId,
messages: [],
thinkingLevel: "off")
let transport = TestChatTransport(historyResponses: [history, history])
let vm = await MainActor.run { ClawdbotChatViewModel(sessionKey: "main", transport: transport) }
let vm = await MainActor.run { MoltbotChatViewModel(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(
ClawdbotChatEventPayload(
MoltbotChatEventPayload(
runId: runId,
sessionKey: "main",
state: "aborted",

View File

@@ -1,5 +1,5 @@
import XCTest
@testable import ClawdbotKit
@testable import MoltbotKit
final class ElevenLabsTTSValidationTests: XCTestCase {
func testValidatedOutputFormatAllowsOnlyMp3Presets() {

View File

@@ -1,7 +1,7 @@
import Foundation
import Testing
@testable import ClawdbotKit
import ClawdbotProtocol
@testable import MoltbotKit
import MoltbotProtocol
struct GatewayNodeSessionTests {
@Test

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import CoreGraphics
import ImageIO
import Testing

View File

@@ -1,5 +1,5 @@
import XCTest
@testable import ClawdbotKit
@testable import MoltbotKit
final class TalkDirectiveTests: XCTestCase {
func testParsesDirectiveAndStripsLine() {

View File

@@ -1,5 +1,5 @@
import XCTest
@testable import ClawdbotKit
@testable import MoltbotKit
final class TalkHistoryTimestampTests: XCTestCase {
func testSecondsTimestampsAreAcceptedWithSmallTolerance() {

View File

@@ -1,5 +1,5 @@
import XCTest
@testable import ClawdbotKit
@testable import MoltbotKit
final class TalkPromptBuilderTests: XCTestCase {
func testBuildIncludesTranscript() {

View File

@@ -1,10 +1,10 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import Testing
@Suite struct ToolDisplayRegistryTests {
@Test func loadsToolDisplayConfigFromBundle() {
let url = ClawdbotKitResources.bundle.url(forResource: "tool-display", withExtension: "json")
let url = MoltbotKitResources.bundle.url(forResource: "tool-display", withExtension: "json")
#expect(url != nil)
}

View File

@@ -4,7 +4,7 @@ import { ContextProvider } from "@lit/context";
import { v0_8 } from "@a2ui/lit";
import "@a2ui/lit/ui";
import { themeContext } from "@clawdbot/a2ui-theme-context";
import { themeContext } from "@moltbot/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 clawdbotTheme = {
const moltbotTheme = {
components: {
AudioPlayer: emptyClasses(),
Button: emptyClasses(),
@@ -152,7 +152,7 @@ const clawdbotTheme = {
},
};
class ClawdbotA2UIHost extends LitElement {
class MoltbotA2UIHost extends LitElement {
static properties = {
surfaces: { state: true },
pendingAction: { state: true },
@@ -162,7 +162,7 @@ class ClawdbotA2UIHost extends LitElement {
#processor = v0_8.Data.createSignalA2uiMessageProcessor();
#themeProvider = new ContextProvider(this, {
context: themeContext,
initialValue: clawdbotTheme,
initialValue: moltbotTheme,
});
surfaces = [];
@@ -177,10 +177,10 @@ class ClawdbotA2UIHost extends LitElement {
position: relative;
box-sizing: border-box;
padding:
var(--clawdbot-a2ui-inset-top, 0px)
var(--clawdbot-a2ui-inset-right, 0px)
var(--clawdbot-a2ui-inset-bottom, 0px)
var(--clawdbot-a2ui-inset-left, 0px);
var(--moltbot-a2ui-inset-top, 0px)
var(--moltbot-a2ui-inset-right, 0px)
var(--moltbot-a2ui-inset-bottom, 0px)
var(--moltbot-a2ui-inset-left, 0px);
}
#surfaces {
@@ -189,14 +189,14 @@ class ClawdbotA2UIHost extends LitElement {
gap: 12px;
height: 100%;
overflow: auto;
padding-bottom: var(--clawdbot-a2ui-scroll-pad-bottom, 0px);
padding-bottom: var(--moltbot-a2ui-scroll-pad-bottom, 0px);
}
.status {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: var(--clawdbot-a2ui-status-top, 12px);
top: var(--moltbot-a2ui-status-top, 12px);
display: inline-flex;
align-items: center;
gap: 8px;
@@ -217,7 +217,7 @@ class ClawdbotA2UIHost extends LitElement {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: var(--clawdbot-a2ui-toast-bottom, 12px);
bottom: var(--moltbot-a2ui-toast-bottom, 12px);
display: inline-flex;
align-items: center;
gap: 8px;
@@ -243,7 +243,7 @@ class ClawdbotA2UIHost extends LitElement {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: var(--clawdbot-a2ui-empty-top, var(--clawdbot-a2ui-status-top, 12px));
top: var(--moltbot-a2ui-empty-top, var(--moltbot-a2ui-status-top, 12px));
text-align: center;
opacity: 0.8;
padding: 10px 12px;
@@ -276,21 +276,23 @@ class ClawdbotA2UIHost extends LitElement {
connectedCallback() {
super.connectedCallback();
globalThis.clawdbotA2UI = {
const api = {
applyMessages: (messages) => this.applyMessages(messages),
reset: () => this.reset(),
getSurfaces: () => Array.from(this.#processor.getSurfaces().keys()),
};
globalThis.moltbotA2UI = api;
globalThis.clawdbotA2UI = api;
this.addEventListener("a2uiaction", (evt) => this.#handleA2UIAction(evt));
this.#statusListener = (evt) => this.#handleActionStatus(evt);
globalThis.addEventListener("clawdbot:a2ui-action-status", this.#statusListener);
globalThis.addEventListener("moltbot:a2ui-action-status", this.#statusListener);
this.#syncSurfaces();
}
disconnectedCallback() {
super.disconnectedCallback();
if (this.#statusListener) {
globalThis.removeEventListener("clawdbot:a2ui-action-status", this.#statusListener);
globalThis.removeEventListener("moltbot:a2ui-action-status", this.#statusListener);
this.#statusListener = null;
}
}
@@ -393,15 +395,20 @@ class ClawdbotA2UIHost extends LitElement {
...(Object.keys(context).length ? { context } : {}),
};
globalThis.__clawdbotLastA2UIAction = userAction;
globalThis.__moltbotLastA2UIAction = userAction;
const handler =
globalThis.webkit?.messageHandlers?.moltbotCanvasA2UIAction ??
globalThis.webkit?.messageHandlers?.clawdbotCanvasA2UIAction ??
globalThis.moltbotCanvasA2UIAction ??
globalThis.clawdbotCanvasA2UIAction;
if (handler?.postMessage) {
try {
// WebKit message handlers support structured objects; Android's JS interface expects strings.
if (handler === globalThis.clawdbotCanvasA2UIAction) {
if (
handler === globalThis.moltbotCanvasA2UIAction ||
handler === globalThis.clawdbotCanvasA2UIAction
) {
handler.postMessage(JSON.stringify({ userAction }));
} else {
handler.postMessage({ userAction });
@@ -481,4 +488,4 @@ class ClawdbotA2UIHost extends LitElement {
}
}
customElements.define("clawdbot-a2ui-host", ClawdbotA2UIHost);
customElements.define("moltbot-a2ui-host", MoltbotA2UIHost);

View File

@@ -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"),
"@clawdbot/a2ui-theme-context": a2uiThemeContext,
"@moltbot/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"),