refactor: rename clawdbot to moltbot with legacy compat
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import AVFoundation
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Foundation
|
||||
|
||||
actor CameraController {
|
||||
@@ -36,7 +36,7 @@ actor CameraController {
|
||||
}
|
||||
}
|
||||
|
||||
func snap(params: ClawdbotCameraSnapParams) async throws -> (
|
||||
func snap(params: MoltbotCameraSnapParams) async throws -> (
|
||||
format: String,
|
||||
base64: String,
|
||||
width: Int,
|
||||
@@ -109,7 +109,7 @@ actor CameraController {
|
||||
height: res.heightPx)
|
||||
}
|
||||
|
||||
func clip(params: ClawdbotCameraClipParams) async throws -> (
|
||||
func clip(params: MoltbotCameraClipParams) async throws -> (
|
||||
format: String,
|
||||
base64: String,
|
||||
durationMs: Int,
|
||||
@@ -161,9 +161,9 @@ actor CameraController {
|
||||
await Self.warmUpCaptureSession()
|
||||
|
||||
let movURL = FileManager().temporaryDirectory
|
||||
.appendingPathComponent("clawdbot-camera-\(UUID().uuidString).mov")
|
||||
.appendingPathComponent("moltbot-camera-\(UUID().uuidString).mov")
|
||||
let mp4URL = FileManager().temporaryDirectory
|
||||
.appendingPathComponent("clawdbot-camera-\(UUID().uuidString).mp4")
|
||||
.appendingPathComponent("moltbot-camera-\(UUID().uuidString).mp4")
|
||||
|
||||
defer {
|
||||
try? FileManager().removeItem(at: movURL)
|
||||
@@ -221,7 +221,7 @@ actor CameraController {
|
||||
}
|
||||
|
||||
private nonisolated static func pickCamera(
|
||||
facing: ClawdbotCameraFacing,
|
||||
facing: MoltbotCameraFacing,
|
||||
deviceId: String?) -> AVCaptureDevice?
|
||||
{
|
||||
if let deviceId, !deviceId.isEmpty {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import ClawdbotChatUI
|
||||
import ClawdbotKit
|
||||
import MoltbotChatUI
|
||||
import MoltbotKit
|
||||
import SwiftUI
|
||||
|
||||
struct ChatSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var viewModel: ClawdbotChatViewModel
|
||||
@State private var viewModel: MoltbotChatViewModel
|
||||
private let userAccent: Color?
|
||||
|
||||
init(gateway: GatewayNodeSession, sessionKey: String, userAccent: Color? = nil) {
|
||||
let transport = IOSGatewayChatTransport(gateway: gateway)
|
||||
self._viewModel = State(
|
||||
initialValue: ClawdbotChatViewModel(
|
||||
initialValue: MoltbotChatViewModel(
|
||||
sessionKey: sessionKey,
|
||||
transport: transport))
|
||||
self.userAccent = userAccent
|
||||
@@ -18,7 +18,7 @@ struct ChatSheet: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ClawdbotChatView(
|
||||
MoltbotChatView(
|
||||
viewModel: self.viewModel,
|
||||
showsSessionSwitcher: true,
|
||||
userAccent: self.userAccent)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import ClawdbotChatUI
|
||||
import ClawdbotKit
|
||||
import ClawdbotProtocol
|
||||
import MoltbotChatUI
|
||||
import MoltbotKit
|
||||
import MoltbotProtocol
|
||||
import Foundation
|
||||
|
||||
struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
|
||||
struct IOSGatewayChatTransport: MoltbotChatTransport, Sendable {
|
||||
private let gateway: GatewayNodeSession
|
||||
|
||||
init(gateway: GatewayNodeSession) {
|
||||
@@ -20,7 +20,7 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
|
||||
_ = try await self.gateway.request(method: "chat.abort", paramsJSON: json, timeoutSeconds: 10)
|
||||
}
|
||||
|
||||
func listSessions(limit: Int?) async throws -> ClawdbotChatSessionsListResponse {
|
||||
func listSessions(limit: Int?) async throws -> MoltbotChatSessionsListResponse {
|
||||
struct Params: Codable {
|
||||
var includeGlobal: Bool
|
||||
var includeUnknown: Bool
|
||||
@@ -29,7 +29,7 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
|
||||
let data = try JSONEncoder().encode(Params(includeGlobal: true, includeUnknown: false, limit: limit))
|
||||
let json = String(data: data, encoding: .utf8)
|
||||
let res = try await self.gateway.request(method: "sessions.list", paramsJSON: json, timeoutSeconds: 15)
|
||||
return try JSONDecoder().decode(ClawdbotChatSessionsListResponse.self, from: res)
|
||||
return try JSONDecoder().decode(MoltbotChatSessionsListResponse.self, from: res)
|
||||
}
|
||||
|
||||
func setActiveSessionKey(_ sessionKey: String) async throws {
|
||||
@@ -39,12 +39,12 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
|
||||
await self.gateway.sendEvent(event: "chat.subscribe", payloadJSON: json)
|
||||
}
|
||||
|
||||
func requestHistory(sessionKey: String) async throws -> ClawdbotChatHistoryPayload {
|
||||
func requestHistory(sessionKey: String) async throws -> MoltbotChatHistoryPayload {
|
||||
struct Params: Codable { var sessionKey: String }
|
||||
let data = try JSONEncoder().encode(Params(sessionKey: sessionKey))
|
||||
let json = String(data: data, encoding: .utf8)
|
||||
let res = try await self.gateway.request(method: "chat.history", paramsJSON: json, timeoutSeconds: 15)
|
||||
return try JSONDecoder().decode(ClawdbotChatHistoryPayload.self, from: res)
|
||||
return try JSONDecoder().decode(MoltbotChatHistoryPayload.self, from: res)
|
||||
}
|
||||
|
||||
func sendMessage(
|
||||
@@ -52,13 +52,13 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
|
||||
message: String,
|
||||
thinking: String,
|
||||
idempotencyKey: String,
|
||||
attachments: [ClawdbotChatAttachmentPayload]) async throws -> ClawdbotChatSendResponse
|
||||
attachments: [MoltbotChatAttachmentPayload]) async throws -> MoltbotChatSendResponse
|
||||
{
|
||||
struct Params: Codable {
|
||||
var sessionKey: String
|
||||
var message: String
|
||||
var thinking: String
|
||||
var attachments: [ClawdbotChatAttachmentPayload]?
|
||||
var attachments: [MoltbotChatAttachmentPayload]?
|
||||
var timeoutMs: Int
|
||||
var idempotencyKey: String
|
||||
}
|
||||
@@ -73,16 +73,16 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
|
||||
let data = try JSONEncoder().encode(params)
|
||||
let json = String(data: data, encoding: .utf8)
|
||||
let res = try await self.gateway.request(method: "chat.send", paramsJSON: json, timeoutSeconds: 35)
|
||||
return try JSONDecoder().decode(ClawdbotChatSendResponse.self, from: res)
|
||||
return try JSONDecoder().decode(MoltbotChatSendResponse.self, from: res)
|
||||
}
|
||||
|
||||
func requestHealth(timeoutMs: Int) async throws -> Bool {
|
||||
let seconds = max(1, Int(ceil(Double(timeoutMs) / 1000.0)))
|
||||
let res = try await self.gateway.request(method: "health", paramsJSON: nil, timeoutSeconds: seconds)
|
||||
return (try? JSONDecoder().decode(ClawdbotGatewayHealthOK.self, from: res))?.ok ?? true
|
||||
return (try? JSONDecoder().decode(MoltbotGatewayHealthOK.self, from: res))?.ok ?? true
|
||||
}
|
||||
|
||||
func events() -> AsyncStream<ClawdbotChatTransportEvent> {
|
||||
func events() -> AsyncStream<MoltbotChatTransportEvent> {
|
||||
AsyncStream { continuation in
|
||||
let task = Task {
|
||||
let stream = await self.gateway.subscribeServerEvents()
|
||||
@@ -97,13 +97,13 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
|
||||
guard let payload = evt.payload else { break }
|
||||
let ok = (try? GatewayPayloadDecoding.decode(
|
||||
payload,
|
||||
as: ClawdbotGatewayHealthOK.self))?.ok ?? true
|
||||
as: MoltbotGatewayHealthOK.self))?.ok ?? true
|
||||
continuation.yield(.health(ok: ok))
|
||||
case "chat":
|
||||
guard let payload = evt.payload else { break }
|
||||
if let chatPayload = try? GatewayPayloadDecoding.decode(
|
||||
payload,
|
||||
as: ClawdbotChatEventPayload.self)
|
||||
as: MoltbotChatEventPayload.self)
|
||||
{
|
||||
continuation.yield(.chat(chatPayload))
|
||||
}
|
||||
@@ -111,7 +111,7 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
|
||||
guard let payload = evt.payload else { break }
|
||||
if let agentPayload = try? GatewayPayloadDecoding.decode(
|
||||
payload,
|
||||
as: ClawdbotAgentEventPayload.self)
|
||||
as: MoltbotAgentEventPayload.self)
|
||||
{
|
||||
continuation.yield(.agent(agentPayload))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct ClawdbotApp: App {
|
||||
struct MoltbotApp: App {
|
||||
@State private var appModel: NodeAppModel
|
||||
@State private var gatewayController: GatewayConnectionController
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Darwin
|
||||
import Foundation
|
||||
import Network
|
||||
@@ -283,7 +283,7 @@ final class GatewayConnectionController {
|
||||
caps: self.currentCaps(),
|
||||
commands: self.currentCommands(),
|
||||
permissions: [:],
|
||||
clientId: "clawdbot-ios",
|
||||
clientId: "moltbot-ios",
|
||||
clientMode: "node",
|
||||
clientDisplayName: displayName)
|
||||
}
|
||||
@@ -304,51 +304,51 @@ final class GatewayConnectionController {
|
||||
}
|
||||
|
||||
private func currentCaps() -> [String] {
|
||||
var caps = [ClawdbotCapability.canvas.rawValue, ClawdbotCapability.screen.rawValue]
|
||||
var caps = [MoltbotCapability.canvas.rawValue, MoltbotCapability.screen.rawValue]
|
||||
|
||||
// Default-on: if the key doesn't exist yet, treat it as enabled.
|
||||
let cameraEnabled =
|
||||
UserDefaults.standard.object(forKey: "camera.enabled") == nil
|
||||
? true
|
||||
: UserDefaults.standard.bool(forKey: "camera.enabled")
|
||||
if cameraEnabled { caps.append(ClawdbotCapability.camera.rawValue) }
|
||||
if cameraEnabled { caps.append(MoltbotCapability.camera.rawValue) }
|
||||
|
||||
let voiceWakeEnabled = UserDefaults.standard.bool(forKey: VoiceWakePreferences.enabledKey)
|
||||
if voiceWakeEnabled { caps.append(ClawdbotCapability.voiceWake.rawValue) }
|
||||
if voiceWakeEnabled { caps.append(MoltbotCapability.voiceWake.rawValue) }
|
||||
|
||||
let locationModeRaw = UserDefaults.standard.string(forKey: "location.enabledMode") ?? "off"
|
||||
let locationMode = ClawdbotLocationMode(rawValue: locationModeRaw) ?? .off
|
||||
if locationMode != .off { caps.append(ClawdbotCapability.location.rawValue) }
|
||||
let locationMode = MoltbotLocationMode(rawValue: locationModeRaw) ?? .off
|
||||
if locationMode != .off { caps.append(MoltbotCapability.location.rawValue) }
|
||||
|
||||
return caps
|
||||
}
|
||||
|
||||
private func currentCommands() -> [String] {
|
||||
var commands: [String] = [
|
||||
ClawdbotCanvasCommand.present.rawValue,
|
||||
ClawdbotCanvasCommand.hide.rawValue,
|
||||
ClawdbotCanvasCommand.navigate.rawValue,
|
||||
ClawdbotCanvasCommand.evalJS.rawValue,
|
||||
ClawdbotCanvasCommand.snapshot.rawValue,
|
||||
ClawdbotCanvasA2UICommand.push.rawValue,
|
||||
ClawdbotCanvasA2UICommand.pushJSONL.rawValue,
|
||||
ClawdbotCanvasA2UICommand.reset.rawValue,
|
||||
ClawdbotScreenCommand.record.rawValue,
|
||||
ClawdbotSystemCommand.notify.rawValue,
|
||||
ClawdbotSystemCommand.which.rawValue,
|
||||
ClawdbotSystemCommand.run.rawValue,
|
||||
ClawdbotSystemCommand.execApprovalsGet.rawValue,
|
||||
ClawdbotSystemCommand.execApprovalsSet.rawValue,
|
||||
MoltbotCanvasCommand.present.rawValue,
|
||||
MoltbotCanvasCommand.hide.rawValue,
|
||||
MoltbotCanvasCommand.navigate.rawValue,
|
||||
MoltbotCanvasCommand.evalJS.rawValue,
|
||||
MoltbotCanvasCommand.snapshot.rawValue,
|
||||
MoltbotCanvasA2UICommand.push.rawValue,
|
||||
MoltbotCanvasA2UICommand.pushJSONL.rawValue,
|
||||
MoltbotCanvasA2UICommand.reset.rawValue,
|
||||
MoltbotScreenCommand.record.rawValue,
|
||||
MoltbotSystemCommand.notify.rawValue,
|
||||
MoltbotSystemCommand.which.rawValue,
|
||||
MoltbotSystemCommand.run.rawValue,
|
||||
MoltbotSystemCommand.execApprovalsGet.rawValue,
|
||||
MoltbotSystemCommand.execApprovalsSet.rawValue,
|
||||
]
|
||||
|
||||
let caps = Set(self.currentCaps())
|
||||
if caps.contains(ClawdbotCapability.camera.rawValue) {
|
||||
commands.append(ClawdbotCameraCommand.list.rawValue)
|
||||
commands.append(ClawdbotCameraCommand.snap.rawValue)
|
||||
commands.append(ClawdbotCameraCommand.clip.rawValue)
|
||||
if caps.contains(MoltbotCapability.camera.rawValue) {
|
||||
commands.append(MoltbotCameraCommand.list.rawValue)
|
||||
commands.append(MoltbotCameraCommand.snap.rawValue)
|
||||
commands.append(MoltbotCameraCommand.clip.rawValue)
|
||||
}
|
||||
if caps.contains(ClawdbotCapability.location.rawValue) {
|
||||
commands.append(ClawdbotLocationCommand.get.rawValue)
|
||||
if caps.contains(MoltbotCapability.location.rawValue) {
|
||||
commands.append(MoltbotLocationCommand.get.rawValue)
|
||||
}
|
||||
|
||||
return commands
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Foundation
|
||||
import Network
|
||||
import Observation
|
||||
@@ -52,11 +52,11 @@ final class GatewayDiscoveryModel {
|
||||
if !self.browsers.isEmpty { return }
|
||||
self.appendDebugLog("start()")
|
||||
|
||||
for domain in ClawdbotBonjour.gatewayServiceDomains {
|
||||
for domain in MoltbotBonjour.gatewayServiceDomains {
|
||||
let params = NWParameters.tcp
|
||||
params.includePeerToPeer = true
|
||||
let browser = NWBrowser(
|
||||
for: .bonjour(type: ClawdbotBonjour.gatewayServiceType, domain: domain),
|
||||
for: .bonjour(type: MoltbotBonjour.gatewayServiceType, domain: domain),
|
||||
using: params)
|
||||
|
||||
browser.stateUpdateHandler = { [weak self] state in
|
||||
@@ -202,7 +202,7 @@ final class GatewayDiscoveryModel {
|
||||
|
||||
private static func prettifyInstanceName(_ decodedName: String) -> String {
|
||||
let normalized = decodedName.split(whereSeparator: \.isWhitespace).joined(separator: " ")
|
||||
let stripped = normalized.replacingOccurrences(of: " (Clawdbot)", with: "")
|
||||
let stripped = normalized.replacingOccurrences(of: " (Moltbot)", with: "")
|
||||
.replacingOccurrences(of: #"\s+\(\d+\)$"#, with: "", options: .regularExpression)
|
||||
return stripped.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Clawdbot</string>
|
||||
<string>Moltbot</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIconName</key>
|
||||
@@ -29,20 +29,20 @@
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_clawdbot-gw._tcp</string>
|
||||
<string>_moltbot-gw._tcp</string>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Clawdbot can capture photos or short video clips when requested via the gateway.</string>
|
||||
<string>Moltbot can capture photos or short video clips when requested via the gateway.</string>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Clawdbot discovers and connects to your Clawdbot gateway on the local network.</string>
|
||||
<string>Moltbot discovers and connects to your Moltbot gateway on the local network.</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>Clawdbot can share your location in the background when you enable Always.</string>
|
||||
<string>Moltbot can share your location in the background when you enable Always.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Clawdbot uses your location when you allow location sharing.</string>
|
||||
<string>Moltbot uses your location when you allow location sharing.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Clawdbot needs microphone access for voice wake.</string>
|
||||
<string>Moltbot needs microphone access for voice wake.</string>
|
||||
<key>NSSpeechRecognitionUsageDescription</key>
|
||||
<string>Clawdbot uses on-device speech recognition for voice wake.</string>
|
||||
<string>Moltbot uses on-device speech recognition for voice wake.</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
|
||||
@@ -30,7 +30,7 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
|
||||
return .fullAccuracy
|
||||
}
|
||||
|
||||
func ensureAuthorization(mode: ClawdbotLocationMode) async -> CLAuthorizationStatus {
|
||||
func ensureAuthorization(mode: MoltbotLocationMode) async -> CLAuthorizationStatus {
|
||||
guard CLLocationManager.locationServicesEnabled() else { return .denied }
|
||||
|
||||
let status = self.manager.authorizationStatus
|
||||
@@ -53,8 +53,8 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
|
||||
}
|
||||
|
||||
func currentLocation(
|
||||
params: ClawdbotLocationGetParams,
|
||||
desiredAccuracy: ClawdbotLocationAccuracy,
|
||||
params: MoltbotLocationGetParams,
|
||||
desiredAccuracy: MoltbotLocationAccuracy,
|
||||
maxAgeMs: Int?,
|
||||
timeoutMs: Int?) async throws -> CLLocation
|
||||
{
|
||||
@@ -93,7 +93,7 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
|
||||
try await AsyncTimeout.withTimeoutMs(timeoutMs: timeoutMs, onTimeout: { Error.timeout }, operation: operation)
|
||||
}
|
||||
|
||||
private static func accuracyValue(_ accuracy: ClawdbotLocationAccuracy) -> CLLocationAccuracy {
|
||||
private static func accuracyValue(_ accuracy: MoltbotLocationAccuracy) -> CLLocationAccuracy {
|
||||
switch accuracy {
|
||||
case .coarse:
|
||||
kCLLocationAccuracyKilometer
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Network
|
||||
import Observation
|
||||
import SwiftUI
|
||||
@@ -90,7 +90,7 @@ final class NodeAppModel {
|
||||
}()
|
||||
guard !userAction.isEmpty else { return }
|
||||
|
||||
guard let name = ClawdbotCanvasA2UIAction.extractActionName(userAction) else { return }
|
||||
guard let name = MoltbotCanvasA2UIAction.extractActionName(userAction) else { return }
|
||||
let actionId: String = {
|
||||
let id = (userAction["id"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
return id.isEmpty ? UUID().uuidString : id
|
||||
@@ -109,15 +109,15 @@ final class NodeAppModel {
|
||||
|
||||
let host = UserDefaults.standard.string(forKey: "node.displayName") ?? UIDevice.current.name
|
||||
let instanceId = (UserDefaults.standard.string(forKey: "node.instanceId") ?? "ios-node").lowercased()
|
||||
let contextJSON = ClawdbotCanvasA2UIAction.compactJSON(userAction["context"])
|
||||
let contextJSON = MoltbotCanvasA2UIAction.compactJSON(userAction["context"])
|
||||
let sessionKey = self.mainSessionKey
|
||||
|
||||
let messageContext = ClawdbotCanvasA2UIAction.AgentMessageContext(
|
||||
let messageContext = MoltbotCanvasA2UIAction.AgentMessageContext(
|
||||
actionName: name,
|
||||
session: .init(key: sessionKey, surfaceId: surfaceId),
|
||||
component: .init(id: sourceComponentId, host: host, instanceId: instanceId),
|
||||
contextJSON: contextJSON)
|
||||
let message = ClawdbotCanvasA2UIAction.formatAgentMessage(messageContext)
|
||||
let message = MoltbotCanvasA2UIAction.formatAgentMessage(messageContext)
|
||||
|
||||
let ok: Bool
|
||||
var errorText: String?
|
||||
@@ -142,7 +142,7 @@ final class NodeAppModel {
|
||||
}
|
||||
}
|
||||
|
||||
let js = ClawdbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId: actionId, ok: ok, error: errorText)
|
||||
let js = MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId: actionId, ok: ok, error: errorText)
|
||||
do {
|
||||
_ = try await self.screen.eval(javaScript: js)
|
||||
} catch {
|
||||
@@ -154,7 +154,7 @@ final class NodeAppModel {
|
||||
guard let raw = await self.gateway.currentCanvasHostUrl() else { return nil }
|
||||
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty, let base = URL(string: trimmed) else { return nil }
|
||||
return base.appendingPathComponent("__clawdbot__/a2ui/").absoluteString + "?platform=ios"
|
||||
return base.appendingPathComponent("__moltbot__/a2ui/").absoluteString + "?platform=ios"
|
||||
}
|
||||
|
||||
private func showA2UIOnConnectIfNeeded() async {
|
||||
@@ -190,7 +190,7 @@ final class NodeAppModel {
|
||||
self.talkMode.setEnabled(enabled)
|
||||
}
|
||||
|
||||
func requestLocationPermissions(mode: ClawdbotLocationMode) async -> Bool {
|
||||
func requestLocationPermissions(mode: MoltbotLocationMode) async -> Bool {
|
||||
guard mode != .off else { return true }
|
||||
let status = await self.locationService.ensureAuthorization(mode: mode)
|
||||
switch status {
|
||||
@@ -272,7 +272,7 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .unavailable,
|
||||
message: "UNAVAILABLE: node not ready"))
|
||||
}
|
||||
@@ -487,7 +487,7 @@ final class NodeAppModel {
|
||||
}
|
||||
|
||||
// iOS gateway forwards to the gateway; no local auth prompts here.
|
||||
// (Key-based unattended auth is handled on macOS for clawdbot:// links.)
|
||||
// (Key-based unattended auth is handled on macOS for moltbot:// links.)
|
||||
let data = try JSONEncoder().encode(link)
|
||||
guard let json = String(bytes: data, encoding: .utf8) else {
|
||||
throw NSError(domain: "NodeAppModel", code: 2, userInfo: [
|
||||
@@ -508,7 +508,7 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .backgroundUnavailable,
|
||||
message: "NODE_BACKGROUND_UNAVAILABLE: canvas/camera/screen commands require foreground"))
|
||||
}
|
||||
@@ -517,36 +517,36 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .unavailable,
|
||||
message: "CAMERA_DISABLED: enable Camera in iOS Settings → Camera → Allow Camera"))
|
||||
}
|
||||
|
||||
do {
|
||||
switch command {
|
||||
case ClawdbotLocationCommand.get.rawValue:
|
||||
case MoltbotLocationCommand.get.rawValue:
|
||||
return try await self.handleLocationInvoke(req)
|
||||
case ClawdbotCanvasCommand.present.rawValue,
|
||||
ClawdbotCanvasCommand.hide.rawValue,
|
||||
ClawdbotCanvasCommand.navigate.rawValue,
|
||||
ClawdbotCanvasCommand.evalJS.rawValue,
|
||||
ClawdbotCanvasCommand.snapshot.rawValue:
|
||||
case MoltbotCanvasCommand.present.rawValue,
|
||||
MoltbotCanvasCommand.hide.rawValue,
|
||||
MoltbotCanvasCommand.navigate.rawValue,
|
||||
MoltbotCanvasCommand.evalJS.rawValue,
|
||||
MoltbotCanvasCommand.snapshot.rawValue:
|
||||
return try await self.handleCanvasInvoke(req)
|
||||
case ClawdbotCanvasA2UICommand.reset.rawValue,
|
||||
ClawdbotCanvasA2UICommand.push.rawValue,
|
||||
ClawdbotCanvasA2UICommand.pushJSONL.rawValue:
|
||||
case MoltbotCanvasA2UICommand.reset.rawValue,
|
||||
MoltbotCanvasA2UICommand.push.rawValue,
|
||||
MoltbotCanvasA2UICommand.pushJSONL.rawValue:
|
||||
return try await self.handleCanvasA2UIInvoke(req)
|
||||
case ClawdbotCameraCommand.list.rawValue,
|
||||
ClawdbotCameraCommand.snap.rawValue,
|
||||
ClawdbotCameraCommand.clip.rawValue:
|
||||
case MoltbotCameraCommand.list.rawValue,
|
||||
MoltbotCameraCommand.snap.rawValue,
|
||||
MoltbotCameraCommand.clip.rawValue:
|
||||
return try await self.handleCameraInvoke(req)
|
||||
case ClawdbotScreenCommand.record.rawValue:
|
||||
case MoltbotScreenCommand.record.rawValue:
|
||||
return try await self.handleScreenRecordInvoke(req)
|
||||
default:
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
|
||||
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
|
||||
}
|
||||
} catch {
|
||||
if command.hasPrefix("camera.") {
|
||||
@@ -556,7 +556,7 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(code: .unavailable, message: error.localizedDescription))
|
||||
error: MoltbotNodeError(code: .unavailable, message: error.localizedDescription))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,7 +570,7 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .unavailable,
|
||||
message: "LOCATION_DISABLED: enable Location in Settings"))
|
||||
}
|
||||
@@ -578,12 +578,12 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .backgroundUnavailable,
|
||||
message: "LOCATION_BACKGROUND_UNAVAILABLE: background location requires Always"))
|
||||
}
|
||||
let params = (try? Self.decodeParams(ClawdbotLocationGetParams.self, from: req.paramsJSON)) ??
|
||||
ClawdbotLocationGetParams()
|
||||
let params = (try? Self.decodeParams(MoltbotLocationGetParams.self, from: req.paramsJSON)) ??
|
||||
MoltbotLocationGetParams()
|
||||
let desired = params.desiredAccuracy ??
|
||||
(self.isLocationPreciseEnabled() ? .precise : .balanced)
|
||||
let status = self.locationService.authorizationStatus()
|
||||
@@ -591,7 +591,7 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .unavailable,
|
||||
message: "LOCATION_PERMISSION_REQUIRED: grant Location permission"))
|
||||
}
|
||||
@@ -599,7 +599,7 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .unavailable,
|
||||
message: "LOCATION_PERMISSION_REQUIRED: enable Always for background access"))
|
||||
}
|
||||
@@ -609,7 +609,7 @@ final class NodeAppModel {
|
||||
maxAgeMs: params.maxAgeMs,
|
||||
timeoutMs: params.timeoutMs)
|
||||
let isPrecise = self.locationService.accuracyAuthorization() == .fullAccuracy
|
||||
let payload = ClawdbotLocationPayload(
|
||||
let payload = MoltbotLocationPayload(
|
||||
lat: location.coordinate.latitude,
|
||||
lon: location.coordinate.longitude,
|
||||
accuracyMeters: location.horizontalAccuracy,
|
||||
@@ -625,9 +625,9 @@ final class NodeAppModel {
|
||||
|
||||
private func handleCanvasInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
|
||||
switch req.command {
|
||||
case ClawdbotCanvasCommand.present.rawValue:
|
||||
let params = (try? Self.decodeParams(ClawdbotCanvasPresentParams.self, from: req.paramsJSON)) ??
|
||||
ClawdbotCanvasPresentParams()
|
||||
case MoltbotCanvasCommand.present.rawValue:
|
||||
let params = (try? Self.decodeParams(MoltbotCanvasPresentParams.self, from: req.paramsJSON)) ??
|
||||
MoltbotCanvasPresentParams()
|
||||
let url = params.url?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
if url.isEmpty {
|
||||
self.screen.showDefaultCanvas()
|
||||
@@ -635,19 +635,19 @@ final class NodeAppModel {
|
||||
self.screen.navigate(to: url)
|
||||
}
|
||||
return BridgeInvokeResponse(id: req.id, ok: true)
|
||||
case ClawdbotCanvasCommand.hide.rawValue:
|
||||
case MoltbotCanvasCommand.hide.rawValue:
|
||||
return BridgeInvokeResponse(id: req.id, ok: true)
|
||||
case ClawdbotCanvasCommand.navigate.rawValue:
|
||||
let params = try Self.decodeParams(ClawdbotCanvasNavigateParams.self, from: req.paramsJSON)
|
||||
case MoltbotCanvasCommand.navigate.rawValue:
|
||||
let params = try Self.decodeParams(MoltbotCanvasNavigateParams.self, from: req.paramsJSON)
|
||||
self.screen.navigate(to: params.url)
|
||||
return BridgeInvokeResponse(id: req.id, ok: true)
|
||||
case ClawdbotCanvasCommand.evalJS.rawValue:
|
||||
let params = try Self.decodeParams(ClawdbotCanvasEvalParams.self, from: req.paramsJSON)
|
||||
case MoltbotCanvasCommand.evalJS.rawValue:
|
||||
let params = try Self.decodeParams(MoltbotCanvasEvalParams.self, from: req.paramsJSON)
|
||||
let result = try await self.screen.eval(javaScript: params.javaScript)
|
||||
let payload = try Self.encodePayload(["result": result])
|
||||
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload)
|
||||
case ClawdbotCanvasCommand.snapshot.rawValue:
|
||||
let params = try? Self.decodeParams(ClawdbotCanvasSnapshotParams.self, from: req.paramsJSON)
|
||||
case MoltbotCanvasCommand.snapshot.rawValue:
|
||||
let params = try? Self.decodeParams(MoltbotCanvasSnapshotParams.self, from: req.paramsJSON)
|
||||
let format = params?.format ?? .jpeg
|
||||
let maxWidth: CGFloat? = {
|
||||
if let raw = params?.maxWidth, raw > 0 { return CGFloat(raw) }
|
||||
@@ -671,19 +671,19 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
|
||||
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
|
||||
}
|
||||
}
|
||||
|
||||
private func handleCanvasA2UIInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
|
||||
let command = req.command
|
||||
switch command {
|
||||
case ClawdbotCanvasA2UICommand.reset.rawValue:
|
||||
case MoltbotCanvasA2UICommand.reset.rawValue:
|
||||
guard let a2uiUrl = await self.resolveA2UIHostURL() else {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .unavailable,
|
||||
message: "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host"))
|
||||
}
|
||||
@@ -692,31 +692,31 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .unavailable,
|
||||
message: "A2UI_HOST_UNAVAILABLE: A2UI host not reachable"))
|
||||
}
|
||||
|
||||
let json = try await self.screen.eval(javaScript: """
|
||||
(() => {
|
||||
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing clawdbotA2UI" });
|
||||
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing moltbotA2UI" });
|
||||
return JSON.stringify(globalThis.clawdbotA2UI.reset());
|
||||
})()
|
||||
""")
|
||||
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: json)
|
||||
case ClawdbotCanvasA2UICommand.push.rawValue, ClawdbotCanvasA2UICommand.pushJSONL.rawValue:
|
||||
case MoltbotCanvasA2UICommand.push.rawValue, MoltbotCanvasA2UICommand.pushJSONL.rawValue:
|
||||
let messages: [AnyCodable]
|
||||
if command == ClawdbotCanvasA2UICommand.pushJSONL.rawValue {
|
||||
let params = try Self.decodeParams(ClawdbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
|
||||
messages = try ClawdbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
|
||||
if command == MoltbotCanvasA2UICommand.pushJSONL.rawValue {
|
||||
let params = try Self.decodeParams(MoltbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
|
||||
messages = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
|
||||
} else {
|
||||
do {
|
||||
let params = try Self.decodeParams(ClawdbotCanvasA2UIPushParams.self, from: req.paramsJSON)
|
||||
let params = try Self.decodeParams(MoltbotCanvasA2UIPushParams.self, from: req.paramsJSON)
|
||||
messages = params.messages
|
||||
} catch {
|
||||
// Be forgiving: some clients still send JSONL payloads to `canvas.a2ui.push`.
|
||||
let params = try Self.decodeParams(ClawdbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
|
||||
messages = try ClawdbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
|
||||
let params = try Self.decodeParams(MoltbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
|
||||
messages = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,7 +724,7 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .unavailable,
|
||||
message: "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host"))
|
||||
}
|
||||
@@ -733,16 +733,16 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(
|
||||
error: MoltbotNodeError(
|
||||
code: .unavailable,
|
||||
message: "A2UI_HOST_UNAVAILABLE: A2UI host not reachable"))
|
||||
}
|
||||
|
||||
let messagesJSON = try ClawdbotCanvasA2UIJSONL.encodeMessagesJSONArray(messages)
|
||||
let messagesJSON = try MoltbotCanvasA2UIJSONL.encodeMessagesJSONArray(messages)
|
||||
let js = """
|
||||
(() => {
|
||||
try {
|
||||
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing clawdbotA2UI" });
|
||||
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing moltbotA2UI" });
|
||||
const messages = \(messagesJSON);
|
||||
return JSON.stringify(globalThis.clawdbotA2UI.applyMessages(messages));
|
||||
} catch (e) {
|
||||
@@ -756,24 +756,24 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
|
||||
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
|
||||
}
|
||||
}
|
||||
|
||||
private func handleCameraInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
|
||||
switch req.command {
|
||||
case ClawdbotCameraCommand.list.rawValue:
|
||||
case MoltbotCameraCommand.list.rawValue:
|
||||
let devices = await self.camera.listDevices()
|
||||
struct Payload: Codable {
|
||||
var devices: [CameraController.CameraDeviceInfo]
|
||||
}
|
||||
let payload = try Self.encodePayload(Payload(devices: devices))
|
||||
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload)
|
||||
case ClawdbotCameraCommand.snap.rawValue:
|
||||
case MoltbotCameraCommand.snap.rawValue:
|
||||
self.showCameraHUD(text: "Taking photo…", kind: .photo)
|
||||
self.triggerCameraFlash()
|
||||
let params = (try? Self.decodeParams(ClawdbotCameraSnapParams.self, from: req.paramsJSON)) ??
|
||||
ClawdbotCameraSnapParams()
|
||||
let params = (try? Self.decodeParams(MoltbotCameraSnapParams.self, from: req.paramsJSON)) ??
|
||||
MoltbotCameraSnapParams()
|
||||
let res = try await self.camera.snap(params: params)
|
||||
|
||||
struct Payload: Codable {
|
||||
@@ -789,9 +789,9 @@ final class NodeAppModel {
|
||||
height: res.height))
|
||||
self.showCameraHUD(text: "Photo captured", kind: .success, autoHideSeconds: 1.6)
|
||||
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload)
|
||||
case ClawdbotCameraCommand.clip.rawValue:
|
||||
let params = (try? Self.decodeParams(ClawdbotCameraClipParams.self, from: req.paramsJSON)) ??
|
||||
ClawdbotCameraClipParams()
|
||||
case MoltbotCameraCommand.clip.rawValue:
|
||||
let params = (try? Self.decodeParams(MoltbotCameraClipParams.self, from: req.paramsJSON)) ??
|
||||
MoltbotCameraClipParams()
|
||||
|
||||
let suspended = (params.includeAudio ?? true) ? self.voiceWake.suspendForExternalAudioCapture() : false
|
||||
defer { self.voiceWake.resumeAfterExternalAudioCapture(wasSuspended: suspended) }
|
||||
@@ -816,13 +816,13 @@ final class NodeAppModel {
|
||||
return BridgeInvokeResponse(
|
||||
id: req.id,
|
||||
ok: false,
|
||||
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
|
||||
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
|
||||
}
|
||||
}
|
||||
|
||||
private func handleScreenRecordInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
|
||||
let params = (try? Self.decodeParams(ClawdbotScreenRecordParams.self, from: req.paramsJSON)) ??
|
||||
ClawdbotScreenRecordParams()
|
||||
let params = (try? Self.decodeParams(MoltbotScreenRecordParams.self, from: req.paramsJSON)) ??
|
||||
MoltbotScreenRecordParams()
|
||||
if let format = params.format, format.lowercased() != "mp4" {
|
||||
throw NSError(domain: "Screen", code: 30, userInfo: [
|
||||
NSLocalizedDescriptionKey: "INVALID_REQUEST: screen format must be mp4",
|
||||
@@ -860,9 +860,9 @@ final class NodeAppModel {
|
||||
}
|
||||
|
||||
private extension NodeAppModel {
|
||||
func locationMode() -> ClawdbotLocationMode {
|
||||
func locationMode() -> MoltbotLocationMode {
|
||||
let raw = UserDefaults.standard.string(forKey: "location.enabledMode") ?? "off"
|
||||
return ClawdbotLocationMode(rawValue: raw) ?? .off
|
||||
return MoltbotLocationMode(rawValue: raw) ?? .off
|
||||
}
|
||||
|
||||
func isLocationPreciseEnabled() -> Bool {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Observation
|
||||
import SwiftUI
|
||||
import WebKit
|
||||
@@ -13,7 +13,7 @@ final class ScreenController {
|
||||
var urlString: String = ""
|
||||
var errorText: String?
|
||||
|
||||
/// Callback invoked when a clawdbot:// deep link is tapped in the canvas
|
||||
/// Callback invoked when a moltbot:// deep link is tapped in the canvas
|
||||
var onDeepLink: ((URL) -> Void)?
|
||||
|
||||
/// Callback invoked when the user clicks an A2UI action (e.g. button) inside the canvas web UI.
|
||||
@@ -101,7 +101,7 @@ final class ScreenController {
|
||||
let js = """
|
||||
(() => {
|
||||
try {
|
||||
const api = globalThis.__clawdbot;
|
||||
const api = globalThis.__moltbot;
|
||||
if (!api) return;
|
||||
if (typeof api.setDebugStatusEnabled === 'function') {
|
||||
api.setDebugStatusEnabled(\(enabled ? "true" : "false"));
|
||||
@@ -184,7 +184,7 @@ final class ScreenController {
|
||||
|
||||
func snapshotBase64(
|
||||
maxWidth: CGFloat? = nil,
|
||||
format: ClawdbotCanvasSnapshotFormat,
|
||||
format: MoltbotCanvasSnapshotFormat,
|
||||
quality: Double? = nil) async throws -> String
|
||||
{
|
||||
let config = WKSnapshotConfiguration()
|
||||
@@ -229,7 +229,7 @@ final class ScreenController {
|
||||
subdirectory: String)
|
||||
-> URL?
|
||||
{
|
||||
let bundle = ClawdbotKitResources.bundle
|
||||
let bundle = MoltbotKitResources.bundle
|
||||
return bundle.url(forResource: name, withExtension: ext, subdirectory: subdirectory)
|
||||
?? bundle.url(forResource: name, withExtension: ext)
|
||||
}
|
||||
@@ -342,7 +342,7 @@ extension Double {
|
||||
|
||||
// MARK: - Navigation Delegate
|
||||
|
||||
/// Handles navigation policy to intercept clawdbot:// deep links from canvas
|
||||
/// Handles navigation policy to intercept moltbot:// deep links from canvas
|
||||
@MainActor
|
||||
private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
|
||||
weak var controller: ScreenController?
|
||||
@@ -357,8 +357,8 @@ private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
// Intercept clawdbot:// deep links
|
||||
if url.scheme == "clawdbot" {
|
||||
// Intercept moltbot:// deep links
|
||||
if url.scheme == "moltbot" {
|
||||
decisionHandler(.cancel)
|
||||
self.controller?.onDeepLink?(url)
|
||||
return
|
||||
@@ -386,7 +386,7 @@ private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
|
||||
}
|
||||
|
||||
private final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
|
||||
static let messageName = "clawdbotCanvasA2UIAction"
|
||||
static let messageName = "moltbotCanvasA2UIAction"
|
||||
static let legacyMessageNames = ["canvas", "a2ui", "userAction", "action"]
|
||||
static let handlerNames = [messageName] + legacyMessageNames
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ final class ScreenRecordService: @unchecked Sendable {
|
||||
return URL(fileURLWithPath: outPath)
|
||||
}
|
||||
return FileManager().temporaryDirectory
|
||||
.appendingPathComponent("clawdbot-screen-record-\(UUID().uuidString).mp4")
|
||||
.appendingPathComponent("moltbot-screen-record-\(UUID().uuidString).mp4")
|
||||
}
|
||||
|
||||
private func startCapture(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import SwiftUI
|
||||
|
||||
struct ScreenTab: View {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import SwiftUI
|
||||
import WebKit
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Network
|
||||
import Observation
|
||||
import SwiftUI
|
||||
@@ -23,7 +23,7 @@ struct SettingsTab: View {
|
||||
@AppStorage("talk.enabled") private var talkEnabled: Bool = false
|
||||
@AppStorage("talk.button.enabled") private var talkButtonEnabled: Bool = true
|
||||
@AppStorage("camera.enabled") private var cameraEnabled: Bool = true
|
||||
@AppStorage("location.enabledMode") private var locationEnabledModeRaw: String = ClawdbotLocationMode.off.rawValue
|
||||
@AppStorage("location.enabledMode") private var locationEnabledModeRaw: String = MoltbotLocationMode.off.rawValue
|
||||
@AppStorage("location.preciseEnabled") private var locationPreciseEnabled: Bool = true
|
||||
@AppStorage("screen.preventSleep") private var preventSleep: Bool = true
|
||||
@AppStorage("gateway.preferredStableID") private var preferredGatewayStableID: String = ""
|
||||
@@ -37,7 +37,7 @@ struct SettingsTab: View {
|
||||
@State private var connectStatus = ConnectStatusStore()
|
||||
@State private var connectingGatewayID: String?
|
||||
@State private var localIPAddress: String?
|
||||
@State private var lastLocationModeRaw: String = ClawdbotLocationMode.off.rawValue
|
||||
@State private var lastLocationModeRaw: String = MoltbotLocationMode.off.rawValue
|
||||
@State private var gatewayToken: String = ""
|
||||
@State private var gatewayPassword: String = ""
|
||||
|
||||
@@ -197,9 +197,9 @@ struct SettingsTab: View {
|
||||
|
||||
Section("Location") {
|
||||
Picker("Location Access", selection: self.$locationEnabledModeRaw) {
|
||||
Text("Off").tag(ClawdbotLocationMode.off.rawValue)
|
||||
Text("While Using").tag(ClawdbotLocationMode.whileUsing.rawValue)
|
||||
Text("Always").tag(ClawdbotLocationMode.always.rawValue)
|
||||
Text("Off").tag(MoltbotLocationMode.off.rawValue)
|
||||
Text("While Using").tag(MoltbotLocationMode.whileUsing.rawValue)
|
||||
Text("Always").tag(MoltbotLocationMode.always.rawValue)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
@@ -213,7 +213,7 @@ struct SettingsTab: View {
|
||||
|
||||
Section("Screen") {
|
||||
Toggle("Prevent Sleep", isOn: self.$preventSleep)
|
||||
Text("Keeps the screen awake while Clawdbot is open.")
|
||||
Text("Keeps the screen awake while Moltbot is open.")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
@@ -261,7 +261,7 @@ struct SettingsTab: View {
|
||||
.onChange(of: self.locationEnabledModeRaw) { _, newValue in
|
||||
let previous = self.lastLocationModeRaw
|
||||
self.lastLocationModeRaw = newValue
|
||||
guard let mode = ClawdbotLocationMode(rawValue: newValue) else { return }
|
||||
guard let mode = MoltbotLocationMode(rawValue: newValue) else { return }
|
||||
Task {
|
||||
let granted = await self.appModel.requestLocationPermissions(mode: mode)
|
||||
if !granted {
|
||||
@@ -336,8 +336,8 @@ struct SettingsTab: View {
|
||||
return "iOS \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)"
|
||||
}
|
||||
|
||||
private var locationMode: ClawdbotLocationMode {
|
||||
ClawdbotLocationMode(rawValue: self.locationEnabledModeRaw) ?? .off
|
||||
private var locationMode: MoltbotLocationMode {
|
||||
MoltbotLocationMode(rawValue: self.locationEnabledModeRaw) ?? .off
|
||||
}
|
||||
|
||||
private func appVersion() -> String {
|
||||
|
||||
@@ -36,7 +36,7 @@ struct VoiceWakeWordsSettingsView: View {
|
||||
Text("Wake Words")
|
||||
} footer: {
|
||||
Text(
|
||||
"Clawdbot reacts when any trigger appears in a transcription. "
|
||||
"Moltbot reacts when any trigger appears in a transcription. "
|
||||
+ "Keep them short to avoid false positives.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import AVFAudio
|
||||
import ClawdbotKit
|
||||
import ClawdbotProtocol
|
||||
import MoltbotKit
|
||||
import MoltbotProtocol
|
||||
import Foundation
|
||||
import Observation
|
||||
import OSLog
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import SwiftUI
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct AppCoverageTests {
|
||||
@Test @MainActor func nodeAppModelUpdatesBackgroundedState() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct CameraControllerClampTests {
|
||||
@Test func clampQualityDefaultsAndBounds() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct CameraControllerErrorTests {
|
||||
@Test func errorDescriptionsAreStable() {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@Suite struct DeepLinkParserTests {
|
||||
@Test func parseRejectsUnknownHost() {
|
||||
let url = URL(string: "clawdbot://nope?message=hi")!
|
||||
let url = URL(string: "moltbot://nope?message=hi")!
|
||||
#expect(DeepLinkParser.parse(url) == nil)
|
||||
}
|
||||
|
||||
@Test func parseHostIsCaseInsensitive() {
|
||||
let url = URL(string: "clawdbot://AGENT?message=Hello")!
|
||||
let url = URL(string: "moltbot://AGENT?message=Hello")!
|
||||
#expect(DeepLinkParser.parse(url) == .agent(.init(
|
||||
message: "Hello",
|
||||
sessionKey: nil,
|
||||
@@ -21,19 +21,19 @@ import Testing
|
||||
key: nil)))
|
||||
}
|
||||
|
||||
@Test func parseRejectsNonClawdbotScheme() {
|
||||
@Test func parseRejectsNonMoltbotScheme() {
|
||||
let url = URL(string: "https://example.com/agent?message=hi")!
|
||||
#expect(DeepLinkParser.parse(url) == nil)
|
||||
}
|
||||
|
||||
@Test func parseRejectsEmptyMessage() {
|
||||
let url = URL(string: "clawdbot://agent?message=%20%20%0A")!
|
||||
let url = URL(string: "moltbot://agent?message=%20%20%0A")!
|
||||
#expect(DeepLinkParser.parse(url) == nil)
|
||||
}
|
||||
|
||||
@Test func parseAgentLinkParsesCommonFields() {
|
||||
let url =
|
||||
URL(string: "clawdbot://agent?message=Hello&deliver=1&sessionKey=node-test&thinking=low&timeoutSeconds=30")!
|
||||
URL(string: "moltbot://agent?message=Hello&deliver=1&sessionKey=node-test&thinking=low&timeoutSeconds=30")!
|
||||
#expect(
|
||||
DeepLinkParser.parse(url) == .agent(
|
||||
.init(
|
||||
@@ -50,7 +50,7 @@ import Testing
|
||||
@Test func parseAgentLinkParsesTargetRoutingFields() {
|
||||
let url =
|
||||
URL(
|
||||
string: "clawdbot://agent?message=Hello%20World&deliver=1&to=%2B15551234567&channel=whatsapp&key=secret")!
|
||||
string: "moltbot://agent?message=Hello%20World&deliver=1&to=%2B15551234567&channel=whatsapp&key=secret")!
|
||||
#expect(
|
||||
DeepLinkParser.parse(url) == .agent(
|
||||
.init(
|
||||
@@ -65,7 +65,7 @@ import Testing
|
||||
}
|
||||
|
||||
@Test func parseRejectsNegativeTimeoutSeconds() {
|
||||
let url = URL(string: "clawdbot://agent?message=Hello&timeoutSeconds=-1")!
|
||||
let url = URL(string: "moltbot://agent?message=Hello&timeoutSeconds=-1")!
|
||||
#expect(DeepLinkParser.parse(url) == .agent(.init(
|
||||
message: "Hello",
|
||||
sessionKey: nil,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Foundation
|
||||
import Testing
|
||||
import UIKit
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws -> T) rethrows -> T {
|
||||
let defaults = UserDefaults.standard
|
||||
@@ -49,31 +49,31 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
|
||||
"node.instanceId": "ios-test",
|
||||
"node.displayName": "Test Node",
|
||||
"camera.enabled": true,
|
||||
"location.enabledMode": ClawdbotLocationMode.always.rawValue,
|
||||
"location.enabledMode": MoltbotLocationMode.always.rawValue,
|
||||
VoiceWakePreferences.enabledKey: true,
|
||||
]) {
|
||||
let appModel = NodeAppModel()
|
||||
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
|
||||
let caps = Set(controller._test_currentCaps())
|
||||
|
||||
#expect(caps.contains(ClawdbotCapability.canvas.rawValue))
|
||||
#expect(caps.contains(ClawdbotCapability.screen.rawValue))
|
||||
#expect(caps.contains(ClawdbotCapability.camera.rawValue))
|
||||
#expect(caps.contains(ClawdbotCapability.location.rawValue))
|
||||
#expect(caps.contains(ClawdbotCapability.voiceWake.rawValue))
|
||||
#expect(caps.contains(MoltbotCapability.canvas.rawValue))
|
||||
#expect(caps.contains(MoltbotCapability.screen.rawValue))
|
||||
#expect(caps.contains(MoltbotCapability.camera.rawValue))
|
||||
#expect(caps.contains(MoltbotCapability.location.rawValue))
|
||||
#expect(caps.contains(MoltbotCapability.voiceWake.rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
@Test @MainActor func currentCommandsIncludeLocationWhenEnabled() {
|
||||
withUserDefaults([
|
||||
"node.instanceId": "ios-test",
|
||||
"location.enabledMode": ClawdbotLocationMode.whileUsing.rawValue,
|
||||
"location.enabledMode": MoltbotLocationMode.whileUsing.rawValue,
|
||||
]) {
|
||||
let appModel = NodeAppModel()
|
||||
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
|
||||
let commands = Set(controller._test_currentCommands())
|
||||
|
||||
#expect(commands.contains(ClawdbotLocationCommand.get.rawValue))
|
||||
#expect(commands.contains(MoltbotLocationCommand.get.rawValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite(.serialized) struct GatewayDiscoveryModelTests {
|
||||
@Test @MainActor func debugLoggingCapturesLifecycleAndResets() {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Network
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct GatewayEndpointIDTests {
|
||||
@Test func stableIDForServiceDecodesAndNormalizesName() {
|
||||
let endpoint = NWEndpoint.service(
|
||||
name: "Clawdbot\\032Gateway \\032 Node\n",
|
||||
type: "_clawdbot-gw._tcp",
|
||||
name: "Moltbot\\032Gateway \\032 Node\n",
|
||||
type: "_moltbot-gw._tcp",
|
||||
domain: "local.",
|
||||
interface: nil)
|
||||
|
||||
#expect(GatewayEndpointID.stableID(endpoint) == "_clawdbot-gw._tcp|local.|Clawdbot Gateway Node")
|
||||
#expect(GatewayEndpointID.stableID(endpoint) == "_moltbot-gw._tcp|local.|Moltbot Gateway Node")
|
||||
}
|
||||
|
||||
@Test func stableIDForNonServiceUsesEndpointDescription() {
|
||||
@@ -21,8 +21,8 @@ import Testing
|
||||
|
||||
@Test func prettyDescriptionDecodesBonjourEscapes() {
|
||||
let endpoint = NWEndpoint.service(
|
||||
name: "Clawdbot\\032Gateway",
|
||||
type: "_clawdbot-gw._tcp",
|
||||
name: "Moltbot\\032Gateway",
|
||||
type: "_moltbot-gw._tcp",
|
||||
domain: "local.",
|
||||
interface: nil)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
private struct KeychainEntry: Hashable {
|
||||
let service: String
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct IOSGatewayChatTransportTests {
|
||||
@Test func requestsFailFastWhenGatewayNotConnected() async {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>ClawdbotTests</string>
|
||||
<string>MoltbotTests</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct KeychainStoreTests {
|
||||
@Test func saveLoadUpdateDeleteRoundTrip() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import Foundation
|
||||
import Testing
|
||||
import UIKit
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws -> T) rethrows -> T {
|
||||
let defaults = UserDefaults.standard
|
||||
@@ -32,7 +32,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
|
||||
@Suite(.serialized) struct NodeAppModelInvokeTests {
|
||||
@Test @MainActor func decodeParamsFailsWithoutJSON() {
|
||||
#expect(throws: Error.self) {
|
||||
_ = try NodeAppModel._test_decodeParams(ClawdbotCanvasNavigateParams.self, from: nil)
|
||||
_ = try NodeAppModel._test_decodeParams(MoltbotCanvasNavigateParams.self, from: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
|
||||
let appModel = NodeAppModel()
|
||||
appModel.setScenePhase(.background)
|
||||
|
||||
let req = BridgeInvokeRequest(id: "bg", command: ClawdbotCanvasCommand.present.rawValue)
|
||||
let req = BridgeInvokeRequest(id: "bg", command: MoltbotCanvasCommand.present.rawValue)
|
||||
let res = await appModel._test_handleInvoke(req)
|
||||
#expect(res.ok == false)
|
||||
#expect(res.error?.code == .backgroundUnavailable)
|
||||
@@ -56,7 +56,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
|
||||
|
||||
@Test @MainActor func handleInvokeRejectsCameraWhenDisabled() async {
|
||||
let appModel = NodeAppModel()
|
||||
let req = BridgeInvokeRequest(id: "cam", command: ClawdbotCameraCommand.snap.rawValue)
|
||||
let req = BridgeInvokeRequest(id: "cam", command: MoltbotCameraCommand.snap.rawValue)
|
||||
|
||||
let defaults = UserDefaults.standard
|
||||
let key = "camera.enabled"
|
||||
@@ -78,13 +78,13 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
|
||||
|
||||
@Test @MainActor func handleInvokeRejectsInvalidScreenFormat() async {
|
||||
let appModel = NodeAppModel()
|
||||
let params = ClawdbotScreenRecordParams(format: "gif")
|
||||
let params = MoltbotScreenRecordParams(format: "gif")
|
||||
let data = try? JSONEncoder().encode(params)
|
||||
let json = data.flatMap { String(data: $0, encoding: .utf8) }
|
||||
|
||||
let req = BridgeInvokeRequest(
|
||||
id: "screen",
|
||||
command: ClawdbotScreenCommand.record.rawValue,
|
||||
command: MoltbotScreenCommand.record.rawValue,
|
||||
paramsJSON: json)
|
||||
|
||||
let res = await appModel._test_handleInvoke(req)
|
||||
@@ -96,28 +96,28 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
|
||||
let appModel = NodeAppModel()
|
||||
appModel.screen.navigate(to: "http://example.com")
|
||||
|
||||
let present = BridgeInvokeRequest(id: "present", command: ClawdbotCanvasCommand.present.rawValue)
|
||||
let present = BridgeInvokeRequest(id: "present", command: MoltbotCanvasCommand.present.rawValue)
|
||||
let presentRes = await appModel._test_handleInvoke(present)
|
||||
#expect(presentRes.ok == true)
|
||||
#expect(appModel.screen.urlString.isEmpty)
|
||||
|
||||
let navigateParams = ClawdbotCanvasNavigateParams(url: "http://localhost:18789/")
|
||||
let navigateParams = MoltbotCanvasNavigateParams(url: "http://localhost:18789/")
|
||||
let navData = try JSONEncoder().encode(navigateParams)
|
||||
let navJSON = String(decoding: navData, as: UTF8.self)
|
||||
let navigate = BridgeInvokeRequest(
|
||||
id: "nav",
|
||||
command: ClawdbotCanvasCommand.navigate.rawValue,
|
||||
command: MoltbotCanvasCommand.navigate.rawValue,
|
||||
paramsJSON: navJSON)
|
||||
let navRes = await appModel._test_handleInvoke(navigate)
|
||||
#expect(navRes.ok == true)
|
||||
#expect(appModel.screen.urlString == "http://localhost:18789/")
|
||||
|
||||
let evalParams = ClawdbotCanvasEvalParams(javaScript: "1+1")
|
||||
let evalParams = MoltbotCanvasEvalParams(javaScript: "1+1")
|
||||
let evalData = try JSONEncoder().encode(evalParams)
|
||||
let evalJSON = String(decoding: evalData, as: UTF8.self)
|
||||
let eval = BridgeInvokeRequest(
|
||||
id: "eval",
|
||||
command: ClawdbotCanvasCommand.evalJS.rawValue,
|
||||
command: MoltbotCanvasCommand.evalJS.rawValue,
|
||||
paramsJSON: evalJSON)
|
||||
let evalRes = await appModel._test_handleInvoke(eval)
|
||||
#expect(evalRes.ok == true)
|
||||
@@ -129,18 +129,18 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
|
||||
@Test @MainActor func handleInvokeA2UICommandsFailWhenHostMissing() async throws {
|
||||
let appModel = NodeAppModel()
|
||||
|
||||
let reset = BridgeInvokeRequest(id: "reset", command: ClawdbotCanvasA2UICommand.reset.rawValue)
|
||||
let reset = BridgeInvokeRequest(id: "reset", command: MoltbotCanvasA2UICommand.reset.rawValue)
|
||||
let resetRes = await appModel._test_handleInvoke(reset)
|
||||
#expect(resetRes.ok == false)
|
||||
#expect(resetRes.error?.message.contains("A2UI_HOST_NOT_CONFIGURED") == true)
|
||||
|
||||
let jsonl = "{\"beginRendering\":{}}"
|
||||
let pushParams = ClawdbotCanvasA2UIPushJSONLParams(jsonl: jsonl)
|
||||
let pushParams = MoltbotCanvasA2UIPushJSONLParams(jsonl: jsonl)
|
||||
let pushData = try JSONEncoder().encode(pushParams)
|
||||
let pushJSON = String(decoding: pushData, as: UTF8.self)
|
||||
let push = BridgeInvokeRequest(
|
||||
id: "push",
|
||||
command: ClawdbotCanvasA2UICommand.pushJSONL.rawValue,
|
||||
command: MoltbotCanvasA2UICommand.pushJSONL.rawValue,
|
||||
paramsJSON: pushJSON)
|
||||
let pushRes = await appModel._test_handleInvoke(push)
|
||||
#expect(pushRes.ok == false)
|
||||
@@ -157,7 +157,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
|
||||
|
||||
@Test @MainActor func handleDeepLinkSetsErrorWhenNotConnected() async {
|
||||
let appModel = NodeAppModel()
|
||||
let url = URL(string: "clawdbot://agent?message=hello")!
|
||||
let url = URL(string: "moltbot://agent?message=hello")!
|
||||
await appModel.handleDeepLink(url: url)
|
||||
#expect(appModel.screen.errorText?.contains("Gateway not connected") == true)
|
||||
}
|
||||
@@ -165,7 +165,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
|
||||
@Test @MainActor func handleDeepLinkRejectsOversizedMessage() async {
|
||||
let appModel = NodeAppModel()
|
||||
let msg = String(repeating: "a", count: 20001)
|
||||
let url = URL(string: "clawdbot://agent?message=\(msg)")!
|
||||
let url = URL(string: "moltbot://agent?message=\(msg)")!
|
||||
await appModel.handleDeepLink(url: url)
|
||||
#expect(appModel.screen.errorText?.contains("Deep link too large") == true)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Testing
|
||||
import WebKit
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct ScreenControllerTests {
|
||||
@Test @MainActor func canvasModeConfiguresWebViewForTouch() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite(.serialized) struct ScreenRecordServiceTests {
|
||||
@Test func clampDefaultsAndBounds() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct SettingsNetworkingHelpersTests {
|
||||
@Test func parseHostPortParsesIPv4() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import ClawdbotKit
|
||||
import MoltbotKit
|
||||
import SwiftUI
|
||||
import Testing
|
||||
import UIKit
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct SwiftUIRenderSmokeTests {
|
||||
@MainActor private static func host(_ view: some View) -> UIWindow {
|
||||
@@ -75,7 +75,7 @@ import UIKit
|
||||
}
|
||||
|
||||
@Test @MainActor func voiceWakeToastBuildsAViewHierarchy() {
|
||||
let root = VoiceWakeToast(command: "clawdbot: do something")
|
||||
let root = VoiceWakeToast(command: "moltbot: do something")
|
||||
_ = Self.host(root)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct VoiceWakeGatewaySyncTests {
|
||||
@Test func decodeGatewayTriggersFromJSONSanitizes() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import SwabbleKit
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct VoiceWakeManagerExtractCommandTests {
|
||||
@Test func extractCommandReturnsNilWhenNoTriggerFound() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import SwabbleKit
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite(.serialized) struct VoiceWakeManagerStateTests {
|
||||
@Test @MainActor func suspendAndResumeCycleUpdatesState() async {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
@testable import Moltbot
|
||||
|
||||
@Suite struct VoiceWakePreferencesTests {
|
||||
@Test func sanitizeTriggerWordsTrimsAndDropsEmpty() {
|
||||
|
||||
@@ -72,8 +72,8 @@ platform :ios do
|
||||
UI.user_error!("Missing IOS_DEVELOPMENT_TEAM (Apple Team ID). Add it to fastlane/.env or export it in your shell.") if team_id.nil? || team_id.strip.empty?
|
||||
|
||||
build_app(
|
||||
project: "Clawdbot.xcodeproj",
|
||||
scheme: "Clawdbot",
|
||||
project: "Moltbot.xcodeproj",
|
||||
scheme: "Moltbot",
|
||||
export_method: "app-store",
|
||||
clean: true,
|
||||
xcargs: "DEVELOPMENT_TEAM=#{team_id} -allowProvisioningUpdates",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# fastlane setup (Clawdbot iOS)
|
||||
# fastlane setup (Moltbot iOS)
|
||||
|
||||
Install:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Clawdbot
|
||||
name: Moltbot
|
||||
options:
|
||||
bundleIdPrefix: com.clawdbot
|
||||
deploymentTarget:
|
||||
@@ -10,33 +10,33 @@ settings:
|
||||
SWIFT_VERSION: "6.0"
|
||||
|
||||
packages:
|
||||
ClawdbotKit:
|
||||
MoltbotKit:
|
||||
path: ../shared/ClawdbotKit
|
||||
Swabble:
|
||||
path: ../../Swabble
|
||||
|
||||
schemes:
|
||||
Clawdbot:
|
||||
Moltbot:
|
||||
shared: true
|
||||
build:
|
||||
targets:
|
||||
Clawdbot: all
|
||||
Moltbot: all
|
||||
test:
|
||||
targets:
|
||||
- ClawdbotTests
|
||||
- MoltbotTests
|
||||
|
||||
targets:
|
||||
Clawdbot:
|
||||
Moltbot:
|
||||
type: application
|
||||
platform: iOS
|
||||
sources:
|
||||
- path: Sources
|
||||
dependencies:
|
||||
- package: ClawdbotKit
|
||||
- package: ClawdbotKit
|
||||
product: ClawdbotChatUI
|
||||
- package: ClawdbotKit
|
||||
product: ClawdbotProtocol
|
||||
- package: MoltbotKit
|
||||
- package: MoltbotKit
|
||||
product: MoltbotChatUI
|
||||
- package: MoltbotKit
|
||||
product: MoltbotProtocol
|
||||
- package: Swabble
|
||||
product: SwabbleKit
|
||||
- sdk: AppIntents.framework
|
||||
@@ -79,7 +79,7 @@ targets:
|
||||
info:
|
||||
path: Sources/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: Clawdbot
|
||||
CFBundleDisplayName: Moltbot
|
||||
CFBundleIconName: AppIcon
|
||||
CFBundleShortVersionString: "2026.1.26"
|
||||
CFBundleVersion: "20260126"
|
||||
@@ -88,16 +88,16 @@ targets:
|
||||
UIApplicationSupportsMultipleScenes: false
|
||||
UIBackgroundModes:
|
||||
- audio
|
||||
NSLocalNetworkUsageDescription: Clawdbot discovers and connects to your Clawdbot gateway on the local network.
|
||||
NSLocalNetworkUsageDescription: Moltbot discovers and connects to your Moltbot gateway on the local network.
|
||||
NSAppTransportSecurity:
|
||||
NSAllowsArbitraryLoadsInWebContent: true
|
||||
NSBonjourServices:
|
||||
- _clawdbot-gw._tcp
|
||||
NSCameraUsageDescription: Clawdbot can capture photos or short video clips when requested via the gateway.
|
||||
NSLocationWhenInUseUsageDescription: Clawdbot uses your location when you allow location sharing.
|
||||
NSLocationAlwaysAndWhenInUseUsageDescription: Clawdbot can share your location in the background when you enable Always.
|
||||
NSMicrophoneUsageDescription: Clawdbot needs microphone access for voice wake.
|
||||
NSSpeechRecognitionUsageDescription: Clawdbot uses on-device speech recognition for voice wake.
|
||||
- _moltbot-gw._tcp
|
||||
NSCameraUsageDescription: Moltbot can capture photos or short video clips when requested via the gateway.
|
||||
NSLocationWhenInUseUsageDescription: Moltbot uses your location when you allow location sharing.
|
||||
NSLocationAlwaysAndWhenInUseUsageDescription: Moltbot can share your location in the background when you enable Always.
|
||||
NSMicrophoneUsageDescription: Moltbot needs microphone access for voice wake.
|
||||
NSSpeechRecognitionUsageDescription: Moltbot uses on-device speech recognition for voice wake.
|
||||
UISupportedInterfaceOrientations:
|
||||
- UIInterfaceOrientationPortrait
|
||||
- UIInterfaceOrientationPortraitUpsideDown
|
||||
@@ -109,13 +109,13 @@ targets:
|
||||
- UIInterfaceOrientationLandscapeLeft
|
||||
- UIInterfaceOrientationLandscapeRight
|
||||
|
||||
ClawdbotTests:
|
||||
MoltbotTests:
|
||||
type: bundle.unit-test
|
||||
platform: iOS
|
||||
sources:
|
||||
- path: Tests
|
||||
dependencies:
|
||||
- target: Clawdbot
|
||||
- target: Moltbot
|
||||
- package: Swabble
|
||||
product: SwabbleKit
|
||||
- sdk: AppIntents.framework
|
||||
@@ -124,11 +124,11 @@ targets:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.clawdbot.ios.tests
|
||||
SWIFT_VERSION: "6.0"
|
||||
SWIFT_STRICT_CONCURRENCY: complete
|
||||
TEST_HOST: "$(BUILT_PRODUCTS_DIR)/Clawdbot.app/Clawdbot"
|
||||
TEST_HOST: "$(BUILT_PRODUCTS_DIR)/Moltbot.app/Moltbot"
|
||||
BUNDLE_LOADER: "$(TEST_HOST)"
|
||||
info:
|
||||
path: Tests/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: ClawdbotTests
|
||||
CFBundleDisplayName: MoltbotTests
|
||||
CFBundleShortVersionString: "2026.1.26"
|
||||
CFBundleVersion: "20260126"
|
||||
|
||||
Reference in New Issue
Block a user