Skip to content

Commit 19ee682

Browse files
authored
Merge branch 'main' into fit/tui_crtl_c
2 parents 44bb8db + f9ea879 commit 19ee682

53 files changed

Lines changed: 1136 additions & 726 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ Docs: https://docs.openclaw.ai
44

55
## Unreleased
66

7+
### Changes
8+
9+
- Android/chat settings: redesign the chat settings sheet with grouped device and media sections, refresh the Connect and Voice tabs, and tighten the chat composer/session header for a denser mobile layout. (#44894) Thanks @obviyus.
10+
- Docker/timezone override: add `OPENCLAW_TZ` so `docker-setup.sh` can pin gateway and CLI containers to a chosen IANA timezone instead of inheriting the daemon default. (#34119) Thanks @Lanfei.
11+
712
### Fixes
813

914
- Windows/gateway install: bound `schtasks` calls and fall back to the Startup-folder login item when task creation hangs, so native `openclaw gateway install` fails fast instead of wedging forever on broken Scheduled Task setups.
1015
- Telegram/media downloads: thread the same direct or proxy transport policy into SSRF-guarded file fetches so inbound attachments keep working when Telegram falls back between env-proxy and direct networking. (#44639) Thanks @obviyus.
1116
- Agents/compaction: compare post-compaction token sanity checks against full-session pre-compaction totals and skip the check when token estimation fails, so sessions with large bootstrap context keep real token counts instead of falling back to unknown. (#28347) thanks @efe-arv.
1217
- Discord/gateway startup: treat plain-text and transient `/gateway/bot` metadata fetch failures as transient startup errors so Discord gateway boot no longer crashes on unhandled rejections. (#44397) Thanks @jalehman.
1318
- Gateway/session reset: preserve `lastAccountId` and `lastThreadId` across gateway session resets so replies keep routing back to the same account and thread after `/reset`. (#44773) Thanks @Lanfei.
19+
- Agents/memory bootstrap: load only one root memory file, preferring `MEMORY.md` and using `memory.md` as a fallback, so case-insensitive Docker mounts no longer inject duplicate memory context. (#26054) Thanks @Lanfei.
20+
- Agents/OpenAI-compatible compat overrides: respect explicit user `models[].compat` opt-ins for non-native `openai-completions` endpoints so usage-in-streaming capability overrides no longer get forced off when the endpoint actually supports them. (#44432) Thanks @cheapestinference.
21+
- Agents/Azure OpenAI startup prompts: rephrase the built-in `/new`, `/reset`, and post-compaction startup instruction so Azure OpenAI deployments no longer hit HTTP 400 false positives from the content filter. (#43403) Thanks @xingsy97.
1422

1523
## 2026.3.12
1624

@@ -93,6 +101,9 @@ Docs: https://docs.openclaw.ai
93101
- Cron/doctor: stop flagging canonical `agentTurn` and `systemEvent` payload kinds as legacy cron storage, while still normalizing whitespace-padded and non-canonical variants. (#44012) Thanks @shuicici.
94102
- ACP/client final-message delivery: preserve terminal assistant text snapshots before resolving `end_turn`, so ACP clients no longer drop the last visible reply when the gateway sends the final message body on the terminal chat event. (#17615) Thanks @pjeby.
95103
- Telegram/Discord status reactions: show a temporary compacting reaction during auto-compaction pauses and restore thinking afterward so the bot no longer appears frozen while context is being compacted. (#35474) thanks @Cypherm.
104+
- Delivery/dedupe: trim completed direct-cron delivery cache correctly and keep mirrored transcript dedupe active even when transcript files contain malformed lines. (#44666) thanks @frankekn.
105+
- CLI/thinking help: add the missing `xhigh` level hints to `openclaw cron add`, `openclaw cron edit`, and `openclaw agent` so the help text matches the levels already accepted at runtime. (#44819) Thanks @kiki830621.
106+
- Agents/Anthropic replay: drop replayed assistant thinking blocks for native Anthropic and Bedrock Claude providers so persisted follow-up turns no longer fail on stored thinking blocks. (#44843) Thanks @jmcte.
96107

97108
## 2026.3.11
98109

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Welcome to the lobster tank! 🦞
2323
- **Jos** - Telegram, API, Nix mode
2424
- GitHub: [@joshp123](https://github.com/joshp123) · X: [@jjpcodes](https://x.com/jjpcodes)
2525

26-
- **Ayaan Zaidi** - Telegram subsystem, iOS app
26+
- **Ayaan Zaidi** - Telegram subsystem, Android app
2727
- GitHub: [@obviyus](https://github.com/obviyus) · X: [@0bviyus](https://x.com/0bviyus)
2828

2929
- **Tyler Yust** - Agents/subagents, cron, BlueBubbles, macOS app

apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt

Lines changed: 121 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Box
88
import androidx.compose.foundation.layout.Column
99
import androidx.compose.foundation.layout.PaddingValues
1010
import androidx.compose.foundation.layout.Row
11+
import androidx.compose.foundation.layout.Spacer
1112
import androidx.compose.foundation.layout.fillMaxWidth
1213
import androidx.compose.foundation.layout.height
1314
import androidx.compose.foundation.layout.padding
@@ -18,8 +19,11 @@ import androidx.compose.foundation.shape.RoundedCornerShape
1819
import androidx.compose.foundation.text.KeyboardOptions
1920
import androidx.compose.foundation.verticalScroll
2021
import androidx.compose.material.icons.Icons
22+
import androidx.compose.material.icons.filled.Cloud
2123
import androidx.compose.material.icons.filled.ExpandLess
2224
import androidx.compose.material.icons.filled.ExpandMore
25+
import androidx.compose.material.icons.filled.Link
26+
import androidx.compose.material.icons.filled.PowerSettingsNew
2327
import androidx.compose.material3.AlertDialog
2428
import androidx.compose.material3.Button
2529
import androidx.compose.material3.ButtonDefaults
@@ -128,96 +132,142 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
128132
verticalArrangement = Arrangement.spacedBy(14.dp),
129133
) {
130134
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
131-
Text("Connection Control", style = mobileCaption1.copy(fontWeight = FontWeight.Bold), color = mobileAccent)
132135
Text("Gateway Connection", style = mobileTitle1, color = mobileText)
133136
Text(
134-
"One primary action. Open advanced controls only when needed.",
137+
if (isConnected) "Your gateway is active and ready." else "Connect to your gateway to get started.",
135138
style = mobileCallout,
136139
color = mobileTextSecondary,
137140
)
138141
}
139142

143+
// Status cards in a unified card group
140144
Surface(
141145
modifier = Modifier.fillMaxWidth(),
142146
shape = RoundedCornerShape(14.dp),
143-
color = mobileSurface,
147+
color = Color.White,
144148
border = BorderStroke(1.dp, mobileBorder),
145149
) {
146-
Column(modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
147-
Text("Active endpoint", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
148-
Text(activeEndpoint, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText)
149-
}
150-
}
151-
152-
Surface(
153-
modifier = Modifier.fillMaxWidth(),
154-
shape = RoundedCornerShape(14.dp),
155-
color = mobileSurface,
156-
border = BorderStroke(1.dp, mobileBorder),
157-
) {
158-
Column(modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
159-
Text("Gateway state", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
160-
Text(statusText, style = mobileBody, color = mobileText)
150+
Column {
151+
Row(
152+
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp),
153+
verticalAlignment = Alignment.CenterVertically,
154+
horizontalArrangement = Arrangement.spacedBy(12.dp),
155+
) {
156+
Surface(
157+
shape = RoundedCornerShape(10.dp),
158+
color = mobileAccentSoft,
159+
) {
160+
Icon(
161+
imageVector = Icons.Default.Link,
162+
contentDescription = null,
163+
modifier = Modifier.padding(8.dp).size(18.dp),
164+
tint = mobileAccent,
165+
)
166+
}
167+
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
168+
Text("Endpoint", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
169+
Text(activeEndpoint, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText)
170+
}
171+
}
172+
HorizontalDivider(color = mobileBorder)
173+
Row(
174+
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp),
175+
verticalAlignment = Alignment.CenterVertically,
176+
horizontalArrangement = Arrangement.spacedBy(12.dp),
177+
) {
178+
Surface(
179+
shape = RoundedCornerShape(10.dp),
180+
color = if (isConnected) mobileSuccessSoft else mobileSurface,
181+
) {
182+
Icon(
183+
imageVector = Icons.Default.Cloud,
184+
contentDescription = null,
185+
modifier = Modifier.padding(8.dp).size(18.dp),
186+
tint = if (isConnected) mobileSuccess else mobileTextTertiary,
187+
)
188+
}
189+
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
190+
Text("Status", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
191+
Text(statusText, style = mobileBody, color = if (isConnected) mobileSuccess else mobileText)
192+
}
193+
}
161194
}
162195
}
163196

164-
Button(
165-
onClick = {
166-
if (isConnected) {
197+
if (isConnected) {
198+
// Outlined secondary button when connected — don't scream "danger"
199+
Button(
200+
onClick = {
167201
viewModel.disconnect()
168202
validationText = null
169-
return@Button
170-
}
171-
if (statusText.contains("operator offline", ignoreCase = true)) {
172-
validationText = null
173-
viewModel.refreshGatewayConnection()
174-
return@Button
175-
}
203+
},
204+
modifier = Modifier.fillMaxWidth().height(48.dp),
205+
shape = RoundedCornerShape(14.dp),
206+
colors =
207+
ButtonDefaults.buttonColors(
208+
containerColor = Color.White,
209+
contentColor = mobileDanger,
210+
),
211+
border = BorderStroke(1.dp, mobileDanger.copy(alpha = 0.4f)),
212+
) {
213+
Icon(Icons.Default.PowerSettingsNew, contentDescription = null, modifier = Modifier.size(18.dp))
214+
Spacer(modifier = Modifier.width(8.dp))
215+
Text("Disconnect", style = mobileHeadline.copy(fontWeight = FontWeight.SemiBold))
216+
}
217+
} else {
218+
Button(
219+
onClick = {
220+
if (statusText.contains("operator offline", ignoreCase = true)) {
221+
validationText = null
222+
viewModel.refreshGatewayConnection()
223+
return@Button
224+
}
176225

177-
val config =
178-
resolveGatewayConnectConfig(
179-
useSetupCode = inputMode == ConnectInputMode.SetupCode,
180-
setupCode = setupCode,
181-
manualHost = manualHostInput,
182-
manualPort = manualPortInput,
183-
manualTls = manualTlsInput,
184-
fallbackToken = gatewayToken,
185-
fallbackPassword = passwordInput,
186-
)
187-
188-
if (config == null) {
189-
validationText =
190-
if (inputMode == ConnectInputMode.SetupCode) {
191-
"Paste a valid setup code to connect."
192-
} else {
193-
"Enter a valid manual host and port to connect."
194-
}
195-
return@Button
196-
}
226+
val config =
227+
resolveGatewayConnectConfig(
228+
useSetupCode = inputMode == ConnectInputMode.SetupCode,
229+
setupCode = setupCode,
230+
manualHost = manualHostInput,
231+
manualPort = manualPortInput,
232+
manualTls = manualTlsInput,
233+
fallbackToken = gatewayToken,
234+
fallbackPassword = passwordInput,
235+
)
197236

198-
validationText = null
199-
viewModel.setManualEnabled(true)
200-
viewModel.setManualHost(config.host)
201-
viewModel.setManualPort(config.port)
202-
viewModel.setManualTls(config.tls)
203-
viewModel.setGatewayBootstrapToken(config.bootstrapToken)
204-
if (config.token.isNotBlank()) {
205-
viewModel.setGatewayToken(config.token)
206-
} else if (config.bootstrapToken.isNotBlank()) {
207-
viewModel.setGatewayToken("")
208-
}
209-
viewModel.setGatewayPassword(config.password)
210-
viewModel.connectManual()
211-
},
212-
modifier = Modifier.fillMaxWidth().height(52.dp),
213-
shape = RoundedCornerShape(14.dp),
214-
colors =
215-
ButtonDefaults.buttonColors(
216-
containerColor = if (isConnected) mobileDanger else mobileAccent,
217-
contentColor = Color.White,
218-
),
219-
) {
220-
Text(primaryLabel, style = mobileHeadline.copy(fontWeight = FontWeight.Bold))
237+
if (config == null) {
238+
validationText =
239+
if (inputMode == ConnectInputMode.SetupCode) {
240+
"Paste a valid setup code to connect."
241+
} else {
242+
"Enter a valid manual host and port to connect."
243+
}
244+
return@Button
245+
}
246+
247+
validationText = null
248+
viewModel.setManualEnabled(true)
249+
viewModel.setManualHost(config.host)
250+
viewModel.setManualPort(config.port)
251+
viewModel.setManualTls(config.tls)
252+
viewModel.setGatewayBootstrapToken(config.bootstrapToken)
253+
if (config.token.isNotBlank()) {
254+
viewModel.setGatewayToken(config.token)
255+
} else if (config.bootstrapToken.isNotBlank()) {
256+
viewModel.setGatewayToken("")
257+
}
258+
viewModel.setGatewayPassword(config.password)
259+
viewModel.connectManual()
260+
},
261+
modifier = Modifier.fillMaxWidth().height(52.dp),
262+
shape = RoundedCornerShape(14.dp),
263+
colors =
264+
ButtonDefaults.buttonColors(
265+
containerColor = mobileAccent,
266+
contentColor = Color.White,
267+
),
268+
) {
269+
Text("Connect Gateway", style = mobileHeadline.copy(fontWeight = FontWeight.Bold))
270+
}
221271
}
222272

223273
Surface(

0 commit comments

Comments
 (0)