refactor: rename to openclaw

This commit is contained in:
Peter Steinberger
2026-01-30 03:15:10 +01:00
parent 4583f88626
commit 9a7160786a
2357 changed files with 16688 additions and 16788 deletions

View File

@@ -1,6 +1,6 @@
## Clawdbot Node (Android) (internal)
## OpenClaw Node (Android) (internal)
Modern Android node app: connects to the **Gateway WebSocket** (`_clawdbot-gw._tcp`) and exposes **Canvas + Chat + Camera**.
Modern Android node app: connects to the **Gateway WebSocket** (`_openclaw-gw._tcp`) and exposes **Canvas + Chat + Camera**.
Notes:
- The node keeps the connection alive via a **foreground service** (persistent notification with a Disconnect action).
@@ -25,7 +25,7 @@ cd apps/android
1) Start the gateway (on your “master” machine):
```bash
pnpm clawdbot gateway --port 18789 --verbose
pnpm openclaw gateway --port 18789 --verbose
```
2) In the Android app:
@@ -34,8 +34,8 @@ pnpm clawdbot gateway --port 18789 --verbose
3) Approve pairing (on the gateway machine):
```bash
clawdbot nodes pending
clawdbot nodes approve <requestId>
openclaw nodes pending
openclaw nodes approve <requestId>
```
More details: `docs/platforms/android.md`.

View File

