refactor: deprecate legacy reply history helpers (#82236)

This commit is contained in:
Peter Steinberger
2026-05-15 18:44:04 +01:00
committed by GitHub
parent fc9798a788
commit 6ca9de1e0a
11 changed files with 125 additions and 11 deletions

View File

@@ -360,10 +360,12 @@ plugin before they become model-visible media.
## History windows
Message-turn code should use `createChannelHistoryWindow(...)` instead of
calling low-level `reply-history` map helpers directly. The window facade keeps
text context, structured `InboundHistory`, history-media normalization, and
clearing behind one core-owned API while still letting the channel choose how a
history line is rendered.
calling low-level `reply-history` map helpers directly. The old map helpers
remain importable as deprecated compatibility exports, but new plugin runtime
code should not call them. The window facade keeps text context, structured
`InboundHistory`, history-media normalization, and clearing behind one
core-owned API while still letting the channel choose how a history line is
rendered.
```typescript
const history = createChannelHistoryWindow({ historyMap: groupHistories });
@@ -389,7 +391,7 @@ const combinedBody = history.buildPendingContext({
The older `buildPendingHistoryContextFromMap`,
`buildInboundHistoryFromMap`, `recordPendingHistoryEntry*`, and
`clearHistoryEntriesIfEnabled` exports remain for compatibility with plugins
`clearHistoryEntries*` exports remain as deprecated compatibility for plugins
that have not migrated yet. New channel work should use the window or the turn
kernel record/finalize options.

View File

@@ -572,7 +572,7 @@ releases.
| `plugin-sdk/webhook-request-guards` | Webhook body guard helpers | Request body read/limit helpers |
| `plugin-sdk/reply-runtime` | Shared reply runtime | Inbound dispatch, heartbeat, reply planner, chunking |
| `plugin-sdk/reply-dispatch-runtime` | Narrow reply dispatch helpers | Finalize, provider dispatch, and conversation-label helpers |
| `plugin-sdk/reply-history` | Reply-history helpers | `buildHistoryContext`, `buildPendingHistoryContextFromMap`, `recordPendingHistoryEntry`, `clearHistoryEntriesIfEnabled` |
| `plugin-sdk/reply-history` | Reply-history helpers | `createChannelHistoryWindow`; deprecated map-helper compatibility exports such as `buildPendingHistoryContextFromMap`, `recordPendingHistoryEntry`, and `clearHistoryEntriesIfEnabled` |
| `plugin-sdk/reply-reference` | Reply reference planning | `createReplyReferencePlanner` |
| `plugin-sdk/reply-chunking` | Reply chunk helpers | Text/markdown chunking helpers |
| `plugin-sdk/session-store-runtime` | Session store helpers | Store path + updated-at helpers |

View File

@@ -244,7 +244,7 @@ focused channel/runtime subpaths, `config-contracts`, `string-coerce-runtime`,
| `plugin-sdk/approval-runtime` | Exec/plugin approval helpers, approval-capability builders, auth/profile helpers, native routing/runtime helpers, and structured approval display path formatting |
| `plugin-sdk/reply-runtime` | Shared inbound/reply runtime helpers, chunking, dispatch, heartbeat, reply planner |
| `plugin-sdk/reply-dispatch-runtime` | Narrow reply dispatch/finalize and conversation-label helpers |
| `plugin-sdk/reply-history` | Shared short-window reply-history compatibility helpers. New message-turn code should use `createChannelHistoryWindow` rather than the lower-level map helpers |
| `plugin-sdk/reply-history` | Shared short-window reply-history helpers. New message-turn code should use `createChannelHistoryWindow`; lower-level map helpers remain deprecated compatibility exports only |
| `plugin-sdk/reply-reference` | `createReplyReferencePlanner` |
| `plugin-sdk/reply-chunking` | Narrow text/markdown chunking helpers |
| `plugin-sdk/session-store-runtime` | Session store path, session-key, updated-at, and store mutation helpers |

View File

@@ -55,6 +55,8 @@ export { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
export { rawDataToString } from "openclaw/plugin-sdk/webhook-ingress";
export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
// Legacy map-helper exports stay for older plugin consumers. New message-turn
// code should use createChannelHistoryWindow.
export {
DEFAULT_GROUP_HISTORY_LIMIT,
createChannelHistoryWindow,

View File

@@ -28,6 +28,8 @@ export {
} from "openclaw/plugin-sdk/runtime-group-policy";
export { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
// Legacy map-helper exports stay for older plugin consumers. New message-turn
// code should use createChannelHistoryWindow.
export {
DEFAULT_GROUP_HISTORY_LIMIT,
createChannelHistoryWindow,

View File

@@ -1,3 +1,5 @@
// Legacy map-helper exports in this facade stay for older plugin consumers.
// New message-turn code should use createChannelHistoryWindow.
export {
applyAccountNameToChannelSection,
applySetupAccountConfigPatch,

View File

@@ -71,6 +71,10 @@ export function appendHistoryEntry<T extends HistoryEntry>(params: {
return history;
}
/**
* @deprecated Plugin message-turn code should use `createChannelHistoryWindow(...).record(...)`.
* This helper remains for core internals and older plugin compatibility.
*/
export function recordPendingHistoryEntry<T extends HistoryEntry>(params: {
historyMap: Map<string, T[]>;
historyKey: string;
@@ -80,6 +84,10 @@ export function recordPendingHistoryEntry<T extends HistoryEntry>(params: {
return appendHistoryEntry(params);
}
/**
* @deprecated Plugin message-turn code should use `createChannelHistoryWindow(...).record(...)`.
* This helper remains for core internals and older plugin compatibility.
*/
export function recordPendingHistoryEntryIfEnabled<T extends HistoryEntry>(params: {
historyMap: Map<string, T[]>;
historyKey: string;
@@ -153,6 +161,11 @@ export function normalizeHistoryMediaEntries(params: {
return out;
}
/**
* @deprecated Plugin message-turn code should use
* `createChannelHistoryWindow(...).recordWithMedia(...)`. This helper remains
* for core internals and older plugin compatibility.
*/
export async function recordPendingHistoryEntryWithMedia<T extends HistoryEntry>(params: {
historyMap: Map<string, T[]>;
historyKey: string;
@@ -217,6 +230,11 @@ export async function recordPendingHistoryEntryWithMedia<T extends HistoryEntry>
});
}
/**
* @deprecated Plugin message-turn code should use
* `createChannelHistoryWindow(...).buildPendingContext(...)`. This helper remains
* for core internals and older plugin compatibility.
*/
export function buildPendingHistoryContextFromMap(params: {
historyMap: Map<string, HistoryEntry[]>;
historyKey: string;
@@ -238,6 +256,11 @@ export function buildPendingHistoryContextFromMap(params: {
});
}
/**
* @deprecated Plugin message-turn code should use
* `createChannelHistoryWindow(...).buildInboundHistory(...)`. This helper remains
* for core internals and older plugin compatibility.
*/
export function buildInboundHistoryFromMap<T extends HistoryEntry>(params: {
historyMap: Map<string, T[]>;
historyKey: string;
@@ -275,6 +298,11 @@ export function buildInboundHistoryFromEntries(params: {
});
}
/**
* @deprecated Prefer `buildHistoryContextFromEntries(...)` for existing entry
* arrays, or `createChannelHistoryWindow(...)` when working from a history map.
* This helper remains for older plugin compatibility.
*/
export function buildHistoryContextFromMap(params: {
historyMap: Map<string, HistoryEntry[]>;
historyKey: string;
@@ -305,6 +333,10 @@ export function buildHistoryContextFromMap(params: {
});
}
/**
* @deprecated Plugin message-turn code should use `createChannelHistoryWindow(...).clear(...)`.
* This helper remains for core internals and older plugin compatibility.
*/
export function clearHistoryEntries(params: {
historyMap: Map<string, HistoryEntry[]>;
historyKey: string;
@@ -312,6 +344,10 @@ export function clearHistoryEntries(params: {
params.historyMap.set(params.historyKey, []);
}
/**
* @deprecated Plugin message-turn code should use `createChannelHistoryWindow(...).clear(...)`.
* This helper remains for core internals and older plugin compatibility.
*/
export function clearHistoryEntriesIfEnabled(params: {
historyMap: Map<string, HistoryEntry[]>;
historyKey: string;

View File

@@ -1,4 +1,4 @@
import { readFileSync } from "node:fs";
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
@@ -43,15 +43,63 @@ const historyWindowFiles = [
const lowLevelHistoryHelpers = [
"buildInboundHistoryFromMap",
"buildHistoryContextFromMap",
"buildPendingHistoryContextFromMap",
"clearHistoryEntries",
"clearHistoryEntriesIfEnabled",
"recordPendingHistoryEntry",
"recordPendingHistoryEntryIfEnabled",
"recordPendingHistoryEntryWithMedia",
];
const legacyReplyHistoryCompatibilityFiles = new Set([
"extensions/mattermost/runtime-api.ts",
"extensions/mattermost/src/mattermost/runtime-api.ts",
"extensions/mattermost/src/runtime-api.ts",
]);
const skippedExtensionScanDirs = new Set([
".cache",
".turbo",
"build",
"coverage",
"dist",
"node_modules",
"tmp",
]);
function readRepoFile(relativePath: string): string {
return readFileSync(path.join(repoRoot, relativePath), "utf8");
return readFileSync(path.join(repoRoot, ...relativePath.split("/")), "utf8");
}
function listTsFiles(relativeDir: string): string[] {
const dir = path.join(repoRoot, ...relativeDir.split("/"));
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const relativePath = path.posix.join(relativeDir, entry.name);
if (entry.isDirectory()) {
if (skippedExtensionScanDirs.has(entry.name)) {
return [];
}
return listTsFiles(relativePath);
}
if (!entry.isFile() || !entry.name.endsWith(".ts") || entry.name.endsWith(".d.ts")) {
return [];
}
return [relativePath];
});
}
function collectReplyHistoryBindings(source: string): Set<string> {
const bindings = new Set<string>();
const importOrExportPattern =
/\b(?:import|export)\s*\{([\s\S]*?)\}\s*from\s*["']openclaw\/plugin-sdk\/reply-history["']/g;
for (const match of source.matchAll(importOrExportPattern)) {
const block = match[1] ?? "";
for (const nameMatch of block.matchAll(/\b[A-Za-z_][A-Za-z0-9_]*\b/g)) {
bindings.add(nameMatch[0]);
}
}
return bindings;
}
describe("message turn migration guardrails", () => {
@@ -73,4 +121,23 @@ describe("message turn migration guardrails", () => {
);
}
});
it("keeps plugin runtime files off deprecated reply-history map helpers", () => {
for (const file of listTsFiles("extensions")) {
if (file.includes(".test.") || file.endsWith(".test.ts")) {
continue;
}
if (legacyReplyHistoryCompatibilityFiles.has(file)) {
continue;
}
const source = readRepoFile(file);
const replyHistoryBindings = collectReplyHistoryBindings(source);
for (const helper of lowLevelHistoryHelpers) {
expect(
replyHistoryBindings.has(helper),
`${file} should use createChannelHistoryWindow instead of ${helper}`,
).toBe(false);
}
}
});
});

View File

@@ -7,6 +7,7 @@ export { resolveControlCommandGate } from "./command-auth.js";
export { formatPairingApproveHint } from "./channel-plugin-common.js";
export type { HistoryEntry } from "./reply-history.js";
export {
createChannelHistoryWindow,
buildPendingHistoryContextFromMap,
clearHistoryEntriesIfEnabled,
recordPendingHistoryEntryIfEnabled,

View File

@@ -1,8 +1,8 @@
/**
* Shared reply-history helpers for plugins that keep short per-thread context windows.
*
* Prefer `createChannelHistoryWindow` for message-turn code. The lower-level map helpers remain
* exported for older plugins and adapter bridges that have not migrated to the channel turn kernel.
* Prefer `createChannelHistoryWindow` for message-turn code. The lower-level map helpers are
* deprecated plugin compatibility exports; core internals still use them behind the facade.
*/
export type { HistoryEntry, HistoryMediaEntry } from "../auto-reply/reply/history.types.js";
export {

View File

@@ -551,11 +551,13 @@ describe("plugin-sdk subpath exports", () => {
"buildInboundHistoryFromMap",
"buildPendingHistoryContextFromMap",
"clearHistoryEntriesIfEnabled",
"createChannelHistoryWindow",
"recordPendingHistoryEntryIfEnabled",
]);
expectSourceMentions("mattermost", [
"buildPendingHistoryContextFromMap",
"clearHistoryEntriesIfEnabled",
"createChannelHistoryWindow",
"formatPairingApproveHint",
"recordPendingHistoryEntryIfEnabled",
"resolveControlCommandGate",