chore(plugins): gate deprecation hygiene in CI and purge internal deprecated usage (#108261)

* chore(plugins): gate deprecation hygiene in CI and purge internal deprecated usage

* fix(agents): migrate auth-profiles oauth import to OAuthProviderId

* fix(whatsapp): use durability param in delivery-recovery test

* chore(scripts): declare shouldRunDeprecationHygieneChecks in check-changed types

* test(plugins): update runtime-api guard for ssrf policy rename
This commit is contained in:
Peter Steinberger
2026-07-15 04:15:23 -07:00
committed by GitHub
parent 8160bbdf2e
commit 0393189243
39 changed files with 128 additions and 86 deletions
@@ -11,7 +11,7 @@ import type {
ProviderNormalizeResolvedModelContext,
} from "openclaw/plugin-sdk/plugin-entry";
import {
ANTHROPIC_BY_MODEL_REPLAY_HOOKS,
buildProviderReplayFamilyHooks,
normalizeProviderId,
resolveClaudeFable5ModelIdentity,
resolveClaudeModelIdentity,
@@ -364,7 +364,9 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
] as const;
const deprecatedTemperatureValidationRe =
/ValidationException[\s\S]*(?:invalid_request_error[\s\S]*)?temperature[\s\S]*deprecated|ValidationException[\s\S]*deprecated[\s\S]*temperature/i;
const anthropicByModelReplayHooks = ANTHROPIC_BY_MODEL_REPLAY_HOOKS;
const anthropicByModelReplayHooks = buildProviderReplayFamilyHooks({
family: "anthropic-by-model",
});
const startupPluginConfig = (api.pluginConfig ?? {}) as AmazonBedrockPluginConfig;
registerApiProvider(
+2 -2
View File
@@ -5,7 +5,7 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { readConfiguredProviderCatalogEntries } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
NATIVE_ANTHROPIC_REPLAY_HOOKS,
buildProviderReplayFamilyHooks,
resolveClaudeThinkingProfile,
} from "openclaw/plugin-sdk/provider-model-shared";
import {
@@ -48,7 +48,7 @@ export default definePluginEntry({
},
},
resolveConfigApiKey: ({ env }) => resolveAnthropicVertexConfigApiKey(env),
...NATIVE_ANTHROPIC_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "native-anthropic-by-model" }),
normalizeResolvedModel: ({ modelId, model }) =>
normalizeAnthropicVertexResolvedModel(modelId, model),
resolveThinkingProfile: ({ modelId, params }) =>
+2 -2
View File
@@ -25,9 +25,9 @@ import {
validateAnthropicSetupToken,
} from "openclaw/plugin-sdk/provider-auth";
import {
buildProviderReplayFamilyHooks,
cloneFirstTemplateModel,
modelCostsEqual,
NATIVE_ANTHROPIC_REPLAY_HOOKS,
type ProviderPlugin,
resolveClaudeFable5ModelIdentity,
resolveClaudeModelIdentity,
@@ -896,7 +896,7 @@ export function buildAnthropicProvider(): ProviderPlugin {
: undefined,
// Publish Claude CLI rows through the provider catalog hook.
augmentModelCatalog: () => buildClaudeCliCatalogEntries(),
...NATIVE_ANTHROPIC_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "native-anthropic-by-model" }),
isModernModelRef: ({ provider, modelId }) =>
matchesAnthropicModernModel(modelId) &&
(!isAnthropicMandatoryClaude5Model(modelId) ||
+2 -2
View File
@@ -8,7 +8,7 @@ import {
readConfiguredProviderCatalogEntries,
type ProviderCatalogContext,
} from "openclaw/plugin-sdk/provider-catalog-shared";
import { OPENAI_COMPATIBLE_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import {
applyArceeConfig,
applyArceeOpenRouterConfig,
@@ -141,7 +141,7 @@ export default definePluginEntry({
}
: undefined;
},
...OPENAI_COMPATIBLE_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "openai-compatible" }),
});
},
});
+2 -2
View File
@@ -5,7 +5,7 @@ import {
readConfiguredProviderCatalogEntries,
} from "openclaw/plugin-sdk/provider-catalog-shared";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { PASSTHROUGH_GEMINI_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import {
createOpenRouterWrapper,
isProxyReasoningUnsupported,
@@ -104,7 +104,7 @@ export default defineSingleProviderPluginEntry({
normalizeConfig: ({ providerConfig }) => providerConfig,
normalizeTransport: ({ api, baseUrl }) =>
baseUrl === "https://api.deepinfra.com/v1/openai" ? { api, baseUrl } : undefined,
...PASSTHROUGH_GEMINI_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "passthrough-gemini" }),
wrapStreamFn: (ctx) => {
const thinkingLevel = isProxyReasoningUnsupported(ctx.modelId)
? undefined
@@ -1,8 +1,8 @@
// Feishu tests cover the shared outbound delivery path.
import { sendDurableMessageBatch } from "openclaw/plugin-sdk/channel-outbound";
import {
createOutboundTestPlugin,
createTestRegistry,
deliverOutboundPayloads,
releasePinnedPluginChannelRegistry,
resetGlobalHookRunner,
setActivePluginRegistry,
@@ -60,7 +60,7 @@ describe("Feishu outbound shared delivery", () => {
});
it("routes oversized presentation media through one media send and chunked fallback text", async () => {
await deliverOutboundPayloads({
await sendDurableMessageBatch({
cfg: {},
channel: "feishu",
to: "chat_1",
+2 -2
View File
@@ -2,10 +2,10 @@
import type { ProviderResolveDynamicModelContext } from "openclaw/plugin-sdk/plugin-entry";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import {
buildProviderReplayFamilyHooks,
cloneFirstTemplateModel,
DEFAULT_CONTEXT_TOKENS,
normalizeModelCompat,
OPENAI_COMPATIBLE_REPLAY_HOOKS,
} from "openclaw/plugin-sdk/provider-model-shared";
import { isFireworksKimiModelId } from "./model-id.js";
import { applyFireworksConfig, FIREWORKS_DEFAULT_MODEL_REF } from "./onboard.js";
@@ -97,7 +97,7 @@ export default defineSingleProviderPluginEntry({
buildProvider: buildFireworksProvider,
allowExplicitBaseUrl: true,
},
...OPENAI_COMPATIBLE_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "openai-compatible" }),
wrapStreamFn: wrapFireworksProviderStream,
resolveThinkingProfile: ({ modelId }) => resolveFireworksThinkingProfile(modelId),
resolveDynamicModel: (ctx) => resolveFireworksDynamicModel(ctx),
+2 -2
View File
@@ -1,7 +1,7 @@
// Kilocode plugin entrypoint registers its OpenClaw integration.
import { readConfiguredProviderCatalogEntries } from "openclaw/plugin-sdk/provider-catalog-shared";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { PASSTHROUGH_GEMINI_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { applyKilocodeConfig, KILOCODE_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildKilocodeProvider, buildKilocodeProviderWithDiscovery } from "./provider-catalog.js";
import { wrapKilocodeProviderStream } from "./stream.js";
@@ -37,7 +37,7 @@ export default defineSingleProviderPluginEntry({
config,
providerId: PROVIDER_ID,
}),
...PASSTHROUGH_GEMINI_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "passthrough-gemini" }),
wrapStreamFn: wrapKilocodeProviderStream,
isCacheTtlEligible: (ctx) => ctx.modelId.startsWith("anthropic/"),
},
-1
View File
@@ -34,7 +34,6 @@ export {
createPinnedDispatcher,
resolvePinnedHostnameWithPolicy,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
ssrfPolicyFromAllowPrivateNetwork,
type LookupFn,
type SsrFPolicy,
} from "openclaw/plugin-sdk/ssrf-runtime";
-1
View File
@@ -71,7 +71,6 @@ export {
isPrivateOrLoopbackHost,
resolvePinnedHostnameWithPolicy,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
ssrfPolicyFromAllowPrivateNetwork,
type LookupFn,
type SsrFPolicy,
} from "openclaw/plugin-sdk/ssrf-runtime";
@@ -1,7 +1,7 @@
// Mattermost tests cover the shared outbound delivery path.
import { sendDurableMessageBatch } from "openclaw/plugin-sdk/channel-outbound";
import {
createTestRegistry,
deliverOutboundPayloads,
releasePinnedPluginChannelRegistry,
setActivePluginRegistry,
} from "openclaw/plugin-sdk/channel-test-helpers";
@@ -43,7 +43,7 @@ describe("Mattermost outbound delivery", () => {
expected: "The pipeline has 3 open deals.",
},
])("sends sanitized $name through the channel handler", async ({ text, expected }) => {
await deliverOutboundPayloads({
await sendDurableMessageBatch({
cfg: {},
channel: "mattermost",
to: "channel:team-1",
+2 -2
View File
@@ -2,7 +2,7 @@
* Meta provider plugin entrypoint.
*/
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { OPENAI_COMPATIBLE_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { applyMetaConfig, META_DEFAULT_MODEL_REF } from "./onboard.js";
import { buildMetaProvider } from "./provider-catalog.js";
import { wrapMetaProviderStream } from "./stream.js";
@@ -40,7 +40,7 @@ export default defineSingleProviderPluginEntry({
buildProvider: buildMetaProvider,
buildStaticProvider: buildMetaProvider,
},
...OPENAI_COMPATIBLE_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "openai-compatible" }),
wrapStreamFn: wrapMetaProviderStream,
resolveThinkingProfile: ({ modelId }) => resolveMetaThinkingProfile(modelId),
},
+3 -2
View File
@@ -6,7 +6,7 @@ import {
type ModelProviderConfig,
type ProviderPlugin,
} from "openclaw/plugin-sdk/provider-model-shared";
import { OPENAI_RESPONSES_STREAM_HOOKS } from "openclaw/plugin-sdk/provider-stream-family";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
import { apiKeyAuthMethod, entraIdAuthMethod } from "./auth.js";
import { prepareFoundryRuntimeAuth } from "./runtime.js";
import {
@@ -25,7 +25,8 @@ import {
type FoundryProviderHooks = Pick<ProviderPlugin, "wrapStreamFn">;
const wrapOpenAIResponsesStreamFn = OPENAI_RESPONSES_STREAM_HOOKS.wrapStreamFn;
const openAIResponsesStreamHooks = buildProviderStreamFamilyHooks("openai-responses-defaults");
const wrapOpenAIResponsesStreamFn = openAIResponsesStreamHooks.wrapStreamFn;
const wrapMicrosoftFoundryStreamFn: NonNullable<FoundryProviderHooks["wrapStreamFn"]> = (ctx) => {
if (ctx.model?.api !== "openai-responses") {
+2 -2
View File
@@ -21,7 +21,7 @@ import {
buildProviderReplayFamilyHooks,
normalizeModelCompat,
} from "openclaw/plugin-sdk/provider-model-shared";
import { MINIMAX_FAST_MODE_STREAM_HOOKS } from "openclaw/plugin-sdk/provider-stream-family";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
import { fetchMinimaxUsage } from "openclaw/plugin-sdk/provider-usage";
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import {
@@ -63,7 +63,7 @@ const HYBRID_ANTHROPIC_OPENAI_REPLAY_HOOKS = buildProviderReplayFamilyHooks({
});
const MINIMAX_PROVIDER_HOOKS = {
...HYBRID_ANTHROPIC_OPENAI_REPLAY_HOOKS,
...MINIMAX_FAST_MODE_STREAM_HOOKS,
...buildProviderStreamFamilyHooks("minimax-fast-mode"),
resolveReasoningOutputMode: () => "native" as const,
resolveThinkingProfile: ({ modelId }: { modelId: string }) =>
resolveMinimaxThinkingProfile(modelId),
+2 -2
View File
@@ -1,7 +1,7 @@
// Moonshot plugin entrypoint registers its OpenClaw integration.
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { buildOpenAICompatibleReplayPolicy } from "openclaw/plugin-sdk/provider-model-shared";
import { MOONSHOT_THINKING_STREAM_HOOKS } from "openclaw/plugin-sdk/provider-stream-family";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
import { applyMoonshotNativeStreamingUsageCompat } from "./api.js";
import { moonshotMediaUnderstandingProvider } from "./media-understanding-provider.js";
import {
@@ -14,7 +14,7 @@ import { KIMI_K2_7_CODE_MODEL_ID, resolveThinkingProfile } from "./provider-poli
import { createKimiWebSearchProvider } from "./src/kimi-web-search-provider.js";
const PROVIDER_ID = "moonshot";
const moonshotThinkingStreamHooks = MOONSHOT_THINKING_STREAM_HOOKS;
const moonshotThinkingStreamHooks = buildProviderStreamFamilyHooks("moonshot-thinking");
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
+3 -3
View File
@@ -25,7 +25,7 @@ import type {
} from "openclaw/plugin-sdk/provider-model-shared";
import {
buildOpenAICompatibleReplayPolicy,
OPENAI_COMPATIBLE_REPLAY_HOOKS,
buildProviderReplayFamilyHooks,
} from "openclaw/plugin-sdk/provider-model-shared";
import {
buildOllamaModelDefinition,
@@ -540,7 +540,7 @@ export default definePluginEntry({
) ?? OLLAMA_CLOUD_BASE_URL,
});
},
...OPENAI_COMPATIBLE_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "openai-compatible" }),
buildReplayPolicy: (ctx) =>
ctx.modelApi === "ollama"
? buildNativeOllamaReplayPolicy()
@@ -671,7 +671,7 @@ export default definePluginEntry({
),
});
},
...OPENAI_COMPATIBLE_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "openai-compatible" }),
buildReplayPolicy: (ctx) =>
ctx.modelApi === "ollama"
? buildNativeOllamaReplayPolicy()
@@ -4,9 +4,7 @@ export type {
OAuthCredentials,
OAuthLoginCallbacks,
OAuthPrompt,
OAuthProvider,
OAuthProviderId,
OAuthProviderInfo,
OAuthProviderInterface,
OAuthSelectOption,
OAuthSelectPrompt,
+4 -3
View File
@@ -6,7 +6,7 @@ import {
matchesExactOrPrefix,
type ProviderPlugin,
} from "openclaw/plugin-sdk/provider-model-shared";
import { OPENAI_RESPONSES_STREAM_HOOKS } from "openclaw/plugin-sdk/provider-stream-family";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import { createOpenAINativeWebSearchWrapper } from "./native-web-search.js";
import { buildOpenAIReplayPolicy } from "./replay-policy.js";
@@ -78,7 +78,8 @@ const resolveOpenAIResponsesWebSocketSessionPolicy: NonNullable<
OpenAIResponsesProviderHooks["resolveWebSocketSessionPolicy"]
> = (ctx) => resolveOpenAIWebSocketSessionPolicy(ctx);
const wrapOpenAIResponsesStreamFn = OPENAI_RESPONSES_STREAM_HOOKS.wrapStreamFn;
const openAIResponsesStreamHooks = buildProviderStreamFamilyHooks("openai-responses-defaults");
const wrapOpenAIResponsesStreamFn = openAIResponsesStreamHooks.wrapStreamFn;
const wrapOpenAIResponsesProviderStreamFn: NonNullable<
OpenAIResponsesProviderHooks["wrapStreamFn"]
> = (ctx) =>
@@ -94,7 +95,7 @@ export function buildOpenAIResponsesProviderHooks(options?: {
return {
buildReplayPolicy: buildOpenAIReplayPolicy,
prepareExtraParams: (ctx) => defaultOpenAIResponsesExtraParams(ctx.extraParams, options),
...OPENAI_RESPONSES_STREAM_HOOKS,
...openAIResponsesStreamHooks,
wrapStreamFn: wrapOpenAIResponsesProviderStreamFn,
resolveTransportTurnState: resolveOpenAIResponsesTransportTurnState,
resolveWebSocketSessionPolicy: resolveOpenAIResponsesWebSocketSessionPolicy,
+2 -2
View File
@@ -1,7 +1,7 @@
// Opencode Go plugin entrypoint registers its OpenClaw integration.
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { PASSTHROUGH_GEMINI_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { applyOpencodeGoConfig, OPENCODE_GO_DEFAULT_MODEL_REF } from "./api.js";
import { opencodeGoMediaUnderstandingProvider } from "./media-understanding-provider.js";
import {
@@ -135,7 +135,7 @@ export default definePluginEntry({
},
},
augmentModelCatalog: () => listOpencodeGoModelCatalogEntries(),
...PASSTHROUGH_GEMINI_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "passthrough-gemini" }),
wrapStreamFn: (ctx) => createOpencodeGoWrapper(ctx.streamFn, ctx.thinkingLevel),
isModernModelRef: () => true,
});
+2 -2
View File
@@ -2,8 +2,8 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import {
buildProviderReplayFamilyHooks,
matchesExactOrPrefix,
PASSTHROUGH_GEMINI_REPLAY_HOOKS,
resolveClaudeThinkingProfile,
} from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -149,7 +149,7 @@ export default definePluginEntry({
},
},
augmentModelCatalog: () => listOpencodeZenModelCatalogEntries(),
...PASSTHROUGH_GEMINI_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "passthrough-gemini" }),
isModernModelRef: ({ modelId }) => isModernOpencodeModel(modelId),
resolveThinkingProfile: ({ modelId }) => resolveClaudeThinkingProfile(modelId),
});
+6 -3
View File
@@ -8,8 +8,8 @@ import {
} from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import {
buildProviderReplayFamilyHooks,
DEFAULT_CONTEXT_TOKENS,
PASSTHROUGH_GEMINI_REPLAY_HOOKS,
} from "openclaw/plugin-sdk/provider-model-shared";
import {
getOpenRouterModelCapabilities,
@@ -263,7 +263,10 @@ export default definePluginEntry({
return OPENROUTER_CACHE_TTL_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix));
}
const passthroughReplayHook = PASSTHROUGH_GEMINI_REPLAY_HOOKS.buildReplayPolicy;
const passthroughGeminiReplayHooks = buildProviderReplayFamilyHooks({
family: "passthrough-gemini",
});
const passthroughReplayHook = passthroughGeminiReplayHooks.buildReplayPolicy;
function buildOpenRouterReplayPolicy(ctx: ProviderReplayPolicyContext): ProviderReplayPolicy {
const base = passthroughReplayHook?.(ctx) ?? {};
// OpenRouter proxies Mistral, which uses non-base62 tool_call_ids and
@@ -352,7 +355,7 @@ export default definePluginEntry({
}
: undefined;
},
...PASSTHROUGH_GEMINI_REPLAY_HOOKS,
...passthroughGeminiReplayHooks,
buildReplayPolicy: buildOpenRouterReplayPolicy,
resolveReasoningOutputMode: () => "native",
supportsXHighThinking: ({ modelId }) => supportsOpenRouterXHighThinking(modelId),
+3 -2
View File
@@ -1,7 +1,7 @@
// Openrouter plugin module implements stream behavior.
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
import type { ProviderWrapStreamFnContext } from "openclaw/plugin-sdk/plugin-entry";
import { OPENROUTER_THINKING_STREAM_HOOKS } from "openclaw/plugin-sdk/provider-stream-family";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream-family";
import { createPayloadPatchStreamWrapper } from "openclaw/plugin-sdk/provider-stream-shared";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { isOpenRouterDeepSeekV4ModelId } from "./models.js";
@@ -12,6 +12,7 @@ import {
} from "./provider-catalog.js";
const log = createSubsystemLogger("openrouter-stream");
const openRouterThinkingStreamHooks = buildProviderStreamFamilyHooks("openrouter-thinking");
function readString(value: unknown): string | undefined {
return typeof value === "string" ? value.trim() : undefined;
@@ -312,7 +313,7 @@ export function wrapOpenRouterProviderStream(
const routedStreamFn = providerRouting
? injectOpenRouterRouting(ctx.streamFn, providerRouting)
: ctx.streamFn;
const wrapStreamFn = OPENROUTER_THINKING_STREAM_HOOKS.wrapStreamFn ?? undefined;
const wrapStreamFn = openRouterThinkingStreamHooks.wrapStreamFn ?? undefined;
if (!wrapStreamFn) {
return createOpenRouterAnthropicPrefillWrapper(
createOpenRouterAuthHeaderWrapper(
@@ -1,10 +1,10 @@
// Slack tests cover outbound delivery plugin behavior.
import { sendDurableMessageBatch } from "openclaw/plugin-sdk/channel-outbound";
import {
addTestHook,
createEmptyPluginRegistry,
createOutboundTestPlugin,
createTestRegistry,
deliverOutboundPayloads,
initializeGlobalHookRunner,
releasePinnedPluginChannelRegistry,
resetGlobalHookRunner,
@@ -68,7 +68,7 @@ describe("slack outbound shared hook wiring", () => {
});
initializeGlobalHookRunner(hookRegistry);
await deliverOutboundPayloads({
await sendDurableMessageBatch({
cfg,
channel: "slack",
to: "C123",
@@ -99,7 +99,7 @@ describe("slack outbound shared hook wiring", () => {
});
it("passes replyToId as Slack threadTs for threaded outbound delivery", async () => {
await deliverOutboundPayloads({
await sendDurableMessageBatch({
cfg,
channel: "slack",
to: "C123",
@@ -131,7 +131,7 @@ describe("slack outbound shared hook wiring", () => {
});
initializeGlobalHookRunner(hookRegistry);
const result = await deliverOutboundPayloads({
const result = await sendDurableMessageBatch({
cfg,
channel: "slack",
to: "C123",
@@ -142,6 +142,6 @@ describe("slack outbound shared hook wiring", () => {
expect(handler).toHaveBeenCalledTimes(1);
expect(sendMessageSlackMock).not.toHaveBeenCalled();
expect(result).toStrictEqual([]);
expect(result).toMatchObject({ status: "suppressed", results: [] });
});
});
-1
View File
@@ -10,7 +10,6 @@ export {
type RuntimeEnv,
SsrFBlockedError,
type SsrFPolicy,
ssrfPolicyFromAllowPrivateNetwork,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
} from "./runtime-api.js";
export { tlonPlugin } from "./src/channel.js";
-1
View File
@@ -9,7 +9,6 @@ export { createLoggerBackedRuntime } from "./src/logger-runtime.js";
export {
fetchWithSsrFGuard,
isBlockedHostnameOrIp,
ssrfPolicyFromAllowPrivateNetwork,
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
type LookupFn,
type SsrFPolicy,
@@ -3,9 +3,9 @@
// cannot drift apart unnoticed.
import http from "node:http";
import { expectDefined } from "@openclaw/normalization-core";
import { sendDurableMessageBatch } from "openclaw/plugin-sdk/channel-outbound";
import {
createTestRegistry,
deliverOutboundPayloads,
releasePinnedPluginChannelRegistry,
setActivePluginRegistry,
} from "openclaw/plugin-sdk/channel-test-helpers";
@@ -102,7 +102,7 @@ describe("tlon outbound assistant-visible sanitization", () => {
},
} as OpenClawConfig;
await deliverOutboundPayloads({
await sendDurableMessageBatch({
cfg,
channel: "tlon",
to: "~sampel-palnet",
@@ -121,7 +121,7 @@ describe("tlon outbound assistant-visible sanitization", () => {
skipQueue: true,
});
await deliverOutboundPayloads({
await sendDurableMessageBatch({
cfg,
channel: "tlon",
to: "chat/~host-ship/general",
@@ -133,7 +133,7 @@ describe("tlon outbound assistant-visible sanitization", () => {
skipQueue: true,
});
await deliverOutboundPayloads({
await sendDurableMessageBatch({
cfg,
channel: "tlon",
to: "~sampel-palnet",
@@ -1,9 +1,9 @@
// Whatsapp tests cover the durable outbound handoff across startup recovery.
import { sendDurableMessageBatch } from "openclaw/plugin-sdk/channel-outbound";
import {
createEmptyPluginRegistry,
createOutboundTestPlugin,
createTestRegistry,
deliverOutboundPayloads,
releasePinnedPluginChannelRegistry,
setActivePluginRegistry,
} from "openclaw/plugin-sdk/channel-test-helpers";
@@ -76,15 +76,18 @@ describe("WhatsApp delivery recovery", () => {
it("keeps pre-connect recovery replayable, then sends exactly once after connect", async () => {
await withStateDirEnv("openclaw-whatsapp-delivery-recovery-", async ({ stateDir }) => {
const initialError = await deliverOutboundPayloads({
const initialResult = await sendDurableMessageBatch({
cfg,
channel: "whatsapp",
to: "+1555",
payloads: [{ text: "queued before listener startup" }],
queuePolicy: "required",
}).catch((err: unknown) => err);
expect(initialError).toMatchObject({
cause: expect.any(PlatformMessageNotDispatchedError),
durability: "required",
});
expect(initialResult).toMatchObject({
status: "failed",
error: {
cause: expect.any(PlatformMessageNotDispatchedError),
},
});
const preConnectLog = await drainDefaultWhatsAppDeliveries(stateDir);
+2 -2
View File
@@ -2,7 +2,7 @@ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
// Xai plugin entrypoint registers its OpenClaw integration.
import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/plugin-entry";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { OPENAI_COMPATIBLE_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { defaultToolStreamExtraParams } from "openclaw/plugin-sdk/provider-stream-shared";
import { jsonResult } from "openclaw/plugin-sdk/provider-web-search";
import {
@@ -249,7 +249,7 @@ export default defineSingleProviderPluginEntry({
provider: buildXaiProvider(),
}),
},
...OPENAI_COMPATIBLE_REPLAY_HOOKS,
...buildProviderReplayFamilyHooks({ family: "openai-compatible" }),
prepareExtraParams: (ctx) => defaultToolStreamExtraParams(ctx.extraParams),
wrapStreamFn: wrapXaiProviderStream,
// Provider-specific fallback auth stays owned by the xAI plugin so core
+1
View File
@@ -46,6 +46,7 @@ export function shouldRunRuntimeSidecarBaselineCheck(paths: string[]): boolean;
export function shouldRunSqliteSessionSchemaBaselineCheck(paths: string[]): boolean;
export function shouldRunPluginSdkApiBaselineCheck(paths: string[]): boolean;
export function shouldRunPluginSdkSurfaceChecks(paths: string[]): boolean;
export function shouldRunDeprecationHygieneChecks(paths: string[]): boolean;
export function shouldRunCanvasA2uiNativeResourceCheck(paths: string[]): boolean;
export function shouldRunAppcastOwnerTest(paths: string[]): boolean;
export function shouldRunTestTempCreationReport(paths: string[]): boolean;
+15
View File
@@ -43,6 +43,8 @@ const PLUGIN_SDK_API_BASELINE_PATH_RE =
/^(?:src\/|packages\/|extensions\/|pnpm-lock\.yaml$|tsconfig\.json$|scripts\/(?:generate-plugin-sdk-api-baseline\.ts|lib\/plugin-sdk-(?:doc-metadata\.ts|entries\.mjs|entrypoints\.json|private-local-only-subpaths\.json))|docs\/\.generated\/plugin-sdk-api-baseline\.sha256$)/u;
const PLUGIN_SDK_SURFACE_PATH_RE =
/^(?:package\.json$|src\/plugin-sdk\/|scripts\/(?:plugin-sdk-surface-report\.mjs|sync-plugin-sdk-exports\.mjs|lib\/plugin-sdk-(?:declaration-budget\.mjs|deprecated-barrel-subpaths\.json|deprecated-public-subpaths\.json|entries\.mjs|entrypoints\.json|private-local-only-subpaths\.json)))/u;
const DEPRECATION_HYGIENE_PATH_RE =
/^(?:package\.json$|src\/|extensions\/|packages\/|scripts\/(?:check-deprecated-api-usage\.mjs$|plugin-boundary-report\.ts$|lib\/plugin-sdk))/u;
const CANVAS_A2UI_NATIVE_RESOURCE_PATH_RE =
/^(?:pnpm-lock\.yaml$|apps\/shared\/OpenClawKit\/Sources\/OpenClawKit\/Resources\/CanvasA2UI\/|extensions\/canvas\/(?:package\.json$|scripts\/bundle-a2ui\.mjs$|src\/host\/a2ui(?:\/(?:index\.html|a2ui\.bundle\.js|\.bundle\.hash)$|-app\/))|scripts\/(?:bundle-a2ui|sync-native-a2ui)\.mjs$)/u;
const CONTROL_UI_I18N_VERIFY_PATH_RE =
@@ -311,6 +313,13 @@ export function shouldRunPluginSdkSurfaceChecks(paths) {
);
}
/** Returns whether changed files can alter deprecated API or plugin-boundary results. */
export function shouldRunDeprecationHygieneChecks(paths) {
return paths.some((changedPath) =>
DEPRECATION_HYGIENE_PATH_RE.test(normalizeChangedPath(changedPath)),
);
}
export function shouldRunCanvasA2uiNativeResourceCheck(paths) {
return paths.some((changedPath) =>
CANVAS_A2UI_NATIVE_RESOURCE_PATH_RE.test(normalizeChangedPath(changedPath)),
@@ -462,6 +471,12 @@ export function createChangedCheckPlan(result, options = {}) {
add("Plugin SDK package exports", ["plugin-sdk:check-exports"]);
add("Plugin SDK surface budget", ["plugin-sdk:surface:check"]);
}
if (result.lanes.all || shouldRunDeprecationHygieneChecks(result.paths)) {
add("deprecated API usage", ["check:deprecated-api-usage"]);
// After 2026-07-24, lapsed compatibility windows intentionally fail this gate
// until their scheduled deletion PRs land.
add("plugin boundaries", ["plugins:boundary-report:ci"]);
}
if (shouldRunCanvasA2uiNativeResourceCheck(result.paths)) {
addCommand(
"Canvas A2UI native resource sync",
-2
View File
@@ -213,10 +213,8 @@ const rules = [
"src/infra/outbound/deliver.ts",
"src/plugin-sdk/channel-message-runtime.ts",
"src/plugin-sdk/channel-message.ts",
"src/plugin-sdk/channel-test-helpers.ts",
"src/plugin-sdk/inbound-reply-dispatch.ts",
"src/plugin-sdk/outbound-runtime.ts",
"src/plugin-sdk/test-helpers/outbound-delivery.ts",
],
message: "use sendDurableMessageBatch or deliverInboundReplyWithMessageSendContext",
},
@@ -1,8 +1,6 @@
[
"agent-config-primitives",
"agent-runtime-test-contracts",
"channel-config-schema-legacy",
"channel-contract-testing",
"channel-envelope",
"channel-inbound-roots",
"channel-lifecycle",
@@ -16,8 +14,6 @@
"channel-runtime",
"channel-secret-runtime",
"channel-streaming",
"channel-target-testing",
"channel-test-helpers",
"command-auth",
"compat",
"config-runtime",
@@ -43,11 +39,7 @@
"music-generation-core",
"outbound-runtime",
"outbound-send-deps",
"plugin-test-api",
"plugin-test-contracts",
"provider-auth-login",
"provider-http-test-mocks",
"provider-test-contracts",
"provider-zai-endpoint",
"reply-dedupe",
"runtime-logger",
@@ -56,9 +48,6 @@
"setup-adapter-runtime",
"telegram-account",
"telegram-command-config",
"test-env",
"test-fixtures",
"test-node-mocks",
"text-runtime",
"webhook-path",
"zalouser",
+3 -3
View File
@@ -12,7 +12,7 @@ import {
getOAuthApiKey,
getOAuthProviders,
type OAuthCredentials,
type OAuthProvider,
type OAuthProviderId,
} from "../../llm/oauth.js";
import {
formatProviderAuthProfileApiKeyWithPlugin,
@@ -67,10 +67,10 @@ function listOAuthProviderIds(): string[] {
const OAUTH_PROVIDER_IDS = new Set<string>(listOAuthProviderIds());
const isOAuthProvider = (provider: string): provider is OAuthProvider =>
const isOAuthProvider = (provider: string): provider is OAuthProviderId =>
OAUTH_PROVIDER_IDS.has(provider);
const resolveOAuthProvider = (provider: string): OAuthProvider | null =>
const resolveOAuthProvider = (provider: string): OAuthProviderId | null =>
isOAuthProvider(provider) ? provider : null;
/** Bearer-token auth modes that are interchangeable (oauth tokens and raw tokens). */
-1
View File
@@ -3,7 +3,6 @@ export type {
OAuthCredentials,
OAuthLoginCallbacks,
OAuthPrompt,
OAuthProvider,
OAuthProviderId,
OAuthProviderInterface,
} from "../../../plugin-sdk/provider-oauth-runtime.js";
-2
View File
@@ -19,8 +19,6 @@ export {
setActivePluginRegistry,
type PluginHookRegistration,
} from "./test-helpers/outbound-delivery.js";
/** @deprecated Direct outbound delivery is runtime substrate; use channel message runtime helpers. */
export { deliverOutboundPayloads } from "./test-helpers/outbound-delivery.js";
export {
createPluginRuntimeMediaMock,
createPluginRuntimeMock,
@@ -11,5 +11,3 @@ export {
setActivePluginRegistry,
} from "../../plugins/runtime.js";
export { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
/** @deprecated Direct outbound delivery is runtime substrate; use channel message runtime helpers. */
export { deliverOutboundPayloads } from "../../infra/outbound/deliver.js";
@@ -149,7 +149,7 @@ const RUNTIME_API_EXPORT_GUARDS: Record<string, readonly string[]> = {
'export { getMatrixScopedEnvVarNames, listMatrixEnvAccountIds, resolveMatrixEnvAccountToken } from "./src/env-vars.js";',
'export { hashMatrixAccessToken, resolveMatrixAccountStorageRoot, resolveMatrixCredentialsDir, resolveMatrixCredentialsFilename, resolveMatrixCredentialsPath, resolveMatrixHomeserverKey, sanitizeMatrixPathSegment } from "./src/storage-paths.js";',
'export { ensureMatrixSdkInstalled, isMatrixSdkAvailable } from "./src/matrix/deps.js";',
'export { assertHttpUrlTargetsPrivateNetwork, closeDispatcher, createPinnedDispatcher, resolvePinnedHostnameWithPolicy, ssrfPolicyFromDangerouslyAllowPrivateNetwork, ssrfPolicyFromAllowPrivateNetwork, type LookupFn, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";',
'export { assertHttpUrlTargetsPrivateNetwork, closeDispatcher, createPinnedDispatcher, resolvePinnedHostnameWithPolicy, ssrfPolicyFromDangerouslyAllowPrivateNetwork, type LookupFn, type SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime";',
'export { setMatrixThreadBindingIdleTimeoutBySessionKey, setMatrixThreadBindingMaxAgeBySessionKey } from "./src/matrix/thread-bindings-shared.js";',
'export { setMatrixRuntime } from "./src/runtime.js";',
'export { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";',
@@ -32,6 +32,7 @@ const SHARED_FAMILY_HOOK_PATTERNS: ReadonlyArray<{
const PROVIDER_BOUNDARY_TEST_SIGNALS = [
/\bregister(?:Single)?ProviderPlugin\s*\(/u,
/\bcreateTestPluginApi\s*\(/u,
/\bcapturePluginRegistration\s*\(/u,
/\bexpectPassthroughReplayPolicy\s*\(/u,
] as const;
const EXPECTED_SENTINEL_SHARED_FAMILY_ASSIGNMENTS: Record<string, ExpectedSharedFamilyContract> = {
@@ -41,8 +42,10 @@ const EXPECTED_SENTINEL_SHARED_FAMILY_ASSIGNMENTS: Record<string, ExpectedShared
},
minimax: {
replayFamilies: ["hybrid-anthropic-openai"],
streamFamilies: ["minimax-fast-mode"],
},
openai: {
streamFamilies: ["openai-responses-defaults"],
toolCompatFamilies: ["openai"],
},
};
+36
View File
@@ -33,6 +33,7 @@ import {
shouldRunRuntimeSidecarBaselineCheck,
shouldRunShrinkwrapGuard,
shouldRunPluginSdkApiBaselineCheck,
shouldRunDeprecationHygieneChecks,
shouldRunPluginSdkSurfaceChecks,
shouldRunSqliteSessionSchemaBaselineCheck,
shouldRunTestTempCreationReport,
@@ -1436,6 +1437,8 @@ describe("scripts/changed-lanes", () => {
"duplicate scan target coverage",
"dependency pin guard",
"format changed files",
"deprecated API usage",
"plugin boundaries",
"package patch guard",
"test temp creation report (warning-only)",
"typecheck core tests",
@@ -1721,6 +1724,8 @@ describe("scripts/changed-lanes", () => {
"deps:pins:check",
"format:check",
"scripts/generate-npm-shrinkwrap.mjs",
"check:deprecated-api-usage",
"plugins:boundary-report:ci",
"deps:patches:check",
"release-metadata:check",
"android:version:check",
@@ -1926,6 +1931,37 @@ describe("scripts/changed-lanes", () => {
);
});
it("runs deprecation hygiene checks for outcome-changing paths and all lanes", () => {
expect(
shouldRunDeprecationHygieneChecks([
"src/plugin-sdk/core.ts",
"extensions/slack/index.ts",
"packages/gateway-protocol/src/index.ts",
"scripts/lib/plugin-sdk-entries.mjs",
"scripts/check-deprecated-api-usage.mjs",
"scripts/plugin-boundary-report.ts",
"src/plugins/compat/registry.ts",
"package.json",
]),
).toBe(true);
expect(shouldRunDeprecationHygieneChecks(["docs/plugins/sdk-migration.md"])).toBe(false);
for (const result of [
detectChangedLanes(["extensions/slack/index.ts"]),
detectChangedLanes(["unknown-surface.foo"]),
]) {
const plan = createChangedCheckPlan(result);
expect(plan.commands).toContainEqual({
name: "deprecated API usage",
args: ["check:deprecated-api-usage"],
});
expect(plan.commands).toContainEqual({
name: "plugin boundaries",
args: ["plugins:boundary-report:ci"],
});
}
});
it("guards release metadata package changes to the top-level version field", () => {
const dir = makeTempRepoRoot(tempDirs, "openclaw-release-metadata-");
git(dir, ["init", "-q", "--initial-branch=main"]);