Skip to content

Commit 7154d87

Browse files
committed
Auto-merge upstream openclaw/openclaw
2 parents bfb94d0 + c476409 commit 7154d87

76 files changed

Lines changed: 3174 additions & 1028 deletions

File tree

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: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22

33
Docs: https://docs.openclaw.ai
44

5-
## 2026.4.12
5+
## Unreleased
6+
7+
### Changes
8+
9+
### Fixes
10+
11+
- Gateway/startup: defer scheduled services until sidecars finish, gate chat history and model listing during sidecar resume, and let Control UI retry startup-gated history loads so Sandbox wake resumes channels first. (#65365) Thanks @lml2468.
12+
13+
## 2026.4.12-beta.1
614

715
### Changes
816

@@ -55,6 +63,7 @@ Docs: https://docs.openclaw.ai
5563
- WhatsApp: centralize per-account connection ownership so reconnects, login recovery, and outbound readiness stay attached to the live socket instead of drifting across monitor and login paths. (#65290) Thanks @mcaxtr and @vincentkoc.
5664
- iMessage: retry transient `watch.subscribe` startup failures before tearing down the monitor, and sanitize startup error logging so brief local transport stalls do not immediately bounce the channel or leak raw imsg RPC payloads into logs. (#65393) Thanks @vincentkoc.
5765
- CLI/audio providers: report env-authenticated providers as configured in `openclaw infer audio providers --json`, while keeping trusted workspace provider env lookup defaults stable during auth setup. (#65491)
66+
- Plugins/install: reinstall bundled runtime packages when the matching platform native optional child is missing, so packaged Windows installs can recover dependencies that were packed on another host OS.
5867

5968
## 2026.4.11
6069

apps/android/app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ android {
6565
applicationId = "ai.openclaw.app"
6666
minSdk = 31
6767
targetSdk = 36
68-
versionCode = 2026041101
69-
versionName = "2026.4.12"
68+
versionCode = 2026041201
69+
versionName = "2026.4.12-beta.1"
7070
ndk {
7171
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
7272
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")

apps/macos/Sources/OpenClaw/Resources/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
<key>CFBundlePackageType</key>
1616
<string>APPL</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>2026.4.12</string>
18+
<string>2026.4.12-beta.1</string>
1919
<key>CFBundleVersion</key>
20-
<string>2026041101</string>
20+
<string>2026041201</string>
2121
<key>CFBundleIconFile</key>
2222
<string>OpenClaw</string>
2323
<key>CFBundleURLTypes</key>

extensions/active-memory/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1199,7 +1199,7 @@ async function readActiveMemorySearchDebug(
11991199
continue;
12001200
}
12011201
const details = asRecord(message.details);
1202-
const debug = asRecord(details?.debug);
1202+
const debug = asRecord(details?.debug) ?? {};
12031203
const warning = normalizeOptionalString(details?.warning);
12041204
const action = normalizeOptionalString(details?.action);
12051205
const error = normalizeOptionalString(details?.error);

extensions/memory-core/src/dreaming-phases.test.ts

Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -497,14 +497,10 @@ describe("memory-core dreaming phases", () => {
497497
const readSpy = vi.spyOn(fs, "readFile");
498498
let transcriptReadCount = 0;
499499
try {
500-
await beforeAgentReply(
501-
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
502-
{ trigger: "heartbeat", workspaceDir },
503-
);
504-
await beforeAgentReply(
505-
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
506-
{ trigger: "heartbeat", workspaceDir },
507-
);
500+
await withDreamingTestClock(async () => {
501+
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5);
502+
await triggerLightDreaming(beforeAgentReply, workspaceDir, 6);
503+
});
508504
} finally {
509505
transcriptReadCount = readSpy.mock.calls.filter(
510506
([target]) => typeof target === "string" && target === transcriptPath,
@@ -779,10 +775,9 @@ describe("memory-core dreaming phases", () => {
779775
);
780776

781777
try {
782-
await beforeAgentReply(
783-
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
784-
{ trigger: "heartbeat", workspaceDir },
785-
);
778+
await withDreamingTestClock(async () => {
779+
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5);
780+
});
786781

787782
const resetPath = path.join(
788783
sessionsDir,
@@ -816,10 +811,9 @@ describe("memory-core dreaming phases", () => {
816811
await fs.utimes(transcriptPath, dayTwo, dayTwo);
817812
await fs.utimes(resetPath, dayTwo, dayTwo);
818813

819-
await beforeAgentReply(
820-
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
821-
{ trigger: "heartbeat", workspaceDir },
822-
);
814+
await withDreamingTestClock(async () => {
815+
await triggerLightDreaming(beforeAgentReply, workspaceDir, 910);
816+
});
823817
} finally {
824818
vi.unstubAllEnvs();
825819
}
@@ -985,18 +979,11 @@ describe("memory-core dreaming phases", () => {
985979
);
986980

987981
try {
988-
await beforeAgentReply(
989-
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
990-
{ trigger: "heartbeat", workspaceDir },
991-
);
992-
await beforeAgentReply(
993-
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
994-
{ trigger: "heartbeat", workspaceDir },
995-
);
996-
await beforeAgentReply(
997-
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
998-
{ trigger: "heartbeat", workspaceDir },
999-
);
982+
await withDreamingTestClock(async () => {
983+
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5);
984+
await triggerLightDreaming(beforeAgentReply, workspaceDir, 6);
985+
await triggerLightDreaming(beforeAgentReply, workspaceDir, 7);
986+
});
1000987
} finally {
1001988
vi.unstubAllEnvs();
1002989
}
@@ -1073,10 +1060,9 @@ describe("memory-core dreaming phases", () => {
10731060
);
10741061

10751062
try {
1076-
await beforeAgentReply(
1077-
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
1078-
{ trigger: "heartbeat", workspaceDir },
1079-
);
1063+
await withDreamingTestClock(async () => {
1064+
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5);
1065+
});
10801066

10811067
await fs.writeFile(
10821068
transcriptPath,
@@ -1095,10 +1081,9 @@ describe("memory-core dreaming phases", () => {
10951081
const dayTwo = new Date("2026-04-06T01:05:00.000Z");
10961082
await fs.utimes(transcriptPath, dayTwo, dayTwo);
10971083

1098-
await beforeAgentReply(
1099-
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
1100-
{ trigger: "heartbeat", workspaceDir },
1101-
);
1084+
await withDreamingTestClock(async () => {
1085+
await triggerLightDreaming(beforeAgentReply, workspaceDir, 910);
1086+
});
11021087
} finally {
11031088
vi.unstubAllEnvs();
11041089
}
@@ -1175,10 +1160,9 @@ describe("memory-core dreaming phases", () => {
11751160
);
11761161

11771162
try {
1178-
await beforeAgentReply(
1179-
{ cleanedBody: "__openclaw_memory_core_light_sleep__" },
1180-
{ trigger: "heartbeat", workspaceDir },
1181-
);
1163+
await withDreamingTestClock(async () => {
1164+
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5);
1165+
});
11821166
} finally {
11831167
vi.unstubAllEnvs();
11841168
}
@@ -1469,10 +1453,13 @@ describe("memory-core dreaming phases", () => {
14691453
await withDreamingTestClock(async () => {
14701454
await triggerLightDreaming(beforeAgentReply, workspaceDir, 5);
14711455
});
1472-
await beforeAgentReply(
1473-
{ cleanedBody: "__openclaw_memory_core_rem_sleep__" },
1474-
{ trigger: "heartbeat", workspaceDir },
1475-
);
1456+
await withDreamingTestClock(async () => {
1457+
setDreamingTestTime(10);
1458+
await beforeAgentReply(
1459+
{ cleanedBody: "__openclaw_memory_core_rem_sleep__" },
1460+
{ trigger: "heartbeat", workspaceDir },
1461+
);
1462+
});
14761463

14771464
const reinforced = await rankShortTermPromotionCandidates({
14781465
workspaceDir,

extensions/openshell/src/backend.e2e.test.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ async function runCommand(params: {
5252
args: string[];
5353
cwd?: string;
5454
env?: NodeJS.ProcessEnv;
55-
stdin?: string | Buffer;
55+
stdin?: string | Uint8Array;
5656
allowFailure?: boolean;
5757
timeoutMs?: number;
5858
}): Promise<ExecResult> {
@@ -117,7 +117,21 @@ async function commandAvailable(command: string): Promise<boolean> {
117117
allowFailure: true,
118118
timeoutMs: 20_000,
119119
});
120-
return result.code === 0 || result.stdout.length > 0 || result.stderr.length > 0;
120+
return result.code === 0;
121+
} catch {
122+
return false;
123+
}
124+
}
125+
126+
async function openshellGatewayAvailable(command: string): Promise<boolean> {
127+
try {
128+
const result = await runCommand({
129+
command,
130+
args: ["gateway", "start", "--help"],
131+
allowFailure: true,
132+
timeoutMs: 20_000,
133+
});
134+
return result.code === 0 && `${result.stdout}\n${result.stderr}`.includes("--name");
121135
} catch {
122136
return false;
123137
}
@@ -338,6 +352,9 @@ describe("openshell sandbox backend e2e", () => {
338352
if (!(await commandAvailable(OPENCLAW_OPENSHELL_COMMAND))) {
339353
return;
340354
}
355+
if (!(await openshellGatewayAvailable(OPENCLAW_OPENSHELL_COMMAND))) {
356+
return;
357+
}
341358

342359
const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-openshell-e2e-"));
343360
const env = openshellEnv(rootDir);

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openclaw",
3-
"version": "2026.4.12",
3+
"version": "2026.4.12-beta.1",
44
"description": "Multi-channel AI gateway with extensible messaging integrations",
55
"keywords": [],
66
"homepage": "https://github.com/openclaw/openclaw#readme",
@@ -1279,7 +1279,7 @@
12791279
"test:docker:plugins": "bash scripts/e2e/plugins-docker.sh",
12801280
"test:docker:qr": "bash scripts/e2e/qr-import-docker.sh",
12811281
"test:e2e": "node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts",
1282-
"test:e2e:openshell": "OPENCLAW_E2E_OPENSHELL=1 node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts test/openshell-sandbox.e2e.test.ts",
1282+
"test:e2e:openshell": "OPENCLAW_E2E_OPENSHELL=1 node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts extensions/openshell/src/backend.e2e.test.ts",
12831283
"test:extension": "node scripts/test-extension.mjs",
12841284
"test:extensions": "node scripts/run-vitest.mjs run --config test/vitest/vitest.extensions.config.ts",
12851285
"test:extensions:batch": "node scripts/test-extension-batch.mjs",

scripts/e2e/parallels-windows-smoke.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ TIMEOUT_INSTALL_S=1200
4343
TIMEOUT_VERIFY_S=120
4444
TIMEOUT_ONBOARD_S=240
4545
TIMEOUT_ONBOARD_PHASE_S=$((TIMEOUT_ONBOARD_S + 60))
46-
TIMEOUT_GATEWAY_S=120
46+
# verify_gateway_reachable runs six 30s probes plus short retry sleeps.
47+
TIMEOUT_GATEWAY_S=240
4748
TIMEOUT_AGENT_S=180
4849

4950
FRESH_MAIN_STATUS="skip"
@@ -1900,7 +1901,7 @@ try {
19001901
Write-ProgressLog 'install.download-tgz'
19011902
Invoke-Logged 'download current tgz' { curl.exe -fsSL $TgzUrl -o $tgz }
19021903
Write-ProgressLog 'install.install-tgz'
1903-
Invoke-Logged 'npm install current tgz' { npm.cmd install -g $tgz --no-fund --no-audit }
1904+
Invoke-Logged 'npm install current tgz' { npm.cmd install -g $tgz --omit=dev --no-fund --no-audit }
19041905
$openclaw = Join-Path $env:APPDATA 'npm\openclaw.cmd'
19051906
Write-ProgressLog 'install.verify-version'
19061907
Invoke-Logged 'openclaw --version' { & $openclaw --version }

scripts/postinstall-bundled-plugins.mjs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,54 @@ function dependencySentinelPath(depName) {
2626
return join("node_modules", ...depName.split("/"), "package.json");
2727
}
2828

29+
const KNOWN_NATIVE_PLATFORMS = new Set([
30+
"aix",
31+
"android",
32+
"darwin",
33+
"freebsd",
34+
"linux",
35+
"openbsd",
36+
"sunos",
37+
"win32",
38+
]);
39+
const KNOWN_NATIVE_ARCHES = new Set(["arm", "arm64", "ia32", "ppc64", "riscv64", "s390x", "x64"]);
40+
41+
function packageNameTokens(name) {
42+
return name
43+
.toLowerCase()
44+
.split(/[/@._-]+/u)
45+
.filter(Boolean);
46+
}
47+
48+
function optionalDependencyTargetsRuntime(name, params = {}) {
49+
const platform = params.platform ?? process.platform;
50+
const arch = params.arch ?? process.arch;
51+
const tokens = new Set(packageNameTokens(name));
52+
const hasNativePlatformToken = [...tokens].some((token) => KNOWN_NATIVE_PLATFORMS.has(token));
53+
const hasNativeArchToken = [...tokens].some((token) => KNOWN_NATIVE_ARCHES.has(token));
54+
return hasNativePlatformToken && hasNativeArchToken && tokens.has(platform) && tokens.has(arch);
55+
}
56+
57+
function runtimeDepNeedsInstall(params) {
58+
const packageJsonPath = join(params.packageRoot, params.dep.sentinelPath);
59+
if (!params.existsSync(packageJsonPath)) {
60+
return true;
61+
}
62+
63+
try {
64+
const packageJson = params.readJson(packageJsonPath);
65+
return Object.keys(packageJson.optionalDependencies ?? {}).some(
66+
(childName) =>
67+
optionalDependencyTargetsRuntime(childName, {
68+
arch: params.arch,
69+
platform: params.platform,
70+
}) && !params.existsSync(join(params.packageRoot, dependencySentinelPath(childName))),
71+
);
72+
} catch {
73+
return true;
74+
}
75+
}
76+
2977
function collectRuntimeDeps(packageJson) {
3078
return {
3179
...packageJson.dependencies,
@@ -184,7 +232,16 @@ export function runBundledPluginPostinstall(params = {}) {
184232
params.runtimeDeps ??
185233
discoverBundledPluginRuntimeDeps({ extensionsDir, existsSync: pathExists });
186234
const missingSpecs = runtimeDeps
187-
.filter((dep) => !pathExists(join(packageRoot, dep.sentinelPath)))
235+
.filter((dep) =>
236+
runtimeDepNeedsInstall({
237+
dep,
238+
existsSync: pathExists,
239+
packageRoot,
240+
arch: params.arch,
241+
platform: params.platform,
242+
readJson: params.readJson ?? readJson,
243+
}),
244+
)
188245
.map((dep) => `${dep.name}@${dep.version}`);
189246

190247
if (missingSpecs.length === 0) {
@@ -201,7 +258,14 @@ export function runBundledPluginPostinstall(params = {}) {
201258
existsSync: pathExists,
202259
platform: params.platform,
203260
comSpec: params.comSpec,
204-
npmArgs: ["install", "--omit=dev", "--no-save", "--package-lock=false", ...missingSpecs],
261+
npmArgs: [
262+
"install",
263+
"--omit=dev",
264+
"--no-save",
265+
"--package-lock=false",
266+
"--legacy-peer-deps",
267+
...missingSpecs,
268+
],
205269
});
206270
const result = spawn(npmRunner.command, npmRunner.args, {
207271
cwd: packageRoot,

src/agents/auth-profiles.markauthprofilefailure.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ vi.mock("../plugins/provider-runtime.js", () => ({
1414

1515
import {
1616
clearRuntimeAuthProfileStoreSnapshots,
17-
calculateAuthProfileCooldownMs,
1817
ensureAuthProfileStore,
19-
markAuthProfileFailure,
20-
} from "./auth-profiles.js";
18+
} from "./auth-profiles/store.js";
19+
import { calculateAuthProfileCooldownMs, markAuthProfileFailure } from "./auth-profiles/usage.js";
2120

2221
type AuthProfileStore = ReturnType<typeof ensureAuthProfileStore>;
2322

0 commit comments

Comments
 (0)