mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix(plugin-sdk): align speech runtime packaging (#89899)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
parent
5537bc9c4d
commit
de152b1f65
9 changed files with 109 additions and 14 deletions
|
|
@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
|
|||
|
||||
### Fixes
|
||||
|
||||
- **Packaged speech runtime:** stop treating package-backed `speech-core` as a bundled plugin sidecar, restoring TTS startup in npm installs while release checks keep true activation-bypassing facades package-complete. (#89899, #89425) Thanks @zhangguiping-xydt.
|
||||
- **Codex app-server protocol:** require app-server 0.142 or newer, remove pre-0.142 wire-shape compatibility, and teach Codex to retrieve deferred native `spawn_agent` through `tool_search` so native subagent task mirroring works on search-capable models. (#101221)
|
||||
- **Android hardware keyboard chat:** send with unmodified Enter on physical keyboards while preserving Shift+Enter and other modified Enter combinations for multiline input. (#101239) Thanks @3ninyt3nin-creator.
|
||||
- **CJK Markdown emphasis:** render adjacent Chinese, Japanese, and Korean emphasis punctuation through the shared Markdown pipeline instead of leaking literal markers across channels. (#101230, #101120) Thanks @nicknmorty.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/strin
|
|||
const QA_ALWAYS_STAGE_RUNTIME_PLUGIN_IDS = Object.freeze([
|
||||
"image-generation-core",
|
||||
"media-understanding-core",
|
||||
"speech-core",
|
||||
]);
|
||||
const QA_OPENAI_PLUGIN_ID = "openai";
|
||||
const QA_BUNDLED_PLUGIN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
||||
|
|
|
|||
|
|
@ -1529,7 +1529,9 @@ describe("qa bundled plugin dir", () => {
|
|||
);
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "qa-channel"), { recursive: true });
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "memory-core"), { recursive: true });
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "speech-core"), { recursive: true });
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "image-generation-core"), {
|
||||
recursive: true,
|
||||
});
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "unused-plugin"), { recursive: true });
|
||||
await mkdir(path.join(repoRoot, "dist", "plugin-sdk"), { recursive: true });
|
||||
await writeFile(
|
||||
|
|
@ -1564,9 +1566,9 @@ describe("qa bundled plugin dir", () => {
|
|||
});
|
||||
|
||||
expect((await readdir(bundledPluginsDir)).toSorted()).toEqual([
|
||||
"image-generation-core",
|
||||
"memory-core",
|
||||
"qa-channel",
|
||||
"speech-core",
|
||||
]);
|
||||
expect(bundledPluginsDir).toBe(
|
||||
path.join(
|
||||
|
|
@ -1590,7 +1592,9 @@ describe("qa bundled plugin dir", () => {
|
|||
expect(qaChannel.accountId).toBe("qa");
|
||||
expect((await lstat(path.join(bundledPluginsDir, "qa-channel"))).isDirectory()).toBe(true);
|
||||
expect((await lstat(path.join(bundledPluginsDir, "memory-core"))).isDirectory()).toBe(true);
|
||||
expect((await lstat(path.join(bundledPluginsDir, "speech-core"))).isDirectory()).toBe(true);
|
||||
expect((await lstat(path.join(bundledPluginsDir, "image-generation-core"))).isDirectory()).toBe(
|
||||
true,
|
||||
);
|
||||
const sharedChunkStat = await lstat(
|
||||
path.join(
|
||||
repoRoot,
|
||||
|
|
@ -2064,9 +2068,9 @@ describe("qa bundled plugin dir", () => {
|
|||
JSON.stringify({ openclaw: { install: { minHostVersion: ">=2026.4.8" } } }),
|
||||
"utf8",
|
||||
);
|
||||
await mkdir(path.join(bundledRoot, "speech-core"), { recursive: true });
|
||||
await mkdir(path.join(bundledRoot, "image-generation-core"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(bundledRoot, "speech-core", "package.json"),
|
||||
path.join(bundledRoot, "image-generation-core", "package.json"),
|
||||
JSON.stringify({ openclaw: { install: { minHostVersion: ">=2026.4.9" } } }),
|
||||
"utf8",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
} from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { formatErrorMessage } from "../src/infra/errors.ts";
|
||||
import { ALWAYS_ALLOWED_RUNTIME_DIR_NAMES } from "../src/plugin-sdk/facade-activation-contract.ts";
|
||||
import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../src/plugins/runtime-sidecar-paths.ts";
|
||||
import { readBoundedResponseText } from "./lib/bounded-response.ts";
|
||||
import { listBundledPluginPackArtifacts } from "./lib/bundled-plugin-build-entries.mjs";
|
||||
|
|
@ -409,6 +410,7 @@ export function collectInstalledPackageErrors(params: {
|
|||
}
|
||||
|
||||
errors.push(...collectInstalledBundledExtensionManifestErrors(params.packageRoot));
|
||||
errors.push(...collectInstalledAlwaysAllowedRuntimeFacadeErrors(params.packageRoot));
|
||||
errors.push(...collectInstalledContextEngineRuntimeErrors(params.packageRoot));
|
||||
errors.push(...collectInstalledPluginSdkZodArtifactErrors(params.packageRoot));
|
||||
errors.push(...collectInstalledPluginSdkDeclarationErrors(params.packageRoot));
|
||||
|
|
@ -417,6 +419,25 @@ export function collectInstalledPackageErrors(params: {
|
|||
return errors;
|
||||
}
|
||||
|
||||
export function collectInstalledAlwaysAllowedRuntimeFacadeErrors(packageRoot: string): string[] {
|
||||
const errors: string[] = [];
|
||||
const activationRuntimePath = "dist/facade-activation-check.runtime.js";
|
||||
if (!existsSync(join(packageRoot, activationRuntimePath))) {
|
||||
errors.push(
|
||||
`installed package is missing required facade activation runtime: ${activationRuntimePath}`,
|
||||
);
|
||||
}
|
||||
for (const dirName of ALWAYS_ALLOWED_RUNTIME_DIR_NAMES) {
|
||||
const relativePath = `dist/extensions/${dirName}/runtime-api.js`;
|
||||
if (!existsSync(join(packageRoot, relativePath))) {
|
||||
errors.push(
|
||||
`installed package allows bundled runtime facade ${dirName}/runtime-api.js but is missing required runtime sidecar: ${relativePath}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
function collectInstalledBundledExtensionIds(packageRoot: string): Set<string> {
|
||||
const extensionsDir = join(packageRoot, "dist", "extensions");
|
||||
if (!existsSync(extensionsDir)) {
|
||||
|
|
|
|||
|
|
@ -25,13 +25,10 @@ import {
|
|||
type PluginManifestRecord,
|
||||
} from "../plugins/manifest-registry.js";
|
||||
import { parseJsonWithJson5Fallback } from "../utils/parse-json-compat.js";
|
||||
import { ALWAYS_ALLOWED_RUNTIME_DIR_NAMES } from "./facade-activation-contract.js";
|
||||
import { resolveRegistryPluginModuleLocationFromRecords } from "./facade-resolution-shared.js";
|
||||
|
||||
const ALWAYS_ALLOWED_RUNTIME_DIR_NAMES = new Set([
|
||||
"image-generation-core",
|
||||
"media-understanding-core",
|
||||
"speech-core",
|
||||
]);
|
||||
const ALWAYS_ALLOWED_RUNTIME_DIR_NAME_SET = new Set<string>(ALWAYS_ALLOWED_RUNTIME_DIR_NAMES);
|
||||
const EMPTY_FACADE_BOUNDARY_CONFIG: OpenClawConfig = {};
|
||||
|
||||
/** Minimal manifest shape needed to decide whether a bundled facade may load. */
|
||||
|
|
@ -265,7 +262,7 @@ export function resolveBundledPluginPublicSurfaceAccess(params: {
|
|||
}): { allowed: boolean; pluginId?: string; reason?: string } {
|
||||
if (
|
||||
params.artifactBasename === "runtime-api.js" &&
|
||||
ALWAYS_ALLOWED_RUNTIME_DIR_NAMES.has(params.dirName)
|
||||
ALWAYS_ALLOWED_RUNTIME_DIR_NAME_SET.has(params.dirName)
|
||||
) {
|
||||
return {
|
||||
allowed: true,
|
||||
|
|
|
|||
6
src/plugin-sdk/facade-activation-contract.ts
Normal file
6
src/plugin-sdk/facade-activation-contract.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Runtime facades with no activation-owned plugin manifest. Every entry must
|
||||
// ship a matching dist/extensions/<id>/runtime-api.js sidecar.
|
||||
export const ALWAYS_ALLOWED_RUNTIME_DIR_NAMES = [
|
||||
"image-generation-core",
|
||||
"media-understanding-core",
|
||||
] as const;
|
||||
|
|
@ -547,10 +547,10 @@ describe("plugin-sdk facade runtime", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("keeps shared runtime-core facades available without plugin activation", () => {
|
||||
it("keeps bundled extension runtime-core facades available without plugin activation", () => {
|
||||
setRuntimeConfigSnapshot({});
|
||||
|
||||
for (const dirName of ["speech-core", "image-generation-core", "media-understanding-core"]) {
|
||||
for (const dirName of ["image-generation-core", "media-understanding-core"]) {
|
||||
expect(
|
||||
resolveActivationCheckBundledPluginPublicSurfaceAccess({
|
||||
dirName,
|
||||
|
|
@ -566,6 +566,23 @@ describe("plugin-sdk facade runtime", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("does not treat package-backed speech-core as a bundled extension facade", () => {
|
||||
setRuntimeConfigSnapshot({});
|
||||
|
||||
expect(
|
||||
resolveActivationCheckBundledPluginPublicSurfaceAccess({
|
||||
dirName: "speech-core",
|
||||
artifactBasename: "runtime-api.js",
|
||||
location: null,
|
||||
sourceExtensionsRoot: "",
|
||||
resolutionKey: "runtime-core:speech-core",
|
||||
}),
|
||||
).toEqual({
|
||||
allowed: false,
|
||||
reason: "no bundled plugin manifest found for speech-core",
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers the source runtime snapshot for facade activation checks", () => {
|
||||
const dir = createTempDirSync("openclaw-facade-source-snapshot-");
|
||||
fs.mkdirSync(path.join(dir, "demo"), { recursive: true });
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { describe, expect, it, vi } from "vitest";
|
|||
import {
|
||||
buildPublishedInstallCommandArgs,
|
||||
buildPublishedInstallScenarios,
|
||||
collectInstalledAlwaysAllowedRuntimeFacadeErrors,
|
||||
collectInstalledBundledExtensionManifestErrors,
|
||||
collectInstalledBundledRuntimeSidecarPaths,
|
||||
collectInstalledContextEngineRuntimeErrors,
|
||||
|
|
@ -447,6 +448,43 @@ describe("collectInstalledPackageErrors", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("collectInstalledAlwaysAllowedRuntimeFacadeErrors", () => {
|
||||
function withInstalledPackageRoot(run: (packageRoot: string) => void): void {
|
||||
const packageRoot = mkdtempSync(join(tmpdir(), "openclaw-postpublish-facade-runtime-"));
|
||||
try {
|
||||
run(packageRoot);
|
||||
} finally {
|
||||
rmSync(packageRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function writeInstalledFile(packageRoot: string, relativePath: string): void {
|
||||
const filePath = join(packageRoot, relativePath);
|
||||
mkdirSync(dirname(filePath), { recursive: true });
|
||||
writeFileSync(filePath, "export {};\n", "utf8");
|
||||
}
|
||||
|
||||
it("reports the activation runtime and every missing allowlisted sidecar", () => {
|
||||
withInstalledPackageRoot((packageRoot) => {
|
||||
expect(collectInstalledAlwaysAllowedRuntimeFacadeErrors(packageRoot)).toEqual([
|
||||
"installed package is missing required facade activation runtime: dist/facade-activation-check.runtime.js",
|
||||
"installed package allows bundled runtime facade image-generation-core/runtime-api.js but is missing required runtime sidecar: dist/extensions/image-generation-core/runtime-api.js.",
|
||||
"installed package allows bundled runtime facade media-understanding-core/runtime-api.js but is missing required runtime sidecar: dist/extensions/media-understanding-core/runtime-api.js.",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts a package with the activation runtime and allowlisted sidecars", () => {
|
||||
withInstalledPackageRoot((packageRoot) => {
|
||||
writeInstalledFile(packageRoot, "dist/facade-activation-check.runtime.js");
|
||||
writeInstalledFile(packageRoot, "dist/extensions/image-generation-core/runtime-api.js");
|
||||
writeInstalledFile(packageRoot, "dist/extensions/media-understanding-core/runtime-api.js");
|
||||
|
||||
expect(collectInstalledAlwaysAllowedRuntimeFacadeErrors(packageRoot)).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("collectInstalledContextEngineRuntimeErrors", () => {
|
||||
function makeInstalledPackageRoot(): string {
|
||||
return mkdtempSync(join(tmpdir(), "openclaw-postpublish-context-engine-"));
|
||||
|
|
|
|||
|
|
@ -729,6 +729,18 @@ describe("collectMissingPackPaths", () => {
|
|||
const packageRoot = join(root, "openclaw");
|
||||
const distDir = join(packageRoot, "dist");
|
||||
mkdirSync(distDir, { recursive: true });
|
||||
for (const relativePath of [
|
||||
"facade-activation-check.runtime.js",
|
||||
"extensions/image-generation-core/runtime-api.js",
|
||||
"extensions/media-understanding-core/runtime-api.js",
|
||||
]) {
|
||||
const filePath = join(distDir, relativePath);
|
||||
mkdirSync(dirname(filePath), { recursive: true });
|
||||
writeFileSync(filePath, "export {};\n");
|
||||
}
|
||||
for (const pluginId of ["image-generation-core", "media-understanding-core"]) {
|
||||
writeFileSync(join(distDir, "extensions", pluginId, "package.json"), "{}\n");
|
||||
}
|
||||
writeFileSync(
|
||||
join(packageRoot, "package.json"),
|
||||
`${JSON.stringify({ name: "openclaw", version: "2026.5.14-beta.3", dependencies: {} })}\n`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue