Skip to content

Commit b70adeb

Browse files
authored
Merge branch 'main' into fix/line-strip-markdown-underscore
2 parents f9871ac + c33375f commit b70adeb

29 files changed

+1137
-137
lines changed

.agent/workflows/update_clawdbot.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
2-
description: Update Clawdbot from upstream when branch has diverged (ahead/behind)
2+
description: Update OpenClaw from upstream when branch has diverged (ahead/behind)
33
---
44

5-
# Clawdbot Upstream Sync Workflow
5+
# OpenClaw Upstream Sync Workflow
66

77
Use this workflow when your fork has diverged from upstream (e.g., "18 commits ahead, 29 commits behind").
88

@@ -132,16 +132,16 @@ pnpm mac:package
132132

133133
```bash
134134
# Kill running app
135-
pkill -x "Clawdbot" || true
135+
pkill -x "OpenClaw" || true
136136

137137
# Move old version
138-
mv /Applications/Clawdbot.app /tmp/Clawdbot-backup.app
138+
mv /Applications/OpenClaw.app /tmp/OpenClaw-backup.app
139139

140140
# Install new build
141-
cp -R dist/Clawdbot.app /Applications/
141+
cp -R dist/OpenClaw.app /Applications/
142142

143143
# Launch
144-
open /Applications/Clawdbot.app
144+
open /Applications/OpenClaw.app
145145
```
146146

147147
---
@@ -235,7 +235,7 @@ If upstream introduced new model configurations:
235235
# Check for OpenRouter API key requirements
236236
grep -r "openrouter\|OPENROUTER" src/ --include="*.ts" --include="*.js"
237237

