Skip to content

Commit 8d5f168

Browse files
committed
Auto-merge upstream openclaw/openclaw
2 parents e2c3825 + 571483a commit 8d5f168

110 files changed

Lines changed: 2848 additions & 776 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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Docs: https://docs.openclaw.ai
66

77
### Changes
88

9+
- Tools/video_generate: allow providers and plugins to return URL-only generated video assets so agent delivery and `openclaw capability video generate --output ...` can forward or stream large videos without requiring the full file in memory first. (#61988) Thanks @xieyongliang.
10+
911
### Fixes
1012

1113
- WhatsApp: honor the configured default account when the active listener helper is used without an explicit account id, so named default accounts do not get registered under `default`. (#53918) Thanks @yhyatt.
@@ -148,6 +150,7 @@ Docs: https://docs.openclaw.ai
148150
- Browser/security: reject strict-policy hostname navigation unless the hostname is an explicit allowlist exception or IP literal, and route CDP HTTP discovery through the pinned SSRF fetch path. (#64367) Thanks @eleqtrizit.
149151
- Models/vLLM: ignore empty `tool_calls` arrays from reasoning-model OpenAI-compatible replies, reset false `toolUse` stop reasons when no actual tool calls were parsed, and stop sending `tool_choice` unless tools are present so vLLM reasoning responses no longer hang indefinitely. (#61197, #61534) Thanks @balajisiva.
150152
- Heartbeat/scheduling: spread interval heartbeats across stable per-agent phases derived from gateway identity, so provider traffic is distributed more uniformly across the configured interval instead of clustering around startup-relative times. (#64560) Thanks @odysseus0.
153+
- Config/media: accept `tools.media.asyncCompletion.directSend` in strict config validation so gateways no longer reject the generated-schema-backed async media completion setting at startup. (#63618) Thanks @qiziAI.
151154

152155
## 2026.4.9
153156

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
7a9bb7a5e4b243e2123af94301ba363d57eddab2baa6378d16cd37a1cb8a55f7 plugin-sdk-api-baseline.json
2-
2bdca027d5fda72399479569927cd34d18b56b242e4b12ac45e7c2352e551c77 plugin-sdk-api-baseline.jsonl
1+
7a5c71593c9efbb936b9632f0b381a6c603e9bce44706b312a0172504fa51ef6 plugin-sdk-api-baseline.json
2+
0b044de57266d20561838a5ae0edbaacaa53b323d4c8c068e701a48f92f0a264 plugin-sdk-api-baseline.jsonl

extensions/video-generation-providers.live.test.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,18 @@ function maybeLoadShellEnvForVideoProviders(providerIds: string[]): void {
149149
});
150150
}
151151

152+
function expectBufferedVideo(
153+
video: { buffer?: Buffer; mimeType: string; fileName?: string } | undefined,
154+
): { buffer: Buffer; mimeType: string; fileName?: string } {
155+
expect(video).toBeDefined();
156+
expect(video?.mimeType.startsWith("video/")).toBe(true);
157+
if (!video?.buffer) {
158+
throw new Error("expected generated video buffer");
159+
}
160+
expect(video.buffer.byteLength).toBeGreaterThan(1024);
161+
return video;
162+
}
163+
152164
describeLive("video generation provider live", () => {
153165
it(
154166
"covers declared video-generation modes with shell/profile auth",
@@ -238,9 +250,7 @@ describeLive("video generation provider live", () => {
238250
});
239251

240252
expect(result.videos.length).toBeGreaterThan(0);
241-
expect(result.videos[0]?.mimeType.startsWith("video/")).toBe(true);
242-
expect(result.videos[0]?.buffer.byteLength).toBeGreaterThan(1024);
243-
generatedVideo = result.videos[0] ?? null;
253+
generatedVideo = expectBufferedVideo(result.videos[0]);
244254
attempted.push(`${testCase.providerId}:generate:${providerModel} (${authLabel})`);
245255
console.error(
246256
`${logPrefix} mode=generate done ms=${Date.now() - startedAt} videos=${result.videos.length}`,
@@ -298,8 +308,7 @@ describeLive("video generation provider live", () => {
298308
});
299309

300310
expect(result.videos.length).toBeGreaterThan(0);
301-
expect(result.videos[0]?.mimeType.startsWith("video/")).toBe(true);
302-
expect(result.videos[0]?.buffer.byteLength).toBeGreaterThan(1024);
311+
expectBufferedVideo(result.videos[0]);
303312
attempted.push(`${testCase.providerId}:imageToVideo:${providerModel} (${authLabel})`);
304313
console.error(
305314
`${logPrefix} mode=imageToVideo done ms=${Date.now() - startedAt} videos=${result.videos.length}`,
@@ -348,8 +357,7 @@ describeLive("video generation provider live", () => {
348357
});
349358

350359
expect(result.videos.length).toBeGreaterThan(0);
351-
expect(result.videos[0]?.mimeType.startsWith("video/")).toBe(true);
352-
expect(result.videos[0]?.buffer.byteLength).toBeGreaterThan(1024);
360+
expectBufferedVideo(result.videos[0]);
353361
attempted.push(`${testCase.providerId}:videoToVideo:${providerModel} (${authLabel})`);
354362
console.error(
355363
`${logPrefix} mode=videoToVideo done ms=${Date.now() - startedAt} videos=${result.videos.length}`,

extensions/vydra/vydra.live.test.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ const registerVydraPlugin = () =>
2424
name: "Vydra Provider",
2525
});
2626

27+
function expectBufferedAsset(
28+
asset: { buffer?: Buffer; mimeType: string } | undefined,
29+
kind: "image" | "video",
30+
minBytes: number,
31+
): void {
32+
expect(asset).toBeDefined();
33+
expect(asset?.mimeType.startsWith(`${kind}/`)).toBe(true);
34+
if (!asset?.buffer) {
35+
throw new Error(`expected generated ${kind} buffer`);
36+
}
37+
expect(asset.buffer.byteLength).toBeGreaterThan(minBytes);
38+
}
39+
2740
describe.skipIf(!LIVE || !VYDRA_API_KEY)("vydra live", () => {
2841
it("generates an image through the registered provider", async () => {
2942
const { imageProviders } = await registerVydraPlugin();
@@ -38,8 +51,7 @@ describe.skipIf(!LIVE || !VYDRA_API_KEY)("vydra live", () => {
3851
});
3952

4053
expect(result.images.length).toBeGreaterThan(0);
41-
expect(result.images[0]?.mimeType.startsWith("image/")).toBe(true);
42-
expect(result.images[0]?.buffer.byteLength).toBeGreaterThan(512);
54+
expectBufferedAsset(result.images[0], "image", 512);
4355
}, 60_000);
4456

4557
it("synthesizes speech through the registered provider", async () => {
@@ -78,8 +90,7 @@ describe.skipIf(!LIVE || !VYDRA_API_KEY)("vydra live", () => {
7890
});
7991

8092
expect(result.videos.length).toBeGreaterThan(0);
81-
expect(result.videos[0]?.mimeType.startsWith("video/")).toBe(true);
82-
expect(result.videos[0]?.buffer.byteLength).toBeGreaterThan(1024);
93+
expectBufferedAsset(result.videos[0], "video", 1024);
8394
},
8495
8 * 60_000,
8596
);
@@ -101,8 +112,7 @@ describe.skipIf(!LIVE || !VYDRA_API_KEY)("vydra live", () => {
101112
});
102113

103114
expect(result.videos.length).toBeGreaterThan(0);
104-
expect(result.videos[0]?.mimeType.startsWith("video/")).toBe(true);
105-
expect(result.videos[0]?.buffer.byteLength).toBeGreaterThan(1024);
115+
expectBufferedAsset(result.videos[0], "video", 1024);
106116
},
107117
15 * 60_000,
108118
);

scripts/lib/ci-node-test-plan.mjs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ const EXCLUDED_FULL_SUITE_SHARDS = new Set([
88

99
const EXCLUDED_PROJECT_CONFIGS = new Set(["test/vitest/vitest.channels.config.ts"]);
1010

11+
function formatNodeTestShardCheckName(shardName) {
12+
const normalizedShardName = shardName.startsWith("core-unit-")
13+
? `core-${shardName.slice("core-unit-".length)}`
14+
: shardName;
15+
return `checks-node-${normalizedShardName}`;
16+
}
17+
1118
export function createNodeTestShards() {
1219
return fullSuiteVitestShards.flatMap((shard) => {
1320
if (EXCLUDED_FULL_SUITE_SHARDS.has(shard.config)) {
@@ -21,7 +28,7 @@ export function createNodeTestShards() {
2128

2229
return [
2330
{
24-
checkName: `checks-node-core-test-${shard.name}`,
31+
checkName: formatNodeTestShardCheckName(shard.name),
2532
shardName: shard.name,
2633
configs,
2734
},

src/agents/auth-profiles/doctor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { OpenClawConfig } from "../../config/config.js";
22
import { buildProviderAuthDoctorHintWithPlugin } from "../../plugins/provider-runtime.runtime.js";
3-
import { normalizeProviderId } from "../model-selection.js";
3+
import { normalizeProviderId } from "../model-selection-normalize.js";
44
import type { AuthProfileStore } from "./types.js";
55

66
/**

src/agents/auth-profiles/external-auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import type { ProviderExternalAuthProfile } from "../../plugins/provider-external-auth.types.js";
12
import { resolveExternalAuthProfilesWithPlugins } from "../../plugins/provider-runtime.js";
2-
import type { ProviderExternalAuthProfile } from "../../plugins/types.js";
33
import type { AuthProfileStore, OAuthCredential } from "./types.js";
44

55
type ExternalAuthProfileMap = Map<string, ProviderExternalAuthProfile>;

src/agents/model-catalog.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type DiscoveredModel = {
2323
input?: ModelInputType[];
2424
};
2525

26-
type PiSdkModule = typeof import("./pi-model-discovery.js");
26+
type PiSdkModule = typeof import("./pi-model-discovery-runtime.js");
2727
type PiRegistryInstance =
2828
| Array<DiscoveredModel>
2929
| {

src/agents/model-selection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from "./agent-scope.js";
2222
import { resolveConfiguredProviderFallback } from "./configured-provider-fallback.js";
2323
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
24-
import type { ModelCatalogEntry } from "./model-catalog.js";
24+
import type { ModelCatalogEntry } from "./model-catalog.types.js";
2525
import { splitTrailingAuthProfile } from "./model-ref-profile.js";
2626
import {
2727
type ModelRef,

src/agents/pi-auth-credentials.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { normalizeOptionalString } from "../shared/string-coerce.js";
22
import type { AuthProfileCredential, AuthProfileStore } from "./auth-profiles.js";
3-
import { normalizeProviderId } from "./model-selection.js";
3+
import { normalizeProviderId } from "./provider-id.js";
44

55
export type PiApiKeyCredential = { type: "api_key"; key: string };
66
export type PiOAuthCredential = {

0 commit comments

Comments
 (0)