refactor(context-engine): retire legacy host param default (#122434)

This commit is contained in:
Peter Steinberger
2026-08-11 21:57:46 -07:00
committed by GitHub
parent 141f943a41
commit 7ecad45a7d
28 changed files with 57 additions and 96 deletions
@@ -1 +1 @@
{"contentHash":"606504bb06b321f5a4fef507f322c297a9295dccdc2e5996ac64603049113408","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
{"contentHash":"48dad42d04caeb62f04f9dbe32b4562ecef695e9f0c97fb891040408400f37bd","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"40d137a31b2ac9f1da776aafcc77b54422b1610c91fe2d6594068e1f7285e91b","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
{"contentHash":"3d1c84bcc585b57a93054889e16fb3be02ff4d599d7d2a0566df6f463f2254a8","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"6667d863dee0c991c58196b0a77fa812fc1800fca9885c866abd04f3df1a03ce","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"}
{"contentHash":"fac71b2c80db2874419a589646fd560e8a8ad43dba93f5a6d26a73c171a0b4ba","entrypoint":"agent-runtime","importSpecifier":"openclaw/plugin-sdk/agent-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"6257c43a60153049dac1af0d828435081f10f6bcd2554e2a67d1c8adb6df973c","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
{"contentHash":"4245891bae7517cf001f637e80d1d813c11385d695eebbd69a61258e83399c4e","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
@@ -1 +1 @@
{"contentHash":"88113b5ae8119ca780a3d93b0e033c87d0c387b21a29b72e72b375f5c77f08b3","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
{"contentHash":"f719599ed89a658109698bc1ef1309e1709e5ae3385b7ce3f1c583343ea23b33","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
@@ -1 +1 @@
{"contentHash":"a85b4bbcd416a76bde8d58af34fab58bdb416a1b2c8476b46a3ffd8540754e04","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
{"contentHash":"d531b32d0544c42f375f628bd7a6b3d3582a91078db37bc048af7c3f23a9e59f","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
@@ -1 +1 @@
{"contentHash":"12e07b9ba7b5b35c1f4e0f4510a073adac00671d292a72700025c86376db22b0","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
{"contentHash":"40b25681a1ee102cf1d2d5b23a29f96e2501ea521404deed59303e59c35089ec","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
@@ -1 +1 @@
{"contentHash":"f36ace33d1e16649ed888032751d5db67e826cdc59f06732a5c13b5185f971ed","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
{"contentHash":"67884fcf5fc91b8b40a3c83e83080e69f54f7750cdd780368af63363f84ddc40","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
@@ -1 +1 @@
{"contentHash":"b70eb302db749674e237eb2d740dacca2c20f883cd7f0581299cf010d3a71863","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"}
{"contentHash":"a7c63538d37122bfe240f75944916a3f01806f7fb7de3ecb976c6fd3f5dcd72e","entrypoint":"config-mutation","importSpecifier":"openclaw/plugin-sdk/config-mutation"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"4339bf4856bafcbf691b25df94a564a9b7bde09c041b9c0b31df3636a6e58462","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"}
{"contentHash":"3f128cc41bb2e44b40402774acd3a967501c72ae5d7b2f9d149a262cc0d5a2da","entrypoint":"config-runtime","importSpecifier":"openclaw/plugin-sdk/config-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"7451e2a5ffc615f6e8fedebbf4327caf3a64a30973828ca776ca8e298052eebf","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
{"contentHash":"ab54be164a2b0affece4d6c196ee64ae9259cac072d4ac6c32dd27daf4941565","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"726e1cc6c7c0463333587b25908a0b4274c9ca2e1c7d696ab7af5cb392aeca07","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
{"contentHash":"21823a6741645b81797b4be6bf16a2d067e7e2a007177fbc5c7a6cab98b08027","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
@@ -1 +1 @@
{"contentHash":"da9273b6213137fdfc3c2f0f22de681c7bd664dfb4fd0fd0f7636b70cad9835b","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
{"contentHash":"80686774b5023519466038e4a20604d9efce462b0cd2915ab5597fb1087ef8af","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
@@ -1 +1 @@
{"contentHash":"2328445ee010703050ecce3ea4b823881172b1da8ffd061cb149a7324ac3b967","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
{"contentHash":"9eb227dd27a3d08b868b1144bd64e1ff22da707af353699b9eb0f6438d4d98c1","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
@@ -1 +1 @@
{"contentHash":"b1b9afd14967ec92dad01bc0a00997b578b9c4ca0f8601597fc58b503fc13606","entrypoint":"model-session-runtime","importSpecifier":"openclaw/plugin-sdk/model-session-runtime"}
{"contentHash":"ecb40006fc5ea974a3e31d0782c86e50c054e19164f864a564bb6f3f3d00dae9","entrypoint":"model-session-runtime","importSpecifier":"openclaw/plugin-sdk/model-session-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"cd50a67407a27b36d6f3e22af00940d1031bfe907fc15be5a72187e68f04d822","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
{"contentHash":"696f33ed5d8f059b82ff4159183b80f7e4af086abb2383bae5c02d909427fe43","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"ba7ea5cfd334e44f5c5c2571a2161961a44f51e64e45074917fab3e69ac7652f","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
{"contentHash":"411769ac67fd1a5b0ea4ada41b45678688e86194d03adc8d8efcad65c5f0c38c","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"7233a8bb04605b4022cb18569b93f062d4f7986ae5ab5dd09e6c655984b56542","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"}
{"contentHash":"d56a974704a97ae438b03ba82aa84314e77bf65a7e7dd9096fd2ab2bc8cbcbec","entrypoint":"provider-auth","importSpecifier":"openclaw/plugin-sdk/provider-auth"}
@@ -1 +1 @@
{"contentHash":"6f6b852b66e41c6f2c15b617bc00148efbafc2d737049b19b45fe3b25eebb9fe","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
{"contentHash":"6539e277a49a27344f2844bcce06f609c5e463bb5a9be9eb1532994f9fec6479","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
+1 -1
View File
@@ -1 +1 @@
{"contentHash":"0686a3b02b9bae23e46af30fdddf46487db53ec6ffe0833ce9cf298aded54362","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
{"contentHash":"9d1c23c498e989db4592749e735d0a09fc41ffeb86ab66f70b6894a2161e1212","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
@@ -1 +1 @@
{"contentHash":"80e15361b1e42280548074fc349fd32a45b55dd622c33bc37a0e2db963852790","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
{"contentHash":"d0b42cb31d5b2e84acb3a63ca79fdd2bc988bde3505de54dc07dd85bb76fd60c","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
+8 -8
View File
@@ -213,13 +213,13 @@ Required members:
| `assemble(params)` | Method | Build context for a model run (returns `AssembleResult`) |
| `compact(params)` | Method | Summarize/reduce context |
Set `info.acceptedHostParams` to the host-added lifecycle fields the engine
accepts. Current keys are `sessionKey`, `prompt`, `runtimeSettings`,
Set `info.acceptedHostParams` to restrict the host-added lifecycle fields the
engine receives. Current keys are `sessionKey`, `prompt`, `runtimeSettings`,
`sessionTarget`, and `runtimeContext`. OpenClaw intersects the declaration with
the fields available for each lifecycle method, so undeclared or unknown keys
are never injected. Engines without this declaration receive the pre-host-field
legacy parameter set through 2026-08-12; after that date, undeclared engines
receive every current host field.
are never injected. Engines without this declaration receive every current
host field; declare an explicit list, including `[]`, when the engine validates
a narrower input shape.
For durable admitted turns, declare both transcript semantics:
@@ -308,9 +308,9 @@ rendered directly to users and does not create a dedicated reporting surface.
- `diagnostics`: closed fallback and degraded reason codes when known
Fields that can be unknown are represented as `null`; discriminator fields such
as runtime mode and selection source remain non-nullable. Engines that accept
`runtimeSettings` must include it in `info.acceptedHostParams` during the
compatibility window.
as runtime mode and selection source remain non-nullable. Engines that restrict
host parameters and accept `runtimeSettings` must include it in
`info.acceptedHostParams`.
### Host requirements
+9 -9
View File
@@ -198,14 +198,14 @@ artifact reader count is zero.
Audit the current migration queue with `pnpm plugins:boundary-report`:
| Flag | Effect |
| ------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `--summary` (or `pnpm plugins:boundary-report:summary`) | Compact counts instead of full detail. |
| `--json` | Machine-readable report. |
| `--owner <id>` | Filter to one plugin or compatibility owner. |
| `--fail-on-cross-owner` | Exit non-zero on cross-owner reserved SDK imports. |
| `--fail-on-eligible-compat` | Exit non-zero when a deprecated compat record's `removeAfter` date has passed. |
| `--fail-on-unclassified-unused-reserved` | Exit non-zero on unused reserved SDK shims. |
| Flag | Effect |
| ------------------------------------------------------- | -------------------------------------------------------------------------- |
| `--summary` (or `pnpm plugins:boundary-report:summary`) | Compact counts instead of full detail. |
| `--json` | Machine-readable report. |
| `--owner <id>` | Filter to one plugin or compatibility owner. |
| `--fail-on-cross-owner` | Exit non-zero on cross-owner reserved SDK imports. |
| `--fail-on-eligible-compat` | Exit non-zero on or after a deprecated compat record's `removeAfter` date. |
| `--fail-on-unclassified-unused-reserved` | Exit non-zero on unused reserved SDK shims. |
`pnpm plugins:boundary-report:ci` runs with all three fail flags. Deprecated
records normally have an explicit `removeAfter` date. A contract tied to a
@@ -1069,7 +1069,7 @@ apps own device capture/playback UX.
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| **Now** | Warning-capable deprecated surfaces emit runtime warnings; repository guards reject deprecated SDK imports from core and bundled plugins. |
| **Pending owner decision** | Records without `removeAfter` or `removalGate` remain deprecated and ineligible until their owner publishes a gate. |
| **Each compat record's `removeAfter` date** | That dated surface becomes eligible for removal; `pnpm plugins:boundary-report --fail-on-eligible-compat` fails CI once the date passes. |
| **Each compat record's `removeAfter` date** | That dated surface becomes eligible for removal; `pnpm plugins:boundary-report --fail-on-eligible-compat` fails CI on or after that date. |
| **Next Plugin SDK major** | `inbound-reply-dispatch` reaches its explicit `next-plugin-sdk-major` gate; it is not date-eligible before that version boundary. |
The remaining public SDK subpaths below have registry-backed removal windows.
+4 -4
View File
@@ -619,10 +619,10 @@ For an end-to-end authoring guide, see
### Exclusive slots
| Method | What it registers |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api.registerContextEngine(id, factory)` | Context engine (one active at a time). Declare accepted host-added lifecycle fields with `info.acceptedHostParams`; undeclared engines receive the legacy field set through 2026-08-12, then receive all current host fields. |
| `api.registerMemoryCapability(capability)` | Unified memory capability |
| Method | What it registers |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `api.registerContextEngine(id, factory)` | Context engine (one active at a time). Use `info.acceptedHostParams` to restrict accepted host-added lifecycle fields; undeclared engines receive all current host fields. |
| `api.registerMemoryCapability(capability)` | Unified memory capability |
To participate in durable admitted turns, context engines must declare
`currentTurnFence: "before-current-turn-entry-v1"` and
@@ -128,27 +128,6 @@ describe("context-engine host parameter projection", () => {
});
});
it("uses the legacy parameter set for undeclared engines during the window", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-07-29T12:00:00Z"));
const assembleCalls: Array<Record<string, unknown>> = [];
const compactCalls: Array<Record<string, unknown>> = [];
const engineId = registerProbeEngine({ assembleCalls, compactCalls });
await invokeHostParamMethods(
await resolveContextEngine({ plugins: { slots: { contextEngine: engineId } } }),
);
for (const call of [...assembleCalls, ...compactCalls]) {
expect(call).not.toHaveProperty("sessionKey");
expect(call).not.toHaveProperty("runtimeSettings");
}
expect(assembleCalls[0]).not.toHaveProperty("prompt");
expect(compactCalls[0]).not.toHaveProperty("sessionTarget");
expect(compactCalls[0]).not.toHaveProperty("runtimeContext");
expect(compactCalls[0]).toHaveProperty("sessionId", "session-1");
});
it("projects host parameters on fresh logical-turn engines", async () => {
const assembleCalls: Array<Record<string, unknown>> = [];
const compactCalls: Array<Record<string, unknown>> = [];
@@ -234,9 +213,7 @@ describe("context-engine host parameter projection", () => {
]);
});
it("passes every host parameter to fresh undeclared engines after the window", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-13T00:00:00Z"));
it("passes every host parameter to fresh undeclared engines", async () => {
const assembleCalls: Array<Record<string, unknown>> = [];
const compactCalls: Array<Record<string, unknown>> = [];
const engineId = registerProbeEngine({ assembleCalls, compactCalls });
@@ -265,15 +242,12 @@ describe("context-engine host parameter projection", () => {
]);
});
it("switches undeclared engines to full parameters after the window", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-07-29T12:00:00Z"));
it("passes every host parameter to resolved undeclared engines", async () => {
const assembleCalls: Array<Record<string, unknown>> = [];
const compactCalls: Array<Record<string, unknown>> = [];
const engineId = registerProbeEngine({ assembleCalls, compactCalls });
const engine = await resolveContextEngine({ plugins: { slots: { contextEngine: engineId } } });
vi.setSystemTime(new Date("2026-08-13T00:00:00Z"));
await invokeHostParamMethods(engine);
expect(assembleCalls[0]).toMatchObject({
@@ -317,15 +291,13 @@ describe("context-engine host parameter projection", () => {
});
it("does not mutate frozen engines reused by a factory", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-07-29T12:00:00Z"));
const engineId = `host-param-frozen-${++engineCounter}`;
const assemble = vi.fn<ContextEngine["assemble"]>(async (params) => ({
messages: params.messages,
estimatedTokens: 0,
}));
class FrozenProbeEngine implements ContextEngine {
readonly #info = { id: engineId, name: "Frozen Probe" };
readonly #info = { id: engineId, name: "Frozen Probe", acceptedHostParams: [] };
get info() {
return this.#info;
@@ -346,7 +318,7 @@ describe("context-engine host parameter projection", () => {
const first = await resolveContextEngine({ plugins: { slots: { contextEngine: engineId } } });
const second = await resolveContextEngine({ plugins: { slots: { contextEngine: engineId } } });
expect(first.info).toEqual({ id: engineId, name: "Frozen Probe" });
expect(first.info).toEqual({ id: engineId, name: "Frozen Probe", acceptedHostParams: [] });
await first.assemble({ sessionId: "session-1", sessionKey: "first", messages: [message] });
await second.assemble({ sessionId: "session-2", sessionKey: "second", messages: [message] });
+1 -11
View File
@@ -2,7 +2,6 @@
import { sanitizeForLog } from "../../packages/terminal-core/src/ansi.js";
import type { OpenClawConfig } from "../config/types.js";
import { createAbortError } from "../infra/abort-signal.js";
import { getPluginCompatRecord } from "../plugins/compat/registry.js";
import type {
ContextEngineFactory,
ContextEngineFactoryContext,
@@ -55,20 +54,11 @@ type ResolvedContextEngineMetadata = {
};
const resolvedEngineMetadata = new WeakMap<ContextEngine, ResolvedContextEngineMetadata>();
const legacyHostParamDefaultRemoveAfter = getPluginCompatRecord(
"context-engine-legacy-host-param-default",
).removeAfter;
function projectContextEngineHostParams(
engine: ContextEngine,
params: Record<string, unknown>,
): Record<string, unknown> {
// Removal(2026-08-12): undeclared engines get full params.
// Contract: context-engine-legacy-host-param-default.
const useLegacyDefault =
legacyHostParamDefaultRemoveAfter !== undefined &&
new Date().toISOString().slice(0, 10) <= legacyHostParamDefaultRemoveAfter;
const accepted = engine.info.acceptedHostParams ?? (useLegacyDefault ? [] : undefined);
const accepted = engine.info.acceptedHostParams;
if (!accepted) {
return params;
}
+5 -6
View File
@@ -13,18 +13,17 @@ export const PLUGIN_COMPAT_RECORDS = [
MEDIA_LEGACY_PROJECTION_COMPAT_RECORD,
{
code: "context-engine-legacy-host-param-default",
status: "deprecated",
status: "removed",
owner: "sdk",
introduced: "2026-07-29",
deprecated: "2026-07-29",
warningStarts: "2026-07-29",
removeAfter: "2026-08-12",
replacement:
"declare `ContextEngineInfo.acceptedHostParams`; full host params after the window",
"`ContextEngineInfo.acceptedHostParams` for restricted projection; omitted declarations receive full host params",
docsPath: "/concepts/context-engine#the-contextengine-interface",
surfaces: ["ContextEngineInfo.acceptedHostParams and undeclared-engine default projection"],
diagnostics: ["plugin compatibility registry and dated runtime removal marker"],
diagnostics: ["plugin compatibility registry and context engine guide"],
tests: ["src/context-engine/host-param-projection.test.ts"],
releaseNote:
"The undeclared context-engine host-parameter compatibility default was removed; engines without `acceptedHostParams` now receive all current host fields.",
},
{
code: "removed-global-api-provider-publication",
+5 -5
View File
@@ -176,17 +176,17 @@ describe("plugin compatibility registry", () => {
);
});
it("tracks the context-engine legacy host-param default through its two-week window", () => {
it("keeps the removed context-engine host-param default as a migration tombstone", () => {
const record = listPluginCompatRecords().find(
(candidate) => candidate.code === "context-engine-legacy-host-param-default",
);
expect(record).toMatchObject({
status: "deprecated",
deprecated: "2026-07-29",
warningStarts: "2026-07-29",
removeAfter: "2026-08-12",
status: "removed",
replacement:
"`ContextEngineInfo.acceptedHostParams` for restricted projection; omitted declarations receive full host params",
});
expect(record?.removeAfter).toBeUndefined();
});
it("keeps deprecated explicit target parser calls inside compatibility shims", () => {