@@ -51,11 +51,16 @@ class ClawRunner(private val context: Context) {
5151 )
5252
5353 private const val DEFAULT_MODEL = " claude-opus-4-6"
54- private const val DEFAULT_MAX_TOKENS = 150
55- private const val DEFAULT_SYSTEM_PROMPT =
54+ private const val LEGACY_DEFAULT_SYSTEM_PROMPT =
5655 " You are ClawWatch, a smart and relaxed voice presence on a watch. " +
5756 " Respond in 1-3 short sentences maximum. No markdown, no lists, no bullet points. " +
5857 " Use plain spoken language. Be natural, helpful, and a little playful when it fits, without sounding formal, salesy, or robotic."
58+ private const val DEFAULT_MAX_TOKENS = 150
59+ private const val DEFAULT_SYSTEM_PROMPT =
60+ " You are ClawWatch, an intelligent and relaxed companion living on a watch. " +
61+ " Reply in 1 to 3 short spoken sentences. No markdown, no bullet points, no stiff assistant phrasing. " +
62+ " Sound natural, grounded, and warm. You can help with the wearer's body, their team rooms, timers, and live information. " +
63+ " Do not keep reminding people that you are a smartwatch unless they ask."
5964
6065 private const val MAX_CONTEXT_MESSAGES = 10
6166 private const val MAX_CONTEXT_CHARS_PER_MESSAGE = 600
@@ -95,22 +100,13 @@ class ClawRunner(private val context: Context) {
95100 fun saveMaxTokens (n : Int ) = prefs.edit().putInt(PREF_MAX_TOKENS , n).apply ()
96101 fun saveRagMode (mode : String ) = prefs.edit().putString(PREF_RAG_MODE , mode).apply ()
97102
98- private fun getStringSetting (key : String , default : String? = null): String? {
99- val secure = prefs.getString(key, null )
100- if (! secure.isNullOrBlank()) return secure
101- val legacy = context
102- .getSharedPreferences(" clawwatch_prefs" , Context .MODE_PRIVATE )
103- .getString(key, null )
104- return legacy ? : default
105- }
106-
107103 fun hasApiKey (): Boolean = prefs.getString(PREF_API_KEY , null )?.isNotBlank() == true
108104 private fun getApiKey (): String? = prefs.getString(PREF_API_KEY , null )
109105 private fun getBraveKey (): String? = prefs.getString(PREF_BRAVE_KEY , null )
110106 private fun getTavilyKey (): String? = prefs.getString(PREF_TAVILY_KEY , null )
111- private fun getAntFarmKey (): String? = getStringSetting (PREF_ANTFARM_KEY )
107+ private fun getAntFarmKey (): String? = prefs.getString (PREF_ANTFARM_KEY , null )
112108 private fun getAntFarmRooms (): List <String > =
113- (getStringSetting (PREF_ANTFARM_ROOMS , DEFAULT_FAMILY_ROOMS ) ? : DEFAULT_FAMILY_ROOMS )
109+ (prefs.getString (PREF_ANTFARM_ROOMS , DEFAULT_FAMILY_ROOMS ) ? : DEFAULT_FAMILY_ROOMS )
114110 .split(' ,' )
115111 .map { it.trim() }
116112 .filter { it.isNotBlank() }
@@ -133,9 +129,17 @@ class ClawRunner(private val context: Context) {
133129 context.assets.open(assetName).use { it.copyTo(configFile.outputStream()) }
134130 }
135131 buildCaBundle()
132+ migrateDefaultPromptIfNeeded()
136133 Log .i(TAG , " ClawRunner ready, home=${homeDir.absolutePath} , rag=${getRagMode()} " )
137134 }
138135
136+ private fun migrateDefaultPromptIfNeeded () {
137+ val current = prefs.getString(PREF_SYSTEM_PROMPT , null )?.trim()
138+ if (current.isNullOrEmpty() || current == LEGACY_DEFAULT_SYSTEM_PROMPT ) {
139+ prefs.edit().putString(PREF_SYSTEM_PROMPT , DEFAULT_SYSTEM_PROMPT ).apply ()
140+ }
141+ }
142+
139143 private fun buildCaBundle () {
140144 if (caBundleFile.exists()) return
141145 val certDirs = listOf (" /apex/com.android.conscrypt/cacerts" , " /system/etc/security/cacerts" )
@@ -436,58 +440,59 @@ class ClawRunner(private val context: Context) {
436440 )
437441 }
438442
439- suspend fun postMessageToRoom (room : String , message : String ): Result <String > = withContext(Dispatchers .IO ) {
440- val antFarmKey = getAntFarmKey()
441- ? : return @withContext Result .failure(
442- RuntimeException (" room access key is missing" )
443- )
444-
445- val targetRoom = room.trim()
446- .ifBlank { getAntFarmRooms().firstOrNull().orEmpty() }
447- .ifBlank {
448- return @withContext Result .failure(RuntimeException (" room name is missing" ))
449- }
450-
451- val cleanBody = message
452- .trim()
453- .replace(Regex (" \\ s+" ), " " )
454- .take(500 )
455- if (cleanBody.isBlank()) {
456- return @withContext Result .failure(RuntimeException (" message was empty" ))
457- }
443+ suspend fun postRoomMessage (message : String , requestedRoom : String? = null): Result <String > =
444+ withContext(Dispatchers .IO ) {
445+ val antFarmKey = getAntFarmKey()
446+ ? : return @withContext Result .failure(
447+ RuntimeException (" I don't have room posting access configured yet." )
448+ )
449+ val room = resolveTargetRoom(requestedRoom)
450+ ? : return @withContext Result .failure(
451+ RuntimeException (" I couldn't find a room to post to." )
452+ )
458453
459- return @withContext try {
460- val encodedRoom = URLEncoder .encode(targetRoom, " UTF-8" )
461- val url = URL (" https://antfarm.world/api/v1/rooms/$encodedRoom /messages" )
462- val conn = url.openConnection() as HttpURLConnection
463- conn.requestMethod = " POST"
464- conn.setRequestProperty(" Accept" , " application/json" )
465- conn.setRequestProperty(" Authorization" , " Bearer $antFarmKey " )
466- conn.setRequestProperty(" Content-Type" , " application/json" )
467- conn.connectTimeout = 10_000
468- conn.readTimeout = 10_000
469- conn.doOutput = true
470- val payload = JSONObject ().apply { put(" body" , cleanBody) }.toString()
471- OutputStreamWriter (conn.outputStream).use { it.write(payload) }
454+ return @withContext try {
455+ val encodedRoom = URLEncoder .encode(room, " UTF-8" )
456+ val url = URL (" https://antfarm.world/api/v1/rooms/$encodedRoom /messages" )
457+ val conn = url.openConnection() as HttpURLConnection
458+ conn.requestMethod = " POST"
459+ conn.setRequestProperty(" Content-Type" , " application/json" )
460+ conn.setRequestProperty(" Accept" , " application/json" )
461+ conn.setRequestProperty(" X-API-Key" , antFarmKey)
462+ conn.connectTimeout = 10_000
463+ conn.readTimeout = 10_000
464+ conn.doOutput = true
465+
466+ val payload = JSONObject ().apply {
467+ put(" body" , message.trim())
468+ }.toString()
469+ OutputStreamWriter (conn.outputStream).use { it.write(payload) }
470+
471+ val code = conn.responseCode
472+ if (code !in 200 .. 299 ) {
473+ val errorBody = conn.errorStream?.bufferedReader()?.use { it.readText() }
474+ Log .w(TAG , " Ant Farm room post failed for $room : $code $errorBody " )
475+ return @withContext Result .failure(
476+ RuntimeException (" I couldn't post to $room right now." )
477+ )
478+ }
472479
473- val code = conn.responseCode
474- val responseText = if (code in 200 .. 299 ) {
475- conn.inputStream.bufferedReader().readText()
476- } else {
477- conn.errorStream?.bufferedReader()?.readText() ? : " HTTP $code "
480+ Result .success(room)
481+ } catch (e: Exception ) {
482+ Log .w(TAG , " Ant Farm room post failed for $room : ${e.message} " )
483+ Result .failure(RuntimeException (" I couldn't post to $room right now." ))
478484 }
485+ }
479486
480- if (code !in 200 .. 299 ) {
481- Log .w(TAG , " Ant Farm room post failed for $targetRoom : $code $responseText " )
482- Result .failure(RuntimeException (" room post failed ($code )" ))
483- } else {
484- Log .i(TAG , " Posted room message to $targetRoom " )
485- Result .success(" Posted to $targetRoom ." )
486- }
487- } catch (e: Exception ) {
488- Log .w(TAG , " Ant Farm room post failed for $targetRoom : ${e.message} " )
489- Result .failure(RuntimeException (" network error while posting to room" ))
487+ private fun resolveTargetRoom (requestedRoom : String? ): String? {
488+ val configuredRooms = getAntFarmRooms()
489+ if (requestedRoom.isNullOrBlank()) {
490+ return configuredRooms.firstOrNull()
490491 }
492+
493+ val normalized = requestedRoom.trim().lowercase()
494+ return configuredRooms.firstOrNull { it.equals(normalized, ignoreCase = true ) }
495+ ? : requestedRoom.trim()
491496 }
492497
493498 // ── Mode 1: Direct (no RAG) ───────────────────────────────────────────────
@@ -521,7 +526,7 @@ class ClawRunner(private val context: Context) {
521526 val conn = url.openConnection() as HttpURLConnection
522527 conn.requestMethod = " GET"
523528 conn.setRequestProperty(" Accept" , " application/json" )
524- conn.setRequestProperty(" Authorization " , " Bearer $ apiKey" )
529+ conn.setRequestProperty(" X-API-Key " , apiKey)
525530 conn.connectTimeout = 10_000
526531 conn.readTimeout = 10_000
527532
0 commit comments