test: stabilize slow extension gates

This commit is contained in:
Peter Steinberger
2026-05-02 00:31:09 +01:00
parent 4b8641094b
commit 8d54b898fb
10 changed files with 108 additions and 47 deletions

View File

@@ -3,8 +3,8 @@ import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entr
export default defineBundledChannelSetupEntry({
importMetaUrl: import.meta.url,
plugin: {
specifier: "./channel-plugin-api.js",
exportName: "qaChannelPlugin",
specifier: "./setup-plugin-api.js",
exportName: "qaChannelSetupPlugin",
},
runtime: {
specifier: "./api.js",

View File

@@ -0,0 +1,3 @@
// Keep bundled setup entry imports narrow so setup loads do not pull the
// broader QA channel runtime and gateway surface.
export { qaChannelSetupPlugin } from "./src/channel.setup.js";

View File

@@ -0,0 +1,43 @@
import { getChatChannelMeta } from "openclaw/plugin-sdk/channel-plugin-common";
import {
listQaChannelAccountIds,
resolveDefaultQaChannelAccountId,
resolveQaChannelAccount,
type ResolvedQaChannelAccount,
} from "./accounts.js";
import { qaChannelPluginConfigSchema } from "./config-schema.js";
import type { ChannelPlugin } from "./runtime-api.js";
import { applyQaSetup } from "./setup.js";
import type { CoreConfig } from "./types.js";
const CHANNEL_ID = "qa-channel" as const;
const meta = { ...getChatChannelMeta(CHANNEL_ID) };
export const qaChannelSetupPlugin: ChannelPlugin<ResolvedQaChannelAccount> = {
id: CHANNEL_ID,
meta,
capabilities: {
chatTypes: ["direct", "group"],
},
reload: { configPrefixes: ["channels.qa-channel"] },
configSchema: qaChannelPluginConfigSchema,
setup: {
applyAccountConfig: ({ cfg, accountId, input }) =>
applyQaSetup({
cfg,
accountId,
input: input as Record<string, unknown>,
}),
},
config: {
listAccountIds: (cfg) => listQaChannelAccountIds(cfg as CoreConfig),
resolveAccount: (cfg, accountId) =>
resolveQaChannelAccount({ cfg: cfg as CoreConfig, accountId }),
defaultAccountId: (cfg) => resolveDefaultQaChannelAccountId(cfg as CoreConfig),
isConfigured: (account) => account.configured,
resolveAllowFrom: ({ cfg, accountId }) =>
resolveQaChannelAccount({ cfg: cfg as CoreConfig, accountId }).config.allowFrom,
resolveDefaultTo: ({ cfg, accountId }) =>
resolveQaChannelAccount({ cfg: cfg as CoreConfig, accountId }).config.defaultTo,
},
};

View File

@@ -130,30 +130,33 @@ async function startQaGatewayLoop(params: { state: QaBusState; baseUrl: string }
const cfg = createQaLabConfig(params.baseUrl);
const account = qaChannelPlugin.config.resolveAccount(cfg, "default");
const abort = new AbortController();
const task = qaChannelPlugin.gateway?.startAccount?.({
accountId: account.accountId,
account,
cfg,
runtime: {
log: () => undefined,
error: () => undefined,
exit: () => undefined,
},
abortSignal: abort.signal,
log: {
info: () => undefined,
warn: () => undefined,
error: () => undefined,
debug: () => undefined,
},
getStatus: () => ({
accountId: account.accountId,
configured: true,
enabled: true,
running: true,
}),
setStatus: () => undefined,
});
const task = Promise.resolve().then(
async () =>
await qaChannelPlugin.gateway?.startAccount?.({
accountId: account.accountId,
account,
cfg,
runtime: {
log: () => undefined,
error: () => undefined,
exit: () => undefined,
},
abortSignal: abort.signal,
log: {
info: () => undefined,
warn: () => undefined,
error: () => undefined,
debug: () => undefined,
},
getStatus: () => ({
accountId: account.accountId,
configured: true,
enabled: true,
running: true,
}),
setStatus: () => undefined,
}),
);
return {
cfg,
async stop() {

View File

@@ -20,7 +20,7 @@ export {
searchQaBusMessages,
sendQaBusMessage,
setQaChannelRuntime,
} from "openclaw/plugin-sdk/qa-channel";
} from "@openclaw/qa-channel/api.js";
export type {
QaBusAttachment,
QaBusConversation,

View File

@@ -1135,12 +1135,8 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
await sleep(statusReactionTiming.errorHoldMs);
if (anyReplyDelivered) {
await statusReactions.clear();
return;
}
await statusReactions.restoreInitial();
})();
} else {
void statusReactions.restoreInitial();
}
} else if (anyReplyDelivered) {
await statusReactions.setDone();

View File

@@ -404,7 +404,7 @@ const IMPORT_SPECIFIER_PATTERN =
const BROAD_CHANGED_ENV_KEY = "OPENCLAW_TEST_CHANGED_BROAD";
const VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS";
const VITEST_NO_OUTPUT_RETRY_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_RETRY";
export const DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_TIMEOUT_MS = "180000";
export const DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_TIMEOUT_MS = "300000";
const VITEST_CONFIG_TARGET_KIND_BY_PATH = new Map(
Object.entries(VITEST_CONFIG_BY_KIND).map(([kind, config]) => [config, kind]),
);

View File

@@ -564,6 +564,8 @@ describe("buildStatusReply subagent summary", () => {
{
OPENCLAW_BUNDLED_PLUGINS_DIR: bundledDir,
OPENCLAW_STATE_DIR: stateDir,
ANTHROPIC_API_KEY: undefined,
ANTHROPIC_OAUTH_TOKEN: undefined,
WORKSPACE_STATUS_CREDENTIALS: credentialPath,
},
async () => {

View File

@@ -24,6 +24,7 @@ const PRIVATE_BUNDLED_SDK_SURFACE_PATTERN =
const GENERIC_CORE_HELPER_FILES = ["src/polls.ts", "src/poll-params.ts"] as const;
const GENERIC_CORE_PLUGIN_OWNER_NAME_PATTERN =
/\b(?:bluebubbles|discord|feishu|googlechat|matrix|mattermost|msteams|slack|telegram|whatsapp|zalo|zalouser)\b/gi;
const PACKAGE_CONTRACT_SCAN_TIMEOUT_MS = 240_000;
const DEPRECATED_EXTENSION_SDK_SPECIFIERS = new Set([
"openclaw/plugin-sdk",
"openclaw/plugin-sdk/channel-config-schema-legacy",
@@ -655,9 +656,13 @@ describe("plugin-sdk package contract guardrails", () => {
expect(collectDeprecatedPackageTestingBridgeDrift()).toEqual([]);
});
it("keeps extension test-api exports consumed", () => {
expect(collectUnusedExtensionTestApiExports()).toEqual([]);
});
it(
"keeps extension test-api exports consumed",
() => {
expect(collectUnusedExtensionTestApiExports()).toEqual([]);
},
PACKAGE_CONTRACT_SCAN_TIMEOUT_MS,
);
it("keeps reserved SDK compatibility subpaths inside their owning bundled plugins", () => {
expect(collectCrossOwnerReservedSdkImports()).toEqual([]);

View File

@@ -54,6 +54,7 @@ function loadCoverageRegistryEntries(): SecretRegistryEntry[] {
const COVERAGE_REGISTRY_ENTRIES = loadCoverageRegistryEntries();
const DEBUG_COVERAGE_BATCHES = process.env.OPENCLAW_DEBUG_RUNTIME_COVERAGE === "1";
const RUNTIME_COVERAGE_TEST_TIMEOUT_MS = 240_000;
const COVERAGE_LOADABLE_PLUGIN_ORIGINS =
buildCoverageLoadablePluginOrigins(COVERAGE_REGISTRY_ENTRIES);
const PLUGIN_OWNED_OPENCLAW_COVERAGE_EXCLUSIONS = new Set([
@@ -564,19 +565,27 @@ describe("secrets runtime target coverage", () => {
({ resolveSecretRefValues } = resolver);
});
it("handles every core and channel openclaw.json registry target when configured as active", async () => {
await expectOpenClawCoverageEntriesResolved(
"openclaw.json core",
collectOpenClawCoverageEntries({ includePluginEntries: false }),
);
});
it(
"handles every core and channel openclaw.json registry target when configured as active",
async () => {
await expectOpenClawCoverageEntriesResolved(
"openclaw.json core",
collectOpenClawCoverageEntries({ includePluginEntries: false }),
);
},
RUNTIME_COVERAGE_TEST_TIMEOUT_MS,
);
it("handles every plugin openclaw.json registry target when configured as active", async () => {
await expectOpenClawCoverageEntriesResolved(
"openclaw.json plugins",
collectOpenClawCoverageEntries({ includePluginEntries: true }),
);
});
it(
"handles every plugin openclaw.json registry target when configured as active",
async () => {
await expectOpenClawCoverageEntriesResolved(
"openclaw.json plugins",
collectOpenClawCoverageEntries({ includePluginEntries: true }),
);
},
RUNTIME_COVERAGE_TEST_TIMEOUT_MS,
);
it("handles every auth-profiles registry target", async () => {
const entries = COVERAGE_REGISTRY_ENTRIES.filter(