fix: repair Buzz plugin packaging (#116865)

Restore standalone Buzz npm and ClawHub packaging by using the shipped QA runner SDK surface and generated dependency lock workflow.

Prepared head SHA: 58d0a52c61
Co-authored-by: Shakker <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
This commit is contained in:
Shakker
2026-07-31 15:32:16 +01:00
committed by GitHub
parent a4b8ec7890
commit c5979d05ef
6 changed files with 210 additions and 336 deletions
+1
View File
@@ -58,6 +58,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- **Buzz plugin packaging:** keep the live QA runner on the shipped QA runner SDK surface and remove the obsolete package shrinkwrap so standalone npm and ClawHub package builds use current host exports and dependency resolutions. Thanks @shakkernerd.
- **Control UI sharing connection isolation:** discard stale visibility and membership mutation results after switching gateways or accounts so previous-connection refreshes and errors cannot update the replacement connection. Fixes #116800. Thanks @shakkernerd.
- **Control UI session refreshes:** preserve explicitly queued list filters and background hydration across later Gateway event invalidation, while keeping append pagination followed by a canonical refresh. Fixes #116697. Thanks @shakkernerd.
- **Gateway device clock skew:** sign device proofs with the Gateway-issued challenge timestamp across TypeScript, Control UI, browser extension, Android, Apple, Linux, and watchOS clients so incorrect local clocks no longer block authentication, while retaining no-challenge compatibility for pre-challenge Control UI servers and older watch-node HTTP endpoints and keeping nonce binding and freshness checks enforced. Fixes #103455.
-137
View File
@@ -1,137 +0,0 @@
{
"name": "@openclaw/buzz",
"version": "2026.7.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/buzz",
"version": "2026.7.2",
"dependencies": {
"nostr-tools": "2.23.12",
"zod": "4.4.3"
},
"peerDependencies": {
"openclaw": ">=2026.7.2"
},
"peerDependenciesMeta": {
"openclaw": {
"optional": true
}
}
},
"node_modules/@noble/ciphers": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz",
"integrity": "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==",
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/curves": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz",
"integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "2.0.1"
},
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
"integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/base": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz",
"integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip32": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-2.0.1.tgz",
"integrity": "sha512-4Md1NI5BzoVP+bhyJaY3K6yMesEFzNS1sE/cP+9nuvE7p/b0kx9XbpDHHFl8dHtufcbdHRUUQdRqLIPHN/s7yA==",
"license": "MIT",
"dependencies": {
"@noble/curves": "2.0.1",
"@noble/hashes": "2.0.1",
"@scure/base": "2.0.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip39": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-2.0.1.tgz",
"integrity": "sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "2.0.1",
"@scure/base": "2.0.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/nostr-tools": {
"version": "2.23.12",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.23.12.tgz",
"integrity": "sha512-dLE9r0b4pCmrOKLUPD0KhUj9IXeh6RwiYoonEWeBh2AaDmlczUjJqJC8pQfggXzKUZ7+uZvhcrKkfwuZk0/e1g==",
"license": "Unlicense",
"dependencies": {
"@noble/ciphers": "2.1.1",
"@noble/curves": "2.0.1",
"@noble/hashes": "2.0.1",
"@scure/base": "2.0.0",
"@scure/bip32": "2.0.1",
"@scure/bip39": "2.0.1",
"nostr-wasm": "0.1.0"
},
"peerDependencies": {
"typescript": ">=5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/nostr-wasm": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz",
"integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==",
"license": "MIT"
},
"node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
+3 -3
View File
@@ -1,13 +1,13 @@
import { Command } from "commander";
import type { LiveTransportQaSuiteCommandOptions } from "openclaw/plugin-sdk/qa-runtime";
import type { LiveTransportQaSuiteCommandOptions } from "openclaw/plugin-sdk/qa-runner-runtime";
import { beforeEach, describe, expect, it, vi } from "vitest";
const runLiveTransportQaSuiteCommand = vi.hoisted(() =>
vi.fn<(params: LiveTransportQaSuiteCommandOptions) => Promise<void>>(async () => {}),
);
vi.mock("openclaw/plugin-sdk/qa-runtime", async (importOriginal) => ({
...(await importOriginal<typeof import("openclaw/plugin-sdk/qa-runtime")>()),
vi.mock("openclaw/plugin-sdk/qa-runner-runtime", async (importOriginal) => ({
...(await importOriginal<typeof import("openclaw/plugin-sdk/qa-runner-runtime")>()),
runLiveTransportQaSuiteCommand,
}));
+1 -1
View File
@@ -4,7 +4,7 @@ import {
runLiveTransportQaSuiteCommand,
type LiveTransportQaCliRegistration,
type LiveTransportQaCommandOptions,
} from "openclaw/plugin-sdk/qa-runtime";
} from "openclaw/plugin-sdk/qa-runner-runtime";
const DEFAULT_BUZZ_QA_SCENARIOS = ["channel-canary", "channel-mention-gating"] as const;
+193
View File
@@ -202,6 +202,193 @@ export type QaRunnerCliRegistration = {
register(qa: Command): void;
};
/** Normalized options passed from live-transport QA CLIs into lane runners. */
export type LiveTransportQaCommandOptions = {
repoRoot?: string;
outputDir?: string;
providerMode?: string;
primaryModel?: string;
alternateModel?: string;
fastMode?: boolean;
allowFailures?: boolean;
failFast?: boolean;
profile?: string;
scenarioIds?: string[];
listScenarios?: boolean;
sutAccountId?: string;
credentialFile?: string;
credentialSource?: string;
credentialRole?: string;
};
export type LiveTransportQaSuiteCommandOptions = {
channelId: string;
credentialMode?: "env-only" | "shared-lease";
defaultProviderMode: string;
envCredentialReason?: string;
laneLabel?: string;
options: LiveTransportQaCommandOptions;
selectScenarioIds: (params: {
profile?: string;
primaryModel: string;
providerMode: string;
scenarioIds?: readonly string[];
}) => string[];
};
type LiveTransportQaCommanderOptions = {
repoRoot?: string;
outputDir?: string;
providerMode?: string;
model?: string;
altModel?: string;
scenario?: string[];
listScenarios?: boolean;
fast?: boolean;
allowFailures?: boolean;
failFast?: boolean;
profile?: string;
sutAccount?: string;
credentialFile?: string;
credentialSource?: string;
credentialRole?: string;
};
/** Commander registration hook for one live-transport QA subcommand. */
export type LiveTransportQaCliRegistration = QaRunnerCliRegistration;
/** Help text customizations for live credential source and role flags. */
export type LiveTransportQaCredentialCliOptions = {
sourceDescription?: string;
roleDescription?: string;
};
/** Declarative command metadata and runner used to install a live-transport QA CLI. */
export type LiveTransportQaCliRegistrationOptions = {
commandName: string;
credentialFileHelp?: string;
credentialOptions?: LiveTransportQaCredentialCliOptions;
defaultProviderMode: string;
description: string;
providerModeHelp: string;
listScenariosHelp?: string;
outputDirHelp: string;
profileHelp?: string;
failFastHelp?: string;
allowFailuresHelp?: string;
scenarioHelp: string;
sutAccountHelp: string;
adapterFactory?: QaRunnerCliRegistration["adapterFactory"];
run: (opts: LiveTransportQaCommandOptions) => Promise<void>;
};
/** Memoize a lazy CLI runtime import so repeated command paths share one loaded module. */
export function createLazyCliRuntimeLoader<T>(load: () => Promise<T>) {
let promise: Promise<T> | null = null;
return async () => {
promise ??= load();
return await promise;
};
}
function collectLiveTransportQaStringOption(value: string, previous: string[]) {
const trimmed = value.trim();
return trimmed ? [...previous, trimmed] : previous;
}
function mapLiveTransportQaCommanderOptions(
opts: LiveTransportQaCommanderOptions,
): LiveTransportQaCommandOptions {
return {
repoRoot: opts.repoRoot,
outputDir: opts.outputDir,
providerMode: opts.providerMode,
primaryModel: opts.model,
alternateModel: opts.altModel,
fastMode: opts.fast,
allowFailures: opts.allowFailures,
failFast: opts.failFast,
profile: opts.profile,
scenarioIds: opts.scenario,
listScenarios: opts.listScenarios,
sutAccountId: opts.sutAccount,
credentialFile: opts.credentialFile,
credentialSource: opts.credentialSource,
credentialRole: opts.credentialRole,
};
}
function registerLiveTransportQaCli(
params: LiveTransportQaCliRegistrationOptions & {
qa: Command;
run: (opts: LiveTransportQaCommandOptions) => Promise<void>;
},
) {
const command = params.qa
.command(params.commandName)
.description(params.description)
.option("--repo-root <path>", "Repository root to target when running from a neutral cwd")
.option("--output-dir <path>", params.outputDirHelp)
.option("--provider-mode <mode>", params.providerModeHelp, params.defaultProviderMode)
.option("--model <ref>", "Primary provider/model ref")
.option("--alt-model <ref>", "Alternate provider/model ref")
.option("--scenario <id>", params.scenarioHelp, collectLiveTransportQaStringOption, [])
.option("--fast", "Enable provider fast mode where supported", false);
if (params.allowFailuresHelp) {
command.option("--allow-failures", params.allowFailuresHelp, false);
}
command.option("--sut-account <id>", params.sutAccountHelp, "sut");
if (params.credentialFileHelp) {
command.option("--credential-file <path>", params.credentialFileHelp);
}
if (params.listScenariosHelp) {
command.option("--list-scenarios", params.listScenariosHelp, false);
}
if (params.profileHelp) {
command.option("--profile <profile>", params.profileHelp);
}
if (params.failFastHelp) {
command.option("--fail-fast", params.failFastHelp, false);
}
if (params.credentialOptions) {
command.option(
"--credential-source <source>",
params.credentialOptions.sourceDescription ??
"Credential source for live lanes: env or convex (default: env)",
);
if (params.credentialOptions.roleDescription) {
command.option("--credential-role <role>", params.credentialOptions.roleDescription);
}
}
command.action(async (opts: LiveTransportQaCommanderOptions) => {
await params.run(mapLiveTransportQaCommanderOptions(opts));
});
}
/** Build a Commander registration object for one live-transport QA command. */
export function createLiveTransportQaCliRegistration(
params: LiveTransportQaCliRegistrationOptions,
): LiveTransportQaCliRegistration {
return {
commandName: params.commandName,
adapterFactory: params.adapterFactory,
register(qa: Command) {
registerLiveTransportQaCli({
...params,
qa,
});
},
};
}
type QaRunnerRuntimeSurface = {
qaRunnerCliRegistrations?: readonly QaRunnerCliRegistration[];
};
@@ -215,6 +402,7 @@ type QaRuntimeSurface = {
},
) => string;
startQaLiveLaneGateway: (...args: unknown[]) => Promise<unknown>;
runLiveTransportQaSuiteCommand: (params: LiveTransportQaSuiteCommandOptions) => Promise<unknown>;
};
/** Resolved QA runner CLI contribution declared by plugin manifest metadata. */
@@ -278,6 +466,11 @@ export function isQaRuntimeAvailable(): boolean {
}
}
/** Run a plugin-owned transport adapter through QA Lab's shared suite host. */
export async function runLiveTransportQaSuiteCommand(params: LiveTransportQaSuiteCommandOptions) {
return await loadQaRuntimeModule().runLiveTransportQaSuiteCommand(params);
}
function listDeclaredQaRunnerPlugins(
env: NodeJS.ProcessEnv | undefined = resolvePrivateQaBundledPluginsEnv(),
): Array<
+12 -195
View File
@@ -4,16 +4,26 @@ import { createServer } from "node:net";
import path from "node:path";
// QA runtime helpers register and execute plugin QA scenarios from local files.
import { toErrorObject } from "@openclaw/normalization-core/error-coercion";
import type { Command } from "commander";
import { formatErrorMessage } from "./error-runtime.js";
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
import { resolvePrivateQaBundledPluginsEnv } from "./private-qa-bundled-env.js";
import { runExec } from "./process-runtime.js";
import type { QaRunnerCliRegistration } from "./qa-runner-runtime.js";
import { fetchWithSsrFGuard } from "./ssrf-runtime.js";
import { normalizeStringEntries } from "./string-coerce-runtime.js";
export { writeGatewayRestartIntentSync } from "../infra/restart-intent.js";
export {
createLazyCliRuntimeLoader,
createLiveTransportQaCliRegistration,
runLiveTransportQaSuiteCommand,
} from "./qa-runner-runtime.js";
export type {
LiveTransportQaCliRegistration,
LiveTransportQaCliRegistrationOptions,
LiveTransportQaCommandOptions,
LiveTransportQaCredentialCliOptions,
LiveTransportQaSuiteCommandOptions,
} from "./qa-runner-runtime.js";
type QaRuntimeSurface = {
defaultQaRuntimeModelForMode: (
@@ -24,7 +34,6 @@ type QaRuntimeSurface = {
},
) => string;
startQaLiveLaneGateway: (...args: unknown[]) => Promise<unknown>;
runLiveTransportQaSuiteCommand: (params: LiveTransportQaSuiteCommandOptions) => Promise<unknown>;
};
function isMissingQaRuntimeError(error: unknown) {
@@ -58,198 +67,6 @@ export function isQaRuntimeAvailable(): boolean {
}
}
/** Normalized options passed from live-transport QA CLIs into lane runners. */
export type LiveTransportQaCommandOptions = {
repoRoot?: string;
outputDir?: string;
providerMode?: string;
primaryModel?: string;
alternateModel?: string;
fastMode?: boolean;
allowFailures?: boolean;
failFast?: boolean;
profile?: string;
scenarioIds?: string[];
listScenarios?: boolean;
sutAccountId?: string;
credentialFile?: string;
credentialSource?: string;
credentialRole?: string;
};
export type LiveTransportQaSuiteCommandOptions = {
channelId: string;
credentialMode?: "env-only" | "shared-lease";
defaultProviderMode: string;
envCredentialReason?: string;
laneLabel?: string;
options: LiveTransportQaCommandOptions;
selectScenarioIds: (params: {
profile?: string;
primaryModel: string;
providerMode: string;
scenarioIds?: readonly string[];
}) => string[];
};
/** Run a plugin-owned transport adapter through QA Lab's shared suite host. */
export async function runLiveTransportQaSuiteCommand(params: LiveTransportQaSuiteCommandOptions) {
return await loadQaRuntimeModule().runLiveTransportQaSuiteCommand(params);
}
type LiveTransportQaCommanderOptions = {
repoRoot?: string;
outputDir?: string;
providerMode?: string;
model?: string;
altModel?: string;
scenario?: string[];
listScenarios?: boolean;
fast?: boolean;
allowFailures?: boolean;
failFast?: boolean;
profile?: string;
sutAccount?: string;
credentialFile?: string;
credentialSource?: string;
credentialRole?: string;
};
/** Commander registration hook for one live-transport QA subcommand. */
export type LiveTransportQaCliRegistration = QaRunnerCliRegistration;
/** Help text customizations for live credential source and role flags. */
export type LiveTransportQaCredentialCliOptions = {
sourceDescription?: string;
roleDescription?: string;
};
/** Declarative command metadata and runner used to install a live-transport QA CLI. */
export type LiveTransportQaCliRegistrationOptions = {
commandName: string;
credentialFileHelp?: string;
credentialOptions?: LiveTransportQaCredentialCliOptions;
defaultProviderMode: string;
description: string;
providerModeHelp: string;
listScenariosHelp?: string;
outputDirHelp: string;
profileHelp?: string;
failFastHelp?: string;
allowFailuresHelp?: string;
scenarioHelp: string;
sutAccountHelp: string;
adapterFactory?: QaRunnerCliRegistration["adapterFactory"];
run: (opts: LiveTransportQaCommandOptions) => Promise<void>;
};
/** Memoize a lazy CLI runtime import so repeated command paths share one loaded module. */
export function createLazyCliRuntimeLoader<T>(load: () => Promise<T>) {
let promise: Promise<T> | null = null;
return async () => {
promise ??= load();
return await promise;
};
}
function collectLiveTransportQaStringOption(value: string, previous: string[]) {
const trimmed = value.trim();
return trimmed ? [...previous, trimmed] : previous;
}
function mapLiveTransportQaCommanderOptions(
opts: LiveTransportQaCommanderOptions,
): LiveTransportQaCommandOptions {
return {
repoRoot: opts.repoRoot,
outputDir: opts.outputDir,
providerMode: opts.providerMode,
primaryModel: opts.model,
alternateModel: opts.altModel,
fastMode: opts.fast,
allowFailures: opts.allowFailures,
failFast: opts.failFast,
profile: opts.profile,
scenarioIds: opts.scenario,
listScenarios: opts.listScenarios,
sutAccountId: opts.sutAccount,
credentialFile: opts.credentialFile,
credentialSource: opts.credentialSource,
credentialRole: opts.credentialRole,
};
}
function registerLiveTransportQaCli(
params: LiveTransportQaCliRegistrationOptions & {
qa: Command;
run: (opts: LiveTransportQaCommandOptions) => Promise<void>;
},
) {
const command = params.qa
.command(params.commandName)
.description(params.description)
.option("--repo-root <path>", "Repository root to target when running from a neutral cwd")
.option("--output-dir <path>", params.outputDirHelp)
.option("--provider-mode <mode>", params.providerModeHelp, params.defaultProviderMode)
.option("--model <ref>", "Primary provider/model ref")
.option("--alt-model <ref>", "Alternate provider/model ref")
.option("--scenario <id>", params.scenarioHelp, collectLiveTransportQaStringOption, [])
.option("--fast", "Enable provider fast mode where supported", false);
if (params.allowFailuresHelp) {
command.option("--allow-failures", params.allowFailuresHelp, false);
}
command.option("--sut-account <id>", params.sutAccountHelp, "sut");
if (params.credentialFileHelp) {
command.option("--credential-file <path>", params.credentialFileHelp);
}
if (params.listScenariosHelp) {
command.option("--list-scenarios", params.listScenariosHelp, false);
}
if (params.profileHelp) {
command.option("--profile <profile>", params.profileHelp);
}
if (params.failFastHelp) {
command.option("--fail-fast", params.failFastHelp, false);
}
if (params.credentialOptions) {
command.option(
"--credential-source <source>",
params.credentialOptions.sourceDescription ??
"Credential source for live lanes: env or convex (default: env)",
);
if (params.credentialOptions.roleDescription) {
command.option("--credential-role <role>", params.credentialOptions.roleDescription);
}
}
command.action(async (opts: LiveTransportQaCommanderOptions) => {
await params.run(mapLiveTransportQaCommanderOptions(opts));
});
}
/** Build a Commander registration object for one live-transport QA command. */
export function createLiveTransportQaCliRegistration(
params: LiveTransportQaCliRegistrationOptions,
): LiveTransportQaCliRegistration {
return {
commandName: params.commandName,
adapterFactory: params.adapterFactory,
register(qa: Command) {
registerLiveTransportQaCli({
...params,
qa,
});
},
};
}
/** Docker command runner abstraction used by QA Docker helpers and tests. */
export type QaDockerRunCommand = (
command: string,