






















1+import { createHash } from "node:crypto";
12import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
23import os from "node:os";
34import path from "node:path";
@@ -60,6 +61,69 @@ const MATRIX_SUBAGENT_MISSING_HOOK_ERROR =
6061"thread=true is unavailable because no channel plugin registered subagent_spawning hooks.";
6162const MATRIX_QA_HOT_RELOAD_RESTART_DELAY_MS = 300_000;
626364+function matrixInboundDedupePluginStateKey(params: {
65+accountId: string;
66+eventId: string;
67+roomId: string;
68+}): string {
69+const accountId = params.accountId.trim() || "sut";
70+const digest = createHash("sha256")
71+.update(accountId)
72+.update("\0")
73+.update(params.roomId.trim())
74+.update("\0")
75+.update(params.eventId.trim())
76+.digest("hex");
77+return `${accountId}:${digest}`;
78+}
79+80+async function writeMatrixInboundDedupePluginStateEntry(params: {
81+accountId: string;
82+eventId: string;
83+roomId: string;
84+stateRoot: string;
85+}) {
86+const sqlite = await import("node:sqlite");
87+const databasePath = path.join(params.stateRoot, "state", "openclaw.sqlite");
88+await mkdir(path.dirname(databasePath), { recursive: true });
89+const db = new sqlite.DatabaseSync(databasePath);
90+try {
91+db.exec(`
92+ CREATE TABLE IF NOT EXISTS plugin_state_entries (
93+ plugin_id TEXT NOT NULL,
94+ namespace TEXT NOT NULL,
95+ entry_key TEXT NOT NULL,
96+ value_json TEXT NOT NULL,
97+ created_at INTEGER NOT NULL,
98+ expires_at INTEGER,
99+ PRIMARY KEY (plugin_id, namespace, entry_key)
100+ );
101+ `);
102+db.prepare(`
103+ INSERT INTO plugin_state_entries (
104+ plugin_id, namespace, entry_key, value_json, created_at, expires_at
105+ ) VALUES (?, ?, ?, ?, ?, ?)
106+ ON CONFLICT(plugin_id, namespace, entry_key) DO UPDATE SET
107+ value_json = excluded.value_json,
108+ created_at = excluded.created_at,
109+ expires_at = excluded.expires_at
110+ `).run(
111+"matrix",
112+"inbound-dedupe",
113+matrixInboundDedupePluginStateKey(params),
114+JSON.stringify({
115+roomId: params.roomId,
116+eventId: params.eventId,
117+ts: Date.now(),
118+}),
119+Date.now(),
120+null,
121+);
122+} finally {
123+db.close();
124+}
125+}
126+63127function requireMatrixQaScenario(id: string): (typeof MATRIX_QA_SCENARIOS)[number] {
64128const scenario = MATRIX_QA_SCENARIOS.find((entry) => entry.id === id);
65129if (!scenario) {
@@ -1958,7 +2022,6 @@ describe("matrix live qa scenarios", () => {
19582022const accountDir = path.join(stateRoot, "matrix", "accounts", "sut", "server", "token");
19592023const staleSyncRoomId = "!stale-sync:matrix-qa.test";
19602024const syncStorePath = path.join(accountDir, "bot-storage.json");
1961-const dedupeStorePath = path.join(accountDir, "inbound-dedupe.json");
19622025await mkdir(accountDir, { recursive: true });
19632026await writeTestJsonFile(path.join(accountDir, "storage-meta.json"), {
19642027accountId: "sut",
@@ -1983,14 +2046,11 @@ describe("matrix live qa scenarios", () => {
19832046const kind = token.includes("STALE_SYNC_DEDUPE_FRESH") ? "fresh" : "first";
19842047callOrder.push(`wait:${kind}`);
19852048if (kind === "first") {
1986-await writeTestJsonFile(dedupeStorePath, {
1987-version: 1,
1988-entries: [
1989-{
1990-key: `${staleSyncRoomId}|$first-trigger`,
1991-ts: Date.now(),
1992-},
1993-],
2049+await writeMatrixInboundDedupePluginStateEntry({
2050+accountId: "sut",
2051+eventId: "$first-trigger",
2052+roomId: staleSyncRoomId,
2053+ stateRoot,
19942054});
19952055}
19962056return {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。