238-
# Update clawdbot.json with fallback chains
238+
# Update openclaw.json with fallback chains
239239
# Add model fallback configurations as needed
240240
```
241241

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,21 @@ Docs: https://docs.openclaw.ai
2222
- Control UI/dashboard: preserve structured gateway shutdown reasons across restart disconnects so config-triggered restarts no longer fall back to `disconnected (1006): no reason`. (#46532) Thanks @vincentkoc.
2323
- Android/chat: theme the thinking dropdown and TLS trust dialogs explicitly so popup surfaces match the active app theme instead of falling back to mismatched Material defaults.
2424
- Z.AI/onboarding: detect a working default model even for explicit `zai-coding-*` endpoint choices, so Coding Plan setup can keep the selected endpoint while defaulting to `glm-5` when available or `glm-4.7` as fallback. (#45969)
25+
- Models/OpenRouter runtime capabilities: fetch uncatalogued OpenRouter model metadata on first use so newly added vision models keep image input instead of silently degrading to text-only, with top-level capability field fallbacks for `/api/v1/models`. (#45824) Thanks @DJjjjhao.
2526
- Z.AI/onboarding: add `glm-5-turbo` to the default Z.AI provider catalog so onboarding-generated configs expose the new model alongside the existing GLM defaults. (#46670) Thanks @tomsun28.
2627
- Zalo Personal/group gating: stop reapplying `dmPolicy.allowFrom` as a sender gate for already-allowlisted groups when `groupAllowFrom` is unset, so any member of an allowed group can trigger replies while DMs stay restricted. (#40146)
2728
- Plugins/install precedence: keep bundled plugins ahead of auto-discovered globals by default, but let an explicitly installed plugin record win its own duplicate-id tie so installed channel plugins load from `~/.openclaw/extensions` after `openclaw plugins install`.
29+
- macOS/canvas actions: keep unattended local agent actions on trusted in-app canvas surfaces only, and stop exposing the deep-link fallback key to arbitrary page scripts. Thanks @vincentkoc.
30+
- Agents/compaction: extend the enclosing run deadline once while compaction is actively in flight, and abort the underlying SDK compaction on timeout/cancel so large-session compactions stop freezing mid-run. (#46889) Thanks @asyncjason.
31+
32+
### Fixes
33+
34+
- Slack/interactive replies: preserve `channelData.slack.blocks` through live DM delivery and preview-finalized edits so Block Kit button and select directives render instead of falling back to raw text. Thanks @vincentkoc.
2835
- Zalo/plugin runtime: export `resolveClientIp` from `openclaw/plugin-sdk/zalo` so installed builds no longer crash on startup when the webhook monitor loads from the packaged extension instead of the monorepo source tree. (#46549) Thanks @No898.
2936
- CI/channel test routing: move the built-in channel suites into `test:channels` and keep them out of `test:extensions`, so extension CI no longer fails after the channel migration while targeted test routing still sends Slack, Signal, and iMessage suites to the right lane. (#46066) Thanks @scoootscooob.
3037
- Browser/profiles: drop the auto-created `chrome-relay` browser profile; users who need the Chrome extension relay must now create their own profile via `openclaw browser create-profile`. (#45777) Thanks @odysseus0.
3138
- Docs/Mintlify: fix MDX marker syntax on Perplexity, Model Providers, Moonshot, and exec approvals pages so local docs preview no longer breaks rendering or leaves stale pages unpublished. (#46695) Thanks @velvet-shark.
39+
- Email/webhook wrapping: sanitize sender and subject metadata before external-content wrapping so metadata fields cannot break the wrapper structure. Thanks @vincentkoc.
3240
- Node/startup: remove leftover debug `console.log("node host PATH: ...")` that printed the resolved PATH on every `openclaw node run` invocation. (#46411)
3341

3442
## 2026.3.13

apps/macos/Sources/OpenClaw/CanvasA2UIActionMessageHandler.swift

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,10 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
1818
func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) {
1919
guard Self.allMessageNames.contains(message.name) else { return }
2020

21-
// Only accept actions from local Canvas content (not arbitrary web pages).
21+
// Only accept actions from the in-app canvas scheme. Local-network HTTP
22+
// pages are regular web content and must not get direct agent dispatch.
2223
guard let webView = message.webView, let url = webView.url else { return }
23-
if let scheme = url.scheme, CanvasScheme.allSchemes.contains(scheme) {
24-
// ok
25-
} else if Self.isLocalNetworkCanvasURL(url) {
26-
// ok
27-
} else {
24+
guard let scheme = url.scheme, CanvasScheme.allSchemes.contains(scheme) else {
2825
return
2926
}
3027

@@ -107,10 +104,5 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
107104
}
108105
}
109106
}
110-
111-
static func isLocalNetworkCanvasURL(_ url: URL) -> Bool {
112-
LocalNetworkURLSupport.isLocalNetworkHTTPURL(url)
113-
}
114-
115107
// Formatting helpers live in OpenClawKit (`OpenClawCanvasA2UIAction`).
116108
}

apps/macos/Sources/OpenClaw/CanvasWindowController.swift

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,24 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
5050

5151
// Bridge A2UI "a2uiaction" DOM events back into the native agent loop.
5252
//
53-
// Prefer WKScriptMessageHandler when WebKit exposes it, otherwise fall back to an unattended deep link
54-
// (includes the app-generated key so it won't prompt).
53+
// Keep the bridge on the trusted in-app canvas scheme only, and do not
54+
// expose unattended deep-link credentials to page JavaScript.
5555
canvasWindowLogger.debug("CanvasWindowController init building A2UI bridge script")
56-
let deepLinkKey = DeepLinkHandler.currentCanvasKey()
5756
let injectedSessionKey = sessionKey.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "main"
57+
let allowedSchemesJSON = (
58+
try? String(
59+
data: JSONSerialization.data(withJSONObject: CanvasScheme.allSchemes),
60+
encoding: .utf8)
61+
) ?? "[]"
5862
let bridgeScript = """
5963
(() => {
6064
try {
61-
const allowedSchemes = \(String(describing: CanvasScheme.allSchemes));
65+
const allowedSchemes = \(allowedSchemesJSON);
6266
const protocol = location.protocol.replace(':', '');
6367
if (!allowedSchemes.includes(protocol)) return;
6468
if (globalThis.__openclawA2UIBridgeInstalled) return;
6569
globalThis.__openclawA2UIBridgeInstalled = true;
6670
67-
const deepLinkKey = \(Self.jsStringLiteral(deepLinkKey));
6871
const sessionKey = \(Self.jsStringLiteral(injectedSessionKey));
6972
const machineName = \(Self.jsStringLiteral(InstanceIdentity.displayName));
7073
const instanceId = \(Self.jsStringLiteral(InstanceIdentity.instanceId));
@@ -104,24 +107,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
104107
return;
105108
}
106109
107-
const ctx = userAction.context ? (' ctx=' + JSON.stringify(userAction.context)) : '';
108-
const message =
109-
'CANVAS_A2UI action=' + userAction.name +
110-
' session=' + sessionKey +
111-
' surface=' + userAction.surfaceId +
112-
' component=' + (userAction.sourceComponentId || '-') +
113-
' host=' + machineName.replace(/\\s+/g, '_') +
114-
' instance=' + instanceId +
115-
ctx +
116-
' default=update_canvas';
117-
const params = new URLSearchParams();
118-
params.set('message', message);
119-
params.set('sessionKey', sessionKey);
120-
params.set('thinking', 'low');
121-
params.set('deliver', 'false');
122-
params.set('channel', 'last');
123-
params.set('key', deepLinkKey);
124-
location.href = 'openclaw://agent?' + params.toString();
110+
// Without the native handler, fail closed instead of exposing an
111+
// unattended deep-link credential to page JavaScript.
125112
} catch {}
126113
}, true);
127114
} catch {}

extensions/tlon/src/monitor/index.ts

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
301301
`[tlon] Using autoDiscoverChannels from settings store: ${effectiveAutoDiscoverChannels}`,
302302
);
303303
}
304-
if (currentSettings.dmAllowlist?.length) {
304+
if (currentSettings.dmAllowlist !== undefined) {
305305
effectiveDmAllowlist = currentSettings.dmAllowlist;
306306
runtime.log?.(
307307
`[tlon] Using dmAllowlist from settings store: ${effectiveDmAllowlist.join(", ")}`,
@@ -322,7 +322,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
322322
`[tlon] Using autoAcceptGroupInvites from settings store: ${effectiveAutoAcceptGroupInvites}`,
323323
);
324324
}
325-
if (currentSettings.groupInviteAllowlist?.length) {
325+
if (currentSettings.groupInviteAllowlist !== undefined) {
326326
effectiveGroupInviteAllowlist = currentSettings.groupInviteAllowlist;
327327
runtime.log?.(
328328
`[tlon] Using groupInviteAllowlist from settings store: ${effectiveGroupInviteAllowlist.join(", ")}`,
@@ -1176,17 +1176,14 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
11761176
return;
11771177
}
11781178

1179-
// Resolve any cited/quoted messages first
1180-
const citedContent = await resolveAllCites(content.content);
11811179
const rawText = extractMessageText(content.content);
1182-
const messageText = citedContent + rawText;
1183-
if (!messageText.trim()) {
1180+
if (!rawText.trim()) {
11841181
return;
11851182
}
11861183

11871184
cacheMessage(nest, {
11881185
author: senderShip,
1189-
content: messageText,
1186+
content: rawText,
11901187
timestamp: content.sent || Date.now(),
11911188
id: messageId,
11921189
});
@@ -1200,7 +1197,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
12001197
// Check if we should respond:
12011198
// 1. Direct mention always triggers response
12021199
// 2. Thread replies where we've participated - respond if relevant (let agent decide)
1203-
const mentioned = isBotMentioned(messageText, botShipName, botNickname ?? undefined);
1200+
const mentioned = isBotMentioned(rawText, botShipName, botNickname ?? undefined);
12041201
const inParticipatedThread =
12051202
isThreadReply && parentId && participatedThreads.has(String(parentId));
12061203

@@ -1227,10 +1224,10 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
12271224
type: "channel",
12281225
requestingShip: senderShip,
12291226
channelNest: nest,
1230-
messagePreview: messageText.substring(0, 100),
1227+
messagePreview: rawText.substring(0, 100),
12311228
originalMessage: {
12321229
messageId: messageId ?? "",
1233-
messageText,
1230+
messageText: rawText,
12341231
messageContent: content.content,
12351232
timestamp: content.sent || Date.now(),
12361233
parentId: parentId ?? undefined,
@@ -1248,6 +1245,10 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
12481245
}
12491246
}
12501247

1248+
// Resolve quoted content only after the sender passed channel authorization.
1249+
const citedContent = await resolveAllCites(content.content);
1250+
const messageText = citedContent + rawText;
1251+
12511252
const parsed = parseChannelNest(nest);
12521253
await processMessage({
12531254
messageId: messageId ?? "",
@@ -1365,15 +1366,15 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
13651366
);
13661367
}
13671368

1368-
// Resolve any cited/quoted messages first
1369-
const citedContent = await resolveAllCites(essay.content);
13701369
const rawText = extractMessageText(essay.content);
1371-
const messageText = citedContent + rawText;
1372-
if (!messageText.trim()) {
1370+
if (!rawText.trim()) {
13731371
return;
13741372
}
1373+
const citedContent = await resolveAllCites(essay.content);
1374+
const resolvedMessageText = citedContent + rawText;
13751375

13761376
// Check if this is the owner sending an approval response
1377+
const messageText = rawText;
13771378
if (isOwner(senderShip) && isApprovalResponse(messageText)) {
13781379
const handled = await handleApprovalResponse(messageText);
13791380
if (handled) {
@@ -1397,7 +1398,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
13971398
await processMessage({
13981399
messageId: messageId ?? "",
13991400
senderShip,
1400-
messageText,
1401+
messageText: resolvedMessageText,
14011402
messageContent: essay.content,
14021403
isGroup: false,
14031404
timestamp: essay.sent || Date.now(),
@@ -1430,7 +1431,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
14301431
await processMessage({
14311432
messageId: messageId ?? "",
14321433
senderShip,
1433-
messageText,
1434+
messageText: resolvedMessageText,
14341435
messageContent: essay.content, // Pass raw content for media extraction
14351436
isGroup: false,
14361437
timestamp: essay.sent || Date.now(),
@@ -1524,8 +1525,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
15241525

15251526
// Update DM allowlist
15261527
if (newSettings.dmAllowlist !== undefined) {
1527-
effectiveDmAllowlist =
1528-
newSettings.dmAllowlist.length > 0 ? newSettings.dmAllowlist : account.dmAllowlist;
1528+
effectiveDmAllowlist = newSettings.dmAllowlist;
15291529
runtime.log?.(`[tlon] Settings: dmAllowlist updated to ${effectiveDmAllowlist.join(", ")}`);
15301530
}
15311531

@@ -1551,10 +1551,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
15511551

15521552
// Update group invite allowlist
15531553
if (newSettings.groupInviteAllowlist !== undefined) {
1554-
effectiveGroupInviteAllowlist =
1555-
newSettings.groupInviteAllowlist.length > 0
1556-
? newSettings.groupInviteAllowlist
1557-
: account.groupInviteAllowlist;
1554+
effectiveGroupInviteAllowlist = newSettings.groupInviteAllowlist;
15581555
runtime.log?.(
15591556
`[tlon] Settings: groupInviteAllowlist updated to ${effectiveGroupInviteAllowlist.join(", ")}`,
15601557
);

skills/apple-reminders/SKILL.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ Use `remindctl` to manage Apple Reminders directly from the terminal.
4040

4141
**DON'T use this skill when:**
4242

43-
- Scheduling Clawdbot tasks or alerts → use `cron` tool with systemEvent instead
43+
- Scheduling OpenClaw tasks or alerts → use `cron` tool with systemEvent instead
4444
- Calendar events or appointments → use Apple Calendar
4545
- Project/work task management → use Notion, GitHub Issues, or task queue
4646
- One-time notifications → use `cron` tool for timed alerts
47-
- User says "remind me" but means a Clawdbot alert → clarify first
47+
- User says "remind me" but means an OpenClaw alert → clarify first
4848

4949
## Setup
5050

@@ -112,7 +112,7 @@ Accepted by `--due` and date filters:
112112

113113
User: "Remind me to check on the deploy in 2 hours"
114114

115-
**Ask:** "Do you want this in Apple Reminders (syncs to your phone) or as a Clawdbot alert (I'll message you here)?"
115+
**Ask:** "Do you want this in Apple Reminders (syncs to your phone) or as an OpenClaw alert (I'll message you here)?"
116116

117117
- Apple Reminders → use this skill
118-
- Clawdbot alert → use `cron` tool with systemEvent
118+
- OpenClaw alert → use `cron` tool with systemEvent

skills/imsg/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Use `imsg` to read and send iMessage/SMS via macOS Messages.app.
4747
- Slack messages → use `slack` skill
4848
- Group chat management (adding/removing members) → not supported
4949
- Bulk/mass messaging → always confirm with user first
50-
- Replying in current conversation → just reply normally (Clawdbot routes automatically)
50+
- Replying in current conversation → just reply normally (OpenClaw routes automatically)
5151

5252
## Requirements
5353

src/agents/context.lookup.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ describe("lookupContextTokens", () => {
9090
}
9191
});
9292

93+
it("skips eager warmup for logs commands that do not need model metadata at startup", async () => {
94+
const loadConfigMock = vi.fn(() => ({ models: {} }));
95+
mockContextModuleDeps(loadConfigMock);
96+
97+
const argvSnapshot = process.argv;
98+
process.argv = ["node", "openclaw", "logs", "--limit", "5"];
99+
try {
100+
await import("./context.js");
101+
expect(loadConfigMock).not.toHaveBeenCalled();
102+
} finally {
103+
process.argv = argvSnapshot;
104+
}
105+
});
106+
93107
it("retries config loading after backoff when an initial load fails", async () => {
94108
vi.useFakeTimers();
95109
const loadConfigMock = vi

src/agents/context.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,24 @@ function getCommandPathFromArgv(argv: string[]): string[] {
108108
return tokens;
109109
}
110110

111+
const SKIP_EAGER_WARMUP_PRIMARY_COMMANDS = new Set([
112+
"backup",
113+
"completion",
114+
"config",
115+
"directory",
116+
"doctor",
117+
"health",
118+
"hooks",
119+
"logs",
120+
"plugins",
121+
"secrets",
122+
"update",
123+
"webhooks",
124+
]);
125+
111126
function shouldSkipEagerContextWindowWarmup(argv: string[] = process.argv): boolean {
112-
const [primary, secondary] = getCommandPathFromArgv(argv);
113-
return primary === "config" && secondary === "validate";
127+
const [primary] = getCommandPathFromArgv(argv);
128+
return primary ? SKIP_EAGER_WARMUP_PRIMARY_COMMANDS.has(primary) : false;
114129
}
115130

116131
function primeConfiguredContextWindows(): OpenClawConfig | undefined {

0 commit comments

Comments
 (0)