diff --git a/src/plugins/compat/plugin-sdk-subpath-records.ts b/src/plugins/compat/plugin-sdk-subpath-records.ts index 01b621cfd520..16dd1320dbc6 100644 --- a/src/plugins/compat/plugin-sdk-subpath-records.ts +++ b/src/plugins/compat/plugin-sdk-subpath-records.ts @@ -129,55 +129,56 @@ export const DEPRECATED_PLUGIN_SDK_SUBPATH_RECORDS = DEPRECATED_PLUGIN_SDK_SUBPA }) satisfies PluginCompatRecord, ) satisfies readonly PluginCompatRecord[]; -const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATHS = [ - "media-understanding", - "memory-host-core", - "plugin-config-runtime", - "tool-plugin", -] as const; - -type SdkReplacement = { replacement: string; docsPath: string }; -const DOCUMENTED_PUBLIC_PLUGIN_SDK_REPLACEMENTS: Record = { - "media-understanding": { +const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS = [ + { + subpath: "media-understanding", + status: "removal-pending", + removeAfter: "2026-09-30", replacement: - "`api.registerMediaUnderstandingProvider(...)` with provider-owned request helpers and types from `openclaw/plugin-sdk/plugin-entry`", + "`api.registerMediaUnderstandingProvider(...)` with provider-owned request helpers and types from `openclaw/plugin-sdk/plugin-entry`; retain the public subpath through the 2026-09-30 window while official plugin consumers migrate", docsPath: "/plugins/architecture", }, - "memory-host-core": { + { + subpath: "memory-host-core", + status: "removal-pending", + removeAfter: "2026-09-30", replacement: - "host-prepared memory prompts via `openclaw/plugin-sdk/core` and memory capability registration through the injected plugin API; retain the facade for companion-plugin public-artifact discovery until a focused read seam exists", + "host-prepared memory prompts via `openclaw/plugin-sdk/core` and memory capability registration through the injected plugin API; retain the facade through the 2026-09-30 window and until a focused public-artifact read seam exists", docsPath: "/plugins/architecture-internals#context-engine-plugins", }, - "plugin-config-runtime": { + { + subpath: "plugin-config-runtime", + status: "removal-pending", + removeAfter: "2026-12-01", replacement: - "`api.pluginConfig`, runtime tool context config, and focused `config-contracts`, `runtime-config-snapshot`, or `config-mutation` subpaths", + "`api.pluginConfig`, runtime tool context config, and focused `config-contracts`, `runtime-config-snapshot`, or `config-mutation` subpaths; retain the public subpath through the 2026-12-01 window while official plugin consumers migrate", docsPath: "/plugins/sdk-runtime", }, -}; + { + subpath: "tool-plugin", + status: "deprecated", + replacement: + "retain the public subpath until plugin authoring has a nonexecuting static metadata replacement for `defineToolPlugin`; `getToolPluginMetadata` currently reads metadata only from an already-executed entry", + docsPath: "/plugins/tool-plugins", + }, +] as const; + +function buildPublicSdkSubpathRecord({ + subpath, + ...compat +}: (typeof BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS)[number]) { + return { + code: `plugin-sdk-${subpath}-public-demotion` as const, + owner: "sdk" as const, + introduced: "2026-07-15", + deprecated: "2026-07-15", + warningStarts: "2026-07-15", + ...compat, + surfaces: [`openclaw/plugin-sdk/${subpath}`], + diagnostics: ["registry-backed public SDK demotion window; no external runtime import warning"], + tests: ["src/plugins/compat/registry.test.ts"], + } satisfies PluginCompatRecord; +} export const BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_RECORDS = - BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATHS.map((subpath) => { - const documented = DOCUMENTED_PUBLIC_PLUGIN_SDK_REPLACEMENTS[subpath]; - return { - code: `plugin-sdk-${subpath}-public-demotion` as const, - status: "removal-pending" as const, - owner: "sdk" as const, - introduced: "2026-07-15", - deprecated: "2026-07-15", - warningStarts: "2026-07-15", - removeAfter: "2026-07-30", - replacement: - subpath === "tool-plugin" - ? "retain the public subpath until plugin authoring has a nonexecuting static metadata replacement for `defineToolPlugin`" - : `${documented?.replacement ?? "define and document a public replacement"}; retain the public subpath until the 2026-07-30 window closes and official plugin consumers migrate`, - docsPath: - subpath === "tool-plugin" - ? "/plugins/tool-plugins" - : (documented?.docsPath ?? "/plugins/sdk-migration"), - surfaces: [`openclaw/plugin-sdk/${subpath}`], - diagnostics: [ - "registry-backed public SDK demotion window; no external runtime import warning", - ], - tests: ["src/plugins/compat/registry.test.ts"], - } satisfies PluginCompatRecord; - }) satisfies readonly PluginCompatRecord[]; + BUNDLED_ONLY_PUBLIC_PLUGIN_SDK_SUBPATH_SEEDS.map(buildPublicSdkSubpathRecord); diff --git a/src/plugins/compat/registry-records.ts b/src/plugins/compat/registry-records.ts index 53eb50823ba8..900fe1dced35 100644 --- a/src/plugins/compat/registry-records.ts +++ b/src/plugins/compat/registry-records.ts @@ -440,14 +440,12 @@ export const PLUGIN_COMPAT_RECORDS = [ }, { code: "agent-harness-sdk-alias", - status: "removal-pending", + status: "deprecated", owner: "agent-runtime", introduced: "2026-04-24", deprecated: "2026-04-25", warningStarts: "2026-04-25", - removeAfter: "2026-07-25", - replacement: - "`openclaw/plugin-sdk/agent-runtime`; retain the public aliases until the shipped SDK contract has a replacement window backed by external-usage proof", + replacement: "none yet; retain until a harness subpath ships and external migration is proven", docsPath: "/plugins/sdk-agent-harness", surfaces: ["openclaw/plugin-sdk/agent-harness", "openclaw/plugin-sdk/agent-harness-runtime"], diagnostics: ["plugin SDK compatibility warning"], diff --git a/src/plugins/compat/registry.test.ts b/src/plugins/compat/registry.test.ts index 2e15f2aaf0e1..94b347f1a167 100644 --- a/src/plugins/compat/registry.test.ts +++ b/src/plugins/compat/registry.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs"; import { beforeAll, describe, expect, it } from "vitest"; import { listGitTrackedFiles } from "../../test-utils/repo-files.js"; -import { listPluginCompatRecords } from "./registry.js"; +import { listPluginCompatRecords, type PluginCompatCode } from "./registry.js"; const datePattern = /^\d{4}-\d{2}-\d{2}$/u; const sourceRootsForDeprecatedCallGuard = [ @@ -21,7 +21,10 @@ const deprecatedTargetParserCompatFiles = new Set([ "src/infra/outbound/outbound-session.test-helpers.ts", "src/plugins/compat/registry.test.ts", ]); -const removalDatePendingCompatCodes = new Set(); +const removalDatePendingCompatCodes = new Set([ + "plugin-sdk-tool-plugin-public-demotion", + "agent-harness-sdk-alias", +]); const deprecationMarkingCodes = [ "plugin-sdk-channel-setup-input-fields", "plugin-sdk-broad-runtime-barrels", @@ -91,25 +94,37 @@ describe("plugin compatibility registry", () => { } }); - it("keeps shipped public contracts pending until their runtime blockers clear", () => { - const records = listPluginCompatRecords().filter( + it("keeps blocked public SDK removals aligned with their actual gates", () => { + const records = new Map(listPluginCompatRecords().map((record) => [record.code, record])); + const staleRemovalWindows = [...records.values()].filter( (record) => record.status === "removal-pending" && record.removeAfter !== undefined && record.removeAfter <= "2026-07-30", ); - expect(records.map((record) => record.code)).toEqual([ - "plugin-sdk-media-understanding-public-demotion", - "plugin-sdk-memory-host-core-public-demotion", - "plugin-sdk-plugin-config-runtime-public-demotion", - "plugin-sdk-tool-plugin-public-demotion", - "agent-harness-sdk-alias", - ]); - for (const record of records) { - expect(record.replacement).toMatch(/retain the public/u); - expect(record.releaseNote).toBeUndefined(); + expect(staleRemovalWindows).toEqual([]); + expect(records.get("plugin-sdk-media-understanding-public-demotion")).toMatchObject({ + status: "removal-pending", + removeAfter: "2026-09-30", + }); + expect(records.get("plugin-sdk-memory-host-core-public-demotion")).toMatchObject({ + status: "removal-pending", + removeAfter: "2026-09-30", + }); + expect(records.get("plugin-sdk-plugin-config-runtime-public-demotion")).toMatchObject({ + status: "removal-pending", + removeAfter: "2026-12-01", + }); + for (const code of removalDatePendingCompatCodes) { + expect(records.get(code)).toMatchObject({ status: "deprecated" }); + expect(records.get(code)?.removeAfter).toBeUndefined(); + expect(records.get(code)?.replacement).toMatch(/retain/u); } + expect(records.get("agent-harness-sdk-alias")?.surfaces).toEqual([ + "openclaw/plugin-sdk/agent-harness", + "openclaw/plugin-sdk/agent-harness-runtime", + ]); }); it("tracks the deprecation-marking families through the approved window", () => { diff --git a/src/plugins/contracts/plugin-sdk-subpaths.test.ts b/src/plugins/contracts/plugin-sdk-subpaths.test.ts index b1bcc5c5c23d..55b7a838ea8c 100644 --- a/src/plugins/contracts/plugin-sdk-subpaths.test.ts +++ b/src/plugins/contracts/plugin-sdk-subpaths.test.ts @@ -1232,6 +1232,8 @@ describe("plugin-sdk subpath exports", () => { ]); expectSourceMentions("setup-tools", ["formatCliCommand", "detectBinary", "formatDocsLink"]); expectSourceMentions("lazy-runtime", ["createLazyRuntimeSurface", "createLazyRuntimeModule"]); + expectSourceMentions("agent-harness", ["./agent-harness-runtime.js"]); + expectSourceMentions("agent-harness-runtime", ["AgentHarness", "EmbeddedPiCompactResult"]); expectSourceOmitsSnippet("agent-runtime", "./sglang.js"); expectSourceOmitsSnippet("agent-runtime", "./vllm.js"); expectSourceOmitsSnippet("agent-runtime", "../../extensions/"); diff --git a/test/scripts/plugin-boundary-report.test.ts b/test/scripts/plugin-boundary-report.test.ts index 004f7f9c1767..7e7602899313 100644 --- a/test/scripts/plugin-boundary-report.test.ts +++ b/test/scripts/plugin-boundary-report.test.ts @@ -54,14 +54,12 @@ describe("plugin-boundary-report", () => { expect(summaryResult.exitCode).toBe(0); expect(summaryResult.stderr).toBe(""); - expect(summary.compat?.removalPendingCount).toBe(5); + expect(summary.compat?.removalPendingCount).toBe(3); expect(summary.compat?.removalPendingDueCount).toEqual(expect.any(Number)); expect(summary.compat?.removalPending?.map((record) => record.code)).toEqual([ - "agent-harness-sdk-alias", "plugin-sdk-media-understanding-public-demotion", "plugin-sdk-memory-host-core-public-demotion", "plugin-sdk-plugin-config-runtime-public-demotion", - "plugin-sdk-tool-plugin-public-demotion", ]); for (const record of summary.compat?.removalPending ?? []) { expect(record.removeAfter).toMatch(/^\d{4}-\d{2}-\d{2}$/u); @@ -84,8 +82,8 @@ describe("plugin-boundary-report", () => { expect(result.exitCode).toBe(0); expect(result.stderr).toBe(""); - expect(result.stdout).toContain("removalPending=5"); - expect(result.stdout).toContain("removal-pending 2026-07-25 agent-harness-sdk-alias"); + expect(result.stdout).toContain("removalPending=3"); + expect(result.stdout).not.toContain("agent-harness-sdk-alias"); expect(result.stdout).toMatch(/blocker=.*retain the public/iu); expect(result.stdout).toMatch(/readerRefs=\d+ readers=/u); });