@@ -8,17 +8,17 @@ plugins {
}
android {
namespace = "bot.molt.android"
namespace = "ai.openclaw.android"
compileSdk = 36
sourceSets {
getByName("main") {
assets.srcDir(file("../../shared/MoltbotKit/Sources/MoltbotKit/Resources"))
assets.srcDir(file("../../shared/OpenClawKit/Sources/OpenClawKit/Resources"))
}
}
defaultConfig {
applicationId = "bot.molt.android"
applicationId = "ai.openclaw.android"
minSdk = 31
targetSdk = 36
versionCode = 202601290
@@ -65,7 +65,7 @@ androidComponents {
val versionName = output.versionName.orNull ?: "0"
val buildType = variant.buildType
val outputFileName = "moltbot-${versionName}-${buildType}.apk"
val outputFileName = "openclaw-${versionName}-${buildType}.apk"
output.outputFileName = outputFileName
}
}

View File

@@ -32,7 +32,7 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/Theme.MoltbotNode">
android:theme="@style/Theme.OpenClawNode">
<service
android:name=".NodeForegroundService"
android:exported="false"

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
enum class CameraHudKind {
Photo,

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
import android.content.Context
import android.os.Build

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
enum class LocationMode(val rawValue: String) {
Off("off"),

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
import android.Manifest
import android.content.pm.ApplicationInfo
@@ -18,8 +18,8 @@ import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import bot.molt.android.ui.RootScreen
import bot.molt.android.ui.MoltbotTheme
import ai.openclaw.android.ui.RootScreen
import ai.openclaw.android.ui.OpenClawTheme
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
@@ -56,7 +56,7 @@ class MainActivity : ComponentActivity() {
}
setContent {
MoltbotTheme {
OpenClawTheme {
Surface(modifier = Modifier) {
RootScreen(viewModel = viewModel)
}

View File

@@ -1,13 +1,13 @@
package bot.molt.android
package ai.openclaw.android
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import bot.molt.android.gateway.GatewayEndpoint
import bot.molt.android.chat.OutgoingAttachment
import bot.molt.android.node.CameraCaptureManager
import bot.molt.android.node.CanvasController
import bot.molt.android.node.ScreenRecordManager
import bot.molt.android.node.SmsManager
import ai.openclaw.android.gateway.GatewayEndpoint
import ai.openclaw.android.chat.OutgoingAttachment
import ai.openclaw.android.node.CameraCaptureManager
import ai.openclaw.android.node.CanvasController
import ai.openclaw.android.node.ScreenRecordManager
import ai.openclaw.android.node.SmsManager
import kotlinx.coroutines.flow.StateFlow
class MainViewModel(app: Application) : AndroidViewModel(app) {

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
import android.app.Application
import android.os.StrictMode

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
import android.app.Notification
import android.app.NotificationChannel
@@ -29,7 +29,7 @@ class NodeForegroundService : Service() {
override fun onCreate() {
super.onCreate()
ensureChannel()
val initial = buildNotification(title = "Moltbot Node", text = "Starting…")
val initial = buildNotification(title = "OpenClaw Node", text = "Starting…")
startForegroundWithTypes(notification = initial, requiresMic = false)
val runtime = (application as NodeApp).runtime
@@ -44,7 +44,7 @@ class NodeForegroundService : Service() {
) { status, server, connected, voiceMode, voiceListening ->
Quint(status, server, connected, voiceMode, voiceListening)
}.collect { (status, server, connected, voiceMode, voiceListening) ->
val title = if (connected) "Moltbot Node · Connected" else "Moltbot Node"
val title = if (connected) "OpenClaw Node · Connected" else "OpenClaw Node"
val voiceSuffix =
if (voiceMode == VoiceWakeMode.Always) {
if (voiceListening) " · Voice Wake: Listening" else " · Voice Wake: Paused"
@@ -91,7 +91,7 @@ class NodeForegroundService : Service() {
"Connection",
NotificationManager.IMPORTANCE_LOW,
).apply {
description = "Moltbot node connection status"
description = "OpenClaw node connection status"
setShowBadge(false)
}
mgr.createNotificationChannel(channel)
@@ -163,7 +163,7 @@ class NodeForegroundService : Service() {
private const val CHANNEL_ID = "connection"
private const val NOTIFICATION_ID = 1
private const val ACTION_STOP = "bot.molt.android.action.STOP"
private const val ACTION_STOP = "ai.openclaw.android.action.STOP"
fun start(context: Context) {
val intent = Intent(context, NodeForegroundService::class.java)

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
import android.Manifest
import android.content.Context
@@ -7,35 +7,35 @@ import android.location.LocationManager
import android.os.Build
import android.os.SystemClock
import androidx.core.content.ContextCompat
import bot.molt.android.chat.ChatController
import bot.molt.android.chat.ChatMessage
import bot.molt.android.chat.ChatPendingToolCall
import bot.molt.android.chat.ChatSessionEntry
import bot.molt.android.chat.OutgoingAttachment
import bot.molt.android.gateway.DeviceAuthStore
import bot.molt.android.gateway.DeviceIdentityStore
import bot.molt.android.gateway.GatewayClientInfo
import bot.molt.android.gateway.GatewayConnectOptions
import bot.molt.android.gateway.GatewayDiscovery
import bot.molt.android.gateway.GatewayEndpoint
import bot.molt.android.gateway.GatewaySession
import bot.molt.android.gateway.GatewayTlsParams
import bot.molt.android.node.CameraCaptureManager
import bot.molt.android.node.LocationCaptureManager
import bot.molt.android.BuildConfig
import bot.molt.android.node.CanvasController
import bot.molt.android.node.ScreenRecordManager
import bot.molt.android.node.SmsManager
import bot.molt.android.protocol.MoltbotCapability
import bot.molt.android.protocol.MoltbotCameraCommand
import bot.molt.android.protocol.MoltbotCanvasA2UIAction
import bot.molt.android.protocol.MoltbotCanvasA2UICommand
import bot.molt.android.protocol.MoltbotCanvasCommand
import bot.molt.android.protocol.MoltbotScreenCommand
import bot.molt.android.protocol.MoltbotLocationCommand
import bot.molt.android.protocol.MoltbotSmsCommand
import bot.molt.android.voice.TalkModeManager
import bot.molt.android.voice.VoiceWakeManager
import ai.openclaw.android.chat.ChatController
import ai.openclaw.android.chat.ChatMessage
import ai.openclaw.android.chat.ChatPendingToolCall
import ai.openclaw.android.chat.ChatSessionEntry
import ai.openclaw.android.chat.OutgoingAttachment
import ai.openclaw.android.gateway.DeviceAuthStore
import ai.openclaw.android.gateway.DeviceIdentityStore
import ai.openclaw.android.gateway.GatewayClientInfo
import ai.openclaw.android.gateway.GatewayConnectOptions
import ai.openclaw.android.gateway.GatewayDiscovery
import ai.openclaw.android.gateway.GatewayEndpoint
import ai.openclaw.android.gateway.GatewaySession
import ai.openclaw.android.gateway.GatewayTlsParams
import ai.openclaw.android.node.CameraCaptureManager
import ai.openclaw.android.node.LocationCaptureManager
import ai.openclaw.android.BuildConfig
import ai.openclaw.android.node.CanvasController
import ai.openclaw.android.node.ScreenRecordManager
import ai.openclaw.android.node.SmsManager
import ai.openclaw.android.protocol.OpenClawCapability
import ai.openclaw.android.protocol.OpenClawCameraCommand
import ai.openclaw.android.protocol.OpenClawCanvasA2UIAction
import ai.openclaw.android.protocol.OpenClawCanvasA2UICommand
import ai.openclaw.android.protocol.OpenClawCanvasCommand
import ai.openclaw.android.protocol.OpenClawScreenCommand
import ai.openclaw.android.protocol.OpenClawLocationCommand
import ai.openclaw.android.protocol.OpenClawSmsCommand
import ai.openclaw.android.voice.TalkModeManager
import ai.openclaw.android.voice.VoiceWakeManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -451,38 +451,38 @@ class NodeRuntime(context: Context) {
private fun buildInvokeCommands(): List<String> =
buildList {
add(MoltbotCanvasCommand.Present.rawValue)
add(MoltbotCanvasCommand.Hide.rawValue)
add(MoltbotCanvasCommand.Navigate.rawValue)
add(MoltbotCanvasCommand.Eval.rawValue)
add(MoltbotCanvasCommand.Snapshot.rawValue)
add(MoltbotCanvasA2UICommand.Push.rawValue)
add(MoltbotCanvasA2UICommand.PushJSONL.rawValue)
add(MoltbotCanvasA2UICommand.Reset.rawValue)
add(MoltbotScreenCommand.Record.rawValue)
add(OpenClawCanvasCommand.Present.rawValue)
add(OpenClawCanvasCommand.Hide.rawValue)
add(OpenClawCanvasCommand.Navigate.rawValue)
add(OpenClawCanvasCommand.Eval.rawValue)
add(OpenClawCanvasCommand.Snapshot.rawValue)
add(OpenClawCanvasA2UICommand.Push.rawValue)
add(OpenClawCanvasA2UICommand.PushJSONL.rawValue)
add(OpenClawCanvasA2UICommand.Reset.rawValue)
add(OpenClawScreenCommand.Record.rawValue)
if (cameraEnabled.value) {
add(MoltbotCameraCommand.Snap.rawValue)
add(MoltbotCameraCommand.Clip.rawValue)
add(OpenClawCameraCommand.Snap.rawValue)
add(OpenClawCameraCommand.Clip.rawValue)
}
if (locationMode.value != LocationMode.Off) {
add(MoltbotLocationCommand.Get.rawValue)
add(OpenClawLocationCommand.Get.rawValue)
}
if (sms.canSendSms()) {
add(MoltbotSmsCommand.Send.rawValue)
add(OpenClawSmsCommand.Send.rawValue)
}
}
private fun buildCapabilities(): List<String> =
buildList {
add(MoltbotCapability.Canvas.rawValue)
add(MoltbotCapability.Screen.rawValue)
if (cameraEnabled.value) add(MoltbotCapability.Camera.rawValue)
if (sms.canSendSms()) add(MoltbotCapability.Sms.rawValue)
add(OpenClawCapability.Canvas.rawValue)
add(OpenClawCapability.Screen.rawValue)
if (cameraEnabled.value) add(OpenClawCapability.Camera.rawValue)
if (sms.canSendSms()) add(OpenClawCapability.Sms.rawValue)
if (voiceWakeMode.value != VoiceWakeMode.Off && hasRecordAudioPermission()) {
add(MoltbotCapability.VoiceWake.rawValue)
add(OpenClawCapability.VoiceWake.rawValue)
}
if (locationMode.value != LocationMode.Off) {
add(MoltbotCapability.Location.rawValue)
add(OpenClawCapability.Location.rawValue)
}
}
@@ -506,7 +506,7 @@ class NodeRuntime(context: Context) {
val version = resolvedVersionName()
val release = Build.VERSION.RELEASE?.trim().orEmpty()
val releaseLabel = if (release.isEmpty()) "unknown" else release
return "MoltbotAndroid/$version (Android $releaseLabel; SDK ${Build.VERSION.SDK_INT})"
return "OpenClawAndroid/$version (Android $releaseLabel; SDK ${Build.VERSION.SDK_INT})"
}
private fun buildClientInfo(clientId: String, clientMode: String): GatewayClientInfo {
@@ -529,7 +529,7 @@ class NodeRuntime(context: Context) {
caps = buildCapabilities(),
commands = buildInvokeCommands(),
permissions = emptyMap(),
client = buildClientInfo(clientId = "moltbot-android", clientMode = "node"),
client = buildClientInfo(clientId = "openclaw-android", clientMode = "node"),
userAgent = buildUserAgent(),
)
}
@@ -541,7 +541,7 @@ class NodeRuntime(context: Context) {
caps = emptyList(),
commands = emptyList(),
permissions = emptyMap(),
client = buildClientInfo(clientId = "moltbot-control-ui", clientMode = "ui"),
client = buildClientInfo(clientId = "openclaw-control-ui", clientMode = "ui"),
userAgent = buildUserAgent(),
)
}
@@ -665,7 +665,7 @@ class NodeRuntime(context: Context) {
val actionId = (userActionObj["id"] as? JsonPrimitive)?.content?.trim().orEmpty().ifEmpty {
java.util.UUID.randomUUID().toString()
}
val name = MoltbotCanvasA2UIAction.extractActionName(userActionObj) ?: return@launch
val name = OpenClawCanvasA2UIAction.extractActionName(userActionObj) ?: return@launch
val surfaceId =
(userActionObj["surfaceId"] as? JsonPrimitive)?.content?.trim().orEmpty().ifEmpty { "main" }
@@ -675,7 +675,7 @@ class NodeRuntime(context: Context) {
val sessionKey = resolveMainSessionKey()
val message =
MoltbotCanvasA2UIAction.formatAgentMessage(
OpenClawCanvasA2UIAction.formatAgentMessage(
actionName = name,
sessionKey = sessionKey,
surfaceId = surfaceId,
@@ -709,7 +709,7 @@ class NodeRuntime(context: Context) {
try {
canvas.eval(
MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(
OpenClawCanvasA2UIAction.jsDispatchA2UIActionStatus(
actionId = actionId,
ok = connected && error == null,
error = error,
@@ -827,10 +827,10 @@ class NodeRuntime(context: Context) {
private suspend fun handleInvoke(command: String, paramsJson: String?): GatewaySession.InvokeResult {
if (
command.startsWith(MoltbotCanvasCommand.NamespacePrefix) ||
command.startsWith(MoltbotCanvasA2UICommand.NamespacePrefix) ||
command.startsWith(MoltbotCameraCommand.NamespacePrefix) ||
command.startsWith(MoltbotScreenCommand.NamespacePrefix)
command.startsWith(OpenClawCanvasCommand.NamespacePrefix) ||
command.startsWith(OpenClawCanvasA2UICommand.NamespacePrefix) ||
command.startsWith(OpenClawCameraCommand.NamespacePrefix) ||
command.startsWith(OpenClawScreenCommand.NamespacePrefix)
) {
if (!isForeground.value) {
return GatewaySession.InvokeResult.error(
@@ -839,13 +839,13 @@ class NodeRuntime(context: Context) {
)
}
}
if (command.startsWith(MoltbotCameraCommand.NamespacePrefix) && !cameraEnabled.value) {
if (command.startsWith(OpenClawCameraCommand.NamespacePrefix) && !cameraEnabled.value) {
return GatewaySession.InvokeResult.error(
code = "CAMERA_DISABLED",
message = "CAMERA_DISABLED: enable Camera in Settings",
)
}
if (command.startsWith(MoltbotLocationCommand.NamespacePrefix) &&
if (command.startsWith(OpenClawLocationCommand.NamespacePrefix) &&
locationMode.value == LocationMode.Off
) {
return GatewaySession.InvokeResult.error(
@@ -855,18 +855,18 @@ class NodeRuntime(context: Context) {
}
return when (command) {
MoltbotCanvasCommand.Present.rawValue -> {
OpenClawCanvasCommand.Present.rawValue -> {
val url = CanvasController.parseNavigateUrl(paramsJson)
canvas.navigate(url)
GatewaySession.InvokeResult.ok(null)
}
MoltbotCanvasCommand.Hide.rawValue -> GatewaySession.InvokeResult.ok(null)
MoltbotCanvasCommand.Navigate.rawValue -> {
OpenClawCanvasCommand.Hide.rawValue -> GatewaySession.InvokeResult.ok(null)
OpenClawCanvasCommand.Navigate.rawValue -> {
val url = CanvasController.parseNavigateUrl(paramsJson)
canvas.navigate(url)
GatewaySession.InvokeResult.ok(null)
}
MoltbotCanvasCommand.Eval.rawValue -> {
OpenClawCanvasCommand.Eval.rawValue -> {
val js =
CanvasController.parseEvalJs(paramsJson)
?: return GatewaySession.InvokeResult.error(
@@ -884,7 +884,7 @@ class NodeRuntime(context: Context) {
}
GatewaySession.InvokeResult.ok("""{"result":${result.toJsonString()}}""")
}
MoltbotCanvasCommand.Snapshot.rawValue -> {
OpenClawCanvasCommand.Snapshot.rawValue -> {
val snapshotParams = CanvasController.parseSnapshotParams(paramsJson)
val base64 =
try {
@@ -901,7 +901,7 @@ class NodeRuntime(context: Context) {
}
GatewaySession.InvokeResult.ok("""{"format":"${snapshotParams.format.rawValue}","base64":"$base64"}""")
}
MoltbotCanvasA2UICommand.Reset.rawValue -> {
OpenClawCanvasA2UICommand.Reset.rawValue -> {
val a2uiUrl = resolveA2uiHostUrl()
?: return GatewaySession.InvokeResult.error(
code = "A2UI_HOST_NOT_CONFIGURED",
@@ -917,7 +917,7 @@ class NodeRuntime(context: Context) {
val res = canvas.eval(a2uiResetJS)
GatewaySession.InvokeResult.ok(res)
}
MoltbotCanvasA2UICommand.Push.rawValue, MoltbotCanvasA2UICommand.PushJSONL.rawValue -> {
OpenClawCanvasA2UICommand.Push.rawValue, OpenClawCanvasA2UICommand.PushJSONL.rawValue -> {
val messages =
try {
decodeA2uiMessages(command, paramsJson)
@@ -940,7 +940,7 @@ class NodeRuntime(context: Context) {
val res = canvas.eval(js)
GatewaySession.InvokeResult.ok(res)
}
MoltbotCameraCommand.Snap.rawValue -> {
OpenClawCameraCommand.Snap.rawValue -> {
showCameraHud(message = "Taking photo…", kind = CameraHudKind.Photo)
triggerCameraFlash()
val res =
@@ -954,7 +954,7 @@ class NodeRuntime(context: Context) {
showCameraHud(message = "Photo captured", kind = CameraHudKind.Success, autoHideMs = 1600)
GatewaySession.InvokeResult.ok(res.payloadJson)
}
MoltbotCameraCommand.Clip.rawValue -> {
OpenClawCameraCommand.Clip.rawValue -> {
val includeAudio = paramsJson?.contains("\"includeAudio\":true") != false
if (includeAudio) externalAudioCaptureActive.value = true
try {
@@ -973,7 +973,7 @@ class NodeRuntime(context: Context) {
if (includeAudio) externalAudioCaptureActive.value = false
}
}
MoltbotLocationCommand.Get.rawValue -> {
OpenClawLocationCommand.Get.rawValue -> {
val mode = locationMode.value
if (!isForeground.value && mode != LocationMode.Always) {
return GatewaySession.InvokeResult.error(
@@ -1026,7 +1026,7 @@ class NodeRuntime(context: Context) {
GatewaySession.InvokeResult.error(code = "LOCATION_UNAVAILABLE", message = message)
}
}
MoltbotScreenCommand.Record.rawValue -> {
OpenClawScreenCommand.Record.rawValue -> {
// Status pill mirrors screen recording state so it stays visible without overlay stacking.
_screenRecordActive.value = true
try {
@@ -1042,7 +1042,7 @@ class NodeRuntime(context: Context) {
_screenRecordActive.value = false
}
}
MoltbotSmsCommand.Send.rawValue -> {
OpenClawSmsCommand.Send.rawValue -> {
val res = sms.send(paramsJson)
if (res.ok) {
GatewaySession.InvokeResult.ok(res.payloadJson)
@@ -1115,7 +1115,7 @@ class NodeRuntime(context: Context) {
val raw = if (nodeRaw.isNotBlank()) nodeRaw else operatorRaw
if (raw.isBlank()) return null
val base = raw.trimEnd('/')
return "${base}/__moltbot__/a2ui/?platform=android"
return "${base}/__openclaw__/a2ui/?platform=android"
}
private suspend fun ensureA2uiReady(a2uiUrl: String): Boolean {
@@ -1150,7 +1150,7 @@ class NodeRuntime(context: Context) {
val jsonlField = (obj["jsonl"] as? JsonPrimitive)?.content?.trim().orEmpty()
val hasMessagesArray = obj["messages"] is JsonArray
if (command == MoltbotCanvasA2UICommand.PushJSONL.rawValue || (!hasMessagesArray && jsonlField.isNotBlank())) {
if (command == OpenClawCanvasA2UICommand.PushJSONL.rawValue || (!hasMessagesArray && jsonlField.isNotBlank())) {
val jsonl = jsonlField
if (jsonl.isBlank()) throw IllegalArgumentException("INVALID_REQUEST: jsonl required")
val messages =
@@ -1207,7 +1207,8 @@ private const val a2uiReadyCheckJS: String =
"""
(() => {
try {
return !!globalThis.clawdbotA2UI && typeof globalThis.clawdbotA2UI.applyMessages === 'function';
const host = globalThis.openclawA2UI;
return !!host && typeof host.applyMessages === 'function';
} catch (_) {
return false;
}
@@ -1218,8 +1219,9 @@ private const val a2uiResetJS: String =
"""
(() => {
try {
if (!globalThis.clawdbotA2UI) return { ok: false, error: "missing moltbotA2UI" };
return globalThis.clawdbotA2UI.reset();
const host = globalThis.openclawA2UI;
if (!host) return { ok: false, error: "missing openclawA2UI" };
return host.reset();
} catch (e) {
return { ok: false, error: String(e?.message ?? e) };
}
@@ -1230,9 +1232,10 @@ private fun a2uiApplyMessagesJS(messagesJson: String): String {
return """
(() => {
try {
if (!globalThis.clawdbotA2UI) return { ok: false, error: "missing moltbotA2UI" };
const host = globalThis.openclawA2UI;
if (!host) return { ok: false, error: "missing openclawA2UI" };
const messages = $messagesJson;
return globalThis.clawdbotA2UI.applyMessages(messages);
return host.applyMessages(messages);
} catch (e) {
return { ok: false, error: String(e?.message ?? e) };
}

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
import android.content.pm.PackageManager
import android.content.Intent
@@ -115,7 +115,7 @@ class PermissionRequester(private val activity: ComponentActivity) {
private fun buildRationaleMessage(permissions: List<String>): String {
val labels = permissions.map { permissionLabel(it) }
return "Moltbot needs ${labels.joinToString(", ")} permissions to continue."
return "OpenClaw needs ${labels.joinToString(", ")} permissions to continue."
}
private fun buildSettingsMessage(permissions: List<String>): String {

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
import android.app.Activity
import android.content.Context
@@ -55,7 +55,7 @@ class ScreenCaptureRequester(private val activity: ComponentActivity) {
suspendCancellableCoroutine { cont ->
AlertDialog.Builder(activity)
.setTitle("Screen recording required")
.setMessage("Moltbot needs to record the screen for this command.")
.setMessage("OpenClaw needs to record the screen for this command.")
.setPositiveButton("Continue") { _, _ -> cont.resume(true) }
.setNegativeButton("Not now") { _, _ -> cont.resume(false) }
.setOnCancelListener { cont.resume(false) }

View File

@@ -1,8 +1,9 @@
@file:Suppress("DEPRECATION")
package bot.molt.android
package ai.openclaw.android
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
@@ -16,11 +17,12 @@ import java.util.UUID
class SecurePrefs(context: Context) {
companion object {
val defaultWakeWords: List<String> = listOf("clawd", "claude")
val defaultWakeWords: List<String> = listOf("openclaw", "claude")
private const val displayNameKey = "node.displayName"
private const val voiceWakeModeKey = "voiceWake.mode"
}
private val appContext = context.applicationContext
private val json = Json { ignoreUnknownKeys = true }
private val masterKey =
@@ -28,14 +30,9 @@ class SecurePrefs(context: Context) {
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
private val prefs =
EncryptedSharedPreferences.create(
context,
"moltbot.node.secure",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
private val prefs: SharedPreferences by lazy {
createPrefs(appContext, "openclaw.node.secure")
}
private val _instanceId = MutableStateFlow(loadOrCreateInstanceId())
val instanceId: StateFlow<String> = _instanceId
@@ -59,28 +56,24 @@ class SecurePrefs(context: Context) {
val preventSleep: StateFlow<Boolean> = _preventSleep
private val _manualEnabled =
MutableStateFlow(readBoolWithMigration("gateway.manual.enabled", "bridge.manual.enabled", false))
MutableStateFlow(prefs.getBoolean("gateway.manual.enabled", false))
val manualEnabled: StateFlow<Boolean> = _manualEnabled
private val _manualHost =
MutableStateFlow(readStringWithMigration("gateway.manual.host", "bridge.manual.host", ""))
MutableStateFlow(prefs.getString("gateway.manual.host", "") ?: "")
val manualHost: StateFlow<String> = _manualHost
private val _manualPort =
MutableStateFlow(readIntWithMigration("gateway.manual.port", "bridge.manual.port", 18789))
MutableStateFlow(prefs.getInt("gateway.manual.port", 18789))
val manualPort: StateFlow<Int> = _manualPort
private val _manualTls =
MutableStateFlow(readBoolWithMigration("gateway.manual.tls", null, true))
MutableStateFlow(prefs.getBoolean("gateway.manual.tls", true))
val manualTls: StateFlow<Boolean> = _manualTls
private val _lastDiscoveredStableId =
MutableStateFlow(
readStringWithMigration(
"gateway.lastDiscoveredStableID",
"bridge.lastDiscoveredStableId",
"",
),
prefs.getString("gateway.lastDiscoveredStableID", "") ?: "",
)
val lastDiscoveredStableId: StateFlow<String> = _lastDiscoveredStableId
@@ -158,9 +151,7 @@ class SecurePrefs(context: Context) {
fun loadGatewayToken(): String? {
val key = "gateway.token.${_instanceId.value}"
val stored = prefs.getString(key, null)?.trim()
if (!stored.isNullOrEmpty()) return stored
val legacy = prefs.getString("bridge.token.${_instanceId.value}", null)?.trim()
return legacy?.takeIf { it.isNotEmpty() }
return stored?.takeIf { it.isNotEmpty() }
}
fun saveGatewayToken(token: String) {
@@ -201,6 +192,16 @@ class SecurePrefs(context: Context) {
prefs.edit { remove(key) }
}
private fun createPrefs(context: Context, name: String): SharedPreferences {
return EncryptedSharedPreferences.create(
context,
name,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
}
private fun loadOrCreateInstanceId(): String {
val existing = prefs.getString("node.instanceId", null)?.trim()
if (!existing.isNullOrBlank()) return existing
@@ -270,39 +271,4 @@ class SecurePrefs(context: Context) {
}
}
private fun readBoolWithMigration(newKey: String, oldKey: String?, defaultValue: Boolean): Boolean {
if (prefs.contains(newKey)) {
return prefs.getBoolean(newKey, defaultValue)
}
if (oldKey != null && prefs.contains(oldKey)) {
val value = prefs.getBoolean(oldKey, defaultValue)
prefs.edit { putBoolean(newKey, value) }
return value
}
return defaultValue
}
private fun readStringWithMigration(newKey: String, oldKey: String?, defaultValue: String): String {
if (prefs.contains(newKey)) {
return prefs.getString(newKey, defaultValue) ?: defaultValue
}
if (oldKey != null && prefs.contains(oldKey)) {
val value = prefs.getString(oldKey, defaultValue) ?: defaultValue
prefs.edit { putString(newKey, value) }
return value
}
return defaultValue
}
private fun readIntWithMigration(newKey: String, oldKey: String?, defaultValue: Int): Int {
if (prefs.contains(newKey)) {
return prefs.getInt(newKey, defaultValue)
}
if (oldKey != null && prefs.contains(oldKey)) {
val value = prefs.getInt(oldKey, defaultValue)
prefs.edit { putInt(newKey, value) }
return value
}
return defaultValue
}
}

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
internal fun normalizeMainKey(raw: String?): String {
val trimmed = raw?.trim()

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
enum class VoiceWakeMode(val rawValue: String) {
Off("off"),

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
object WakeWords {
const val maxWords: Int = 32

View File

@@ -1,6 +1,6 @@
package bot.molt.android.chat
package ai.openclaw.android.chat
import bot.molt.android.gateway.GatewaySession
import ai.openclaw.android.gateway.GatewaySession
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope

View File

@@ -1,4 +1,4 @@
package bot.molt.android.chat
package ai.openclaw.android.chat
data class ChatMessage(
val id: String,

View File

@@ -1,4 +1,4 @@
package bot.molt.android.gateway
package ai.openclaw.android.gateway
object BonjourEscapes {
fun decode(input: String): String {

View File

@@ -1,6 +1,6 @@
package bot.molt.android.gateway
package ai.openclaw.android.gateway
import bot.molt.android.SecurePrefs
import ai.openclaw.android.SecurePrefs
class DeviceAuthStore(private val prefs: SecurePrefs) {
fun loadToken(deviceId: String, role: String): String? {

View File

@@ -1,4 +1,4 @@
package bot.molt.android.gateway
package ai.openclaw.android.gateway
import android.content.Context
import android.util.Base64
@@ -21,7 +21,7 @@ data class DeviceIdentity(
class DeviceIdentityStore(context: Context) {
private val json = Json { ignoreUnknownKeys = true }
private val identityFile = File(context.filesDir, "moltbot/identity/device.json")
private val identityFile = File(context.filesDir, "openclaw/identity/device.json")
@Synchronized
fun loadOrCreate(): DeviceIdentity {
@@ -65,9 +65,13 @@ class DeviceIdentityStore(context: Context) {
}
private fun load(): DeviceIdentity? {
return readIdentity(identityFile)
}
private fun readIdentity(file: File): DeviceIdentity? {
return try {
if (!identityFile.exists()) return null
val raw = identityFile.readText(Charsets.UTF_8)
if (!file.exists()) return null
val raw = file.readText(Charsets.UTF_8)
val decoded = json.decodeFromString(DeviceIdentity.serializer(), raw)
if (decoded.deviceId.isBlank() ||
decoded.publicKeyRawBase64.isBlank() ||

View File

@@ -1,4 +1,4 @@
package bot.molt.android.gateway
package ai.openclaw.android.gateway
import android.content.Context
import android.net.ConnectivityManager
@@ -51,9 +51,9 @@ class GatewayDiscovery(
private val nsd = context.getSystemService(NsdManager::class.java)
private val connectivity = context.getSystemService(ConnectivityManager::class.java)
private val dns = DnsResolver.getInstance()
private val serviceType = "_moltbot-gw._tcp."
private val wideAreaDomain = "moltbot.internal."
private val logTag = "Moltbot/GatewayDiscovery"
private val serviceType = "_openclaw-gw._tcp."
private val wideAreaDomain = System.getenv("OPENCLAW_WIDE_AREA_DOMAIN")
private val logTag = "OpenClaw/GatewayDiscovery"
private val localById = ConcurrentHashMap<String, GatewayEndpoint>()
private val unicastById = ConcurrentHashMap<String, GatewayEndpoint>()
@@ -91,7 +91,9 @@ class GatewayDiscovery(
init {
startLocalDiscovery()
startUnicastDiscovery(wideAreaDomain)
if (!wideAreaDomain.isNullOrBlank()) {
startUnicastDiscovery(wideAreaDomain)
}
}
private fun startLocalDiscovery() {

View File

@@ -1,4 +1,4 @@
package bot.molt.android.gateway
package ai.openclaw.android.gateway
data class GatewayEndpoint(
val stableId: String,

View File

@@ -1,3 +1,3 @@
package bot.molt.android.gateway
package ai.openclaw.android.gateway
const val GATEWAY_PROTOCOL_VERSION = 3

View File

@@ -1,4 +1,4 @@
package bot.molt.android.gateway
package ai.openclaw.android.gateway
import android.util.Log
import java.util.Locale
@@ -148,7 +148,7 @@ class GatewaySession(
try {
conn.request("node.event", params, timeoutMs = 8_000)
} catch (err: Throwable) {
Log.w("MoltbotGateway", "node.event failed: ${err.message ?: err::class.java.simpleName}")
Log.w("OpenClawGateway", "node.event failed: ${err.message ?: err::class.java.simpleName}")
}
}
@@ -181,7 +181,7 @@ class GatewaySession(
private val connectNonceDeferred = CompletableDeferred<String?>()
private val client: OkHttpClient = buildClient()
private var socket: WebSocket? = null
private val loggerTag = "MoltbotGateway"
private val loggerTag = "OpenClawGateway"
val remoteAddress: String =
if (endpoint.host.contains(":")) {

View File

@@ -1,4 +1,4 @@
package bot.molt.android.gateway
package ai.openclaw.android.gateway
import android.annotation.SuppressLint
import java.security.MessageDigest

View File

@@ -1,4 +1,4 @@
package bot.molt.android.node
package ai.openclaw.android.node
import android.Manifest
import android.content.Context
@@ -22,7 +22,7 @@ import androidx.camera.video.VideoRecordEvent
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.core.graphics.scale
import bot.molt.android.PermissionRequester
import ai.openclaw.android.PermissionRequester
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
@@ -155,7 +155,7 @@ class CameraCaptureManager(private val context: Context) {
provider.unbindAll()
provider.bindToLifecycle(owner, selector, videoCapture)
val file = File.createTempFile("moltbot-clip-", ".mp4")
val file = File.createTempFile("openclaw-clip-", ".mp4")
val outputOptions = FileOutputOptions.Builder(file).build()
val finalized = kotlinx.coroutines.CompletableDeferred<VideoRecordEvent.Finalize>()
@@ -285,7 +285,7 @@ private suspend fun Context.cameraProvider(): ProcessCameraProvider =
/** Returns (jpegBytes, exifOrientation) so caller can rotate the decoded bitmap. */
private suspend fun ImageCapture.takeJpegWithExif(executor: Executor): Pair<ByteArray, Int> =
suspendCancellableCoroutine { cont ->
val file = File.createTempFile("moltbot-snap-", ".jpg")
val file = File.createTempFile("openclaw-snap-", ".jpg")
val options = ImageCapture.OutputFileOptions.Builder(file).build()
takePicture(
options,

View File

@@ -1,4 +1,4 @@
package bot.molt.android.node
package ai.openclaw.android.node
import android.graphics.Bitmap
import android.graphics.Canvas
@@ -17,7 +17,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import bot.molt.android.BuildConfig
import ai.openclaw.android.BuildConfig
import kotlin.coroutines.resume
class CanvasController {
@@ -84,12 +84,12 @@ class CanvasController {
withWebViewOnMain { wv ->
if (currentUrl == null) {
if (BuildConfig.DEBUG) {
Log.d("MoltbotCanvas", "load scaffold: $scaffoldAssetUrl")
Log.d("OpenClawCanvas", "load scaffold: $scaffoldAssetUrl")
}
wv.loadUrl(scaffoldAssetUrl)
} else {
if (BuildConfig.DEBUG) {
Log.d("MoltbotCanvas", "load url: $currentUrl")
Log.d("OpenClawCanvas", "load url: $currentUrl")
}
wv.loadUrl(currentUrl)
}
@@ -106,7 +106,7 @@ class CanvasController {
val js = """
(() => {
try {
const api = globalThis.__moltbot;
const api = globalThis.__openclaw;
if (!api) return;
if (typeof api.setDebugStatusEnabled === 'function') {
api.setDebugStatusEnabled(${if (enabled) "true" else "false"});

View File

@@ -1,4 +1,4 @@
package bot.molt.android.node
package ai.openclaw.android.node
import kotlin.math.max
import kotlin.math.min

View File

@@ -1,4 +1,4 @@
package bot.molt.android.node
package ai.openclaw.android.node
import android.Manifest
import android.content.Context

View File

@@ -1,4 +1,4 @@
package bot.molt.android.node
package ai.openclaw.android.node
import android.content.Context
import android.hardware.display.DisplayManager
@@ -6,7 +6,7 @@ import android.media.MediaRecorder
import android.media.projection.MediaProjectionManager
import android.os.Build
import android.util.Base64
import bot.molt.android.ScreenCaptureRequester
import ai.openclaw.android.ScreenCaptureRequester
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
@@ -17,13 +17,13 @@ class ScreenRecordManager(private val context: Context) {
data class Payload(val payloadJson: String)
@Volatile private var screenCaptureRequester: ScreenCaptureRequester? = null
@Volatile private var permissionRequester: bot.molt.android.PermissionRequester? = null
@Volatile private var permissionRequester: ai.openclaw.android.PermissionRequester? = null
fun attachScreenCaptureRequester(requester: ScreenCaptureRequester) {
screenCaptureRequester = requester
}
fun attachPermissionRequester(requester: bot.molt.android.PermissionRequester) {
fun attachPermissionRequester(requester: ai.openclaw.android.PermissionRequester) {
permissionRequester = requester
}
@@ -63,7 +63,7 @@ class ScreenRecordManager(private val context: Context) {
val height = metrics.heightPixels
val densityDpi = metrics.densityDpi
val file = File.createTempFile("moltbot-screen-", ".mp4")
val file = File.createTempFile("openclaw-screen-", ".mp4")
if (includeAudio) ensureMicPermission()
val recorder = createMediaRecorder()
@@ -90,7 +90,7 @@ class ScreenRecordManager(private val context: Context) {
val surface = recorder.surface
virtualDisplay =
projection.createVirtualDisplay(
"moltbot-screen",
"openclaw-screen",
width,
height,
densityDpi,

View File

@@ -1,4 +1,4 @@
package bot.molt.android.node
package ai.openclaw.android.node
import android.Manifest
import android.content.Context
@@ -11,7 +11,7 @@ import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.encodeToString
import bot.molt.android.PermissionRequester
import ai.openclaw.android.PermissionRequester
/**
* Sends SMS messages via the Android SMS API.

View File

@@ -1,9 +1,9 @@
package bot.molt.android.protocol
package ai.openclaw.android.protocol
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
object MoltbotCanvasA2UIAction {
object OpenClawCanvasA2UIAction {
fun extractActionName(userAction: JsonObject): String? {
val name =
(userAction["name"] as? JsonPrimitive)
@@ -61,6 +61,6 @@ object MoltbotCanvasA2UIAction {
val err = (error ?: "").replace("\\", "\\\\").replace("\"", "\\\"")
val okLiteral = if (ok) "true" else "false"
val idEscaped = actionId.replace("\\", "\\\\").replace("\"", "\\\"")
return "window.dispatchEvent(new CustomEvent('moltbot:a2ui-action-status', { detail: { id: \"${idEscaped}\", ok: ${okLiteral}, error: \"${err}\" } }));"
return "window.dispatchEvent(new CustomEvent('openclaw:a2ui-action-status', { detail: { id: \"${idEscaped}\", ok: ${okLiteral}, error: \"${err}\" } }));"
}
}

View File

@@ -1,6 +1,6 @@
package bot.molt.android.protocol
package ai.openclaw.android.protocol
enum class MoltbotCapability(val rawValue: String) {
enum class OpenClawCapability(val rawValue: String) {
Canvas("canvas"),
Camera("camera"),
Screen("screen"),
@@ -9,7 +9,7 @@ enum class MoltbotCapability(val rawValue: String) {
Location("location"),
}
enum class MoltbotCanvasCommand(val rawValue: String) {
enum class OpenClawCanvasCommand(val rawValue: String) {
Present("canvas.present"),
Hide("canvas.hide"),
Navigate("canvas.navigate"),
@@ -22,7 +22,7 @@ enum class MoltbotCanvasCommand(val rawValue: String) {
}
}
enum class MoltbotCanvasA2UICommand(val rawValue: String) {
enum class OpenClawCanvasA2UICommand(val rawValue: String) {
Push("canvas.a2ui.push"),
PushJSONL("canvas.a2ui.pushJSONL"),
Reset("canvas.a2ui.reset"),
@@ -33,7 +33,7 @@ enum class MoltbotCanvasA2UICommand(val rawValue: String) {
}
}
enum class MoltbotCameraCommand(val rawValue: String) {
enum class OpenClawCameraCommand(val rawValue: String) {
Snap("camera.snap"),
Clip("camera.clip"),
;
@@ -43,7 +43,7 @@ enum class MoltbotCameraCommand(val rawValue: String) {
}
}
enum class MoltbotScreenCommand(val rawValue: String) {
enum class OpenClawScreenCommand(val rawValue: String) {
Record("screen.record"),
;
@@ -52,7 +52,7 @@ enum class MoltbotScreenCommand(val rawValue: String) {
}
}
enum class MoltbotSmsCommand(val rawValue: String) {
enum class OpenClawSmsCommand(val rawValue: String) {
Send("sms.send"),
;
@@ -61,7 +61,7 @@ enum class MoltbotSmsCommand(val rawValue: String) {
}
}
enum class MoltbotLocationCommand(val rawValue: String) {
enum class OpenClawLocationCommand(val rawValue: String) {
Get("location.get"),
;

View File

@@ -1,4 +1,4 @@
package bot.molt.android.tools
package ai.openclaw.android.tools
import android.content.Context
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui
package ai.openclaw.android.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box

View File

@@ -1,8 +1,8 @@
package bot.molt.android.ui
package ai.openclaw.android.ui
import androidx.compose.runtime.Composable
import bot.molt.android.MainViewModel
import bot.molt.android.ui.chat.ChatSheetContent
import ai.openclaw.android.MainViewModel
import ai.openclaw.android.ui.chat.ChatSheetContent
@Composable
fun ChatSheet(viewModel: MainViewModel) {

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui
package ai.openclaw.android.ui
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
@@ -9,7 +9,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
@Composable
fun MoltbotTheme(content: @Composable () -> Unit) {
fun OpenClawTheme(content: @Composable () -> Unit) {
val context = LocalContext.current
val isDark = isSystemInDarkTheme()
val colorScheme = if (isDark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui
package ai.openclaw.android.ui
import android.annotation.SuppressLint
import android.Manifest
@@ -65,8 +65,8 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import androidx.core.content.ContextCompat
import bot.molt.android.CameraHudKind
import bot.molt.android.MainViewModel
import ai.openclaw.android.CameraHudKind
import ai.openclaw.android.MainViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -333,7 +333,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
disableForceDarkIfSupported(settings)
}
if (isDebuggable) {
Log.d("MoltbotWebView", "userAgent: ${settings.userAgentString}")
Log.d("OpenClawWebView", "userAgent: ${settings.userAgentString}")
}
isScrollContainer = true
overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS
@@ -348,7 +348,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
) {
if (!isDebuggable) return
if (!request.isForMainFrame) return
Log.e("MoltbotWebView", "onReceivedError: ${error.errorCode} ${error.description} ${request.url}")
Log.e("OpenClawWebView", "onReceivedError: ${error.errorCode} ${error.description} ${request.url}")
}
override fun onReceivedHttpError(
@@ -359,14 +359,14 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
if (!isDebuggable) return
if (!request.isForMainFrame) return
Log.e(
"MoltbotWebView",
"OpenClawWebView",
"onReceivedHttpError: ${errorResponse.statusCode} ${errorResponse.reasonPhrase} ${request.url}",
)
}
override fun onPageFinished(view: WebView, url: String?) {
if (isDebuggable) {
Log.d("MoltbotWebView", "onPageFinished: $url")
Log.d("OpenClawWebView", "onPageFinished: $url")
}
viewModel.canvas.onPageFinished()
}
@@ -377,7 +377,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
): Boolean {
if (isDebuggable) {
Log.e(
"MoltbotWebView",
"OpenClawWebView",
"onRenderProcessGone didCrash=${detail.didCrash()} priorityAtExit=${detail.rendererPriorityAtExit()}",
)
}
@@ -390,7 +390,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
if (!isDebuggable) return false
val msg = consoleMessage ?: return false
Log.d(
"MoltbotWebView",
"OpenClawWebView",
"console ${msg.messageLevel()} @ ${msg.sourceId()}:${msg.lineNumber()} ${msg.message()}",
)
return false
@@ -403,10 +403,6 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
viewModel.handleCanvasA2UIActionFromWebView(payload)
}
addJavascriptInterface(a2uiBridge, CanvasA2UIActionBridge.interfaceName)
addJavascriptInterface(
CanvasA2UIActionLegacyBridge(a2uiBridge),
CanvasA2UIActionLegacyBridge.interfaceName,
)
viewModel.canvas.attach(this)
}
},
@@ -428,22 +424,6 @@ private class CanvasA2UIActionBridge(private val onMessage: (String) -> Unit) {
}
companion object {
const val interfaceName: String = "moltbotCanvasA2UIAction"
}
}
private class CanvasA2UIActionLegacyBridge(private val bridge: CanvasA2UIActionBridge) {
@JavascriptInterface
fun canvasAction(payload: String?) {
bridge.postMessage(payload)
}
@JavascriptInterface
fun postMessage(payload: String?) {
bridge.postMessage(payload)
}
companion object {
const val interfaceName: String = "Android"
const val interfaceName: String = "openclawCanvasA2UIAction"
}
}

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui
package ai.openclaw.android.ui
import android.Manifest
import android.content.Context
@@ -58,12 +58,12 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import bot.molt.android.BuildConfig
import bot.molt.android.LocationMode
import bot.molt.android.MainViewModel
import bot.molt.android.NodeForegroundService
import bot.molt.android.VoiceWakeMode
import bot.molt.android.WakeWords
import ai.openclaw.android.BuildConfig
import ai.openclaw.android.LocationMode
import ai.openclaw.android.MainViewModel
import ai.openclaw.android.NodeForegroundService
import ai.openclaw.android.VoiceWakeMode
import ai.openclaw.android.WakeWords
@Composable
fun SettingsSheet(viewModel: MainViewModel) {
@@ -457,7 +457,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
Column(verticalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.fillMaxWidth()) {
ListItem(
headlineContent = { Text("Foreground Only") },
supportingContent = { Text("Listens only while Moltbot is open.") },
supportingContent = { Text("Listens only while OpenClaw is open.") },
trailingContent = {
RadioButton(
selected = voiceWakeMode == VoiceWakeMode.Foreground,
@@ -603,7 +603,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
)
ListItem(
headlineContent = { Text("While Using") },
supportingContent = { Text("Only while Moltbot is open.") },
supportingContent = { Text("Only while OpenClaw is open.") },
trailingContent = {
RadioButton(
selected = locationMode == LocationMode.WhileUsing,
@@ -650,7 +650,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
item {
ListItem(
headlineContent = { Text("Prevent Sleep") },
supportingContent = { Text("Keeps the screen awake while Moltbot is open.") },
supportingContent = { Text("Keeps the screen awake while OpenClaw is open.") },
trailingContent = { Switch(checked = preventSleep, onCheckedChange = viewModel::setPreventSleep) },
)
}

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui
package ai.openclaw.android.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui
package ai.openclaw.android.ui
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui.chat
package ai.openclaw.android.ui.chat
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -38,7 +38,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import bot.molt.android.chat.ChatSessionEntry
import ai.openclaw.android.chat.ChatSessionEntry
@Composable
fun ChatComposer(
@@ -143,7 +143,7 @@ fun ChatComposer(
value = input,
onValueChange = { input = it },
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("Message Clawd") },
placeholder = { Text("Message OpenClaw…") },
minLines = 2,
maxLines = 6,
)

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui.chat
package ai.openclaw.android.ui.chat
import android.graphics.BitmapFactory
import android.util.Base64

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui.chat
package ai.openclaw.android.ui.chat
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -20,8 +20,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.unit.dp
import bot.molt.android.chat.ChatMessage
import bot.molt.android.chat.ChatPendingToolCall
import ai.openclaw.android.chat.ChatMessage
import ai.openclaw.android.chat.ChatPendingToolCall
@Composable
fun ChatMessageListCard(
@@ -103,7 +103,7 @@ private fun EmptyChatHint(modifier: Modifier = Modifier) {
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(
text = "Message Clawd",
text = "Message OpenClaw…",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui.chat
package ai.openclaw.android.ui.chat
import android.graphics.BitmapFactory
import android.util.Base64
@@ -31,10 +31,10 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.Image
import bot.molt.android.chat.ChatMessage
import bot.molt.android.chat.ChatMessageContent
import bot.molt.android.chat.ChatPendingToolCall
import bot.molt.android.tools.ToolDisplayRegistry
import ai.openclaw.android.chat.ChatMessage
import ai.openclaw.android.chat.ChatMessageContent
import ai.openclaw.android.chat.ChatPendingToolCall
import ai.openclaw.android.tools.ToolDisplayRegistry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import androidx.compose.ui.platform.LocalContext

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui.chat
package ai.openclaw.android.ui.chat
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import bot.molt.android.chat.ChatSessionEntry
import ai.openclaw.android.chat.ChatSessionEntry
@Composable
fun ChatSessionsDialog(

View File

@@ -1,4 +1,4 @@
package bot.molt.android.ui.chat
package ai.openclaw.android.ui.chat
import android.content.ContentResolver
import android.net.Uri
@@ -19,8 +19,8 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import bot.molt.android.MainViewModel
import bot.molt.android.chat.OutgoingAttachment
import ai.openclaw.android.MainViewModel
import ai.openclaw.android.chat.OutgoingAttachment
import java.io.ByteArrayOutputStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

View File

@@ -1,6 +1,6 @@
package bot.molt.android.ui.chat
package ai.openclaw.android.ui.chat
import bot.molt.android.chat.ChatSessionEntry
import ai.openclaw.android.chat.ChatSessionEntry
private const val RECENT_WINDOW_MS = 24 * 60 * 60 * 1000L

View File

@@ -1,4 +1,4 @@
package bot.molt.android.voice
package ai.openclaw.android.voice
import android.media.MediaDataSource
import kotlin.math.min

View File

@@ -1,4 +1,4 @@
package bot.molt.android.voice
package ai.openclaw.android.voice
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement

View File

@@ -1,4 +1,4 @@
package bot.molt.android.voice
package ai.openclaw.android.voice
import android.Manifest
import android.content.Context
@@ -20,9 +20,9 @@ import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import android.util.Log
import androidx.core.content.ContextCompat
import bot.molt.android.gateway.GatewaySession
import bot.molt.android.isCanonicalMainSessionKey
import bot.molt.android.normalizeMainKey
import ai.openclaw.android.gateway.GatewaySession
import ai.openclaw.android.isCanonicalMainSessionKey
import ai.openclaw.android.normalizeMainKey
import java.net.HttpURLConnection
import java.net.URL
import java.util.UUID

View File

@@ -1,4 +1,4 @@
package bot.molt.android.voice
package ai.openclaw.android.voice
object VoiceWakeCommandExtractor {
fun extractCommand(text: String, triggerWords: List<String>): String? {

View File

@@ -1,4 +1,4 @@
package bot.molt.android.voice
package ai.openclaw.android.voice
import android.content.Context
import android.content.Intent

View File

@@ -1,3 +1,3 @@
<resources>
<string name="app_name">Moltbot Node</string>
<string name="app_name">OpenClaw Node</string>
</resources>

View File

@@ -1,5 +1,5 @@
<resources>
<style name="Theme.MoltbotNode" parent="Theme.Material3.DayNight.NoActionBar">
<style name="Theme.OpenClawNode" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">false</item>

View File

@@ -4,7 +4,7 @@
<base-config cleartextTrafficPermitted="true" tools:ignore="InsecureBaseConfiguration" />
<!-- Allow HTTP for tailnet/local dev endpoints (e.g. canvas/background web). -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">moltbot.internal</domain>
<domain includeSubdomains="true">openclaw.local</domain>
</domain-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">ts.net</domain>

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
import android.app.Notification
import android.content.Intent

View File

@@ -1,4 +1,4 @@
package bot.molt.android
package ai.openclaw.android
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
@@ -7,12 +7,12 @@ import org.junit.Test
class WakeWordsTest {
@Test
fun parseCommaSeparatedTrimsAndDropsEmpty() {
assertEquals(listOf("clawd", "claude"), WakeWords.parseCommaSeparated(" clawd , claude, , "))
assertEquals(listOf("openclaw", "claude"), WakeWords.parseCommaSeparated(" openclaw , claude, , "))
}
@Test
fun sanitizeTrimsCapsAndFallsBack() {
val defaults = listOf("clawd", "claude")
val defaults = listOf("openclaw", "claude")
val long = "x".repeat(WakeWords.maxWordLength + 10)
val words = listOf(" ", " hello ", long)
@@ -26,7 +26,7 @@ class WakeWordsTest {
@Test
fun sanitizeLimitsWordCount() {
val defaults = listOf("clawd")
val defaults = listOf("openclaw")
val words = (1..(WakeWords.maxWords + 5)).map { "w$it" }
val sanitized = WakeWords.sanitize(words, defaults)
assertEquals(WakeWords.maxWords, sanitized.size)
@@ -36,15 +36,15 @@ class WakeWordsTest {
@Test
fun parseIfChangedSkipsWhenUnchanged() {
val current = listOf("clawd", "claude")
val parsed = WakeWords.parseIfChanged(" clawd , claude ", current)
val current = listOf("openclaw", "claude")
val parsed = WakeWords.parseIfChanged(" openclaw , claude ", current)
assertNull(parsed)
}
@Test
fun parseIfChangedReturnsUpdatedList() {
val current = listOf("clawd")
val parsed = WakeWords.parseIfChanged(" clawd , jarvis ", current)
assertEquals(listOf("clawd", "jarvis"), parsed)
val current = listOf("openclaw")
val parsed = WakeWords.parseIfChanged(" openclaw , jarvis ", current)
assertEquals(listOf("openclaw", "jarvis"), parsed)
}
}

View File

@@ -1,4 +1,4 @@
package bot.molt.android.gateway
package ai.openclaw.android.gateway
import org.junit.Assert.assertEquals
import org.junit.Test
@@ -12,7 +12,7 @@ class BonjourEscapesTest {
@Test
fun decodeDecodesDecimalEscapes() {
assertEquals("Moltbot Gateway", BonjourEscapes.decode("Moltbot\\032Gateway"))
assertEquals("OpenClaw Gateway", BonjourEscapes.decode("OpenClaw\\032Gateway"))
assertEquals("A B", BonjourEscapes.decode("A\\032B"))
assertEquals("Peter\u2019s Mac", BonjourEscapes.decode("Peter\\226\\128\\153s Mac"))
}

View File

@@ -1,4 +1,4 @@
package bot.molt.android.node
package ai.openclaw.android.node
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull

View File

@@ -1,4 +1,4 @@
package bot.molt.android.node
package ai.openclaw.android.node
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue

View File

@@ -1,4 +1,4 @@
package bot.molt.android.node
package ai.openclaw.android.node
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

View File

@@ -1,28 +1,28 @@
package bot.molt.android.protocol
package ai.openclaw.android.protocol
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import org.junit.Assert.assertEquals
import org.junit.Test
class MoltbotCanvasA2UIActionTest {
class OpenClawCanvasA2UIActionTest {
@Test
fun extractActionNameAcceptsNameOrAction() {
val nameObj = Json.parseToJsonElement("{\"name\":\"Hello\"}").jsonObject
assertEquals("Hello", MoltbotCanvasA2UIAction.extractActionName(nameObj))
assertEquals("Hello", OpenClawCanvasA2UIAction.extractActionName(nameObj))
val actionObj = Json.parseToJsonElement("{\"action\":\"Wave\"}").jsonObject
assertEquals("Wave", MoltbotCanvasA2UIAction.extractActionName(actionObj))
assertEquals("Wave", OpenClawCanvasA2UIAction.extractActionName(actionObj))
val fallbackObj =
Json.parseToJsonElement("{\"name\":\" \",\"action\":\"Fallback\"}").jsonObject
assertEquals("Fallback", MoltbotCanvasA2UIAction.extractActionName(fallbackObj))
assertEquals("Fallback", OpenClawCanvasA2UIAction.extractActionName(fallbackObj))
}
@Test
fun formatAgentMessageMatchesSharedSpec() {
val msg =
MoltbotCanvasA2UIAction.formatAgentMessage(
OpenClawCanvasA2UIAction.formatAgentMessage(
actionName = "Get Weather",
sessionKey = "main",
surfaceId = "main",
@@ -40,9 +40,9 @@ class MoltbotCanvasA2UIActionTest {
@Test
fun jsDispatchA2uiStatusIsStable() {
val js = MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId = "a1", ok = true, error = null)
val js = OpenClawCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId = "a1", ok = true, error = null)
assertEquals(
"window.dispatchEvent(new CustomEvent('moltbot:a2ui-action-status', { detail: { id: \"a1\", ok: true, error: \"\" } }));",
"window.dispatchEvent(new CustomEvent('openclaw:a2ui-action-status', { detail: { id: \"a1\", ok: true, error: \"\" } }));",
js,
)
}

View File

@@ -0,0 +1,35 @@
package ai.openclaw.android.protocol
import org.junit.Assert.assertEquals
import org.junit.Test
class OpenClawProtocolConstantsTest {
@Test
fun canvasCommandsUseStableStrings() {
assertEquals("canvas.present", OpenClawCanvasCommand.Present.rawValue)
assertEquals("canvas.hide", OpenClawCanvasCommand.Hide.rawValue)
assertEquals("canvas.navigate", OpenClawCanvasCommand.Navigate.rawValue)
assertEquals("canvas.eval", OpenClawCanvasCommand.Eval.rawValue)
assertEquals("canvas.snapshot", OpenClawCanvasCommand.Snapshot.rawValue)
}
@Test
fun a2uiCommandsUseStableStrings() {
assertEquals("canvas.a2ui.push", OpenClawCanvasA2UICommand.Push.rawValue)
assertEquals("canvas.a2ui.pushJSONL", OpenClawCanvasA2UICommand.PushJSONL.rawValue)
assertEquals("canvas.a2ui.reset", OpenClawCanvasA2UICommand.Reset.rawValue)
}
@Test
fun capabilitiesUseStableStrings() {
assertEquals("canvas", OpenClawCapability.Canvas.rawValue)
assertEquals("camera", OpenClawCapability.Camera.rawValue)
assertEquals("screen", OpenClawCapability.Screen.rawValue)
assertEquals("voiceWake", OpenClawCapability.VoiceWake.rawValue)
}
@Test
fun screenCommandsUseStableStrings() {
assertEquals("screen.record", OpenClawScreenCommand.Record.rawValue)
}
}

View File

@@ -1,6 +1,6 @@
package bot.molt.android.ui.chat
package ai.openclaw.android.ui.chat
import bot.molt.android.chat.ChatSessionEntry
import ai.openclaw.android.chat.ChatSessionEntry
import org.junit.Assert.assertEquals
import org.junit.Test

View File

@@ -1,4 +1,4 @@
package bot.molt.android.voice
package ai.openclaw.android.voice
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull

View File

@@ -1,4 +1,4 @@
package bot.molt.android.voice
package ai.openclaw.android.voice
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
@@ -7,13 +7,13 @@ import org.junit.Test
class VoiceWakeCommandExtractorTest {
@Test
fun extractsCommandAfterTriggerWord() {
val res = VoiceWakeCommandExtractor.extractCommand("Claude take a photo", listOf("clawd", "claude"))
val res = VoiceWakeCommandExtractor.extractCommand("Claude take a photo", listOf("openclaw", "claude"))
assertEquals("take a photo", res)
}
@Test
fun extractsCommandWithPunctuation() {
val res = VoiceWakeCommandExtractor.extractCommand("hey clawd, what's the weather?", listOf("clawd"))
val res = VoiceWakeCommandExtractor.extractCommand("hey openclaw, what's the weather?", listOf("openclaw"))
assertEquals("what's the weather?", res)
}

View File

@@ -1,35 +0,0 @@
package bot.molt.android.protocol
import org.junit.Assert.assertEquals
import org.junit.Test
class MoltbotProtocolConstantsTest {
@Test
fun canvasCommandsUseStableStrings() {
assertEquals("canvas.present", MoltbotCanvasCommand.Present.rawValue)
assertEquals("canvas.hide", MoltbotCanvasCommand.Hide.rawValue)
assertEquals("canvas.navigate", MoltbotCanvasCommand.Navigate.rawValue)
assertEquals("canvas.eval", MoltbotCanvasCommand.Eval.rawValue)
assertEquals("canvas.snapshot", MoltbotCanvasCommand.Snapshot.rawValue)
}
@Test
fun a2uiCommandsUseStableStrings() {
assertEquals("canvas.a2ui.push", MoltbotCanvasA2UICommand.Push.rawValue)
assertEquals("canvas.a2ui.pushJSONL", MoltbotCanvasA2UICommand.PushJSONL.rawValue)
assertEquals("canvas.a2ui.reset", MoltbotCanvasA2UICommand.Reset.rawValue)
}
@Test
fun capabilitiesUseStableStrings() {
assertEquals("canvas", MoltbotCapability.Canvas.rawValue)
assertEquals("camera", MoltbotCapability.Camera.rawValue)
assertEquals("screen", MoltbotCapability.Screen.rawValue)
assertEquals("voiceWake", MoltbotCapability.VoiceWake.rawValue)
}
@Test
fun screenCommandsUseStableStrings() {
assertEquals("screen.record", MoltbotScreenCommand.Record.rawValue)
}
}

View File

@@ -14,5 +14,5 @@ dependencyResolutionManagement {
}
}
rootProject.name = "MoltbotNodeAndroid"
rootProject.name = "OpenClawNodeAndroid"
include(":app")

View File

@@ -1,4 +1,4 @@
# Clawdbot (iOS)
# OpenClaw (iOS)
Internal-only SwiftUI app scaffold.
@@ -11,11 +11,11 @@ brew install swiftformat swiftlint
```bash
cd apps/ios
xcodegen generate
open Clawdbot.xcodeproj
open OpenClaw.xcodeproj
```
## Shared packages
- `../shared/MoltbotKit` — shared types/constants used by iOS (and later macOS bridge + gateway routing).
- `../shared/OpenClawKit` — shared types/constants used by iOS (and later macOS bridge + gateway routing).
## fastlane
```bash

View File

@@ -1,5 +1,5 @@
import AVFoundation
import MoltbotKit
import OpenClawKit
import Foundation
actor CameraController {
@@ -36,7 +36,7 @@ actor CameraController {
}
}
func snap(params: MoltbotCameraSnapParams) async throws -> (
func snap(params: OpenClawCameraSnapParams) async throws -> (
format: String,
base64: String,
width: Int,
@@ -109,7 +109,7 @@ actor CameraController {
height: res.heightPx)
}
func clip(params: MoltbotCameraClipParams) async throws -> (
func clip(params: OpenClawCameraClipParams) async throws -> (
format: String,
base64: String,
durationMs: Int,
@@ -161,9 +161,9 @@ actor CameraController {
await Self.warmUpCaptureSession()
let movURL = FileManager().temporaryDirectory
.appendingPathComponent("moltbot-camera-\(UUID().uuidString).mov")
.appendingPathComponent("openclaw-camera-\(UUID().uuidString).mov")
let mp4URL = FileManager().temporaryDirectory
.appendingPathComponent("moltbot-camera-\(UUID().uuidString).mp4")
.appendingPathComponent("openclaw-camera-\(UUID().uuidString).mp4")
defer {
try? FileManager().removeItem(at: movURL)
@@ -221,7 +221,7 @@ actor CameraController {
}
private nonisolated static func pickCamera(
facing: MoltbotCameraFacing,
facing: OpenClawCameraFacing,
deviceId: String?) -> AVCaptureDevice?
{
if let deviceId, !deviceId.isEmpty {

View File

@@ -1,16 +1,16 @@
import MoltbotChatUI
import MoltbotKit
import OpenClawChatUI
import OpenClawKit
import SwiftUI
struct ChatSheet: View {
@Environment(\.dismiss) private var dismiss
@State private var viewModel: MoltbotChatViewModel
@State private var viewModel: OpenClawChatViewModel
private let userAccent: Color?
init(gateway: GatewayNodeSession, sessionKey: String, userAccent: Color? = nil) {
let transport = IOSGatewayChatTransport(gateway: gateway)
self._viewModel = State(
initialValue: MoltbotChatViewModel(
initialValue: OpenClawChatViewModel(
sessionKey: sessionKey,
transport: transport))
self.userAccent = userAccent
@@ -18,7 +18,7 @@ struct ChatSheet: View {
var body: some View {
NavigationStack {
MoltbotChatView(
OpenClawChatView(
viewModel: self.viewModel,
showsSessionSwitcher: true,
userAccent: self.userAccent)

View File

@@ -1,9 +1,9 @@
import MoltbotChatUI
import MoltbotKit
import MoltbotProtocol
import OpenClawChatUI
import OpenClawKit
import OpenClawProtocol
import Foundation
struct IOSGatewayChatTransport: MoltbotChatTransport, Sendable {
struct IOSGatewayChatTransport: OpenClawChatTransport, Sendable {
private let gateway: GatewayNodeSession
init(gateway: GatewayNodeSession) {
@@ -20,7 +20,7 @@ struct IOSGatewayChatTransport: MoltbotChatTransport, Sendable {
_ = try await self.gateway.request(method: "chat.abort", paramsJSON: json, timeoutSeconds: 10)
}
func listSessions(limit: Int?) async throws -> MoltbotChatSessionsListResponse {
func listSessions(limit: Int?) async throws -> OpenClawChatSessionsListResponse {
struct Params: Codable {
var includeGlobal: Bool
var includeUnknown: Bool
@@ -29,7 +29,7 @@ struct IOSGatewayChatTransport: MoltbotChatTransport, 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(MoltbotChatSessionsListResponse.self, from: res)
return try JSONDecoder().decode(OpenClawChatSessionsListResponse.self, from: res)
}
func setActiveSessionKey(_ sessionKey: String) async throws {
@@ -39,12 +39,12 @@ struct IOSGatewayChatTransport: MoltbotChatTransport, Sendable {
await self.gateway.sendEvent(event: "chat.subscribe", payloadJSON: json)
}
func requestHistory(sessionKey: String) async throws -> MoltbotChatHistoryPayload {
func requestHistory(sessionKey: String) async throws -> OpenClawChatHistoryPayload {
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(MoltbotChatHistoryPayload.self, from: res)
return try JSONDecoder().decode(OpenClawChatHistoryPayload.self, from: res)
}
func sendMessage(
@@ -52,13 +52,13 @@ struct IOSGatewayChatTransport: MoltbotChatTransport, Sendable {
message: String,
thinking: String,
idempotencyKey: String,
attachments: [MoltbotChatAttachmentPayload]) async throws -> MoltbotChatSendResponse
attachments: [OpenClawChatAttachmentPayload]) async throws -> OpenClawChatSendResponse
{
struct Params: Codable {
var sessionKey: String
var message: String
var thinking: String
var attachments: [MoltbotChatAttachmentPayload]?
var attachments: [OpenClawChatAttachmentPayload]?
var timeoutMs: Int
var idempotencyKey: String
}
@@ -73,16 +73,16 @@ struct IOSGatewayChatTransport: MoltbotChatTransport, 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(MoltbotChatSendResponse.self, from: res)
return try JSONDecoder().decode(OpenClawChatSendResponse.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(MoltbotGatewayHealthOK.self, from: res))?.ok ?? true
return (try? JSONDecoder().decode(OpenClawGatewayHealthOK.self, from: res))?.ok ?? true
}
func events() -> AsyncStream<MoltbotChatTransportEvent> {
func events() -> AsyncStream<OpenClawChatTransportEvent> {
AsyncStream { continuation in
let task = Task {
let stream = await self.gateway.subscribeServerEvents()
@@ -97,13 +97,13 @@ struct IOSGatewayChatTransport: MoltbotChatTransport, Sendable {
guard let payload = evt.payload else { break }
let ok = (try? GatewayPayloadDecoding.decode(
payload,
as: MoltbotGatewayHealthOK.self))?.ok ?? true
as: OpenClawGatewayHealthOK.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: MoltbotChatEventPayload.self)
as: OpenClawChatEventPayload.self)
{
continuation.yield(.chat(chatPayload))
}
@@ -111,7 +111,7 @@ struct IOSGatewayChatTransport: MoltbotChatTransport, Sendable {
guard let payload = evt.payload else { break }
if let agentPayload = try? GatewayPayloadDecoding.decode(
payload,
as: MoltbotAgentEventPayload.self)
as: OpenClawAgentEventPayload.self)
{
continuation.yield(.agent(agentPayload))
}

View File

@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import Darwin
import Foundation
import Network
@@ -283,7 +283,7 @@ final class GatewayConnectionController {
caps: self.currentCaps(),
commands: self.currentCommands(),
permissions: [:],
clientId: "moltbot-ios",
clientId: "openclaw-ios",
clientMode: "node",
clientDisplayName: displayName)
}
@@ -304,51 +304,51 @@ final class GatewayConnectionController {
}
private func currentCaps() -> [String] {
var caps = [MoltbotCapability.canvas.rawValue, MoltbotCapability.screen.rawValue]
var caps = [OpenClawCapability.canvas.rawValue, OpenClawCapability.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(MoltbotCapability.camera.rawValue) }
if cameraEnabled { caps.append(OpenClawCapability.camera.rawValue) }
let voiceWakeEnabled = UserDefaults.standard.bool(forKey: VoiceWakePreferences.enabledKey)
if voiceWakeEnabled { caps.append(MoltbotCapability.voiceWake.rawValue) }
if voiceWakeEnabled { caps.append(OpenClawCapability.voiceWake.rawValue) }
let locationModeRaw = UserDefaults.standard.string(forKey: "location.enabledMode") ?? "off"
let locationMode = MoltbotLocationMode(rawValue: locationModeRaw) ?? .off
if locationMode != .off { caps.append(MoltbotCapability.location.rawValue) }
let locationMode = OpenClawLocationMode(rawValue: locationModeRaw) ?? .off
if locationMode != .off { caps.append(OpenClawCapability.location.rawValue) }
return caps
}
private func currentCommands() -> [String] {
var commands: [String] = [
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,
OpenClawCanvasCommand.present.rawValue,
OpenClawCanvasCommand.hide.rawValue,
OpenClawCanvasCommand.navigate.rawValue,
OpenClawCanvasCommand.evalJS.rawValue,
OpenClawCanvasCommand.snapshot.rawValue,
OpenClawCanvasA2UICommand.push.rawValue,
OpenClawCanvasA2UICommand.pushJSONL.rawValue,
OpenClawCanvasA2UICommand.reset.rawValue,
OpenClawScreenCommand.record.rawValue,
OpenClawSystemCommand.notify.rawValue,
OpenClawSystemCommand.which.rawValue,
OpenClawSystemCommand.run.rawValue,
OpenClawSystemCommand.execApprovalsGet.rawValue,
OpenClawSystemCommand.execApprovalsSet.rawValue,
]
let caps = Set(self.currentCaps())
if caps.contains(MoltbotCapability.camera.rawValue) {
commands.append(MoltbotCameraCommand.list.rawValue)
commands.append(MoltbotCameraCommand.snap.rawValue)
commands.append(MoltbotCameraCommand.clip.rawValue)
if caps.contains(OpenClawCapability.camera.rawValue) {
commands.append(OpenClawCameraCommand.list.rawValue)
commands.append(OpenClawCameraCommand.snap.rawValue)
commands.append(OpenClawCameraCommand.clip.rawValue)
}
if caps.contains(MoltbotCapability.location.rawValue) {
commands.append(MoltbotLocationCommand.get.rawValue)
if caps.contains(OpenClawCapability.location.rawValue) {
commands.append(OpenClawLocationCommand.get.rawValue)
}
return commands

View File

@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import Foundation
import Network
import Observation
@@ -52,11 +52,11 @@ final class GatewayDiscoveryModel {
if !self.browsers.isEmpty { return }
self.appendDebugLog("start()")
for domain in MoltbotBonjour.gatewayServiceDomains {
for domain in OpenClawBonjour.gatewayServiceDomains {
let params = NWParameters.tcp
params.includePeerToPeer = true
let browser = NWBrowser(
for: .bonjour(type: MoltbotBonjour.gatewayServiceType, domain: domain),
for: .bonjour(type: OpenClawBonjour.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: " (Moltbot)", with: "")
let stripped = normalized.replacingOccurrences(of: " (OpenClaw)", with: "")
.replacingOccurrences(of: #"\s+\(\d+\)$"#, with: "", options: .regularExpression)
return stripped.trimmingCharacters(in: .whitespacesAndNewlines)
}

View File

@@ -1,11 +1,8 @@
import Foundation
enum GatewaySettingsStore {
private static let gatewayService = "bot.molt.gateway"
private static let legacyGatewayService = "com.clawdbot.gateway"
private static let legacyBridgeService = "com.clawdbot.bridge"
private static let nodeService = "bot.molt.node"
private static let legacyNodeService = "com.clawdbot.node"
private static let gatewayService = "ai.openclaw.gateway"
private static let nodeService = "ai.openclaw.node"
private static let instanceIdDefaultsKey = "node.instanceId"
private static let preferredGatewayStableIDDefaultsKey = "gateway.preferredStableID"
@@ -16,13 +13,6 @@ enum GatewaySettingsStore {
private static let manualTlsDefaultsKey = "gateway.manual.tls"
private static let discoveryDebugLogsDefaultsKey = "gateway.discovery.debugLogs"
private static let legacyPreferredBridgeStableIDDefaultsKey = "bridge.preferredStableID"
private static let legacyLastDiscoveredBridgeStableIDDefaultsKey = "bridge.lastDiscoveredStableID"
private static let legacyManualEnabledDefaultsKey = "bridge.manual.enabled"
private static let legacyManualHostDefaultsKey = "bridge.manual.host"
private static let legacyManualPortDefaultsKey = "bridge.manual.port"
private static let legacyDiscoveryDebugLogsDefaultsKey = "bridge.discovery.debugLogs"
private static let instanceIdAccount = "instanceId"
private static let preferredGatewayStableIDAccount = "preferredStableID"
private static let lastDiscoveredGatewayStableIDAccount = "lastDiscoveredStableID"
@@ -31,7 +21,6 @@ enum GatewaySettingsStore {
self.ensureStableInstanceID()
self.ensurePreferredGatewayStableID()
self.ensureLastDiscoveredGatewayStableID()
self.migrateLegacyDefaults()
}
static func loadStableInstanceID() -> String? {
@@ -42,14 +31,6 @@ enum GatewaySettingsStore {
return value
}
if let legacy = KeychainStore.loadString(service: self.legacyNodeService, account: self.instanceIdAccount)?
.trimmingCharacters(in: .whitespacesAndNewlines),
!legacy.isEmpty
{
_ = KeychainStore.saveString(legacy, service: self.nodeService, account: self.instanceIdAccount)
return legacy
}
return nil
}
@@ -67,19 +48,6 @@ enum GatewaySettingsStore {
return value
}
if let legacy = KeychainStore.loadString(
service: self.legacyGatewayService,
account: self.preferredGatewayStableIDAccount
)?.trimmingCharacters(in: .whitespacesAndNewlines),
!legacy.isEmpty
{
_ = KeychainStore.saveString(
legacy,
service: self.gatewayService,
account: self.preferredGatewayStableIDAccount)
return legacy
}
return nil
}
@@ -100,19 +68,6 @@ enum GatewaySettingsStore {
return value
}
if let legacy = KeychainStore.loadString(
service: self.legacyGatewayService,
account: self.lastDiscoveredGatewayStableIDAccount
)?.trimmingCharacters(in: .whitespacesAndNewlines),
!legacy.isEmpty
{
_ = KeychainStore.saveString(
legacy,
service: self.gatewayService,
account: self.lastDiscoveredGatewayStableIDAccount)
return legacy
}
return nil
}
@@ -128,14 +83,6 @@ enum GatewaySettingsStore {
let token = KeychainStore.loadString(service: self.gatewayService, account: account)?
.trimmingCharacters(in: .whitespacesAndNewlines)
if token?.isEmpty == false { return token }
let legacyAccount = self.legacyBridgeTokenAccount(instanceId: instanceId)
let legacy = KeychainStore.loadString(service: self.legacyBridgeService, account: legacyAccount)?
.trimmingCharacters(in: .whitespacesAndNewlines)
if let legacy, !legacy.isEmpty {
_ = KeychainStore.saveString(legacy, service: self.gatewayService, account: account)
return legacy
}
return nil
}
@@ -164,10 +111,6 @@ enum GatewaySettingsStore {
"gateway-token.\(instanceId)"
}
private static func legacyBridgeTokenAccount(instanceId: String) -> String {
"bridge-token.\(instanceId)"
}
private static func gatewayPasswordAccount(instanceId: String) -> String {
"gateway-password.\(instanceId)"
}
@@ -231,54 +174,4 @@ enum GatewaySettingsStore {
}
}
private static func migrateLegacyDefaults() {
let defaults = UserDefaults.standard
if defaults.string(forKey: self.preferredGatewayStableIDDefaultsKey)?.isEmpty != false,
let legacy = defaults.string(forKey: self.legacyPreferredBridgeStableIDDefaultsKey),
!legacy.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
defaults.set(legacy, forKey: self.preferredGatewayStableIDDefaultsKey)
self.savePreferredGatewayStableID(legacy)
}
if defaults.string(forKey: self.lastDiscoveredGatewayStableIDDefaultsKey)?.isEmpty != false,
let legacy = defaults.string(forKey: self.legacyLastDiscoveredBridgeStableIDDefaultsKey),
!legacy.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
defaults.set(legacy, forKey: self.lastDiscoveredGatewayStableIDDefaultsKey)
self.saveLastDiscoveredGatewayStableID(legacy)
}
if defaults.object(forKey: self.manualEnabledDefaultsKey) == nil,
defaults.object(forKey: self.legacyManualEnabledDefaultsKey) != nil
{
defaults.set(
defaults.bool(forKey: self.legacyManualEnabledDefaultsKey),
forKey: self.manualEnabledDefaultsKey)
}
if defaults.string(forKey: self.manualHostDefaultsKey)?.isEmpty != false,
let legacy = defaults.string(forKey: self.legacyManualHostDefaultsKey),
!legacy.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
defaults.set(legacy, forKey: self.manualHostDefaultsKey)
}
if defaults.integer(forKey: self.manualPortDefaultsKey) == 0,
defaults.integer(forKey: self.legacyManualPortDefaultsKey) > 0
{
defaults.set(
defaults.integer(forKey: self.legacyManualPortDefaultsKey),
forKey: self.manualPortDefaultsKey)
}
if defaults.object(forKey: self.discoveryDebugLogsDefaultsKey) == nil,
defaults.object(forKey: self.legacyDiscoveryDebugLogsDefaultsKey) != nil
{
defaults.set(
defaults.bool(forKey: self.legacyDiscoveryDebugLogsDefaultsKey),
forKey: self.discoveryDebugLogsDefaultsKey)
}
}
}

View File

@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Moltbot</string>
<string>OpenClaw</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconName</key>
@@ -29,20 +29,20 @@
</dict>
<key>NSBonjourServices</key>
<array>
<string>_moltbot-gw._tcp</string>
<string>_openclaw-gw._tcp</string>
</array>
<key>NSCameraUsageDescription</key>
<string>Moltbot can capture photos or short video clips when requested via the gateway.</string>
<string>OpenClaw can capture photos or short video clips when requested via the gateway.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Moltbot discovers and connects to your Moltbot gateway on the local network.</string>
<string>OpenClaw discovers and connects to your OpenClaw gateway on the local network.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Moltbot can share your location in the background when you enable Always.</string>
<string>OpenClaw can share your location in the background when you enable Always.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Moltbot uses your location when you allow location sharing.</string>
<string>OpenClaw uses your location when you allow location sharing.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Moltbot needs microphone access for voice wake.</string>
<string>OpenClaw needs microphone access for voice wake.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Moltbot uses on-device speech recognition for voice wake.</string>
<string>OpenClaw uses on-device speech recognition for voice wake.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>

View File

@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import CoreLocation
import Foundation
@@ -30,7 +30,7 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
return .fullAccuracy
}
func ensureAuthorization(mode: MoltbotLocationMode) async -> CLAuthorizationStatus {
func ensureAuthorization(mode: OpenClawLocationMode) 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: MoltbotLocationGetParams,
desiredAccuracy: MoltbotLocationAccuracy,
params: OpenClawLocationGetParams,
desiredAccuracy: OpenClawLocationAccuracy,
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: MoltbotLocationAccuracy) -> CLLocationAccuracy {
private static func accuracyValue(_ accuracy: OpenClawLocationAccuracy) -> CLLocationAccuracy {
switch accuracy {
case .coarse:
kCLLocationAccuracyKilometer

View File

@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import Network
import Observation
import SwiftUI
@@ -90,7 +90,7 @@ final class NodeAppModel {
}()
guard !userAction.isEmpty else { return }
guard let name = MoltbotCanvasA2UIAction.extractActionName(userAction) else { return }
guard let name = OpenClawCanvasA2UIAction.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 = MoltbotCanvasA2UIAction.compactJSON(userAction["context"])
let contextJSON = OpenClawCanvasA2UIAction.compactJSON(userAction["context"])
let sessionKey = self.mainSessionKey
let messageContext = MoltbotCanvasA2UIAction.AgentMessageContext(
let messageContext = OpenClawCanvasA2UIAction.AgentMessageContext(
actionName: name,
session: .init(key: sessionKey, surfaceId: surfaceId),
component: .init(id: sourceComponentId, host: host, instanceId: instanceId),
contextJSON: contextJSON)
let message = MoltbotCanvasA2UIAction.formatAgentMessage(messageContext)
let message = OpenClawCanvasA2UIAction.formatAgentMessage(messageContext)
let ok: Bool
var errorText: String?
@@ -142,7 +142,7 @@ final class NodeAppModel {
}
}
let js = MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId: actionId, ok: ok, error: errorText)
let js = OpenClawCanvasA2UIAction.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("__moltbot__/a2ui/").absoluteString + "?platform=ios"
return base.appendingPathComponent("__openclaw__/a2ui/").absoluteString + "?platform=ios"
}
private func showA2UIOnConnectIfNeeded() async {
@@ -190,7 +190,7 @@ final class NodeAppModel {
self.talkMode.setEnabled(enabled)
}
func requestLocationPermissions(mode: MoltbotLocationMode) async -> Bool {
func requestLocationPermissions(mode: OpenClawLocationMode) 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: MoltbotNodeError(
error: OpenClawNodeError(
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 moltbot:// links.)
// (Key-based unattended auth is handled on macOS for openclaw:// 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: MoltbotNodeError(
error: OpenClawNodeError(
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: MoltbotNodeError(
error: OpenClawNodeError(
code: .unavailable,
message: "CAMERA_DISABLED: enable Camera in iOS Settings → Camera → Allow Camera"))
}
do {
switch command {
case MoltbotLocationCommand.get.rawValue:
case OpenClawLocationCommand.get.rawValue:
return try await self.handleLocationInvoke(req)
case MoltbotCanvasCommand.present.rawValue,
MoltbotCanvasCommand.hide.rawValue,
MoltbotCanvasCommand.navigate.rawValue,
MoltbotCanvasCommand.evalJS.rawValue,
MoltbotCanvasCommand.snapshot.rawValue:
case OpenClawCanvasCommand.present.rawValue,
OpenClawCanvasCommand.hide.rawValue,
OpenClawCanvasCommand.navigate.rawValue,
OpenClawCanvasCommand.evalJS.rawValue,
OpenClawCanvasCommand.snapshot.rawValue:
return try await self.handleCanvasInvoke(req)
case MoltbotCanvasA2UICommand.reset.rawValue,
MoltbotCanvasA2UICommand.push.rawValue,
MoltbotCanvasA2UICommand.pushJSONL.rawValue:
case OpenClawCanvasA2UICommand.reset.rawValue,
OpenClawCanvasA2UICommand.push.rawValue,
OpenClawCanvasA2UICommand.pushJSONL.rawValue:
return try await self.handleCanvasA2UIInvoke(req)
case MoltbotCameraCommand.list.rawValue,
MoltbotCameraCommand.snap.rawValue,
MoltbotCameraCommand.clip.rawValue:
case OpenClawCameraCommand.list.rawValue,
OpenClawCameraCommand.snap.rawValue,
OpenClawCameraCommand.clip.rawValue:
return try await self.handleCameraInvoke(req)
case MoltbotScreenCommand.record.rawValue:
case OpenClawScreenCommand.record.rawValue:
return try await self.handleScreenRecordInvoke(req)
default:
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
error: OpenClawNodeError(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: MoltbotNodeError(code: .unavailable, message: error.localizedDescription))
error: OpenClawNodeError(code: .unavailable, message: error.localizedDescription))
}
}
@@ -570,7 +570,7 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: MoltbotNodeError(
error: OpenClawNodeError(
code: .unavailable,
message: "LOCATION_DISABLED: enable Location in Settings"))
}
@@ -578,12 +578,12 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: MoltbotNodeError(
error: OpenClawNodeError(
code: .backgroundUnavailable,
message: "LOCATION_BACKGROUND_UNAVAILABLE: background location requires Always"))
}
let params = (try? Self.decodeParams(MoltbotLocationGetParams.self, from: req.paramsJSON)) ??
MoltbotLocationGetParams()
let params = (try? Self.decodeParams(OpenClawLocationGetParams.self, from: req.paramsJSON)) ??
OpenClawLocationGetParams()
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: MoltbotNodeError(
error: OpenClawNodeError(
code: .unavailable,
message: "LOCATION_PERMISSION_REQUIRED: grant Location permission"))
}
@@ -599,7 +599,7 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: MoltbotNodeError(
error: OpenClawNodeError(
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 = MoltbotLocationPayload(
let payload = OpenClawLocationPayload(
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 MoltbotCanvasCommand.present.rawValue:
let params = (try? Self.decodeParams(MoltbotCanvasPresentParams.self, from: req.paramsJSON)) ??
MoltbotCanvasPresentParams()
case OpenClawCanvasCommand.present.rawValue:
let params = (try? Self.decodeParams(OpenClawCanvasPresentParams.self, from: req.paramsJSON)) ??
OpenClawCanvasPresentParams()
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 MoltbotCanvasCommand.hide.rawValue:
case OpenClawCanvasCommand.hide.rawValue:
return BridgeInvokeResponse(id: req.id, ok: true)
case MoltbotCanvasCommand.navigate.rawValue:
let params = try Self.decodeParams(MoltbotCanvasNavigateParams.self, from: req.paramsJSON)
case OpenClawCanvasCommand.navigate.rawValue:
let params = try Self.decodeParams(OpenClawCanvasNavigateParams.self, from: req.paramsJSON)
self.screen.navigate(to: params.url)
return BridgeInvokeResponse(id: req.id, ok: true)
case MoltbotCanvasCommand.evalJS.rawValue:
let params = try Self.decodeParams(MoltbotCanvasEvalParams.self, from: req.paramsJSON)
case OpenClawCanvasCommand.evalJS.rawValue:
let params = try Self.decodeParams(OpenClawCanvasEvalParams.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 MoltbotCanvasCommand.snapshot.rawValue:
let params = try? Self.decodeParams(MoltbotCanvasSnapshotParams.self, from: req.paramsJSON)
case OpenClawCanvasCommand.snapshot.rawValue:
let params = try? Self.decodeParams(OpenClawCanvasSnapshotParams.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: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
error: OpenClawNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
}
}
private func handleCanvasA2UIInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
let command = req.command
switch command {
case MoltbotCanvasA2UICommand.reset.rawValue:
case OpenClawCanvasA2UICommand.reset.rawValue:
guard let a2uiUrl = await self.resolveA2UIHostURL() else {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: MoltbotNodeError(
error: OpenClawNodeError(
code: .unavailable,
message: "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host"))
}
@@ -692,31 +692,32 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: MoltbotNodeError(
error: OpenClawNodeError(
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 moltbotA2UI" });
return JSON.stringify(globalThis.clawdbotA2UI.reset());
const host = globalThis.openclawA2UI;
if (!host) return JSON.stringify({ ok: false, error: "missing openclawA2UI" });
return JSON.stringify(host.reset());
})()
""")
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: json)
case MoltbotCanvasA2UICommand.push.rawValue, MoltbotCanvasA2UICommand.pushJSONL.rawValue:
case OpenClawCanvasA2UICommand.push.rawValue, OpenClawCanvasA2UICommand.pushJSONL.rawValue:
let messages: [AnyCodable]
if command == MoltbotCanvasA2UICommand.pushJSONL.rawValue {
let params = try Self.decodeParams(MoltbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
messages = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
if command == OpenClawCanvasA2UICommand.pushJSONL.rawValue {
let params = try Self.decodeParams(OpenClawCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
messages = try OpenClawCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
} else {
do {
let params = try Self.decodeParams(MoltbotCanvasA2UIPushParams.self, from: req.paramsJSON)
let params = try Self.decodeParams(OpenClawCanvasA2UIPushParams.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(MoltbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
messages = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
let params = try Self.decodeParams(OpenClawCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
messages = try OpenClawCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
}
}
@@ -724,7 +725,7 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: MoltbotNodeError(
error: OpenClawNodeError(
code: .unavailable,
message: "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host"))
}
@@ -733,18 +734,19 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: MoltbotNodeError(
error: OpenClawNodeError(
code: .unavailable,
message: "A2UI_HOST_UNAVAILABLE: A2UI host not reachable"))
}
let messagesJSON = try MoltbotCanvasA2UIJSONL.encodeMessagesJSONArray(messages)
let messagesJSON = try OpenClawCanvasA2UIJSONL.encodeMessagesJSONArray(messages)
let js = """
(() => {
try {
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing moltbotA2UI" });
const host = globalThis.openclawA2UI;
if (!host) return JSON.stringify({ ok: false, error: "missing openclawA2UI" });
const messages = \(messagesJSON);
return JSON.stringify(globalThis.clawdbotA2UI.applyMessages(messages));
return JSON.stringify(host.applyMessages(messages));
} catch (e) {
return JSON.stringify({ ok: false, error: String(e?.message ?? e) });
}
@@ -756,24 +758,24 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
error: OpenClawNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
}
}
private func handleCameraInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
switch req.command {
case MoltbotCameraCommand.list.rawValue:
case OpenClawCameraCommand.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 MoltbotCameraCommand.snap.rawValue:
case OpenClawCameraCommand.snap.rawValue:
self.showCameraHUD(text: "Taking photo…", kind: .photo)
self.triggerCameraFlash()
let params = (try? Self.decodeParams(MoltbotCameraSnapParams.self, from: req.paramsJSON)) ??
MoltbotCameraSnapParams()
let params = (try? Self.decodeParams(OpenClawCameraSnapParams.self, from: req.paramsJSON)) ??
OpenClawCameraSnapParams()
let res = try await self.camera.snap(params: params)
struct Payload: Codable {
@@ -789,9 +791,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 MoltbotCameraCommand.clip.rawValue:
let params = (try? Self.decodeParams(MoltbotCameraClipParams.self, from: req.paramsJSON)) ??
MoltbotCameraClipParams()
case OpenClawCameraCommand.clip.rawValue:
let params = (try? Self.decodeParams(OpenClawCameraClipParams.self, from: req.paramsJSON)) ??
OpenClawCameraClipParams()
let suspended = (params.includeAudio ?? true) ? self.voiceWake.suspendForExternalAudioCapture() : false
defer { self.voiceWake.resumeAfterExternalAudioCapture(wasSuspended: suspended) }
@@ -816,13 +818,13 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
error: OpenClawNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
}
}
private func handleScreenRecordInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
let params = (try? Self.decodeParams(MoltbotScreenRecordParams.self, from: req.paramsJSON)) ??
MoltbotScreenRecordParams()
let params = (try? Self.decodeParams(OpenClawScreenRecordParams.self, from: req.paramsJSON)) ??
OpenClawScreenRecordParams()
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 +862,9 @@ final class NodeAppModel {
}
private extension NodeAppModel {
func locationMode() -> MoltbotLocationMode {
func locationMode() -> OpenClawLocationMode {
let raw = UserDefaults.standard.string(forKey: "location.enabledMode") ?? "off"
return MoltbotLocationMode(rawValue: raw) ?? .off
return OpenClawLocationMode(rawValue: raw) ?? .off
}
func isLocationPreciseEnabled() -> Bool {

View File

@@ -1,7 +1,7 @@
import SwiftUI
@main
struct MoltbotApp: App {
struct OpenClawApp: App {
@State private var appModel: NodeAppModel
@State private var gatewayController: GatewayConnectionController
@Environment(\.scenePhase) private var scenePhase

View File

@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import Observation
import SwiftUI
import WebKit
@@ -13,7 +13,7 @@ final class ScreenController {
var urlString: String = ""
var errorText: String?
/// Callback invoked when a moltbot:// deep link is tapped in the canvas
/// Callback invoked when an openclaw:// 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.__moltbot;
const api = globalThis.__openclaw;
if (!api) return;
if (typeof api.setDebugStatusEnabled === 'function') {
api.setDebugStatusEnabled(\(enabled ? "true" : "false"));
@@ -124,7 +124,8 @@ final class ScreenController {
let res = try await self.eval(javaScript: """
(() => {
try {
return !!globalThis.clawdbotA2UI && typeof globalThis.clawdbotA2UI.applyMessages === 'function';
const host = globalThis.openclawA2UI;
return !!host && typeof host.applyMessages === 'function';
} catch (_) { return false; }
})()
""")
@@ -184,7 +185,7 @@ final class ScreenController {
func snapshotBase64(
maxWidth: CGFloat? = nil,
format: MoltbotCanvasSnapshotFormat,
format: OpenClawCanvasSnapshotFormat,
quality: Double? = nil) async throws -> String
{
let config = WKSnapshotConfiguration()
@@ -229,7 +230,7 @@ final class ScreenController {
subdirectory: String)
-> URL?
{
let bundle = MoltbotKitResources.bundle
let bundle = OpenClawKitResources.bundle
return bundle.url(forResource: name, withExtension: ext, subdirectory: subdirectory)
?? bundle.url(forResource: name, withExtension: ext)
}
@@ -342,7 +343,7 @@ extension Double {
// MARK: - Navigation Delegate
/// Handles navigation policy to intercept moltbot:// deep links from canvas
/// Handles navigation policy to intercept openclaw:// deep links from canvas
@MainActor
private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
weak var controller: ScreenController?
@@ -357,8 +358,8 @@ private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
return
}
// Intercept moltbot:// deep links
if url.scheme == "moltbot" {
// Intercept openclaw:// deep links.
if url.scheme?.lowercased() == "openclaw" {
decisionHandler(.cancel)
self.controller?.onDeepLink?(url)
return
@@ -386,14 +387,13 @@ private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
}
private final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
static let messageName = "moltbotCanvasA2UIAction"
static let legacyMessageNames = ["canvas", "a2ui", "userAction", "action"]
static let handlerNames = [messageName] + legacyMessageNames
static let messageName = "openclawCanvasA2UIAction"
static let handlerNames = [messageName]
weak var controller: ScreenController?
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) {
guard message.name == Self.messageName else { return }
guard Self.handlerNames.contains(message.name) else { return }
guard let controller else { return }
guard let url = message.webView?.url else { return }

View File

@@ -105,7 +105,7 @@ final class ScreenRecordService: @unchecked Sendable {
return URL(fileURLWithPath: outPath)
}
return FileManager().temporaryDirectory
.appendingPathComponent("moltbot-screen-record-\(UUID().uuidString).mp4")
.appendingPathComponent("openclaw-screen-record-\(UUID().uuidString).mp4")
}
private func startCapture(

View File

@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import SwiftUI
struct ScreenTab: View {

View File

@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
import SwiftUI
import WebKit

View File

@@ -1,4 +1,4 @@
import MoltbotKit
import OpenClawKit
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 = MoltbotLocationMode.off.rawValue
@AppStorage("location.enabledMode") private var locationEnabledModeRaw: String = OpenClawLocationMode.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 = MoltbotLocationMode.off.rawValue
@State private var lastLocationModeRaw: String = OpenClawLocationMode.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(MoltbotLocationMode.off.rawValue)
Text("While Using").tag(MoltbotLocationMode.whileUsing.rawValue)
Text("Always").tag(MoltbotLocationMode.always.rawValue)
Text("Off").tag(OpenClawLocationMode.off.rawValue)
Text("While Using").tag(OpenClawLocationMode.whileUsing.rawValue)
Text("Always").tag(OpenClawLocationMode.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 Moltbot is open.")
Text("Keeps the screen awake while OpenClaw 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 = MoltbotLocationMode(rawValue: newValue) else { return }
guard let mode = OpenClawLocationMode(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: MoltbotLocationMode {
MoltbotLocationMode(rawValue: self.locationEnabledModeRaw) ?? .off
private var locationMode: OpenClawLocationMode {
OpenClawLocationMode(rawValue: self.locationEnabledModeRaw) ?? .off
}
private func appVersion() -> String {

View File

@@ -36,7 +36,7 @@ struct VoiceWakeWordsSettingsView: View {
Text("Wake Words")
} footer: {
Text(
"Moltbot reacts when any trigger appears in a transcription. "
"OpenClaw reacts when any trigger appears in a transcription. "
+ "Keep them short to avoid false positives.")
}
}

View File

@@ -1,6 +1,6 @@
import AVFAudio
import MoltbotKit
import MoltbotProtocol
import OpenClawKit
import OpenClawProtocol
import Foundation
import Observation
import OSLog

View File

@@ -5,7 +5,7 @@ enum VoiceWakePreferences {
static let triggerWordsKey = "voiceWake.triggerWords"
// Keep defaults aligned with the mac app.
static let defaultTriggerWords: [String] = ["clawd", "claude"]
static let defaultTriggerWords: [String] = ["openclaw", "claude"]
static let maxWords = 32
static let maxWordLength = 64

View File

@@ -6,7 +6,7 @@ Sources/Gateway/KeychainStore.swift
Sources/Camera/CameraController.swift
Sources/Chat/ChatSheet.swift
Sources/Chat/IOSGatewayChatTransport.swift
Sources/ClawdbotApp.swift
Sources/OpenClawApp.swift
Sources/Location/LocationService.swift
Sources/Model/NodeAppModel.swift
Sources/RootCanvas.swift
@@ -24,37 +24,37 @@ Sources/Status/VoiceWakeToast.swift
Sources/Voice/VoiceTab.swift
Sources/Voice/VoiceWakeManager.swift
Sources/Voice/VoiceWakePreferences.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatComposer.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatMarkdownRenderer.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatMarkdownPreprocessor.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatMessageViews.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatModels.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatPayloadDecoding.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatSessions.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatSheets.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatTheme.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatTransport.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatView.swift
../shared/MoltbotKit/Sources/MoltbotChatUI/ChatViewModel.swift
../shared/MoltbotKit/Sources/MoltbotKit/AnyCodable.swift
../shared/MoltbotKit/Sources/MoltbotKit/BonjourEscapes.swift
../shared/MoltbotKit/Sources/MoltbotKit/BonjourTypes.swift
../shared/MoltbotKit/Sources/MoltbotKit/BridgeFrames.swift
../shared/MoltbotKit/Sources/MoltbotKit/CameraCommands.swift
../shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UIAction.swift
../shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UICommands.swift
../shared/MoltbotKit/Sources/MoltbotKit/CanvasA2UIJSONL.swift
../shared/MoltbotKit/Sources/MoltbotKit/CanvasCommandParams.swift
../shared/MoltbotKit/Sources/MoltbotKit/CanvasCommands.swift
../shared/MoltbotKit/Sources/MoltbotKit/Capabilities.swift
../shared/MoltbotKit/Sources/MoltbotKit/ClawdbotKitResources.swift
../shared/MoltbotKit/Sources/MoltbotKit/DeepLinks.swift
../shared/MoltbotKit/Sources/MoltbotKit/JPEGTranscoder.swift
../shared/MoltbotKit/Sources/MoltbotKit/NodeError.swift
../shared/MoltbotKit/Sources/MoltbotKit/ScreenCommands.swift
../shared/MoltbotKit/Sources/MoltbotKit/StoragePaths.swift
../shared/MoltbotKit/Sources/MoltbotKit/SystemCommands.swift
../shared/MoltbotKit/Sources/MoltbotKit/TalkDirective.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownPreprocessor.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatModels.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatPayloadDecoding.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatSessions.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatSheets.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatTheme.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatTransport.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift
../shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift
../shared/OpenClawKit/Sources/OpenClawKit/AnyCodable.swift
../shared/OpenClawKit/Sources/OpenClawKit/BonjourEscapes.swift
../shared/OpenClawKit/Sources/OpenClawKit/BonjourTypes.swift
../shared/OpenClawKit/Sources/OpenClawKit/BridgeFrames.swift
../shared/OpenClawKit/Sources/OpenClawKit/CameraCommands.swift
../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UIAction.swift
../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UICommands.swift
../shared/OpenClawKit/Sources/OpenClawKit/CanvasA2UIJSONL.swift
../shared/OpenClawKit/Sources/OpenClawKit/CanvasCommandParams.swift
../shared/OpenClawKit/Sources/OpenClawKit/CanvasCommands.swift
../shared/OpenClawKit/Sources/OpenClawKit/Capabilities.swift
../shared/OpenClawKit/Sources/OpenClawKit/OpenClawKitResources.swift
../shared/OpenClawKit/Sources/OpenClawKit/DeepLinks.swift
../shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift
../shared/OpenClawKit/Sources/OpenClawKit/NodeError.swift
../shared/OpenClawKit/Sources/OpenClawKit/ScreenCommands.swift
../shared/OpenClawKit/Sources/OpenClawKit/StoragePaths.swift
../shared/OpenClawKit/Sources/OpenClawKit/SystemCommands.swift
../shared/OpenClawKit/Sources/OpenClawKit/TalkDirective.swift
../../Swabble/Sources/SwabbleKit/WakeWordGate.swift
Sources/Voice/TalkModeManager.swift
Sources/Voice/TalkOrbOverlay.swift

View File

@@ -1,6 +1,6 @@
import SwiftUI
import Testing
@testable import Moltbot
@testable import OpenClaw
@Suite struct AppCoverageTests {
@Test @MainActor func nodeAppModelUpdatesBackgroundedState() {

View File

@@ -1,5 +1,5 @@
import Testing
@testable import Moltbot
@testable import OpenClaw
@Suite struct CameraControllerClampTests {
@Test func clampQualityDefaultsAndBounds() {

View File

@@ -1,5 +1,5 @@
import Testing
@testable import Moltbot
@testable import OpenClaw
@Suite struct CameraControllerErrorTests {
@Test func errorDescriptionsAreStable() {

View File

@@ -1,15 +1,15 @@
import MoltbotKit
import OpenClawKit
import Foundation
import Testing
@Suite struct DeepLinkParserTests {
@Test func parseRejectsUnknownHost() {
let url = URL(string: "moltbot://nope?message=hi")!
let url = URL(string: "openclaw://nope?message=hi")!
#expect(DeepLinkParser.parse(url) == nil)
}
@Test func parseHostIsCaseInsensitive() {
let url = URL(string: "moltbot://AGENT?message=Hello")!
let url = URL(string: "openclaw://AGENT?message=Hello")!
#expect(DeepLinkParser.parse(url) == .agent(.init(
message: "Hello",
sessionKey: nil,
@@ -21,19 +21,19 @@ import Testing
key: nil)))
}
@Test func parseRejectsNonMoltbotScheme() {
@Test func parseRejectsNonOpenClawScheme() {
let url = URL(string: "https://example.com/agent?message=hi")!
#expect(DeepLinkParser.parse(url) == nil)
}
@Test func parseRejectsEmptyMessage() {
let url = URL(string: "moltbot://agent?message=%20%20%0A")!
let url = URL(string: "openclaw://agent?message=%20%20%0A")!
#expect(DeepLinkParser.parse(url) == nil)
}
@Test func parseAgentLinkParsesCommonFields() {
let url =
URL(string: "moltbot://agent?message=Hello&deliver=1&sessionKey=node-test&thinking=low&timeoutSeconds=30")!
URL(string: "openclaw://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: "moltbot://agent?message=Hello%20World&deliver=1&to=%2B15551234567&channel=whatsapp&key=secret")!
string: "openclaw://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: "moltbot://agent?message=Hello&timeoutSeconds=-1")!
let url = URL(string: "openclaw://agent?message=Hello&timeoutSeconds=-1")!
#expect(DeepLinkParser.parse(url) == .agent(.init(
message: "Hello",
sessionKey: nil,

View File

@@ -1,8 +1,8 @@
import MoltbotKit
import OpenClawKit
import Foundation
import Testing
import UIKit
@testable import Moltbot
@testable import OpenClaw
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": MoltbotLocationMode.always.rawValue,
"location.enabledMode": OpenClawLocationMode.always.rawValue,
VoiceWakePreferences.enabledKey: true,
]) {
let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
let caps = Set(controller._test_currentCaps())
#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))
#expect(caps.contains(OpenClawCapability.canvas.rawValue))
#expect(caps.contains(OpenClawCapability.screen.rawValue))
#expect(caps.contains(OpenClawCapability.camera.rawValue))
#expect(caps.contains(OpenClawCapability.location.rawValue))
#expect(caps.contains(OpenClawCapability.voiceWake.rawValue))
}
}
@Test @MainActor func currentCommandsIncludeLocationWhenEnabled() {
withUserDefaults([
"node.instanceId": "ios-test",
"location.enabledMode": MoltbotLocationMode.whileUsing.rawValue,
"location.enabledMode": OpenClawLocationMode.whileUsing.rawValue,
]) {
let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
let commands = Set(controller._test_currentCommands())
#expect(commands.contains(MoltbotLocationCommand.get.rawValue))
#expect(commands.contains(OpenClawLocationCommand.get.rawValue))
}
}
}

View File

@@ -1,5 +1,5 @@
import Testing
@testable import Moltbot
@testable import OpenClaw
@Suite(.serialized) struct GatewayDiscoveryModelTests {
@Test @MainActor func debugLoggingCapturesLifecycleAndResets() {

View File

@@ -1,17 +1,17 @@
import MoltbotKit
import OpenClawKit
import Network
import Testing
@testable import Moltbot
@testable import OpenClaw
@Suite struct GatewayEndpointIDTests {
@Test func stableIDForServiceDecodesAndNormalizesName() {
let endpoint = NWEndpoint.service(
name: "Moltbot\\032Gateway \\032 Node\n",
type: "_moltbot-gw._tcp",
name: "OpenClaw\\032Gateway \\032 Node\n",
type: "_openclaw-gw._tcp",
domain: "local.",
interface: nil)
#expect(GatewayEndpointID.stableID(endpoint) == "_moltbot-gw._tcp|local.|Moltbot Gateway Node")
#expect(GatewayEndpointID.stableID(endpoint) == "_openclaw-gw._tcp|local.|OpenClaw Gateway Node")
}
@Test func stableIDForNonServiceUsesEndpointDescription() {
@@ -21,8 +21,8 @@ import Testing
@Test func prettyDescriptionDecodesBonjourEscapes() {
let endpoint = NWEndpoint.service(
name: "Moltbot\\032Gateway",
type: "_moltbot-gw._tcp",
name: "OpenClaw\\032Gateway",
type: "_openclaw-gw._tcp",
domain: "local.",
interface: nil)

View File

@@ -1,6 +1,6 @@
import Foundation
import Testing
@testable import Moltbot
@testable import OpenClaw
private struct KeychainEntry: Hashable {
let service: String

View File

@@ -1,6 +1,6 @@
import MoltbotKit
import OpenClawKit
import Testing
@testable import Moltbot
@testable import OpenClaw
@Suite struct IOSGatewayChatTransportTests {
@Test func requestsFailFastWhenGatewayNotConnected() async {

Some files were not shown because too many files have changed in this diff Show More