diff --git a/config/knip.config.ts b/config/knip.config.ts index 831e5fc80b2c..1e4c1a92681e 100644 --- a/config/knip.config.ts +++ b/config/knip.config.ts @@ -362,7 +362,6 @@ const config = { "scripts/**": ["exports", "nsExports", "types", "nsTypes", "enumMembers", "namespaceMembers"], // The full-tree companion config makes tests entrypoints; these contracts // are intentionally test-only in the production graph. - "src/boards/board-layout.ts": ["types"], "src/boards/board-notices.ts": ["exports"], "src/boards/board-store.ts": ["exports"], // Test and E2E callers reach these hooks through runtime.test-support.ts; @@ -375,28 +374,20 @@ const config = { // tests; the full-tree scan still audits every named export against those consumers. "src/agents/harness/registry.ts": ["exports"], "src/context-engine/registry.ts": ["exports", "types"], - "src/plugins/command-registration.ts": ["exports"], "src/plugins/compaction-provider.ts": ["exports"], "src/plugins/interactive-registry.ts": ["exports"], - "src/plugins/loader-module-runtime.ts": ["exports"], "src/plugins/memory-state.ts": ["exports", "types"], "src/plugins/session-discussion-registry.ts": ["exports"], "src/tasks/detached-task-runtime-state.ts": ["exports"], // Focused media tests consume these explicit seams; production uses the helpers in-module. "src/agents/embedded-agent-subscribe.handlers.lifecycle.ts": ["exports"], "src/gateway/server-methods/chat-webchat-media.ts": ["exports"], - // GatewayBoardProvider and boardExists are constructed/asserted by the - // focused Control UI provider tests, not by a separate production module. - "ui/src/lib/board/provider.ts": ["exports"], // Greeting cache/fact contracts (hash, alert text, store shapes) are // asserted by the focused greeting unit tests, not by another prod module. "src/system-agent/greeting.ts": ["exports", "types"], // Focused tests consume these diagnostic/test seams; production code uses // the surrounding runtime helpers rather than importing the exports. "extensions/signal/src/setup-core.ts": ["exports"], - // The resolver's executable-path validation is covered through focused tests; - // production imports only the narrower op resolver. - "extensions/onepassword/onepassword-op-path.js": ["exports"], // Focused CLI tests exercise plan construction through this explicit test seam. "extensions/onepassword/src/secret-ref-cli.ts": ["exports"], // Mirror config parsing, redaction mapping, cap fitting, and the runner are diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index 591372de26b9..3b1cc755a320 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -3,8 +3,6 @@ summary: "Migrate from the legacy backwards-compatibility layer to the modern pl title: "Plugin SDK migration" sidebarTitle: "Migrate to SDK" read_when: - - You see the OPENCLAW_PLUGIN_SDK_COMPAT_DEPRECATED warning - - You see the OPENCLAW_EXTENSION_API_DEPRECATED warning - You used api.registerEmbeddedExtensionFactory before OpenClaw 2026.4.25 - You are updating a plugin to the modern plugin architecture - You maintain an external OpenClaw plugin @@ -1047,15 +1045,6 @@ All core plugins have already migrated. External plugins should migrate before the next major release. Run `pnpm plugins:boundary-report` to see which compat records are due soonest for the surfaces your plugin uses. -## Suppressing the warnings temporarily - -```bash -OPENCLAW_SUPPRESS_PLUGIN_SDK_COMPAT_WARNING=1 openclaw gateway run -OPENCLAW_SUPPRESS_EXTENSION_API_WARNING=1 openclaw gateway run -``` - -This is a temporary escape hatch, not a permanent solution. - ## Related - [Getting Started](/plugins/building-plugins) - build your first plugin diff --git a/docs/reference/test.md b/docs/reference/test.md index f08383399194..f3def3a4ec3e 100644 --- a/docs/reference/test.md +++ b/docs/reference/test.md @@ -111,7 +111,7 @@ Test wrapper runs end with a short `[test] passed|failed|skipped ... in ...` sum ## Gateway and E2E -- Gateway integration is opt-in: `OPENCLAW_TEST_INCLUDE_GATEWAY=1 pnpm test` or `pnpm test:gateway`. +- Gateway tests are included in the untargeted `pnpm test` full suite; run them alone with `pnpm test:gateway`. - `pnpm test:e2e`: repo E2E aggregate = `pnpm test:e2e:gateway && pnpm test:ui:e2e`. - `pnpm test:e2e:gateway`: gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults to `threads` + `isolate: false` with adaptive workers in `vitest.e2e.config.ts`; tune with `OPENCLAW_E2E_WORKERS=`, verbose logs with `OPENCLAW_E2E_VERBOSE=1`. - `pnpm test:live`: provider live tests (Claude/Minimax/DeepSeek/z.ai/etc, gated by `*.live.test.ts`). Requires API keys and `LIVE=1` (or `OPENCLAW_LIVE_TEST=1`) to unskip; verbose output with `OPENCLAW_LIVE_TEST_QUIET=0`. diff --git a/extensions/onepassword/onepassword-op-path.d.ts b/extensions/onepassword/onepassword-op-path.d.ts index eb28069df5e3..e276f383a656 100644 --- a/extensions/onepassword/onepassword-op-path.d.ts +++ b/extensions/onepassword/onepassword-op-path.d.ts @@ -1,5 +1,3 @@ -export function resolveTrustedExecutablePath(targetPath: string): Promise; - export function resolveTrustedOnePasswordDirectoryPath(targetPath: string): Promise; export function resolveTrustedOnePasswordCli(options?: { diff --git a/extensions/onepassword/onepassword-op-path.js b/extensions/onepassword/onepassword-op-path.js index 3784dbe14cd4..4571d43744a1 100644 --- a/extensions/onepassword/onepassword-op-path.js +++ b/extensions/onepassword/onepassword-op-path.js @@ -5,7 +5,7 @@ function errorCode(error) { return error && typeof error === "object" && "code" in error ? error.code : undefined; } -export const resolveTrustedExecutablePath = pluginSecretRefSetup.resolveTrustedExecutablePath; +const resolveTrustedExecutablePath = pluginSecretRefSetup.resolveTrustedExecutablePath; export const resolveTrustedOnePasswordDirectoryPath = pluginSecretRefSetup.resolveTrustedDirectoryPath; diff --git a/extensions/onepassword/src/op-path.test.ts b/extensions/onepassword/src/op-path.test.ts index 5d28098f9d27..1176a8e26975 100644 --- a/extensions/onepassword/src/op-path.test.ts +++ b/extensions/onepassword/src/op-path.test.ts @@ -1,10 +1,12 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import { pluginSecretRefSetup } from "openclaw/plugin-sdk/secret-ref-runtime"; import { describe, expect, it } from "vitest"; -import { resolveTrustedExecutablePath } from "../onepassword-op-path.js"; import { createTrustedNodeFixture } from "./trusted-node.test-support.js"; +const { resolveTrustedExecutablePath } = pluginSecretRefSetup; + describe("1Password CLI owner trust", () => { it("copies the Node fixture without mutating the installed runtime", async () => { const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-op-path-")); diff --git a/src/agents/embedded-agent-runner-extraparams.test.ts b/src/agents/embedded-agent-runner-extraparams.test.ts index 33af32640564..e46ed8fac6d4 100644 --- a/src/agents/embedded-agent-runner-extraparams.test.ts +++ b/src/agents/embedded-agent-runner-extraparams.test.ts @@ -302,7 +302,6 @@ import { createCodexNativeWebSearchWrapper, createOpenAIAttributionHeadersWrapper, createOpenAICompletionsStrictMessageKeysWrapper, - createOpenAIDefaultTransportWrapper, createOpenAIFastModeWrapper, createOpenAIReasoningCompatibilityWrapper, createOpenAIResponsesContextManagementWrapper, @@ -346,13 +345,10 @@ function installFullProviderRuntimeDepsForTest() { resolveProviderExtraParamsForTransport: () => undefined, wrapProviderStreamFn: (params) => { if (params.provider === "openai") { - return createTestOpenAIProviderWrapper(params, true); - } - if (params.provider === "openai") { - return createTestOpenAIProviderWrapper(params, false); + return createTestOpenAIProviderWrapper(params); } if (params.provider === "azure-openai" || params.provider === "azure-openai-responses") { - return createTestOpenAIProviderWrapper(params, false); + return createTestOpenAIProviderWrapper(params); } if (params.provider === "amazon-bedrock") { return isAnthropicFamilyCacheTtlEligible({ @@ -444,14 +440,8 @@ function withMinimalProviderRuntimeDepsForTest(run: () => T): T { } } -function createTestOpenAIProviderWrapper( - params: WrapProviderStreamFnParams, - withDefaultTransport: boolean, -): StreamFn { +function createTestOpenAIProviderWrapper(params: WrapProviderStreamFnParams): StreamFn { let streamFn = params.context.streamFn; - if (withDefaultTransport) { - streamFn = createOpenAIDefaultTransportWrapper(streamFn); - } streamFn = createOpenAIAttributionHeadersWrapper(streamFn); if (resolveOpenAIFastMode(params.context.extraParams)) { diff --git a/src/boards/board-layout.test.ts b/src/boards/board-layout.test.ts index 127976fa3fc1..3fec621d8651 100644 --- a/src/boards/board-layout.test.ts +++ b/src/boards/board-layout.test.ts @@ -1,7 +1,8 @@ import { describe, expect, it } from "vitest"; -import type { BoardLayout } from "./board-layout.js"; import { applyBoardOps, BoardValidationError, normalizeBoardLayout } from "./board-layout.js"; +type BoardLayout = Parameters[0]; + function layout(): BoardLayout { return { tabs: [ diff --git a/src/boards/board-layout.ts b/src/boards/board-layout.ts index 92cad0c3b81a..ffb6944a6d21 100644 --- a/src/boards/board-layout.ts +++ b/src/boards/board-layout.ts @@ -14,7 +14,7 @@ export const BOARD_SIZE_PRESETS = { } as const; export type BoardSize = keyof typeof BOARD_SIZE_PRESETS; -export type BoardLayout = Pick; +type BoardLayout = Pick; type BoardValidationErrorCode = "conflict" | "invalid_operation" | "not_found"; export class BoardValidationError extends Error { diff --git a/src/llm/providers/stream-wrappers/openai.ts b/src/llm/providers/stream-wrappers/openai.ts index 7cb9fd739ca0..6d4c362fcc12 100644 --- a/src/llm/providers/stream-wrappers/openai.ts +++ b/src/llm/providers/stream-wrappers/openai.ts @@ -747,18 +747,6 @@ export function createCodexNativeWebSearchWrapper( }); }; } -/** @deprecated OpenAI provider-owned stream helper; do not use from third-party plugins. */ -export function createOpenAIDefaultTransportWrapper(baseStreamFn: StreamFn | undefined): StreamFn { - const underlying = baseStreamFn ?? streamSimple; - return (model, context, options) => { - const mergedOptions = { - ...options, - transport: options?.transport ?? "auto", - } as SimpleStreamOptions; - return underlying(model, context, mergedOptions); - }; -} - /** @deprecated OpenAI provider-owned stream helper; do not use from third-party plugins. */ export function createOpenAIAttributionHeadersWrapper( baseStreamFn: StreamFn | undefined, diff --git a/src/plugin-sdk/provider-stream.ts b/src/plugin-sdk/provider-stream.ts index 643735501571..bff29250bbec 100644 --- a/src/plugin-sdk/provider-stream.ts +++ b/src/plugin-sdk/provider-stream.ts @@ -219,7 +219,6 @@ export { createMinimaxFastModeWrapper } from "../llm/providers/stream-wrappers/m export { createOpenAIAttributionHeadersWrapper, createCodexNativeWebSearchWrapper, - createOpenAIDefaultTransportWrapper, createOpenAIFastModeWrapper, createOpenAIReasoningCompatibilityWrapper, createOpenAIResponsesContextManagementWrapper, diff --git a/src/plugins/api-lifecycle.test.ts b/src/plugins/api-lifecycle.test.ts index 6016d6626cdd..a336c42fc78f 100644 --- a/src/plugins/api-lifecycle.test.ts +++ b/src/plugins/api-lifecycle.test.ts @@ -4,7 +4,8 @@ import { expectDefined } from "@openclaw/normalization-core"; import { describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { buildPluginApi } from "./api-builder.js"; -import { runPluginRegisterSync } from "./loader-module-runtime.js"; +import { runPluginRegisterSyncInRegistry } from "./loader-module-runtime.js"; +import { createEmptyPluginRegistry } from "./registry-empty.js"; import type { PluginRuntime } from "./runtime/types.js"; import type { OpenClawPluginApi } from "./types.js"; @@ -21,9 +22,14 @@ function captureRegisteredPluginApi(handlers: Parameters[ handlers, }); let captured: OpenClawPluginApi | undefined; - runPluginRegisterSync((pluginApi) => { - captured = pluginApi; - }, api); + runPluginRegisterSyncInRegistry( + (pluginApi) => { + captured = pluginApi; + }, + api, + createEmptyPluginRegistry(), + "late-call-fixture", + ); return expectDefined(captured, "captured plugin api"); } diff --git a/src/plugins/command-registration.ts b/src/plugins/command-registration.ts index e1f7d2121701..b18aba1ad0d1 100644 --- a/src/plugins/command-registration.ts +++ b/src/plugins/command-registration.ts @@ -120,7 +120,7 @@ function validateCommandName( * Returns an error message if invalid, or null if valid. * Shared by both the global registration path and snapshot (non-activating) loads. */ -export function validatePluginCommandDefinition( +function validatePluginCommandDefinition( command: OpenClawPluginCommandDefinition, opts?: { allowReservedCommandNames?: boolean }, ): string | null { diff --git a/src/plugins/contracts/host-hooks.contract.test.ts b/src/plugins/contracts/host-hooks.contract.test.ts index d8f5af1fd201..81ea19b338da 100644 --- a/src/plugins/contracts/host-hooks.contract.test.ts +++ b/src/plugins/contracts/host-hooks.contract.test.ts @@ -29,7 +29,7 @@ import type { AgentToolResultMiddlewareContext, AgentToolResultMiddlewareEvent, } from "../agent-tool-result-middleware-types.js"; -import { validatePluginCommandDefinition } from "../command-registration.js"; +import { registerPluginCommandInRegistry } from "../command-registration.js"; import { executePluginCommand } from "../commands.js"; import { createHookRunner } from "../hooks.js"; import { cleanupReplacedPluginHostRegistry, runPluginHostCleanup } from "../host-hook-cleanup.js"; @@ -2169,28 +2169,28 @@ describe("host-hook fixture plugin contract", () => { pluginRoot: registration.rootDir, }; expect( - validatePluginCommandDefinition({ + registerPluginCommandInRegistry(registry.registry, "invalid-command-fixture", { name: "invalid-scopes-fixture", description: "Invalid scopes.", requiredScopes: "operator.approvals" as never, handler: () => ({ text: "unused" }), - }), + }).error, ).toBe("Command requiredScopes must be an array of operator scopes"); expect( - validatePluginCommandDefinition({ + registerPluginCommandInRegistry(registry.registry, "invalid-command-fixture", { name: "unknown-scopes-fixture", description: "Unknown scopes.", requiredScopes: ["operator.unknown" as never], handler: () => ({ text: "unused" }), - }), + }).error, ).toBe("Command requiredScopes contains unknown operator scope: operator.unknown"); expect( - validatePluginCommandDefinition({ + registerPluginCommandInRegistry(registry.registry, "invalid-command-fixture", { name: "invalid-owner-status-fixture", description: "Invalid owner status exposure.", exposeSenderIsOwner: "yes" as never, handler: () => ({ text: "unused" }), - }), + }).error, ).toBe("Command exposeSenderIsOwner must be a boolean"); await expect( diff --git a/src/plugins/contracts/run-context-lifecycle.contract.test.ts b/src/plugins/contracts/run-context-lifecycle.contract.test.ts index 6dc1ebec55c9..e429f8cb7edc 100644 --- a/src/plugins/contracts/run-context-lifecycle.contract.test.ts +++ b/src/plugins/contracts/run-context-lifecycle.contract.test.ts @@ -21,7 +21,7 @@ import { listPluginSessionSchedulerJobs, PLUGIN_TERMINAL_EVENT_CLEANUP_WAIT_MS, } from "../host-hook-runtime.test-fixtures.js"; -import { runPluginRegisterSync } from "../loader-module-runtime.js"; +import { runPluginRegisterSyncInRegistry } from "../loader-module-runtime.js"; import { createEmptyPluginRegistry } from "../registry-empty.js"; import { setActivePluginRegistry } from "../runtime.js"; import { createPluginRecord } from "../status.test-helpers.js"; @@ -69,9 +69,14 @@ describe("plugin run context lifecycle", () => { name: "Late Run Context Plugin", }), register(api) { - runPluginRegisterSync((guardedApi) => { - capturedApi = guardedApi; - }, api); + runPluginRegisterSyncInRegistry( + (guardedApi) => { + capturedApi = guardedApi; + }, + api, + registry.registry, + "late-run-context-plugin", + ); }, }); setActivePluginRegistry(registry.registry); diff --git a/src/plugins/loader-module-runtime.ts b/src/plugins/loader-module-runtime.ts index 91c6b8b2f132..a4198a04ad28 100644 --- a/src/plugins/loader-module-runtime.ts +++ b/src/plugins/loader-module-runtime.ts @@ -81,7 +81,7 @@ function createGuardedPluginRegistrationApi(api: OpenClawPluginApi): { }; } -export function runPluginRegisterSync( +function runPluginRegisterSync( register: NonNullable, api: Parameters>[0], ): void { diff --git a/ui/src/lib/board/gateway-provider.test.ts b/ui/src/lib/board/gateway-provider.test.ts index dd10fbd0f816..75f49b92a367 100644 --- a/ui/src/lib/board/gateway-provider.test.ts +++ b/ui/src/lib/board/gateway-provider.test.ts @@ -1,7 +1,8 @@ // @vitest-environment node import type { EventFrame } from "@openclaw/gateway-protocol"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { GatewayBoardProvider, type BoardProvider } from "./provider.ts"; +import { GatewayBoardProvider } from "./gateway-provider.ts"; +import type { BoardProvider } from "./provider.ts"; afterEach(() => { vi.useRealTimers(); diff --git a/ui/src/lib/board/provider.mcp-app.test.ts b/ui/src/lib/board/provider.mcp-app.test.ts index 77f720773160..a1146ffc7e6f 100644 --- a/ui/src/lib/board/provider.mcp-app.test.ts +++ b/ui/src/lib/board/provider.mcp-app.test.ts @@ -1,7 +1,7 @@ // @vitest-environment node import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { GatewayBoardProvider } from "./gateway-provider.ts"; import { BoardMcpAppViewCache } from "./mcp-app-view-cache.ts"; -import { GatewayBoardProvider } from "./provider.ts"; let mockLocation: { search: string }; diff --git a/ui/src/lib/board/provider.test.ts b/ui/src/lib/board/provider.test.ts index e4f621f2d544..09febf73f6da 100644 --- a/ui/src/lib/board/provider.test.ts +++ b/ui/src/lib/board/provider.test.ts @@ -1,12 +1,12 @@ // @vitest-environment node import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { GatewayBoardProvider } from "./gateway-provider.ts"; import { registerBoardProviderLeaseCases } from "./provider.lease-cases.test-support.ts"; import { acquireBoardProviderForSession, boardExists, boardProviderForSession, canvasWidgetNameForDocument, - GatewayBoardProvider, hasLoadedBoardSnapshot, mcpAppWidgetNameForViewId, recordSessionBoardAvailability, diff --git a/ui/src/lib/board/provider.ts b/ui/src/lib/board/provider.ts index 406435b70278..2e763ba7641a 100644 --- a/ui/src/lib/board/provider.ts +++ b/ui/src/lib/board/provider.ts @@ -26,7 +26,6 @@ export type { BoardCommandEvent }; export type { BoardProvider } from "./provider-types.ts"; export type { BoardViewCallbacks, BoardWidgetAppViewState } from "./view-types.ts"; export { canvasWidgetNameForDocument, mcpAppWidgetNameForViewId } from "./widget-names.ts"; -export { GatewayBoardProvider } from "./gateway-provider.ts"; type BoardGatewayClient = Pick;