diff --git a/appcast.xml b/appcast.xml
index 0146a02c43d..e22b5e78418 100644
--- a/appcast.xml
+++ b/appcast.xml
@@ -6,7 +6,7 @@
2026.4.5
Mon, 06 Apr 2026 04:55:17 +0100
https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml
- 2026040501
+ 2026040590
2026.4.5
15.0
OpenClaw 2026.4.5
@@ -436,4 +436,4 @@
-
\ No newline at end of file
+
diff --git a/extensions/tsconfig.package-boundary.paths.json b/extensions/tsconfig.package-boundary.paths.json
index b0e1ca3909f..5c49336a4ae 100644
--- a/extensions/tsconfig.package-boundary.paths.json
+++ b/extensions/tsconfig.package-boundary.paths.json
@@ -3,8 +3,11 @@
"compilerOptions": {
"paths": {
"openclaw/extension-api": ["../src/extensionAPI.ts"],
- "openclaw/plugin-sdk": ["../src/plugin-sdk/index.ts"],
- "openclaw/plugin-sdk/*": ["../src/plugin-sdk/*.ts"],
+ "openclaw/plugin-sdk": ["../packages/plugin-sdk/dist/src/plugin-sdk/index.d.ts"],
+ "openclaw/plugin-sdk/*": [
+ "../packages/plugin-sdk/dist/src/plugin-sdk/*.d.ts",
+ "../packages/plugin-sdk/dist/packages/plugin-sdk/src/*.d.ts"
+ ],
"openclaw/plugin-sdk/account-id": ["../src/plugin-sdk/account-id.ts"],
"@openclaw/*": ["../extensions/*"],
"@openclaw/plugin-sdk/*": ["../packages/plugin-sdk/dist/packages/plugin-sdk/src/*.d.ts"]
diff --git a/extensions/xai/.boundary-stubs/anthropic-vertex-api.d.ts b/extensions/xai/.boundary-stubs/anthropic-vertex-api.d.ts
new file mode 100644
index 00000000000..4c25821c837
--- /dev/null
+++ b/extensions/xai/.boundary-stubs/anthropic-vertex-api.d.ts
@@ -0,0 +1,2 @@
+export const resolveAnthropicVertexClientRegion: (...args: unknown[]) => unknown;
+export const resolveAnthropicVertexProjectId: (...args: unknown[]) => unknown;
diff --git a/extensions/xai/.boundary-stubs/ollama-api.d.ts b/extensions/xai/.boundary-stubs/ollama-api.d.ts
new file mode 100644
index 00000000000..0deafeb53c7
--- /dev/null
+++ b/extensions/xai/.boundary-stubs/ollama-api.d.ts
@@ -0,0 +1 @@
+export const resolveOllamaApiBase: (...args: unknown[]) => unknown;
diff --git a/extensions/xai/.boundary-stubs/ollama-runtime-api.d.ts b/extensions/xai/.boundary-stubs/ollama-runtime-api.d.ts
new file mode 100644
index 00000000000..74041aaf2ea
--- /dev/null
+++ b/extensions/xai/.boundary-stubs/ollama-runtime-api.d.ts
@@ -0,0 +1,16 @@
+export type OllamaEmbeddingClient = unknown;
+
+export const buildAssistantMessage: (...args: unknown[]) => unknown;
+export const buildOllamaChatRequest: (...args: unknown[]) => unknown;
+export const convertToOllamaMessages: (...args: unknown[]) => unknown;
+export const createConfiguredOllamaCompatNumCtxWrapper: (...args: unknown[]) => unknown;
+export const createConfiguredOllamaCompatStreamWrapper: (...args: unknown[]) => unknown;
+export const createConfiguredOllamaStreamFn: (...args: unknown[]) => unknown;
+export const createOllamaStreamFn: (...args: unknown[]) => unknown;
+export const createOllamaEmbeddingProvider: (...args: unknown[]) => unknown;
+export const isOllamaCompatProvider: (...args: unknown[]) => unknown;
+export const resolveOllamaBaseUrlForRun: (...args: unknown[]) => unknown;
+export const resolveOllamaCompatNumCtxEnabled: (...args: unknown[]) => unknown;
+export const shouldInjectOllamaCompatNumCtx: (...args: unknown[]) => unknown;
+export const parseNdjsonStream: (...args: unknown[]) => unknown;
+export const wrapOllamaCompatNumCtx: (...args: unknown[]) => unknown;
diff --git a/extensions/xai/.boundary-stubs/speech-core-runtime-api.d.ts b/extensions/xai/.boundary-stubs/speech-core-runtime-api.d.ts
new file mode 100644
index 00000000000..522fb60a362
--- /dev/null
+++ b/extensions/xai/.boundary-stubs/speech-core-runtime-api.d.ts
@@ -0,0 +1,32 @@
+export type ResolvedTtsConfig = unknown;
+export type ResolvedTtsModelOverrides = unknown;
+export type TtsDirectiveOverrides = unknown;
+export type TtsDirectiveParseResult = unknown;
+export type TtsResult = unknown;
+export type TtsSynthesisResult = unknown;
+export type TtsTelephonyResult = unknown;
+
+export const _test: unknown;
+export const buildTtsSystemPromptHint: (...args: unknown[]) => unknown;
+export const getLastTtsAttempt: (...args: unknown[]) => unknown;
+export const getResolvedSpeechProviderConfig: (...args: unknown[]) => unknown;
+export const getTtsMaxLength: (...args: unknown[]) => unknown;
+export const getTtsProvider: (...args: unknown[]) => unknown;
+export const isSummarizationEnabled: (...args: unknown[]) => unknown;
+export const isTtsEnabled: (...args: unknown[]) => unknown;
+export const isTtsProviderConfigured: (...args: unknown[]) => unknown;
+export const listSpeechVoices: (...args: unknown[]) => unknown;
+export const maybeApplyTtsToPayload: (...args: unknown[]) => unknown;
+export const resolveTtsAutoMode: (...args: unknown[]) => unknown;
+export const resolveTtsConfig: (...args: unknown[]) => unknown;
+export const resolveTtsPrefsPath: (...args: unknown[]) => unknown;
+export const resolveTtsProviderOrder: (...args: unknown[]) => unknown;
+export const setLastTtsAttempt: (...args: unknown[]) => unknown;
+export const setSummarizationEnabled: (...args: unknown[]) => unknown;
+export const setTtsAutoMode: (...args: unknown[]) => unknown;
+export const setTtsEnabled: (...args: unknown[]) => unknown;
+export const setTtsMaxLength: (...args: unknown[]) => unknown;
+export const setTtsProvider: (...args: unknown[]) => unknown;
+export const synthesizeSpeech: (...args: unknown[]) => unknown;
+export const textToSpeech: (...args: unknown[]) => unknown;
+export const textToSpeechTelephony: (...args: unknown[]) => unknown;
diff --git a/extensions/xai/tsconfig.json b/extensions/xai/tsconfig.json
index b1eac8b8a69..b237dac1b3f 100644
--- a/extensions/xai/tsconfig.json
+++ b/extensions/xai/tsconfig.json
@@ -1,7 +1,22 @@
{
"extends": "../tsconfig.package-boundary.base.json",
"compilerOptions": {
- "rootDir": "."
+ "rootDir": ".",
+ "paths": {
+ "openclaw/extension-api": ["../../src/extensionAPI.ts"],
+ "openclaw/plugin-sdk": ["../../packages/plugin-sdk/dist/src/plugin-sdk/index.d.ts"],
+ "openclaw/plugin-sdk/*": [
+ "../../packages/plugin-sdk/dist/src/plugin-sdk/*.d.ts",
+ "../../packages/plugin-sdk/dist/packages/plugin-sdk/src/*.d.ts"
+ ],
+ "openclaw/plugin-sdk/account-id": ["../../src/plugin-sdk/account-id.ts"],
+ "@openclaw/*": ["../*"],
+ "@openclaw/plugin-sdk/*": ["../../packages/plugin-sdk/dist/packages/plugin-sdk/src/*.d.ts"],
+ "@openclaw/anthropic-vertex/api.js": ["./.boundary-stubs/anthropic-vertex-api.d.ts"],
+ "@openclaw/ollama/api.js": ["./.boundary-stubs/ollama-api.d.ts"],
+ "@openclaw/ollama/runtime-api.js": ["./.boundary-stubs/ollama-runtime-api.d.ts"],
+ "@openclaw/speech-core/runtime-api.js": ["./.boundary-stubs/speech-core-runtime-api.d.ts"]
+ }
},
"include": ["./*.ts", "./src/**/*.ts"],
"exclude": ["./**/*.test.ts", "./dist/**", "./node_modules/**"]
diff --git a/src/agents/bash-tools.exec-host-gateway.test.ts b/src/agents/bash-tools.exec-host-gateway.test.ts
index 4ff2e0f6599..bebfd1881f8 100644
--- a/src/agents/bash-tools.exec-host-gateway.test.ts
+++ b/src/agents/bash-tools.exec-host-gateway.test.ts
@@ -1,5 +1,12 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+const INLINE_EVAL_HIT = {
+ executable: "python3",
+ normalizedExecutable: "python3",
+ flag: "-c",
+ argv: ["python3", "-c", "print(1)"],
+};
+
const createAndRegisterDefaultExecApprovalRequestMock = vi.hoisted(() => vi.fn());
const buildExecApprovalPendingToolResultMock = vi.hoisted(() => vi.fn());
const buildExecApprovalFollowupTargetMock = vi.hoisted(() => vi.fn(() => null));
@@ -58,7 +65,14 @@ const enforceStrictInlineEvalApprovalBoundaryMock = vi.hoisted(() =>
),
);
const detectInterpreterInlineEvalArgvMock = vi.hoisted(() =>
- vi.fn((): { kind: string } | null => null),
+ vi.fn(
+ (): {
+ executable: string;
+ normalizedExecutable: string;
+ flag: string;
+ argv: string[];
+ } | null => null,
+ ),
);
vi.mock("../infra/exec-approvals.js", () => ({
@@ -295,7 +309,7 @@ describe("processGatewayAllowlist", () => {
hostAsk: "always",
askFallback: "full",
});
- detectInterpreterInlineEvalArgvMock.mockReturnValue({ kind: "python-c" });
+ detectInterpreterInlineEvalArgvMock.mockReturnValue(INLINE_EVAL_HIT);
resolveApprovalDecisionOrUndefinedMock.mockResolvedValue(null);
createExecApprovalDecisionStateMock.mockReturnValue({
baseDecision: { timedOut: true },
@@ -341,7 +355,7 @@ describe("processGatewayAllowlist", () => {
hostAsk: "always",
askFallback: "allowlist",
});
- detectInterpreterInlineEvalArgvMock.mockReturnValue({ kind: "python-c" });
+ detectInterpreterInlineEvalArgvMock.mockReturnValue(INLINE_EVAL_HIT);
resolveApprovalDecisionOrUndefinedMock.mockResolvedValue(null);
createExecApprovalDecisionStateMock.mockReturnValue({
baseDecision: { timedOut: true },
diff --git a/src/agents/bash-tools.exec-host-node.test.ts b/src/agents/bash-tools.exec-host-node.test.ts
index 9af7b30b960..9fdee702a10 100644
--- a/src/agents/bash-tools.exec-host-node.test.ts
+++ b/src/agents/bash-tools.exec-host-node.test.ts
@@ -1,5 +1,12 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
+const INLINE_EVAL_HIT = {
+ executable: "python3",
+ normalizedExecutable: "python3",
+ flag: "-c",
+ argv: ["python3", "-c", "print(1)"],
+};
+
const preparedPlan = vi.hoisted(() => ({
argv: ["bun", "./script.ts"],
cwd: "/tmp/work",
@@ -60,7 +67,14 @@ const registerExecApprovalRequestForHostOrThrowMock = vi.hoisted(() =>
vi.fn(async () => undefined),
);
const detectInterpreterInlineEvalArgvMock = vi.hoisted(() =>
- vi.fn((): { kind: string } | null => null),
+ vi.fn(
+ (): {
+ executable: string;
+ normalizedExecutable: string;
+ flag: string;
+ argv: string[];
+ } | null => null,
+ ),
);
vi.mock("../infra/exec-approvals.js", () => ({
@@ -264,7 +278,7 @@ describe("executeNodeHostCommand", () => {
});
it("denies timed-out inline-eval requests instead of invoking the node", async () => {
- detectInterpreterInlineEvalArgvMock.mockReturnValue({ kind: "python-c" });
+ detectInterpreterInlineEvalArgvMock.mockReturnValue(INLINE_EVAL_HIT);
resolveApprovalDecisionOrUndefinedMock.mockResolvedValue(null);
createExecApprovalDecisionStateMock.mockReturnValue({
baseDecision: { timedOut: true },
diff --git a/test/test-env.test.ts b/test/test-env.test.ts
index 5e8c30d8067..4a1e2ca94f3 100644
--- a/test/test-env.test.ts
+++ b/test/test-env.test.ts
@@ -71,11 +71,17 @@ describe("installTestEnv", () => {
},
channels: {
telegram: {
- streamMode: "block",
- chunkMode: "newline",
- blockStreaming: true,
- draftChunk: {
- minChars: 120,
+ streaming: {
+ mode: "block",
+ chunkMode: "newline",
+ block: {
+ enabled: true,
+ },
+ preview: {
+ chunk: {
+ minChars: 120,
+ },
+ },
},
},
},
@@ -127,7 +133,7 @@ describe("installTestEnv", () => {
expect(copiedConfig.agents?.defaults?.agentDir).toBeUndefined();
expect(copiedConfig.agents?.list?.[0]?.workspace).toBeUndefined();
expect(copiedConfig.agents?.list?.[0]?.agentDir).toBeUndefined();
- expect(copiedConfig.channels?.telegram?.streaming).toMatchObject({
+ expect(copiedConfig.channels?.telegram?.streaming).toEqual({
mode: "block",
chunkMode: "newline",
block: { enabled: true },