diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index e8610e2b28f..8eff8a180b0 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -95362e415e8400ab74d81938d0c6b57ba82ba1447826fe02bd9a62de932f0b7b plugin-sdk-api-baseline.json -fefd2248a26be6f563bb777aeb9cf73ae6a479f1ed16c74be93eef7b9e35356e plugin-sdk-api-baseline.jsonl +282897f1e044b79bd444e164646e492602f987fa0fefb20a3336aa0b2445e6b7 plugin-sdk-api-baseline.json +49cc5b3330e19a86a4884c71d08070034a39463d7e25ab8eca124f65028f1114 plugin-sdk-api-baseline.jsonl diff --git a/packages/plugin-package-contract/src/index.ts b/packages/plugin-package-contract/src/index.ts index 579ba4a5901..b7734e2f092 100644 --- a/packages/plugin-package-contract/src/index.ts +++ b/packages/plugin-package-contract/src/index.ts @@ -1,6 +1,3 @@ -import { normalizeOptionalString } from "../../../src/shared/string-coerce.js"; -import { isRecord } from "../../../src/utils.js"; - export type JsonObject = Record; export type ExternalPluginCompatibility = { @@ -25,6 +22,18 @@ export const EXTERNAL_CODE_PLUGIN_REQUIRED_FIELD_PATHS = [ "openclaw.build.openclawVersion", ] as const; +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function normalizeOptionalString(value: unknown): string | undefined { + if (typeof value !== "string") { + return undefined; + } + const trimmed = value.trim(); + return trimmed ? trimmed : undefined; +} + function readOpenClawBlock(packageJson: unknown) { const root = isRecord(packageJson) ? packageJson : undefined; const openclaw = isRecord(root?.openclaw) ? root.openclaw : undefined; diff --git a/packages/plugin-sdk/src/runtime-doctor.ts b/packages/plugin-sdk/src/runtime-doctor.ts index 5658a0b5193..ef686b2bd44 100644 --- a/packages/plugin-sdk/src/runtime-doctor.ts +++ b/packages/plugin-sdk/src/runtime-doctor.ts @@ -1,13 +1 @@ -export { collectProviderDangerousNameMatchingScopes } from "../../../src/config/dangerous-name-matching.js"; -export { - asObjectRecord, - hasLegacyAccountStreamingAliases, - hasLegacyStreamingAliases, - normalizeLegacyDmAliases, - normalizeLegacyStreamingAliases, -} from "../../../src/config/channel-compat-normalization.js"; -export { - detectPluginInstallPathIssue, - formatPluginInstallPathIssue, -} from "../../../src/infra/plugin-install-path-warnings.js"; -export { removePluginFromConfig } from "../../../src/plugins/uninstall.js"; +export * from "../../../src/plugin-sdk/runtime-doctor.js"; diff --git a/packages/plugin-sdk/src/text-runtime.ts b/packages/plugin-sdk/src/text-runtime.ts index 969a8b82c73..2f39480c7c3 100644 --- a/packages/plugin-sdk/src/text-runtime.ts +++ b/packages/plugin-sdk/src/text-runtime.ts @@ -1,6 +1 @@ -export { - normalizeLowercaseStringOrEmpty, - normalizeOptionalLowercaseString, - normalizeOptionalString, - readStringValue, -} from "../../../src/shared/string-coerce.js"; +export * from "../../../src/plugin-sdk/text-runtime.js"; diff --git a/src/plugins/contracts/extension-package-project-boundaries.test.ts b/src/plugins/contracts/extension-package-project-boundaries.test.ts index 096b9118470..b8d8ca84bdd 100644 --- a/src/plugins/contracts/extension-package-project-boundaries.test.ts +++ b/src/plugins/contracts/extension-package-project-boundaries.test.ts @@ -1,5 +1,5 @@ -import { existsSync, readFileSync } from "node:fs"; -import { resolve } from "node:path"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { relative, resolve } from "node:path"; import { describe, expect, it } from "vitest"; import { collectExtensionsWithTsconfig, @@ -55,12 +55,38 @@ const MEMORY_HOST_SDK_EXPORTS = [ "./secret", "./status", ] as const; +const MEMORY_HOST_SDK_ALLOWED_CORE_BRIDGE_FILES = [ + "packages/memory-host-sdk/src/host/openclaw-runtime.ts", +] as const; // oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Test helper lets assertions ascribe JSON file shape. function readJsonFile(relativePath: string): T { return JSON.parse(readFileSync(resolve(REPO_ROOT, relativePath), "utf8")) as T; } +function collectCodeFiles(relativeDir: string): string[] { + const dir = resolve(REPO_ROOT, relativeDir); + const files: string[] = []; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const nextPath = resolve(dir, entry.name); + if (entry.isDirectory()) { + files.push(...collectCodeFiles(relative(REPO_ROOT, nextPath).replaceAll("\\", "/"))); + continue; + } + if (entry.isFile() && /\.(?:[cm]?ts|tsx|mts|cts)$/u.test(entry.name)) { + files.push(relative(REPO_ROOT, nextPath).replaceAll("\\", "/")); + } + } + return files.toSorted(); +} + +function collectCoreReferenceFiles(relativeDir: string): string[] { + return collectCodeFiles(relativeDir).filter((file) => { + const source = readFileSync(resolve(REPO_ROOT, file), "utf8"); + return source.includes("../../../../src/") || source.includes("../../../src/"); + }); +} + describe("opt-in extension package boundaries", () => { it("keeps path aliases in a dedicated shared config", () => { const pathsConfig = readJsonFile(EXTENSION_PACKAGE_BOUNDARY_PATHS_CONFIG); @@ -209,5 +235,13 @@ describe("opt-in extension package boundaries", () => { const source = readFileSync(resolve(REPO_ROOT, "packages/memory-host-sdk", target), "utf8"); expect(source, target).not.toContain("src/memory-host-sdk/"); } + + expect(collectCoreReferenceFiles("packages/memory-host-sdk/src")).toEqual([ + ...MEMORY_HOST_SDK_ALLOWED_CORE_BRIDGE_FILES, + ]); + }); + + it("keeps plugin-package-contract independent from core internals", () => { + expect(collectCoreReferenceFiles("packages/plugin-package-contract/src")).toEqual([]); }); });