From 662de55e0785ecb06e5c73b53e0055f5e5ac49e2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 21:58:04 +0100 Subject: [PATCH] refactor: expose extension sdk boundary seams --- .../.generated/plugin-sdk-api-baseline.sha256 | 4 +- docs/plugins/sdk-subpaths.md | 2 +- .../app-server/context-engine-projection.ts | 2 +- .../codex/src/app-server/event-projector.ts | 2 +- .../codex/src/app-server/run-attempt.ts | 2 +- .../codex/src/app-server/transcript-mirror.ts | 2 +- .../check-no-extension-test-core-imports.ts | 63 +++++++++++++++---- src/plugin-sdk/agent-harness-runtime.ts | 9 ++- src/plugin-sdk/agent-runtime.ts | 1 + src/plugin-sdk/config-runtime.ts | 1 + src/plugin-sdk/config-schema.ts | 2 + src/plugin-sdk/diagnostic-runtime.ts | 3 + src/plugin-sdk/hook-runtime.ts | 4 ++ .../memory-core-host-engine-embeddings.ts | 4 ++ .../memory-core-host-runtime-core.ts | 7 ++- src/plugin-sdk/provider-env-vars.ts | 1 + src/plugin-sdk/provider-transport-runtime.ts | 1 + src/plugin-sdk/reply-chunking.ts | 6 +- src/plugin-sdk/runtime-secret-resolution.ts | 2 + src/plugin-sdk/testing.ts | 61 +++++++++++++++++- 20 files changed, 154 insertions(+), 25 deletions(-) diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index ca13e5e6ba0..26fd1bcf83c 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -eab8d67d3601d697253d2cc265ea3b18c719a0fa8ca6e983bf1edcd38b9382e7 plugin-sdk-api-baseline.json -8c7e737467cb84d3a1c80c677cb94b26fe4d5739d154245c48d744aef80f53f8 plugin-sdk-api-baseline.jsonl +5b59db88172a184e0cbac3301c51304686b6184c952ae5ed9f09b001dd65e3f3 plugin-sdk-api-baseline.json +a195e76ddd1e09cb4023b247a684cd4e93c2662971adb4378ec126a78c537582 plugin-sdk-api-baseline.jsonl diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index 19ab9426fae..bbd13571cbb 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -257,7 +257,7 @@ 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` | Public extension test helpers including `installCommonResolveTargetErrorCases`, `shouldAckReaction`, `writeSkill`, `createTestRegistry`, and live generation env loading | + | `plugin-sdk/testing` | Public extension test helpers including plugin registry/runtime mocks, schema/media/live-test helpers, `installCommonResolveTargetErrorCases`, `writeSkill`, `createTestRegistry`, and live generation env loading | diff --git a/extensions/codex/src/app-server/context-engine-projection.ts b/extensions/codex/src/app-server/context-engine-projection.ts index 7d83f363255..8846ad16b93 100644 --- a/extensions/codex/src/app-server/context-engine-projection.ts +++ b/extensions/codex/src/app-server/context-engine-projection.ts @@ -1,4 +1,4 @@ -import type { AgentMessage } from "@mariozechner/pi-agent-core"; +import type { AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime"; export type CodexContextProjection = { developerInstructionAddition?: string; diff --git a/extensions/codex/src/app-server/event-projector.ts b/extensions/codex/src/app-server/event-projector.ts index dc40dc6260b..149274ad99a 100644 --- a/extensions/codex/src/app-server/event-projector.ts +++ b/extensions/codex/src/app-server/event-projector.ts @@ -1,4 +1,3 @@ -import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { AssistantMessage, Usage } from "@mariozechner/pi-ai"; import { SessionManager } from "@mariozechner/pi-coding-agent"; import { @@ -13,6 +12,7 @@ import { runAgentHarnessAfterCompactionHook, runAgentHarnessBeforeCompactionHook, TOOL_PROGRESS_OUTPUT_MAX_CHARS, + type AgentMessage, type EmbeddedRunAttemptParams, type EmbeddedRunAttemptResult, type MessagingToolSend, diff --git a/extensions/codex/src/app-server/run-attempt.ts b/extensions/codex/src/app-server/run-attempt.ts index b6cd07225f4..6cb84089837 100644 --- a/extensions/codex/src/app-server/run-attempt.ts +++ b/extensions/codex/src/app-server/run-attempt.ts @@ -1,5 +1,4 @@ import fs from "node:fs/promises"; -import type { AgentMessage } from "@mariozechner/pi-agent-core"; import { SessionManager } from "@mariozechner/pi-coding-agent"; import { assembleHarnessContextEngine, @@ -29,6 +28,7 @@ import { registerNativeHookRelay, setActiveEmbeddedRun, supportsModelTools, + type AgentMessage, type EmbeddedRunAttemptParams, type EmbeddedRunAttemptResult, type NativeHookRelayEvent, diff --git a/extensions/codex/src/app-server/transcript-mirror.ts b/extensions/codex/src/app-server/transcript-mirror.ts index 9f20e310750..5a39912647b 100644 --- a/extensions/codex/src/app-server/transcript-mirror.ts +++ b/extensions/codex/src/app-server/transcript-mirror.ts @@ -1,11 +1,11 @@ import fs from "node:fs/promises"; import path from "node:path"; -import type { AgentMessage } from "@mariozechner/pi-agent-core"; import { SessionManager } from "@mariozechner/pi-coding-agent"; import { acquireSessionWriteLock, emitSessionTranscriptUpdate, runAgentHarnessBeforeMessageWriteHook, + type AgentMessage, } from "openclaw/plugin-sdk/agent-harness-runtime"; export async function mirrorCodexAppServerTranscript(params: { diff --git a/scripts/check-no-extension-test-core-imports.ts b/scripts/check-no-extension-test-core-imports.ts index 46a99b413ae..898f16bb980 100644 --- a/scripts/check-no-extension-test-core-imports.ts +++ b/scripts/check-no-extension-test-core-imports.ts @@ -2,6 +2,8 @@ import fs from "node:fs"; import path from "node:path"; import { collectFilesSync, relativeToCwd } from "./check-file-utils.js"; +type Offender = { file: string; hint: string; line?: number; specifier?: string }; + const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [ { pattern: /["']openclaw\/plugin-sdk["']/, @@ -33,13 +35,10 @@ const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [ }, ]; -const FORBIDDEN_TEST_SUPPORT_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [ - { - pattern: - /\b(?:import|export)\b[\s\S]*?\bfrom\s*["'](?:\.\.\/){2,}src\/(?:agents|channels|config|infra|plugins|routing|security|test-helpers|test-utils)\/[^"']+["']/, - hint: "Use openclaw/plugin-sdk/testing or a focused plugin-sdk test/runtime subpath instead of core internals.", - }, -]; +const STATIC_RELATIVE_MODULE_PATTERN = /\b(?:import|export)\b[\s\S]*?\bfrom\s*["']([^"']+)["']/g; + +const RELATIVE_CORE_HINT = + "Use openclaw/plugin-sdk/testing or a focused plugin-sdk test/runtime subpath instead of core internals."; function isExtensionTestFile(filePath: string): boolean { return /\.test\.[cm]?[jt]sx?$/u.test(filePath) || /\.e2e\.test\.[cm]?[jt]sx?$/u.test(filePath); @@ -56,23 +55,57 @@ function collectExtensionTestFiles(rootDir: string): string[] { }); } +function lineNumberForOffset(content: string, offset: number): number { + let line = 1; + for (let index = 0; index < offset; index += 1) { + if (content.charCodeAt(index) === 10) { + line += 1; + } + } + return line; +} + +function resolvesToRepoSrc(filePath: string, specifier: string): boolean { + if (!specifier.startsWith(".")) { + return false; + } + const resolved = path.resolve(path.dirname(filePath), specifier); + const repoRelative = path.relative(process.cwd(), resolved).replaceAll(path.sep, "/"); + return repoRelative === "src" || repoRelative.startsWith("src/"); +} + +function collectRelativeCoreImportOffenders(filePath: string, content: string): Offender[] { + const offenders: Offender[] = []; + for (const match of content.matchAll(STATIC_RELATIVE_MODULE_PATTERN)) { + const specifier = match[1]; + if (!specifier || !resolvesToRepoSrc(filePath, specifier)) { + continue; + } + offenders.push({ + file: filePath, + hint: RELATIVE_CORE_HINT, + line: lineNumberForOffset(content, match.index ?? 0), + specifier, + }); + } + return offenders; +} + function main() { const extensionsDir = path.join(process.cwd(), "extensions"); const files = collectExtensionTestFiles(extensionsDir); - const offenders: Array<{ file: string; hint: string }> = []; + const offenders: Offender[] = []; for (const file of files) { const content = fs.readFileSync(file, "utf8"); - const rules = isExtensionTestSupportFile(file) - ? [...FORBIDDEN_PATTERNS, ...FORBIDDEN_TEST_SUPPORT_PATTERNS] - : FORBIDDEN_PATTERNS; - for (const rule of rules) { + for (const rule of FORBIDDEN_PATTERNS) { if (!rule.pattern.test(content)) { continue; } offenders.push({ file, hint: rule.hint }); break; } + offenders.push(...collectRelativeCoreImportOffenders(file, content)); } if (offenders.length > 0) { @@ -80,7 +113,11 @@ function main() { "Extension test files must stay on extension test bridges or public plugin-sdk surfaces.", ); for (const offender of offenders.toSorted((a, b) => a.file.localeCompare(b.file))) { - console.error(`- ${relativeToCwd(offender.file)}: ${offender.hint}`); + const location = offender.line + ? `${relativeToCwd(offender.file)}:${offender.line}` + : relativeToCwd(offender.file); + const specifier = offender.specifier ? ` (${offender.specifier})` : ""; + console.error(`- ${location}${specifier}: ${offender.hint}`); } process.exit(1); } diff --git a/src/plugin-sdk/agent-harness-runtime.ts b/src/plugin-sdk/agent-harness-runtime.ts index c6e67147517..1616f5acc3f 100644 --- a/src/plugin-sdk/agent-harness-runtime.ts +++ b/src/plugin-sdk/agent-harness-runtime.ts @@ -9,6 +9,7 @@ import { truncateUtf16Safe } from "../utils.js"; export const TOOL_PROGRESS_OUTPUT_MAX_CHARS = 8_000; +export type { AgentMessage } from "@mariozechner/pi-agent-core"; export type { AgentHarness, AgentHarnessAttemptParams, @@ -24,11 +25,12 @@ export type { EmbeddedRunAttemptParams, EmbeddedRunAttemptResult, } from "../agents/pi-embedded-runner/run/types.js"; +export type { ContextEngine as HarnessContextEngine } from "../context-engine/types.js"; export type { CompactEmbeddedPiSessionParams } from "../agents/pi-embedded-runner/compact.js"; export type { EmbeddedPiCompactResult } from "../agents/pi-embedded-runner/types.js"; export type { AnyAgentTool } from "../agents/tools/common.js"; export type { MessagingToolSend } from "../agents/pi-embedded-messaging.types.js"; -export type { AgentApprovalEventData } from "../infra/agent-events.js"; +export type { AgentApprovalEventData, AgentEventPayload } from "../infra/agent-events.js"; export type { ExecApprovalDecision } from "../infra/exec-approvals.js"; export type { NormalizedUsage } from "../agents/usage.js"; export type { @@ -57,8 +59,10 @@ export type { export { VERSION as OPENCLAW_VERSION } from "../version.js"; export { formatErrorMessage } from "../infra/errors.js"; export { formatApprovalDisplayPath } from "../infra/approval-display-paths.js"; -export { emitAgentEvent } from "../infra/agent-events.js"; +export { emitAgentEvent, onAgentEvent, resetAgentEventsForTest } from "../infra/agent-events.js"; export { log as embeddedAgentLog } from "../agents/pi-embedded-runner/logger.js"; +export { buildAgentRuntimePlan } from "../agents/runtime-plan/build.js"; +export { classifyEmbeddedPiRunResultForModelFallback } from "../agents/pi-embedded-runner/result-fallback-classifier.js"; export { resolveEmbeddedAgentRuntime } from "../agents/pi-embedded-runner/runtime.js"; export { resolveUserPath } from "../utils.js"; export { callGatewayTool } from "../agents/tools/gateway.js"; @@ -123,6 +127,7 @@ export { } from "../agents/harness/lifecycle-hook-helpers.js"; export { buildNativeHookRelayCommand, + __testing as nativeHookRelayTesting, registerNativeHookRelay, } from "../agents/harness/native-hook-relay.js"; diff --git a/src/plugin-sdk/agent-runtime.ts b/src/plugin-sdk/agent-runtime.ts index 6781f9718be..68c3196b867 100644 --- a/src/plugin-sdk/agent-runtime.ts +++ b/src/plugin-sdk/agent-runtime.ts @@ -1,6 +1,7 @@ // Public agent/model/runtime helpers for plugins that integrate with core agent flows. export * from "../agents/agent-scope.js"; +export { resolveOpenClawAgentDir } from "../agents/agent-paths.js"; export * from "../agents/current-time.js"; export * from "../agents/date-time.js"; export * from "../agents/defaults.js"; diff --git a/src/plugin-sdk/config-runtime.ts b/src/plugin-sdk/config-runtime.ts index d5dd3d612ea..8fe5fc4138f 100644 --- a/src/plugin-sdk/config-runtime.ts +++ b/src/plugin-sdk/config-runtime.ts @@ -8,6 +8,7 @@ export { resolvePluginConfigObject, } from "./plugin-config-runtime.js"; export { + clearConfigCache, clearRuntimeConfigSnapshot, getRuntimeConfigSourceSnapshot, getRuntimeConfigSnapshot, diff --git a/src/plugin-sdk/config-schema.ts b/src/plugin-sdk/config-schema.ts index 4435fc243df..052e975a97b 100644 --- a/src/plugin-sdk/config-schema.ts +++ b/src/plugin-sdk/config-schema.ts @@ -1,2 +1,4 @@ /** Root OpenClaw configuration Zod schema — the full `openclaw.json` shape. */ export { OpenClawSchema } from "../config/zod-schema.js"; +export { validateJsonSchemaValue } from "../plugins/schema-validator.js"; +export type { JsonSchemaObject } from "../shared/json-schema.types.js"; diff --git a/src/plugin-sdk/diagnostic-runtime.ts b/src/plugin-sdk/diagnostic-runtime.ts index 0a5587bbfc7..e208828c218 100644 --- a/src/plugin-sdk/diagnostic-runtime.ts +++ b/src/plugin-sdk/diagnostic-runtime.ts @@ -7,8 +7,11 @@ export type { } from "../infra/diagnostic-events.js"; export { emitDiagnosticEvent, + emitTrustedDiagnosticEvent, isDiagnosticsEnabled, + onInternalDiagnosticEvent, onDiagnosticEvent, + resetDiagnosticEventsForTest, } from "../infra/diagnostic-events.js"; export type { DiagnosticTraceContext } from "../infra/diagnostic-trace-context.js"; export { diff --git a/src/plugin-sdk/hook-runtime.ts b/src/plugin-sdk/hook-runtime.ts index dd67f98cf04..0ad2e7ebdb3 100644 --- a/src/plugin-sdk/hook-runtime.ts +++ b/src/plugin-sdk/hook-runtime.ts @@ -3,3 +3,7 @@ export * from "../hooks/fire-and-forget.js"; export * from "../hooks/internal-hooks.js"; export * from "../hooks/message-hook-mappers.js"; +export { + initializeGlobalHookRunner, + resetGlobalHookRunner, +} from "../plugins/hook-runner-global.js"; diff --git a/src/plugin-sdk/memory-core-host-engine-embeddings.ts b/src/plugin-sdk/memory-core-host-engine-embeddings.ts index 06a545bd682..eb3a89ea54c 100644 --- a/src/plugin-sdk/memory-core-host-engine-embeddings.ts +++ b/src/plugin-sdk/memory-core-host-engine-embeddings.ts @@ -1 +1,5 @@ export * from "../memory-host-sdk/engine-embeddings.js"; +export { + clearMemoryEmbeddingProviders, + registerMemoryEmbeddingProvider, +} from "../plugins/memory-embedding-providers.js"; diff --git a/src/plugin-sdk/memory-core-host-runtime-core.ts b/src/plugin-sdk/memory-core-host-runtime-core.ts index 926ba385377..908e5827241 100644 --- a/src/plugin-sdk/memory-core-host-runtime-core.ts +++ b/src/plugin-sdk/memory-core-host-runtime-core.ts @@ -5,4 +5,9 @@ export type { MemoryCorpusSupplement, MemoryCorpusSupplementRegistration, } from "../plugins/memory-state.js"; -export { listMemoryCorpusSupplements } from "../plugins/memory-state.js"; +export { + clearMemoryPluginState, + listMemoryCorpusSupplements, + registerMemoryCapability, + registerMemoryCorpusSupplement, +} from "../plugins/memory-state.js"; diff --git a/src/plugin-sdk/provider-env-vars.ts b/src/plugin-sdk/provider-env-vars.ts index 039ec220c62..1845a3a7fbd 100644 --- a/src/plugin-sdk/provider-env-vars.ts +++ b/src/plugin-sdk/provider-env-vars.ts @@ -4,4 +4,5 @@ export { getProviderEnvVars, listKnownProviderAuthEnvVarNames, omitEnvKeysCaseInsensitive, + resolveProviderAuthEnvVarCandidates, } from "../secrets/provider-env-vars.js"; diff --git a/src/plugin-sdk/provider-transport-runtime.ts b/src/plugin-sdk/provider-transport-runtime.ts index eef71f7d299..20c7618cc85 100644 --- a/src/plugin-sdk/provider-transport-runtime.ts +++ b/src/plugin-sdk/provider-transport-runtime.ts @@ -1,4 +1,5 @@ export { buildGuardedModelFetch } from "../agents/provider-transport-fetch.js"; +export { buildOpenAICompletionsParams } from "../agents/openai-transport-stream.js"; export { stripSystemPromptCacheBoundary } from "../agents/system-prompt-cache-boundary.js"; export { transformTransportMessages } from "../agents/transport-message-transform.js"; export { diff --git a/src/plugin-sdk/reply-chunking.ts b/src/plugin-sdk/reply-chunking.ts index 687f4c7ac88..eae86dd00e3 100644 --- a/src/plugin-sdk/reply-chunking.ts +++ b/src/plugin-sdk/reply-chunking.ts @@ -6,5 +6,9 @@ export { resolveTextChunkLimit, } from "../auto-reply/chunk.js"; export type { ChunkMode } from "../auto-reply/chunk.js"; -export { isSilentReplyText, SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js"; +export { + isSilentReplyPayloadText, + isSilentReplyText, + SILENT_REPLY_TOKEN, +} from "../auto-reply/tokens.js"; export type { ReplyPayload } from "./reply-payload.js"; diff --git a/src/plugin-sdk/runtime-secret-resolution.ts b/src/plugin-sdk/runtime-secret-resolution.ts index d1be75fae8b..80133ecd307 100644 --- a/src/plugin-sdk/runtime-secret-resolution.ts +++ b/src/plugin-sdk/runtime-secret-resolution.ts @@ -1,2 +1,4 @@ export { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js"; export { getChannelsCommandSecretTargetIds } from "../cli/command-secret-targets.js"; +export { resolveSecretRefValues } from "../secrets/resolve.js"; +export { applyResolvedAssignments, createResolverContext } from "../secrets/runtime-shared.js"; diff --git a/src/plugin-sdk/testing.ts b/src/plugin-sdk/testing.ts index 11d2adfb84d..cfc85fcbc6d 100644 --- a/src/plugin-sdk/testing.ts +++ b/src/plugin-sdk/testing.ts @@ -30,6 +30,10 @@ export { resetPluginRuntimeStateForTest, setActivePluginRegistry, } from "../plugins/runtime.js"; +export { + listImportedBundledPluginFacadeIds, + resetFacadeRuntimeStateForTest, +} from "./facade-runtime.js"; export { capturePluginRegistration } from "../plugins/captured-registration.js"; export { resolveProviderPluginChoice } from "../plugins/provider-auth-choice.runtime.js"; export type { PluginRuntime } from "../plugins/runtime/types.js"; @@ -40,9 +44,28 @@ export { createRequestCaptureJsonFetch, installPinnedHostnameTestHooks, } from "../media-understanding/audio.test-helpers.ts"; -export { isLiveTestEnabled } from "../agents/live-test-helpers.js"; +export { + createSingleUserPromptMessage, + extractNonEmptyAssistantText, + isLiveProfileKeyModeEnabled, + isLiveTestEnabled, +} from "../agents/live-test-helpers.js"; export { createSandboxTestContext } from "../agents/sandbox/test-fixtures.js"; export { writeSkill } from "../agents/skills.e2e-test-helpers.js"; +export { + castAgentMessage, + makeAgentAssistantMessage, + makeAgentUserMessage, +} from "../agents/test-helpers/agent-message-fixtures.js"; +export { collectProviderApiKeys } from "../agents/live-auth-keys.js"; +export { isModelNotFoundErrorMessage } from "../agents/live-model-errors.js"; +export { + isAuthErrorMessage, + isBillingErrorMessage, + isOverloadedErrorMessage, + isServerErrorMessage, + isTimeoutErrorMessage, +} from "../agents/pi-embedded-helpers/failover-matches.js"; export { maybeLoadShellEnvForGenerationProviders } from "../test-utils/generation-live-test-helpers.js"; export { __testing } from "../acp/control-plane/manager.js"; export { __testing as acpManagerTesting } from "../acp/control-plane/manager.js"; @@ -50,6 +73,36 @@ export { runAcpRuntimeAdapterContract } from "../acp/runtime/adapter-contract.te export { handleAcpCommand } from "../auto-reply/reply/commands-acp.js"; export { buildCommandTestParams } from "../auto-reply/reply/commands-spawn.test-harness.js"; export { peekSystemEvents, resetSystemEventsForTest } from "../infra/system-events.js"; +export { isTruthyEnvValue } from "../infra/env.js"; +export { getShellEnvAppliedKeys } from "../infra/shell-env.js"; +export { encodePngRgba, fillPixel } from "../media/png-encode.js"; +export { + parseLiveCsvFilter as parseCsvFilter, + parseProviderModelMap, + redactLiveApiKey, +} from "../media-generation/live-test-helpers.js"; +export { + DEFAULT_LIVE_MUSIC_MODELS, + resolveConfiguredLiveMusicModels, + resolveLiveMusicAuthStore, +} from "../music-generation/live-test-helpers.js"; +export { + canRunBufferBackedImageToVideoLiveLane, + canRunBufferBackedVideoToVideoLiveLane, + DEFAULT_LIVE_VIDEO_MODELS, + resolveConfiguredLiveVideoModels, + resolveLiveVideoAuthStore, + resolveLiveVideoResolution, +} from "../video-generation/live-test-helpers.js"; +export { normalizeVideoGenerationDuration } from "../video-generation/duration-support.js"; +export { parseVideoGenerationModelRef } from "../video-generation/model-ref.js"; +export type { + GeneratedVideoAsset, + VideoGenerationMode, + VideoGenerationModeCapabilities, + VideoGenerationProvider, + VideoGenerationRequest, +} from "../video-generation/types.js"; export { jsonResponse, requestBodyText, requestUrl } from "../test-helpers/http.js"; export { mockPinnedHostnameResolution } from "../test-helpers/ssrf.js"; export { createTestRegistry } from "../test-utils/channel-plugins.js"; @@ -62,3 +115,9 @@ export { expectGeneratedTokenPersistedToGatewayAuth } from "../test-utils/auth-t export { captureEnv, withEnv, withEnvAsync } from "../test-utils/env.js"; export { withFetchPreconnect, type FetchMock } from "../test-utils/fetch-mock.js"; export { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js"; +export { createMockPluginRegistry } from "../plugins/hooks.test-helpers.js"; +export { buildPluginApi } from "../plugins/api-builder.js"; +export { + createCapturedPluginRegistration, + type CapturedPluginRegistration, +} from "../plugins/captured-registration.js";