diff --git a/CHANGELOG.md b/CHANGELOG.md index 21e937d8de6..04a124363bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai - Gateway/runtime: reuse the current plugin metadata snapshot for provider discovery so repeated model-provider discovery avoids rebuilding plugin manifest metadata. Thanks @shakkernerd. - Gateway/startup: pass the plugin metadata snapshot from config validation into plugin bootstrap so startup reuses one manifest product instead of rebuilding plugin metadata. Thanks @shakkernerd. - Plugin SDK/testing: move core-only channel contract fixtures under the channel contract test tree and retire the old `test/helpers/channels` bridge directory so plugin tests stay on focused SDK surfaces. Thanks @vincentkoc. +- Plugin SDK/testing: expose native agent-runtime contract fixtures through `plugin-sdk/agent-runtime-test-contracts`, move sandbox config fixtures into the focused generic fixture subpath, and block extension tests from importing repo-only `test/helpers` bridges. Thanks @vincentkoc. - Plugin SDK/testing: expose generic module reload, bundled-path, Node builtin mock, channel pairing/envelope, HTTP server, temp-home, replay-policy, and live STT helpers through focused SDK test subpaths so extension tests no longer depend on repo-only helper bridges. Thanks @vincentkoc. - Plugin SDK: move maintained bundled channels off the deprecated `channel-config-schema-legacy` subpath, add an explicit bundled-channel schema SDK surface, and track both remaining legacy test/config compatibility barrels with dated removal windows. Thanks @vincentkoc. - Plugin SDK/testing: expose media provider capability assertions and provider HTTP mocks through focused SDK test subpaths, and retire the repo-only media-generation test helper bridge. Thanks @vincentkoc. diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 873aebb88e1..73bf91a4476 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -6b4c0c05e24f13c08daebf36a9853e66056ab677e10dac9c35e8d514f87ca74e plugin-sdk-api-baseline.json -e6ea9f91aa5118bf2c61cb50f774d96740509723e862b9b618e2061b04504dc7 plugin-sdk-api-baseline.jsonl +6c33fafd66396c2c769b8f8d1f5d0792beb8b4b2d23b5f0323ff2bb2d0a79805 plugin-sdk-api-baseline.json +27336f313c029843be409d22d79caf6f45884cf37458d9f52c0a88c4e8b268fd plugin-sdk-api-baseline.jsonl diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index 74eba9f8604..f7b93493164 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -16,25 +16,26 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview) ## Plugin entry -| Subpath | Key exports | -| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `plugin-sdk/plugin-entry` | `definePluginEntry` | -| `plugin-sdk/core` | `defineChannelPluginEntry`, `createChatChannelPlugin`, `createChannelPluginBase`, `defineSetupPluginEntry`, `buildChannelConfigSchema` | -| `plugin-sdk/config-schema` | `OpenClawSchema` | -| `plugin-sdk/provider-entry` | `defineSingleProviderPluginEntry` | -| `plugin-sdk/testing` | Broad compatibility barrel for legacy plugin tests; prefer focused test subpaths for new extension tests | -| `plugin-sdk/plugin-test-api` | Minimal `OpenClawPluginApi` mock builder for direct plugin registration unit tests | -| `plugin-sdk/channel-test-helpers` | Channel account lifecycle, directory, send-config, runtime mock, hook, bundled channel entry, envelope timestamp, pairing reply, and generic channel contract test helpers | -| `plugin-sdk/channel-target-testing` | Shared channel target-resolution error-case test suite | -| `plugin-sdk/plugin-test-contracts` | Plugin registration, package manifest, public artifact, runtime API, import side-effect, and direct import contract helpers | -| `plugin-sdk/plugin-test-runtime` | Plugin runtime, registry, provider-registration, setup-wizard, and runtime task-flow fixtures for tests | -| `plugin-sdk/provider-test-contracts` | Provider runtime, auth, discovery, onboard, catalog, media capability, replay policy, realtime STT live-audio, web-search/fetch, and wizard contract helpers | -| `plugin-sdk/provider-http-test-mocks` | Opt-in Vitest HTTP/auth mocks for provider tests that exercise `plugin-sdk/provider-http` | -| `plugin-sdk/test-env` | Test environment, fetch/network, disposable HTTP server, incoming request, live-test, temporary filesystem, and time-control fixtures | -| `plugin-sdk/test-fixtures` | Generic CLI, sandbox, skill, agent-message, system-event, module reload, bundled plugin path, terminal, chunking, auth-token, and typed-case test fixtures | -| `plugin-sdk/test-node-mocks` | Focused Node builtin mock helpers for use inside Vitest `vi.mock("node:*")` factories | -| `plugin-sdk/migration` | Migration provider item helpers such as `createMigrationItem`, reason constants, item status markers, redaction helpers, and `summarizeMigrationItems` | -| `plugin-sdk/migration-runtime` | Runtime migration helpers such as `copyMigrationFileItem` and `writeMigrationReport` | +| Subpath | Key exports | +| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `plugin-sdk/plugin-entry` | `definePluginEntry` | +| `plugin-sdk/core` | `defineChannelPluginEntry`, `createChatChannelPlugin`, `createChannelPluginBase`, `defineSetupPluginEntry`, `buildChannelConfigSchema` | +| `plugin-sdk/config-schema` | `OpenClawSchema` | +| `plugin-sdk/provider-entry` | `defineSingleProviderPluginEntry` | +| `plugin-sdk/testing` | Broad compatibility barrel for legacy plugin tests; prefer focused test subpaths for new extension tests | +| `plugin-sdk/plugin-test-api` | Minimal `OpenClawPluginApi` mock builder for direct plugin registration unit tests | +| `plugin-sdk/agent-runtime-test-contracts` | Native agent-runtime adapter contract fixtures for auth profiles, delivery suppression, fallback classification, tool hooks, prompt overlays, schemas, and transcript repair | +| `plugin-sdk/channel-test-helpers` | Channel account lifecycle, directory, send-config, runtime mock, hook, bundled channel entry, envelope timestamp, pairing reply, and generic channel contract test helpers | +| `plugin-sdk/channel-target-testing` | Shared channel target-resolution error-case test suite | +| `plugin-sdk/plugin-test-contracts` | Plugin registration, package manifest, public artifact, runtime API, import side-effect, and direct import contract helpers | +| `plugin-sdk/plugin-test-runtime` | Plugin runtime, registry, provider-registration, setup-wizard, and runtime task-flow fixtures for tests | +| `plugin-sdk/provider-test-contracts` | Provider runtime, auth, discovery, onboard, catalog, media capability, replay policy, realtime STT live-audio, web-search/fetch, and wizard contract helpers | +| `plugin-sdk/provider-http-test-mocks` | Opt-in Vitest HTTP/auth mocks for provider tests that exercise `plugin-sdk/provider-http` | +| `plugin-sdk/test-env` | Test environment, fetch/network, disposable HTTP server, incoming request, live-test, temporary filesystem, and time-control fixtures | +| `plugin-sdk/test-fixtures` | Generic CLI, sandbox, skill, agent-message, system-event, module reload, bundled plugin path, terminal, chunking, auth-token, and typed-case test fixtures | +| `plugin-sdk/test-node-mocks` | Focused Node builtin mock helpers for use inside Vitest `vi.mock("node:*")` factories | +| `plugin-sdk/migration` | Migration provider item helpers such as `createMigrationItem`, reason constants, item status markers, redaction helpers, and `summarizeMigrationItems` | +| `plugin-sdk/migration-runtime` | Runtime migration helpers such as `copyMigrationFileItem` and `writeMigrationReport` | @@ -271,8 +272,9 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview) | `plugin-sdk/webhook-path` | Webhook path normalization helpers | | `plugin-sdk/web-media` | Shared remote/local media loading helpers | | `plugin-sdk/zod` | Re-exported `zod` for plugin SDK consumers | - | `plugin-sdk/testing` | Broad compatibility barrel for legacy plugin tests. New extension tests should import focused SDK subpaths such as `plugin-sdk/plugin-test-runtime`, `plugin-sdk/channel-test-helpers`, `plugin-sdk/test-env`, or `plugin-sdk/test-fixtures` instead | + | `plugin-sdk/testing` | Broad compatibility barrel for legacy plugin tests. New extension tests should import focused SDK subpaths such as `plugin-sdk/agent-runtime-test-contracts`, `plugin-sdk/plugin-test-runtime`, `plugin-sdk/channel-test-helpers`, `plugin-sdk/test-env`, or `plugin-sdk/test-fixtures` instead | | `plugin-sdk/plugin-test-api` | Minimal `createTestPluginApi` helper for direct plugin registration unit tests without importing repo test helper bridges | + | `plugin-sdk/agent-runtime-test-contracts` | Native agent-runtime adapter contract fixtures for auth, delivery, fallback, tool-hook, prompt-overlay, schema, and transcript projection tests | | `plugin-sdk/channel-test-helpers` | Channel-oriented test helpers for generic actions/setup/status contracts, directory assertions, account startup lifecycle, send-config threading, runtime mocks, status issues, outbound delivery, and hook registration | | `plugin-sdk/channel-target-testing` | Shared target-resolution error-case suite for channel tests | | `plugin-sdk/plugin-test-contracts` | Plugin package, registration, public artifact, direct import, runtime API, and import side-effect contract helpers | diff --git a/docs/plugins/sdk-testing.md b/docs/plugins/sdk-testing.md index 930bd13bab0..495f4fa05af 100644 --- a/docs/plugins/sdk-testing.md +++ b/docs/plugins/sdk-testing.md @@ -21,6 +21,8 @@ plugins. **Plugin API mock import:** `openclaw/plugin-sdk/plugin-test-api` +**Agent runtime contract import:** `openclaw/plugin-sdk/agent-runtime-test-contracts` + **Channel contract import:** `openclaw/plugin-sdk/channel-contract-testing` **Channel test helper import:** `openclaw/plugin-sdk/channel-test-helpers` @@ -50,6 +52,7 @@ import { removeAckReactionAfterReply, } from "openclaw/plugin-sdk/channel-feedback"; import { installCommonResolveTargetErrorCases } from "openclaw/plugin-sdk/channel-target-testing"; +import { AUTH_PROFILE_RUNTIME_CONTRACT } from "openclaw/plugin-sdk/agent-runtime-test-contracts"; import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api"; import { expectChannelInboundContextContract } from "openclaw/plugin-sdk/channel-contract-testing"; import { createStartAccountContext } from "openclaw/plugin-sdk/channel-test-helpers"; @@ -68,80 +71,84 @@ import { mockNodeBuiltinModule } from "openclaw/plugin-sdk/test-node-mocks"; ### Available exports -| Export | Purpose | -| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| `createTestPluginApi` | Build a minimal plugin API mock for direct registration unit tests. Import from `plugin-sdk/plugin-test-api` | -| `expectChannelInboundContextContract` | Assert channel inbound context shape. Import from `plugin-sdk/channel-contract-testing` | -| `installChannelOutboundPayloadContractSuite` | Install channel outbound payload contract cases. Import from `plugin-sdk/channel-contract-testing` | -| `createStartAccountContext` | Build channel account lifecycle contexts. Import from `plugin-sdk/channel-test-helpers` | -| `installChannelActionsContractSuite` | Install generic channel message-action contract cases. Import from `plugin-sdk/channel-test-helpers` | -| `installChannelSetupContractSuite` | Install generic channel setup contract cases. Import from `plugin-sdk/channel-test-helpers` | -| `installChannelStatusContractSuite` | Install generic channel status contract cases. Import from `plugin-sdk/channel-test-helpers` | -| `expectDirectoryIds` | Assert channel directory ids from a directory-list function. Import from `plugin-sdk/channel-test-helpers` | -| `assertBundledChannelEntries` | Assert bundled channel entrypoints expose the expected public contract. Import from `plugin-sdk/channel-test-helpers` | -| `formatEnvelopeTimestamp` | Format deterministic envelope timestamps. Import from `plugin-sdk/channel-test-helpers` | -| `expectPairingReplyText` | Assert channel pairing reply text and extract its code. Import from `plugin-sdk/channel-test-helpers` | -| `describePluginRegistrationContract` | Install plugin registration contract checks. Import from `plugin-sdk/plugin-test-contracts` | -| `registerSingleProviderPlugin` | Register one provider plugin in loader smoke tests. Import from `plugin-sdk/plugin-test-runtime` | -| `registerProviderPlugin` | Capture all provider kinds from one plugin. Import from `plugin-sdk/plugin-test-runtime` | -| `registerProviderPlugins` | Capture provider registrations across multiple plugins. Import from `plugin-sdk/plugin-test-runtime` | -| `requireRegisteredProvider` | Assert that a provider collection contains an id. Import from `plugin-sdk/plugin-test-runtime` | -| `createRuntimeEnv` | Build a mocked CLI/plugin runtime environment. Import from `plugin-sdk/plugin-test-runtime` | -| `createPluginSetupWizardStatus` | Build setup status helpers for channel plugins. Import from `plugin-sdk/plugin-test-runtime` | -| `describeOpenAIProviderRuntimeContract` | Install provider-family runtime contract checks. Import from `plugin-sdk/provider-test-contracts` | -| `expectPassthroughReplayPolicy` | Assert provider replay policies pass through provider-owned tools and metadata. Import from `plugin-sdk/provider-test-contracts` | -| `runRealtimeSttLiveTest` | Run a live realtime STT provider test with shared audio fixtures. Import from `plugin-sdk/provider-test-contracts` | -| `normalizeTranscriptForMatch` | Normalize live transcript output before fuzzy assertions. Import from `plugin-sdk/provider-test-contracts` | -| `expectExplicitVideoGenerationCapabilities` | Assert video providers declare explicit generation mode capabilities. Import from `plugin-sdk/provider-test-contracts` | -| `expectExplicitMusicGenerationCapabilities` | Assert music providers declare explicit generation/edit capabilities. Import from `plugin-sdk/provider-test-contracts` | -| `mockSuccessfulDashscopeVideoTask` | Install a successful DashScope-compatible video task response. Import from `plugin-sdk/provider-test-contracts` | -| `getProviderHttpMocks` | Access opt-in provider HTTP/auth Vitest mocks. Import from `plugin-sdk/provider-http-test-mocks` | -| `installProviderHttpMockCleanup` | Reset provider HTTP/auth mocks after each test. Import from `plugin-sdk/provider-http-test-mocks` | -| `installCommonResolveTargetErrorCases` | Shared test cases for target resolution error handling. Import from `plugin-sdk/channel-target-testing` | -| `shouldAckReaction` | Check whether a channel should add an ack reaction. Import from `plugin-sdk/channel-feedback` | -| `removeAckReactionAfterReply` | Remove ack reaction after reply delivery. Import from `plugin-sdk/channel-feedback` | -| `createTestRegistry` | Build a channel plugin registry fixture. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | -| `createEmptyPluginRegistry` | Build an empty plugin registry fixture. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | -| `setActivePluginRegistry` | Install a registry fixture for plugin runtime tests. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | -| `createRequestCaptureJsonFetch` | Capture JSON fetch requests in media helper tests. Import from `plugin-sdk/test-env` | -| `withServer` | Run tests against a disposable local HTTP server. Import from `plugin-sdk/test-env` | -| `createMockIncomingRequest` | Build a minimal incoming HTTP request object. Import from `plugin-sdk/test-env` | -| `withFetchPreconnect` | Run fetch tests with preconnect hooks installed. Import from `plugin-sdk/test-env` | -| `withEnv` / `withEnvAsync` | Temporarily patch environment variables. Import from `plugin-sdk/test-env` | -| `createTempHomeEnv` / `withTempHome` / `withTempDir` | Create isolated filesystem test fixtures. Import from `plugin-sdk/test-env` | -| `createMockServerResponse` | Create a minimal HTTP server response mock. Import from `plugin-sdk/test-env` | -| `createCliRuntimeCapture` | Capture CLI runtime output in tests. Import from `plugin-sdk/test-fixtures` | -| `importFreshModule` | Import an ESM module with a fresh query token to bypass module cache. Import from `plugin-sdk/test-fixtures` | -| `bundledPluginRoot` / `bundledPluginFile` | Resolve bundled plugin source or dist fixture paths. Import from `plugin-sdk/test-fixtures` | -| `mockNodeBuiltinModule` | Install narrow Node builtin Vitest mocks. Import from `plugin-sdk/test-node-mocks` | -| `createSandboxTestContext` | Build sandbox test contexts. Import from `plugin-sdk/test-fixtures` | -| `writeSkill` | Write skill fixtures. Import from `plugin-sdk/test-fixtures` | -| `makeAgentAssistantMessage` | Build agent transcript message fixtures. Import from `plugin-sdk/test-fixtures` | -| `peekSystemEvents` / `resetSystemEventsForTest` | Inspect and reset system event fixtures. Import from `plugin-sdk/test-fixtures` | -| `sanitizeTerminalText` | Sanitize terminal output for assertions. Import from `plugin-sdk/test-fixtures` | -| `countLines` / `hasBalancedFences` | Assert chunking output shape. Import from `plugin-sdk/test-fixtures` | -| `runProviderCatalog` | Execute a provider catalog hook with test dependencies | -| `resolveProviderWizardOptions` | Resolve provider setup wizard choices in contract tests | -| `resolveProviderModelPickerEntries` | Resolve provider model-picker entries in contract tests | -| `buildProviderPluginMethodChoice` | Build provider wizard choice ids for assertions | -| `setProviderWizardProvidersResolverForTest` | Inject provider wizard providers for isolated tests | -| `createProviderUsageFetch` | Build provider usage fetch fixtures | -| `useFrozenTime` / `useRealTime` | Freeze and restore timers for time-sensitive tests. Import from `plugin-sdk/test-env` | -| `createTestWizardPrompter` | Build a mocked setup wizard prompter | -| `createRuntimeTaskFlow` | Create isolated runtime task-flow state | -| `typedCases` | Preserve literal types for table-driven tests. Import from `plugin-sdk/test-fixtures` | +| Export | Purpose | +| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `createTestPluginApi` | Build a minimal plugin API mock for direct registration unit tests. Import from `plugin-sdk/plugin-test-api` | +| `AUTH_PROFILE_RUNTIME_CONTRACT` | Shared auth-profile contract fixture for native agent runtime adapters. Import from `plugin-sdk/agent-runtime-test-contracts` | +| `DELIVERY_NO_REPLY_RUNTIME_CONTRACT` | Shared delivery suppression contract fixture for native agent runtime adapters. Import from `plugin-sdk/agent-runtime-test-contracts` | +| `OUTCOME_FALLBACK_RUNTIME_CONTRACT` | Shared fallback-classification contract fixture for native agent runtime adapters. Import from `plugin-sdk/agent-runtime-test-contracts` | +| `createParameterFreeTool` | Build dynamic-tool schema fixtures for native runtime contract tests. Import from `plugin-sdk/agent-runtime-test-contracts` | +| `expectChannelInboundContextContract` | Assert channel inbound context shape. Import from `plugin-sdk/channel-contract-testing` | +| `installChannelOutboundPayloadContractSuite` | Install channel outbound payload contract cases. Import from `plugin-sdk/channel-contract-testing` | +| `createStartAccountContext` | Build channel account lifecycle contexts. Import from `plugin-sdk/channel-test-helpers` | +| `installChannelActionsContractSuite` | Install generic channel message-action contract cases. Import from `plugin-sdk/channel-test-helpers` | +| `installChannelSetupContractSuite` | Install generic channel setup contract cases. Import from `plugin-sdk/channel-test-helpers` | +| `installChannelStatusContractSuite` | Install generic channel status contract cases. Import from `plugin-sdk/channel-test-helpers` | +| `expectDirectoryIds` | Assert channel directory ids from a directory-list function. Import from `plugin-sdk/channel-test-helpers` | +| `assertBundledChannelEntries` | Assert bundled channel entrypoints expose the expected public contract. Import from `plugin-sdk/channel-test-helpers` | +| `formatEnvelopeTimestamp` | Format deterministic envelope timestamps. Import from `plugin-sdk/channel-test-helpers` | +| `expectPairingReplyText` | Assert channel pairing reply text and extract its code. Import from `plugin-sdk/channel-test-helpers` | +| `describePluginRegistrationContract` | Install plugin registration contract checks. Import from `plugin-sdk/plugin-test-contracts` | +| `registerSingleProviderPlugin` | Register one provider plugin in loader smoke tests. Import from `plugin-sdk/plugin-test-runtime` | +| `registerProviderPlugin` | Capture all provider kinds from one plugin. Import from `plugin-sdk/plugin-test-runtime` | +| `registerProviderPlugins` | Capture provider registrations across multiple plugins. Import from `plugin-sdk/plugin-test-runtime` | +| `requireRegisteredProvider` | Assert that a provider collection contains an id. Import from `plugin-sdk/plugin-test-runtime` | +| `createRuntimeEnv` | Build a mocked CLI/plugin runtime environment. Import from `plugin-sdk/plugin-test-runtime` | +| `createPluginSetupWizardStatus` | Build setup status helpers for channel plugins. Import from `plugin-sdk/plugin-test-runtime` | +| `describeOpenAIProviderRuntimeContract` | Install provider-family runtime contract checks. Import from `plugin-sdk/provider-test-contracts` | +| `expectPassthroughReplayPolicy` | Assert provider replay policies pass through provider-owned tools and metadata. Import from `plugin-sdk/provider-test-contracts` | +| `runRealtimeSttLiveTest` | Run a live realtime STT provider test with shared audio fixtures. Import from `plugin-sdk/provider-test-contracts` | +| `normalizeTranscriptForMatch` | Normalize live transcript output before fuzzy assertions. Import from `plugin-sdk/provider-test-contracts` | +| `expectExplicitVideoGenerationCapabilities` | Assert video providers declare explicit generation mode capabilities. Import from `plugin-sdk/provider-test-contracts` | +| `expectExplicitMusicGenerationCapabilities` | Assert music providers declare explicit generation/edit capabilities. Import from `plugin-sdk/provider-test-contracts` | +| `mockSuccessfulDashscopeVideoTask` | Install a successful DashScope-compatible video task response. Import from `plugin-sdk/provider-test-contracts` | +| `getProviderHttpMocks` | Access opt-in provider HTTP/auth Vitest mocks. Import from `plugin-sdk/provider-http-test-mocks` | +| `installProviderHttpMockCleanup` | Reset provider HTTP/auth mocks after each test. Import from `plugin-sdk/provider-http-test-mocks` | +| `installCommonResolveTargetErrorCases` | Shared test cases for target resolution error handling. Import from `plugin-sdk/channel-target-testing` | +| `shouldAckReaction` | Check whether a channel should add an ack reaction. Import from `plugin-sdk/channel-feedback` | +| `removeAckReactionAfterReply` | Remove ack reaction after reply delivery. Import from `plugin-sdk/channel-feedback` | +| `createTestRegistry` | Build a channel plugin registry fixture. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | +| `createEmptyPluginRegistry` | Build an empty plugin registry fixture. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | +| `setActivePluginRegistry` | Install a registry fixture for plugin runtime tests. Import from `plugin-sdk/plugin-test-runtime` or `plugin-sdk/channel-test-helpers` | +| `createRequestCaptureJsonFetch` | Capture JSON fetch requests in media helper tests. Import from `plugin-sdk/test-env` | +| `withServer` | Run tests against a disposable local HTTP server. Import from `plugin-sdk/test-env` | +| `createMockIncomingRequest` | Build a minimal incoming HTTP request object. Import from `plugin-sdk/test-env` | +| `withFetchPreconnect` | Run fetch tests with preconnect hooks installed. Import from `plugin-sdk/test-env` | +| `withEnv` / `withEnvAsync` | Temporarily patch environment variables. Import from `plugin-sdk/test-env` | +| `createTempHomeEnv` / `withTempHome` / `withTempDir` | Create isolated filesystem test fixtures. Import from `plugin-sdk/test-env` | +| `createMockServerResponse` | Create a minimal HTTP server response mock. Import from `plugin-sdk/test-env` | +| `createCliRuntimeCapture` | Capture CLI runtime output in tests. Import from `plugin-sdk/test-fixtures` | +| `importFreshModule` | Import an ESM module with a fresh query token to bypass module cache. Import from `plugin-sdk/test-fixtures` | +| `bundledPluginRoot` / `bundledPluginFile` | Resolve bundled plugin source or dist fixture paths. Import from `plugin-sdk/test-fixtures` | +| `mockNodeBuiltinModule` | Install narrow Node builtin Vitest mocks. Import from `plugin-sdk/test-node-mocks` | +| `createSandboxTestContext` | Build sandbox test contexts. Import from `plugin-sdk/test-fixtures` | +| `writeSkill` | Write skill fixtures. Import from `plugin-sdk/test-fixtures` | +| `makeAgentAssistantMessage` | Build agent transcript message fixtures. Import from `plugin-sdk/test-fixtures` | +| `peekSystemEvents` / `resetSystemEventsForTest` | Inspect and reset system event fixtures. Import from `plugin-sdk/test-fixtures` | +| `sanitizeTerminalText` | Sanitize terminal output for assertions. Import from `plugin-sdk/test-fixtures` | +| `countLines` / `hasBalancedFences` | Assert chunking output shape. Import from `plugin-sdk/test-fixtures` | +| `runProviderCatalog` | Execute a provider catalog hook with test dependencies | +| `resolveProviderWizardOptions` | Resolve provider setup wizard choices in contract tests | +| `resolveProviderModelPickerEntries` | Resolve provider model-picker entries in contract tests | +| `buildProviderPluginMethodChoice` | Build provider wizard choice ids for assertions | +| `setProviderWizardProvidersResolverForTest` | Inject provider wizard providers for isolated tests | +| `createProviderUsageFetch` | Build provider usage fetch fixtures | +| `useFrozenTime` / `useRealTime` | Freeze and restore timers for time-sensitive tests. Import from `plugin-sdk/test-env` | +| `createTestWizardPrompter` | Build a mocked setup wizard prompter | +| `createRuntimeTaskFlow` | Create isolated runtime task-flow state | +| `typedCases` | Preserve literal types for table-driven tests. Import from `plugin-sdk/test-fixtures` | Bundled-plugin contract suites also use SDK testing subpaths for test-only registry, manifest, public-artifact, and runtime fixture helpers. Core-only suites that depend on bundled OpenClaw inventory stay under `src/plugins/contracts`. Keep new extension tests on a documented focused SDK subpath such as `plugin-sdk/plugin-test-api`, `plugin-sdk/channel-contract-testing`, -`plugin-sdk/channel-test-helpers`, `plugin-sdk/plugin-test-contracts`, -`plugin-sdk/plugin-test-runtime`, `plugin-sdk/provider-test-contracts`, -`plugin-sdk/provider-http-test-mocks`, `plugin-sdk/test-env`, or -`plugin-sdk/test-fixtures` rather than importing the broad `plugin-sdk/testing` -compatibility barrel, repo `src/**` files, or repo `test/helpers/plugins/*` -bridges directly. +`plugin-sdk/agent-runtime-test-contracts`, `plugin-sdk/channel-test-helpers`, +`plugin-sdk/plugin-test-contracts`, `plugin-sdk/plugin-test-runtime`, +`plugin-sdk/provider-test-contracts`, `plugin-sdk/provider-http-test-mocks`, +`plugin-sdk/test-env`, or `plugin-sdk/test-fixtures` rather than importing the +broad `plugin-sdk/testing` compatibility barrel, repo `src/**` files, or repo +`test/helpers/*` bridges directly. ### Types diff --git a/extensions/codex/prompt-overlay-runtime-contract.test.ts b/extensions/codex/prompt-overlay-runtime-contract.test.ts index e35d64022e2..126caeb9d59 100644 --- a/extensions/codex/prompt-overlay-runtime-contract.test.ts +++ b/extensions/codex/prompt-overlay-runtime-contract.test.ts @@ -1,10 +1,10 @@ -import { describe, expect, it } from "vitest"; import { codexPromptOverlayContext, GPT5_CONTRACT_MODEL_ID, NON_GPT5_CONTRACT_MODEL_ID, sharedGpt5PersonalityConfig, -} from "../../test/helpers/agents/prompt-overlay-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { describe, expect, it } from "vitest"; import { buildCodexProvider } from "./provider.js"; describe("Codex prompt overlay runtime contract", () => { diff --git a/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts b/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts index 2a6cb65ad5b..b18db8bdbd1 100644 --- a/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/auth-profile-runtime-contract.test.ts @@ -5,8 +5,8 @@ import { abortAgentHarnessRun, type EmbeddedRunAttemptParams, } from "openclaw/plugin-sdk/agent-harness"; +import { AUTH_PROFILE_RUNTIME_CONTRACT } from "openclaw/plugin-sdk/agent-runtime-test-contracts"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { AUTH_PROFILE_RUNTIME_CONTRACT } from "../../../../test/helpers/agents/auth-profile-runtime-contract.js"; import { runCodexAppServerAttempt, __testing } from "./run-attempt.js"; import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js"; import { createCodexTestModel } from "./test-support.js"; diff --git a/extensions/codex/src/app-server/delivery-no-reply-runtime-contract.test.ts b/extensions/codex/src/app-server/delivery-no-reply-runtime-contract.test.ts index c65b7a77a7c..7b8c7d16061 100644 --- a/extensions/codex/src/app-server/delivery-no-reply-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/delivery-no-reply-runtime-contract.test.ts @@ -3,9 +3,9 @@ import os from "node:os"; import path from "node:path"; import { SessionManager } from "@mariozechner/pi-coding-agent"; import type { EmbeddedRunAttemptParams } from "openclaw/plugin-sdk/agent-harness"; +import { DELIVERY_NO_REPLY_RUNTIME_CONTRACT } from "openclaw/plugin-sdk/agent-runtime-test-contracts"; import { isSilentReplyPayloadText } from "openclaw/plugin-sdk/reply-chunking"; import { afterEach, describe, expect, it } from "vitest"; -import { DELIVERY_NO_REPLY_RUNTIME_CONTRACT } from "../../../../test/helpers/agents/delivery-no-reply-runtime-contract.js"; import { CodexAppServerEventProjector } from "./event-projector.js"; import { createCodexTestModel } from "./test-support.js"; diff --git a/extensions/codex/src/app-server/openclaw-owned-tool-runtime-contract.test.ts b/extensions/codex/src/app-server/openclaw-owned-tool-runtime-contract.test.ts index 9af937eba8b..b63fd13527b 100644 --- a/extensions/codex/src/app-server/openclaw-owned-tool-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/openclaw-owned-tool-runtime-contract.test.ts @@ -1,13 +1,13 @@ import type { AnyAgentTool } from "openclaw/plugin-sdk/agent-harness"; import { wrapToolWithBeforeToolCallHook } from "openclaw/plugin-sdk/agent-harness-runtime"; -import { afterEach, describe, expect, it, vi } from "vitest"; import { installCodexToolResultMiddleware, installOpenClawOwnedToolHooks, mediaToolResult, resetOpenClawOwnedToolHooks, textToolResult, -} from "../../../../test/helpers/agents/openclaw-owned-tool-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { createCodexDynamicToolBridge } from "./dynamic-tools.js"; function createContractTool(overrides: Partial): AnyAgentTool { diff --git a/extensions/codex/src/app-server/outcome-fallback-runtime-contract.test.ts b/extensions/codex/src/app-server/outcome-fallback-runtime-contract.test.ts index 65790c798fd..0abb54cbe2c 100644 --- a/extensions/codex/src/app-server/outcome-fallback-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/outcome-fallback-runtime-contract.test.ts @@ -4,11 +4,11 @@ import path from "node:path"; import { SessionManager } from "@mariozechner/pi-coding-agent"; import type { EmbeddedRunAttemptParams } from "openclaw/plugin-sdk/agent-harness"; import { classifyEmbeddedPiRunResultForModelFallback } from "openclaw/plugin-sdk/agent-harness-runtime"; -import { afterEach, describe, expect, it } from "vitest"; import { createContractRunResult, OUTCOME_FALLBACK_RUNTIME_CONTRACT, -} from "../../../../test/helpers/agents/outcome-fallback-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { afterEach, describe, expect, it } from "vitest"; import { CodexAppServerEventProjector, type CodexAppServerToolTelemetry, diff --git a/extensions/codex/src/app-server/schema-normalization-runtime-contract.test.ts b/extensions/codex/src/app-server/schema-normalization-runtime-contract.test.ts index 678842439e5..e434374e9bb 100644 --- a/extensions/codex/src/app-server/schema-normalization-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/schema-normalization-runtime-contract.test.ts @@ -2,12 +2,12 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import type { EmbeddedRunAttemptParams } from "openclaw/plugin-sdk/agent-harness"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createParameterFreeTool, createPermissiveTool, normalizedParameterFreeSchema, -} from "../../../../test/helpers/agents/schema-normalization-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createCodexTestModel } from "./test-support.js"; import { startOrResumeThread } from "./thread-lifecycle.js"; diff --git a/extensions/codex/src/app-server/transcript-repair-runtime-contract.test.ts b/extensions/codex/src/app-server/transcript-repair-runtime-contract.test.ts index 4f8850f4c77..d09f65351c8 100644 --- a/extensions/codex/src/app-server/transcript-repair-runtime-contract.test.ts +++ b/extensions/codex/src/app-server/transcript-repair-runtime-contract.test.ts @@ -1,10 +1,10 @@ -import { describe, expect, it } from "vitest"; import { assistantHistoryMessage, currentPromptHistoryMessage, mediaOnlyHistoryMessage, structuredHistoryMessage, -} from "../../../../test/helpers/agents/transcript-repair-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { describe, expect, it } from "vitest"; import { projectContextEngineAssemblyForCodex } from "./context-engine-projection.js"; describe("Codex transcript projection runtime contract", () => { diff --git a/extensions/mattermost/src/channel.test.ts b/extensions/mattermost/src/channel.test.ts index 1034e0b55cb..6bde71aa6f8 100644 --- a/extensions/mattermost/src/channel.test.ts +++ b/extensions/mattermost/src/channel.test.ts @@ -2,11 +2,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../runtime-api.js"; import { createChannelReplyPipeline } from "../runtime-api.js"; -vi.mock("../../../test/helpers/config/bundled-channel-config-runtime.js", () => ({ - getBundledChannelRuntimeMap: () => new Map(), - getBundledChannelConfigSchemaMap: () => new Map(), -})); - const { sendMessageMattermostMock, mockFetchGuard } = vi.hoisted(() => ({ sendMessageMattermostMock: vi.fn(), mockFetchGuard: vi.fn(async (p: { url: string; init?: RequestInit }) => { diff --git a/extensions/mattermost/src/setup.test.ts b/extensions/mattermost/src/setup.test.ts index bc48c3d1669..c013af7e9c4 100644 --- a/extensions/mattermost/src/setup.test.ts +++ b/extensions/mattermost/src/setup.test.ts @@ -3,11 +3,6 @@ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig, OpenClawPluginApi } from "../runtime-api.js"; -vi.mock("../../../test/helpers/config/bundled-channel-config-runtime.js", () => ({ - getBundledChannelRuntimeMap: () => new Map(), - getBundledChannelConfigSchemaMap: () => new Map(), -})); - const resolveMattermostAccount = vi.hoisted(() => vi.fn()); const normalizeMattermostBaseUrl = vi.hoisted(() => vi.fn((value: string | undefined) => value)); const hasConfiguredSecretInput = vi.hoisted(() => vi.fn((value: unknown) => Boolean(value))); diff --git a/extensions/nextcloud-talk/src/channel.core.test.ts b/extensions/nextcloud-talk/src/channel.core.test.ts index db0b5905153..a607d8af60b 100644 --- a/extensions/nextcloud-talk/src/channel.core.test.ts +++ b/extensions/nextcloud-talk/src/channel.core.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { describe, expect, it } from "vitest"; import { nextcloudTalkConfigAdapter, nextcloudTalkPairingTextAdapter, @@ -7,11 +7,6 @@ import { import { NextcloudTalkConfigSchema } from "./config-schema.js"; import type { CoreConfig } from "./types.js"; -vi.mock("../../../test/helpers/config/bundled-channel-config-runtime.js", () => ({ - getBundledChannelRuntimeMap: () => new Map(), - getBundledChannelConfigSchemaMap: () => new Map(), -})); - describe("nextcloud talk channel core", () => { it("accepts SecretRef botSecret and apiPassword at top-level", () => { const result = NextcloudTalkConfigSchema.safeParse({ diff --git a/extensions/openshell/src/backend.e2e.test.ts b/extensions/openshell/src/backend.e2e.test.ts index 3711a4cc508..817fca095aa 100644 --- a/extensions/openshell/src/backend.e2e.test.ts +++ b/extensions/openshell/src/backend.e2e.test.ts @@ -4,12 +4,12 @@ import net from "node:net"; import os from "node:os"; import path from "node:path"; import { createSandboxTestContext } from "openclaw/plugin-sdk/test-fixtures"; -import { describe, expect, it } from "vitest"; import { createSandboxBrowserConfig, createSandboxPruneConfig, createSandboxSshConfig, -} from "../../../test/helpers/sandbox-fixtures.js"; +} from "openclaw/plugin-sdk/test-fixtures"; +import { describe, expect, it } from "vitest"; import { createOpenShellSandboxBackendFactory } from "./backend.js"; import { resolveOpenShellPluginConfig } from "./config.js"; diff --git a/package.json b/package.json index 805efd27dbd..cbc132d8a79 100644 --- a/package.json +++ b/package.json @@ -502,6 +502,10 @@ "types": "./dist/plugin-sdk/lazy-runtime.d.ts", "default": "./dist/plugin-sdk/lazy-runtime.js" }, + "./plugin-sdk/agent-runtime-test-contracts": { + "types": "./dist/plugin-sdk/agent-runtime-test-contracts.d.ts", + "default": "./dist/plugin-sdk/agent-runtime-test-contracts.js" + }, "./plugin-sdk/channel-target-testing": { "types": "./dist/plugin-sdk/channel-target-testing.d.ts", "default": "./dist/plugin-sdk/channel-target-testing.js" diff --git a/scripts/check-no-extension-test-core-imports.ts b/scripts/check-no-extension-test-core-imports.ts index b28e7be179c..3c42d2b471c 100644 --- a/scripts/check-no-extension-test-core-imports.ts +++ b/scripts/check-no-extension-test-core-imports.ts @@ -57,6 +57,10 @@ const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [ /["'](?:\.\.\/)+(?:test\/helpers\/(?:provider-replay-policy|stt-live-audio)\.(?:js|ts))["']/, hint: "Use openclaw/plugin-sdk/provider-test-contracts instead of repo-only provider test helper bridges.", }, + { + pattern: /["'](?:\.\.\/)+(?:test\/helpers\/)[^"']+["']/, + hint: "Use a documented openclaw/plugin-sdk test subpath instead of repo-only test helper bridges.", + }, { pattern: /["'](?:\.\.\/)+(?:src\/channels\/plugins\/contracts\/test-helpers\/)[^"']+["']/, hint: "Use openclaw/plugin-sdk/channel-test-helpers or another focused SDK test subpath instead of core-only channel contract helpers.", @@ -145,6 +149,14 @@ const RETIRED_EXTENSION_TEST_HELPER_BRIDGE_FILES = [ "test/helpers/provider-replay-policy.ts", "test/helpers/stt-live-audio.ts", "test/helpers/temp-home.ts", + "test/helpers/agents/auth-profile-runtime-contract.ts", + "test/helpers/agents/delivery-no-reply-runtime-contract.ts", + "test/helpers/agents/openclaw-owned-tool-runtime-contract.ts", + "test/helpers/agents/outcome-fallback-runtime-contract.ts", + "test/helpers/agents/prompt-overlay-runtime-contract.ts", + "test/helpers/agents/schema-normalization-runtime-contract.ts", + "test/helpers/agents/transcript-repair-runtime-contract.ts", + "test/helpers/sandbox-fixtures.ts", ]; function isExtensionTestFile(filePath: string): boolean { diff --git a/scripts/lib/plugin-sdk-doc-metadata.ts b/scripts/lib/plugin-sdk-doc-metadata.ts index 64e48c09afd..8d50a8a7383 100644 --- a/scripts/lib/plugin-sdk-doc-metadata.ts +++ b/scripts/lib/plugin-sdk-doc-metadata.ts @@ -113,6 +113,9 @@ export const pluginSdkDocMetadata = { "channel-test-helpers": { category: "utilities", }, + "agent-runtime-test-contracts": { + category: "utilities", + }, "channel-target-testing": { category: "utilities", }, diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index fbe330b50aa..5548c393212 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -109,6 +109,7 @@ "acp-binding-runtime", "acp-binding-resolve-runtime", "lazy-runtime", + "agent-runtime-test-contracts", "channel-target-testing", "channel-test-helpers", "plugin-test-api", diff --git a/src/agents/auth-profile-runtime-contract.test.ts b/src/agents/auth-profile-runtime-contract.test.ts index 7ebd959d943..e09e3d42b6f 100644 --- a/src/agents/auth-profile-runtime-contract.test.ts +++ b/src/agents/auth-profile-runtime-contract.test.ts @@ -1,12 +1,12 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { AUTH_PROFILE_RUNTIME_CONTRACT, createAuthAliasManifestRegistry, expectedForwardedAuthProfile, -} from "../../test/helpers/agents/auth-profile-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { SessionEntry } from "../config/sessions.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type * as ManifestRegistryModule from "../plugins/manifest-registry.js"; diff --git a/src/agents/openclaw-owned-tool-runtime-contract.test.ts b/src/agents/openclaw-owned-tool-runtime-contract.test.ts index dd24a0e1535..15b63344d83 100644 --- a/src/agents/openclaw-owned-tool-runtime-contract.test.ts +++ b/src/agents/openclaw-owned-tool-runtime-contract.test.ts @@ -1,11 +1,11 @@ import type { AgentTool } from "@mariozechner/pi-agent-core"; import type { ExtensionContext } from "@mariozechner/pi-coding-agent"; -import { afterEach, describe, expect, it, vi } from "vitest"; import { installOpenClawOwnedToolHooks, resetOpenClawOwnedToolHooks, textToolResult, -} from "../../test/helpers/agents/openclaw-owned-tool-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { afterEach, describe, expect, it, vi } from "vitest"; import type { MessagingToolSend } from "./pi-embedded-messaging.types.js"; import { handleToolExecutionEnd, diff --git a/src/agents/outcome-fallback-runtime-contract.test.ts b/src/agents/outcome-fallback-runtime-contract.test.ts index 69d10884e48..d5fe9ecdaa2 100644 --- a/src/agents/outcome-fallback-runtime-contract.test.ts +++ b/src/agents/outcome-fallback-runtime-contract.test.ts @@ -1,9 +1,9 @@ -import { describe, expect, it, vi } from "vitest"; import { createContractFallbackConfig, createContractRunResult, OUTCOME_FALLBACK_RUNTIME_CONTRACT, -} from "../../test/helpers/agents/outcome-fallback-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { runWithModelFallback } from "./model-fallback.js"; import { classifyEmbeddedPiRunResultForModelFallback } from "./pi-embedded-runner/result-fallback-classifier.js"; diff --git a/src/agents/pi-embedded-runner/run/transcript-repair-runtime-contract.test.ts b/src/agents/pi-embedded-runner/run/transcript-repair-runtime-contract.test.ts index c7181da2b85..dc1418320af 100644 --- a/src/agents/pi-embedded-runner/run/transcript-repair-runtime-contract.test.ts +++ b/src/agents/pi-embedded-runner/run/transcript-repair-runtime-contract.test.ts @@ -1,10 +1,10 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; import { inlineDataUriOrphanLeaf, QUEUED_USER_MESSAGE_MARKER, structuredOrphanLeaf, textOrphanLeaf, -} from "../../../../test/helpers/agents/transcript-repair-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { mergeOrphanedTrailingUserPrompt } from "./attempt.prompt-helpers.js"; import { DEFAULT_MESSAGE_MERGE_STRATEGY_ID, diff --git a/src/agents/prompt-overlay-runtime-contract.test.ts b/src/agents/prompt-overlay-runtime-contract.test.ts index 28cbd597aca..27de7d7d967 100644 --- a/src/agents/prompt-overlay-runtime-contract.test.ts +++ b/src/agents/prompt-overlay-runtime-contract.test.ts @@ -1,4 +1,3 @@ -import { describe, expect, it } from "vitest"; import { GPT5_CONTRACT_MODEL_ID, GPT5_PREFIXED_CONTRACT_MODEL_ID, @@ -9,7 +8,8 @@ import { OPENAI_CONTRACT_PROVIDER_ID, openAiPluginPersonalityConfig, sharedGpt5PersonalityConfig, -} from "../../test/helpers/agents/prompt-overlay-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { describe, expect, it } from "vitest"; import { resolveGpt5SystemPromptContribution } from "./gpt5-prompt-overlay.js"; describe("GPT-5 prompt overlay runtime contract", () => { diff --git a/src/agents/runtime-plan/build.test.ts b/src/agents/runtime-plan/build.test.ts index 5f702cef48d..5e8b424d428 100644 --- a/src/agents/runtime-plan/build.test.ts +++ b/src/agents/runtime-plan/build.test.ts @@ -1,5 +1,5 @@ +import { createParameterFreeTool } from "openclaw/plugin-sdk/agent-runtime-test-contracts"; import { describe, expect, it, vi } from "vitest"; -import { createParameterFreeTool } from "../../../test/helpers/agents/schema-normalization-runtime-contract.js"; import { buildAgentRuntimePlan } from "./build.js"; vi.mock("../../plugins/provider-hook-runtime.js", () => ({ diff --git a/src/agents/runtime-plan/tools.test.ts b/src/agents/runtime-plan/tools.test.ts index e98ea8d1cd0..e13956c23d7 100644 --- a/src/agents/runtime-plan/tools.test.ts +++ b/src/agents/runtime-plan/tools.test.ts @@ -1,10 +1,10 @@ import type { AgentTool } from "@mariozechner/pi-agent-core"; -import { beforeEach, describe, expect, it, vi } from "vitest"; import { createNativeOpenAIResponsesModel, createParameterFreeTool, normalizedParameterFreeSchema, -} from "../../../test/helpers/agents/schema-normalization-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import { logAgentRuntimeToolDiagnostics, normalizeAgentRuntimeTools } from "./tools.js"; import type { AgentRuntimePlan } from "./types.js"; diff --git a/src/agents/sandbox/ssh-backend.test.ts b/src/agents/sandbox/ssh-backend.test.ts index dbbb02e98cb..75eb0e5bb05 100644 --- a/src/agents/sandbox/ssh-backend.test.ts +++ b/src/agents/sandbox/ssh-backend.test.ts @@ -1,11 +1,11 @@ import os from "node:os"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createSandboxBrowserConfig, createSandboxPruneConfig, createSandboxSshConfig, -} from "../../../test/helpers/sandbox-fixtures.js"; +} from "openclaw/plugin-sdk/test-fixtures"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; import type { SandboxConfig } from "./types.js"; diff --git a/src/agents/schema-normalization-runtime-contract.test.ts b/src/agents/schema-normalization-runtime-contract.test.ts index 343c952b287..bb16bf715ea 100644 --- a/src/agents/schema-normalization-runtime-contract.test.ts +++ b/src/agents/schema-normalization-runtime-contract.test.ts @@ -1,12 +1,12 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; -import { describe, expect, it } from "vitest"; import { createNativeOpenAIResponsesModel, createParameterFreeTool, createPermissiveTool, createStrictCompatibleTool, normalizedParameterFreeSchema, -} from "../../test/helpers/agents/schema-normalization-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { describe, expect, it } from "vitest"; import { buildProviderToolCompatFamilyHooks } from "../plugin-sdk/provider-tools.js"; import { buildOpenAIResponsesParams } from "./openai-transport-stream.js"; import { convertTools as convertWebSocketTools } from "./openai-ws-message-conversion.js"; diff --git a/src/auto-reply/reply/followup-runner.test.ts b/src/auto-reply/reply/followup-runner.test.ts index f726dfffd12..51b902d57ad 100644 --- a/src/auto-reply/reply/followup-runner.test.ts +++ b/src/auto-reply/reply/followup-runner.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; +import { DELIVERY_NO_REPLY_RUNTIME_CONTRACT } from "openclaw/plugin-sdk/agent-runtime-test-contracts"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { DELIVERY_NO_REPLY_RUNTIME_CONTRACT } from "../../../test/helpers/agents/delivery-no-reply-runtime-contract.js"; import type { OpenClawConfig } from "../../config/config.js"; import type { SessionEntry } from "../../config/sessions/types.js"; import type { FollowupRun, QueueSettings } from "./queue.js"; diff --git a/src/plugin-sdk/agent-runtime-test-contracts.ts b/src/plugin-sdk/agent-runtime-test-contracts.ts new file mode 100644 index 00000000000..6a33a3e4cb0 --- /dev/null +++ b/src/plugin-sdk/agent-runtime-test-contracts.ts @@ -0,0 +1,51 @@ +// Focused public test contracts for native agent-runtime adapters. + +export { + AUTH_PROFILE_RUNTIME_CONTRACT, + createAuthAliasManifestRegistry, + expectedForwardedAuthProfile, +} from "./test-helpers/agents/auth-profile-runtime-contract.js"; +export { DELIVERY_NO_REPLY_RUNTIME_CONTRACT } from "./test-helpers/agents/delivery-no-reply-runtime-contract.js"; +export { + installCodexToolResultMiddleware, + installOpenClawOwnedToolHooks, + mediaToolResult, + resetOpenClawOwnedToolHooks, + textToolResult, +} from "./test-helpers/agents/openclaw-owned-tool-runtime-contract.js"; +export { + createContractFallbackConfig, + createContractRunResult, + OUTCOME_FALLBACK_RUNTIME_CONTRACT, +} from "./test-helpers/agents/outcome-fallback-runtime-contract.js"; +export { + CODEX_CONTRACT_PROVIDER_ID, + codexPromptOverlayContext, + GPT5_CONTRACT_MODEL_ID, + GPT5_PREFIXED_CONTRACT_MODEL_ID, + NON_GPT5_CONTRACT_MODEL_ID, + NON_OPENAI_CONTRACT_PROVIDER_ID, + OPENAI_CODEX_CONTRACT_PROVIDER_ID, + OPENAI_CONTRACT_PROVIDER_ID, + openAiPluginPersonalityConfig, + sharedGpt5PersonalityConfig, +} from "./test-helpers/agents/prompt-overlay-runtime-contract.js"; +export { + createNativeOpenAICodexResponsesModel, + createNativeOpenAIResponsesModel, + createParameterFreeTool, + createPermissiveTool, + createProxyOpenAIResponsesModel, + createStrictCompatibleTool, + normalizedParameterFreeSchema, +} from "./test-helpers/agents/schema-normalization-runtime-contract.js"; +export { + assistantHistoryMessage, + currentPromptHistoryMessage, + inlineDataUriOrphanLeaf, + mediaOnlyHistoryMessage, + QUEUED_USER_MESSAGE_MARKER, + structuredHistoryMessage, + structuredOrphanLeaf, + textOrphanLeaf, +} from "./test-helpers/agents/transcript-repair-runtime-contract.js"; diff --git a/src/plugin-sdk/schema-normalization-runtime-contract.test.ts b/src/plugin-sdk/schema-normalization-runtime-contract.test.ts index 53dce1fbffe..76ddaa4b4f0 100644 --- a/src/plugin-sdk/schema-normalization-runtime-contract.test.ts +++ b/src/plugin-sdk/schema-normalization-runtime-contract.test.ts @@ -1,4 +1,3 @@ -import { describe, expect, it } from "vitest"; import { createNativeOpenAICodexResponsesModel, createNativeOpenAIResponsesModel, @@ -6,7 +5,8 @@ import { createPermissiveTool, createProxyOpenAIResponsesModel, normalizedParameterFreeSchema, -} from "../../test/helpers/agents/schema-normalization-runtime-contract.js"; +} from "openclaw/plugin-sdk/agent-runtime-test-contracts"; +import { describe, expect, it } from "vitest"; import { buildProviderToolCompatFamilyHooks } from "./provider-tools.js"; describe("OpenAI-family schema normalization runtime contract", () => { diff --git a/src/plugin-sdk/test-fixtures.ts b/src/plugin-sdk/test-fixtures.ts index e8d246a5334..c1c9ace4794 100644 --- a/src/plugin-sdk/test-fixtures.ts +++ b/src/plugin-sdk/test-fixtures.ts @@ -9,6 +9,11 @@ export { } from "../cli/test-runtime-capture.js"; export type { CliMockOutputRuntime, CliRuntimeCapture } from "../cli/test-runtime-capture.js"; export { createSandboxTestContext } from "../agents/sandbox/test-fixtures.js"; +export { + createSandboxBrowserConfig, + createSandboxPruneConfig, + createSandboxSshConfig, +} from "./test-helpers/sandbox-fixtures.js"; export { writeSkill } from "../agents/skills.e2e-test-helpers.js"; export { castAgentMessage, diff --git a/src/plugin-sdk/test-helpers/agents/auth-profile-runtime-contract.ts b/src/plugin-sdk/test-helpers/agents/auth-profile-runtime-contract.ts new file mode 100644 index 00000000000..38cf1637562 --- /dev/null +++ b/src/plugin-sdk/test-helpers/agents/auth-profile-runtime-contract.ts @@ -0,0 +1,60 @@ +import { + resolveProviderIdForAuth, + type ProviderAuthAliasLookupParams, +} from "../../../agents/provider-auth-aliases.js"; +import type { PluginManifestRegistry } from "../../../plugins/manifest-registry.js"; + +export const AUTH_PROFILE_RUNTIME_CONTRACT = { + sessionId: "session-auth-contract", + sessionKey: "agent:main:auth-contract", + runId: "run-auth-contract", + workspacePrompt: "continue with the bound Codex profile", + openAiProvider: "openai", + openAiCodexProvider: "openai-codex", + codexCliProvider: "codex-cli", + codexHarnessProvider: "codex", + claudeCliProvider: "claude-cli", + openAiProfileId: "openai:work", + openAiCodexProfileId: "openai-codex:work", + anthropicProfileId: "anthropic:work", +} as const; + +export function createAuthAliasManifestRegistry(): PluginManifestRegistry { + return { + plugins: [ + { + id: "openai", + origin: "bundled", + channels: [], + providers: [], + cliBackends: [], + skills: [], + hooks: [], + rootDir: "/tmp/openclaw-auth-contract-plugin", + source: "test", + manifestPath: "/tmp/openclaw-auth-contract-plugin/plugin.json", + providerAuthChoices: [ + { + provider: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider, + method: "oauth", + choiceId: AUTH_PROFILE_RUNTIME_CONTRACT.openAiCodexProvider, + deprecatedChoiceIds: [AUTH_PROFILE_RUNTIME_CONTRACT.codexCliProvider], + }, + ], + }, + ], + diagnostics: [], + }; +} + +export function expectedForwardedAuthProfile(params: { + provider: string; + authProfileProvider: string; + aliasLookupParams: ProviderAuthAliasLookupParams; + sessionAuthProfileId: string | undefined; +}): string | undefined { + return resolveProviderIdForAuth(params.provider, params.aliasLookupParams) === + resolveProviderIdForAuth(params.authProfileProvider, params.aliasLookupParams) + ? params.sessionAuthProfileId + : undefined; +} diff --git a/src/plugin-sdk/test-helpers/agents/openclaw-owned-tool-runtime-contract.ts b/src/plugin-sdk/test-helpers/agents/openclaw-owned-tool-runtime-contract.ts new file mode 100644 index 00000000000..3a172f89111 --- /dev/null +++ b/src/plugin-sdk/test-helpers/agents/openclaw-owned-tool-runtime-contract.ts @@ -0,0 +1,94 @@ +import type { AgentToolResult } from "@mariozechner/pi-agent-core"; +import { vi } from "vitest"; +import { __testing as beforeToolCallTesting } from "../../../agents/pi-tools.before-tool-call.js"; +import type { + CodexAppServerExtensionFactory, + CodexAppServerToolResultEvent, +} from "../../../plugins/codex-app-server-extension-types.js"; +import { + initializeGlobalHookRunner, + resetGlobalHookRunner, +} from "../../../plugins/hook-runner-global.js"; +import { createMockPluginRegistry } from "../../../plugins/hooks.test-helpers.js"; +import { createEmptyPluginRegistry } from "../../../plugins/registry-empty.js"; +import { + resetPluginRuntimeStateForTest, + setActivePluginRegistry, +} from "../../../plugins/runtime.js"; + +export function textToolResult( + text: string, + details: Record = {}, +): AgentToolResult { + return { + content: [{ type: "text", text }], + details, + }; +} + +export function mediaToolResult( + text: string, + mediaUrl: string, + audioAsVoice = false, +): AgentToolResult { + return textToolResult(text, { + media: { + mediaUrl, + ...(audioAsVoice ? { audioAsVoice } : {}), + }, + }); +} + +export function installOpenClawOwnedToolHooks(params?: { + adjustedParams?: Record; + blockReason?: string; +}) { + const beforeToolCall = vi.fn(async () => { + if (params?.blockReason) { + return { + block: true, + blockReason: params.blockReason, + }; + } + return params?.adjustedParams ? { params: params.adjustedParams } : {}; + }); + const afterToolCall = vi.fn(async () => {}); + initializeGlobalHookRunner( + createMockPluginRegistry([ + { hookName: "before_tool_call", handler: beforeToolCall }, + { hookName: "after_tool_call", handler: afterToolCall }, + ]), + ); + return { beforeToolCall, afterToolCall }; +} + +/** + * Installs only the Codex app-server `tool_result` middleware fixture. + * Pair with `installOpenClawOwnedToolHooks()` when a test asserts before/after hook behavior. + */ +export function installCodexToolResultMiddleware( + handler: (event: CodexAppServerToolResultEvent) => AgentToolResult, +) { + const middleware = vi.fn(async (event: CodexAppServerToolResultEvent) => ({ + result: handler(event), + })); + const registry = createEmptyPluginRegistry(); + const factory: CodexAppServerExtensionFactory = async (codex) => { + codex.on("tool_result", middleware); + }; + registry.codexAppServerExtensionFactories.push({ + pluginId: "runtime-contract", + pluginName: "Runtime Contract", + rawFactory: factory, + factory, + source: "test", + }); + setActivePluginRegistry(registry); + return { middleware }; +} + +export function resetOpenClawOwnedToolHooks(): void { + resetGlobalHookRunner(); + resetPluginRuntimeStateForTest(); + beforeToolCallTesting.adjustedParamsByToolCallId.clear(); +} diff --git a/src/plugin-sdk/test-helpers/agents/outcome-fallback-runtime-contract.ts b/src/plugin-sdk/test-helpers/agents/outcome-fallback-runtime-contract.ts new file mode 100644 index 00000000000..b9be03c09cc --- /dev/null +++ b/src/plugin-sdk/test-helpers/agents/outcome-fallback-runtime-contract.ts @@ -0,0 +1,48 @@ +import type { EmbeddedPiRunResult } from "../../../agents/pi-embedded-runner/types.js"; + +export const OUTCOME_FALLBACK_RUNTIME_CONTRACT = { + primaryProvider: "openai-codex", + primaryModel: "gpt-5.4", + fallbackProvider: "anthropic", + fallbackModel: "claude-haiku-3-5", + sessionId: "session-outcome-contract", + sessionKey: "agent:main:outcome-contract", + runId: "run-outcome-contract", + prompt: "finish the contract turn", + reasoningOnlyText: "I need to reason about this before answering.", + planningOnlyText: "Inspect state, then decide the next step.", +} as const; + +export function createContractRunResult( + overrides: Partial = {}, +): EmbeddedPiRunResult { + const { meta, ...rest } = overrides; + return { + payloads: [], + didSendViaMessagingTool: false, + messagingToolSentTexts: [], + messagingToolSentMediaUrls: [], + messagingToolSentTargets: [], + successfulCronAdds: 0, + ...rest, + meta: { + durationMs: 1, + ...meta, + }, + }; +} + +export function createContractFallbackConfig() { + return { + agents: { + defaults: { + model: { + primary: `${OUTCOME_FALLBACK_RUNTIME_CONTRACT.primaryProvider}/${OUTCOME_FALLBACK_RUNTIME_CONTRACT.primaryModel}`, + fallbacks: [ + `${OUTCOME_FALLBACK_RUNTIME_CONTRACT.fallbackProvider}/${OUTCOME_FALLBACK_RUNTIME_CONTRACT.fallbackModel}`, + ], + }, + }, + }, + } as const; +} diff --git a/src/plugin-sdk/test-helpers/agents/prompt-overlay-runtime-contract.ts b/src/plugin-sdk/test-helpers/agents/prompt-overlay-runtime-contract.ts new file mode 100644 index 00000000000..4bf4564ba71 --- /dev/null +++ b/src/plugin-sdk/test-helpers/agents/prompt-overlay-runtime-contract.ts @@ -0,0 +1,48 @@ +import type { OpenClawConfig } from "../../../config/types.openclaw.js"; +import type { ProviderSystemPromptContributionContext } from "../../../plugins/types.js"; + +export const GPT5_CONTRACT_MODEL_ID = "gpt-5.4"; +export const GPT5_PREFIXED_CONTRACT_MODEL_ID = "openai/gpt-5.4"; +export const NON_GPT5_CONTRACT_MODEL_ID = "gpt-4.1"; +export const OPENAI_CONTRACT_PROVIDER_ID = "openai"; +export const OPENAI_CODEX_CONTRACT_PROVIDER_ID = "openai-codex"; +export const CODEX_CONTRACT_PROVIDER_ID = "codex"; +export const NON_OPENAI_CONTRACT_PROVIDER_ID = "openrouter"; + +export function openAiPluginPersonalityConfig(personality: "friendly" | "off"): OpenClawConfig { + return { + plugins: { + entries: { + openai: { + config: { personality }, + }, + }, + }, + } satisfies OpenClawConfig; +} + +export function sharedGpt5PersonalityConfig(personality: "friendly" | "off"): OpenClawConfig { + return { + agents: { + defaults: { + promptOverlays: { + gpt5: { personality }, + }, + }, + }, + } satisfies OpenClawConfig; +} + +export function codexPromptOverlayContext(params?: { + modelId?: string; + config?: OpenClawConfig; +}): ProviderSystemPromptContributionContext { + return { + provider: CODEX_CONTRACT_PROVIDER_ID, + modelId: params?.modelId ?? GPT5_CONTRACT_MODEL_ID, + promptMode: "full", + agentDir: "/tmp/openclaw-codex-prompt-contract-agent", + workspaceDir: "/tmp/openclaw-codex-prompt-contract-workspace", + ...(params?.config ? { config: params.config } : {}), + }; +} diff --git a/src/plugin-sdk/test-helpers/sandbox-fixtures.ts b/src/plugin-sdk/test-helpers/sandbox-fixtures.ts new file mode 100644 index 00000000000..df15199c45f --- /dev/null +++ b/src/plugin-sdk/test-helpers/sandbox-fixtures.ts @@ -0,0 +1,48 @@ +import type { + SandboxBrowserConfig, + SandboxPruneConfig, + SandboxSshConfig, +} from "../../agents/sandbox/types.js"; + +export function createSandboxBrowserConfig( + overrides: Partial = {}, +): SandboxBrowserConfig { + return { + enabled: false, + image: "openclaw-browser", + containerPrefix: "openclaw-browser-", + network: "bridge", + cdpPort: 9222, + vncPort: 5900, + noVncPort: 6080, + headless: true, + enableNoVnc: false, + allowHostControl: false, + autoStart: false, + autoStartTimeoutMs: 1000, + ...overrides, + }; +} + +export function createSandboxPruneConfig( + overrides: Partial = {}, +): SandboxPruneConfig { + return { + idleHours: 24, + maxAgeDays: 7, + ...overrides, + }; +} + +export function createSandboxSshConfig( + workspaceRoot: string, + overrides: Partial = {}, +): SandboxSshConfig { + return { + command: "ssh", + workspaceRoot, + strictHostKeyChecking: true, + updateHostKeys: true, + ...overrides, + }; +} diff --git a/src/plugins/contracts/plugin-sdk-subpaths.test.ts b/src/plugins/contracts/plugin-sdk-subpaths.test.ts index e1aecbd6864..164658047bf 100644 --- a/src/plugins/contracts/plugin-sdk-subpaths.test.ts +++ b/src/plugins/contracts/plugin-sdk-subpaths.test.ts @@ -53,6 +53,7 @@ const sourceCache = new Map(); const repoTsFilesCache = new Map(); const representativeRuntimeSmokeSubpaths = ["channel-runtime", "conversation-runtime"] as const; const PUBLIC_SDK_TEST_HELPER_SUBPATHS = [ + "agent-runtime-test-contracts", "channel-contract-testing", "channel-target-testing", "channel-test-helpers", @@ -745,6 +746,12 @@ describe("plugin-sdk subpath exports", () => { "createPluginSetupWizardStatus", "runProviderCatalog", ]); + expectSourceMentions("agent-runtime-test-contracts", [ + "AUTH_PROFILE_RUNTIME_CONTRACT", + "DELIVERY_NO_REPLY_RUNTIME_CONTRACT", + "createParameterFreeTool", + "QUEUED_USER_MESSAGE_MARKER", + ]); expectSourceMentions("channel-test-helpers", [ "assertBundledChannelEntries", "formatEnvelopeTimestamp", diff --git a/test/extension-test-boundary.test.ts b/test/extension-test-boundary.test.ts index 71dc5679cdb..e5e1a21918c 100644 --- a/test/extension-test-boundary.test.ts +++ b/test/extension-test-boundary.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import path from "node:path"; import { GUARDED_EXTENSION_PUBLIC_SURFACE_BASENAMES } from "openclaw/plugin-sdk/plugin-test-contracts"; +import { BUNDLED_PLUGIN_PATH_PREFIX } from "openclaw/plugin-sdk/test-fixtures"; import { describe, expect, it } from "vitest"; -import { BUNDLED_PLUGIN_PATH_PREFIX } from "./helpers/bundled-plugin-paths.js"; const repoRoot = path.resolve(import.meta.dirname, ".."); const ALLOWED_EXTENSION_PUBLIC_SURFACE_BASENAMES = new Set( @@ -217,6 +217,7 @@ describe("non-extension test boundaries", () => { /["'](?:\.\.\/)+(?:test\/helpers\/channels\/)[^"']+["']/u, /["'](?:\.\.\/)+(?:src\/channels\/plugins\/contracts\/test-helpers\/)[^"']+["']/u, /["'](?:\.\.\/)+(?:test\/helpers\/plugins\/)[^"']+["']/u, + /["'](?:\.\.\/)+(?:test\/helpers\/)[^"']+["']/u, ]; const files = walkCode(path.join(repoRoot, "extensions"));