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")