





















@@ -1,4 +1,4 @@
1-import { readFileSync } from "node:fs";
1+import { readdirSync, readFileSync } from "node:fs";
22import path from "node:path";
33import { fileURLToPath } from "node:url";
44import { describe, expect, it } from "vitest";
@@ -43,15 +43,63 @@ const historyWindowFiles = [
43434444const lowLevelHistoryHelpers = [
4545"buildInboundHistoryFromMap",
46+"buildHistoryContextFromMap",
4647"buildPendingHistoryContextFromMap",
48+"clearHistoryEntries",
4749"clearHistoryEntriesIfEnabled",
4850"recordPendingHistoryEntry",
4951"recordPendingHistoryEntryIfEnabled",
5052"recordPendingHistoryEntryWithMedia",
5153];
525455+const legacyReplyHistoryCompatibilityFiles = new Set([
56+"extensions/mattermost/runtime-api.ts",
57+"extensions/mattermost/src/mattermost/runtime-api.ts",
58+"extensions/mattermost/src/runtime-api.ts",
59+]);
60+61+const skippedExtensionScanDirs = new Set([
62+".cache",
63+".turbo",
64+"build",
65+"coverage",
66+"dist",
67+"node_modules",
68+"tmp",
69+]);
70+5371function readRepoFile(relativePath: string): string {
54-return readFileSync(path.join(repoRoot, relativePath), "utf8");
72+return readFileSync(path.join(repoRoot, ...relativePath.split("/")), "utf8");
73+}
74+75+function listTsFiles(relativeDir: string): string[] {
76+const dir = path.join(repoRoot, ...relativeDir.split("/"));
77+return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
78+const relativePath = path.posix.join(relativeDir, entry.name);
79+if (entry.isDirectory()) {
80+if (skippedExtensionScanDirs.has(entry.name)) {
81+return [];
82+}
83+return listTsFiles(relativePath);
84+}
85+if (!entry.isFile() || !entry.name.endsWith(".ts") || entry.name.endsWith(".d.ts")) {
86+return [];
87+}
88+return [relativePath];
89+});
90+}
91+92+function collectReplyHistoryBindings(source: string): Set<string> {
93+const bindings = new Set<string>();
94+const importOrExportPattern =
95+/\b(?:import|export)\s*\{([\s\S]*?)\}\s*from\s*["']openclaw\/plugin-sdk\/reply-history["']/g;
96+for (const match of source.matchAll(importOrExportPattern)) {
97+const block = match[1] ?? "";
98+for (const nameMatch of block.matchAll(/\b[A-Za-z_][A-Za-z0-9_]*\b/g)) {
99+bindings.add(nameMatch[0]);
100+}
101+}
102+return bindings;
55103}
5610457105describe("message turn migration guardrails", () => {
@@ -73,4 +121,23 @@ describe("message turn migration guardrails", () => {
73121);
74122}
75123});
124+125+it("keeps plugin runtime files off deprecated reply-history map helpers", () => {
126+for (const file of listTsFiles("extensions")) {
127+if (file.includes(".test.") || file.endsWith(".test.ts")) {
128+continue;
129+}
130+if (legacyReplyHistoryCompatibilityFiles.has(file)) {
131+continue;
132+}
133+const source = readRepoFile(file);
134+const replyHistoryBindings = collectReplyHistoryBindings(source);
135+for (const helper of lowLevelHistoryHelpers) {
136+expect(
137+replyHistoryBindings.has(helper),
138+`${file} should use createChannelHistoryWindow instead of ${helper}`,
139+).toBe(false);
140+}
141+}
142+});
76143});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。