@openclaw/matrix 2026.7.2-beta.7 → 2026.8.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/{account-selection-kqquRzCf.js → account-selection-CWuhuFn1.js} +7 -8
  2. package/dist/{accounts-DfngqebB.js → accounts-BCzyubQt.js} +3 -3
  3. package/dist/api.js +7 -10
  4. package/dist/{approval-handler.runtime-DSeaIsGa.js → approval-handler.runtime-CJXCUdRg.js} +5 -5
  5. package/dist/{approval-ids-BdJGPAJT.js → approval-ids-7rf7etWE.js} +1 -1
  6. package/dist/{approval-reaction-auth-BjOpELxu.js → approval-reaction-auth-wBwbEDpZ.js} +2 -2
  7. package/dist/{approval-reactions-D-BxBK_L.js → approval-reactions-tXh9KYrW.js} +2 -8
  8. package/dist/auth-presence.js +2 -2
  9. package/dist/{channel-vj788dNZ.js → channel-VSrHgTON.js} +156 -136
  10. package/dist/channel-plugin-api.js +1 -1
  11. package/dist/{channel.runtime-CLoF2S9T.js → channel.runtime-pOOL0uT_.js} +70 -56
  12. package/dist/{config-schema-Djxw3C6b.js → channel.setup-PpxRP9Du.js} +50 -3
  13. package/dist/{cli-DMqfDE-q.js → cli-CkTfKC_c.js} +16 -16
  14. package/dist/{cli-metadata-C7D53SND.js → cli-metadata-B1d1aGM5.js} +1 -1
  15. package/dist/cli-metadata.js +1 -1
  16. package/dist/client-CeGw0G5S.js +784 -0
  17. package/dist/{client-bootstrap-XsIkHNt0.js → client-bootstrap-BFwT_2VT.js} +25 -61
  18. package/dist/{client-6eRENr7k.js → client-sJesKQmp.js} +4 -4
  19. package/dist/client-support-DLj4vDeu.js +93 -0
  20. package/dist/config-update-Bor_Pjsv.js +150 -0
  21. package/dist/contract-api.js +2 -2
  22. package/dist/{credentials-W2EmwRhu.js → credentials-BdAaWuiR.js} +2 -1
  23. package/dist/credentials-read-B37cQknZ.js +51 -0
  24. package/dist/credentials-state-B3cuUFwZ.js +27 -0
  25. package/dist/{credentials-write.runtime-Be9zNSUw.js → credentials-write.runtime-nAV2-5uR.js} +1 -1
  26. package/dist/{crypto-runtime-D9_6J5tZ.js → crypto-runtime-Bnz3zEcS.js} +55 -32
  27. package/dist/direct-room-RinHjtqX.js +71 -0
  28. package/dist/{directory-live-C8dVBiCc.js → directory-live-Bvg2wAnR.js} +1 -2
  29. package/dist/{doctor-CM8ijNpW.js → doctor-EWf9CDso.js} +2 -2
  30. package/dist/{doctor-contract-BMSJkkAE.js → doctor-contract-DEt4O4Zt.js} +2 -3
  31. package/dist/doctor-contract-api.js +202 -90
  32. package/dist/{draft-stream-CtvOXq0Z.js → draft-stream-BRi7PRRS.js} +20 -23
  33. package/dist/{encryption-guidance-CbEFe38F.js → encryption-guidance-DmLY8NiO.js} +1 -1
  34. package/dist/{env-auth-CQsYbz-p.js → env-auth-MVS-Vv41.js} +4 -3
  35. package/dist/{env-vars-B0NxQnPk.js → env-vars-dGqak9dN.js} +1 -1
  36. package/dist/{errors-C47hvAF8.js → errors-DKVS3_DO.js} +2 -5
  37. package/dist/{file-sync-store-DiiFsayX.js → file-sync-store-DjskBX2o.js} +24 -3
  38. package/dist/helper-api.js +2 -2
  39. package/dist/{idb-persistence-B6x7dMsF.js → idb-persistence-BM0P27Zg.js} +3 -2
  40. package/dist/{inbound-dedupe-CaBLblef.js → inbound-dedupe-RvSLqdsz.js} +2 -2
  41. package/dist/index.js +1 -1
  42. package/dist/{logger-D0GCSDQq.js → logger-B_71hN6W.js} +1 -1
  43. package/dist/{logging-DV-p52N0.js → logging-DRjvUOIh.js} +1 -1
  44. package/dist/{media-text-BTwzADS_.js → media-text-JudlYOSc.js} +1 -1
  45. package/dist/{messages-DxQJkhxs.js → messages-BW0s-6Y9.js} +7 -7
  46. package/dist/{monitor-VyWlSLwo.js → monitor-Ddfc-vjX.js} +394 -384
  47. package/dist/plugin-entry.handlers.runtime.js +3 -3
  48. package/dist/probe.runtime-Dr91B_X3.js +2 -0
  49. package/dist/profile-D-Wubo0A.js +108 -0
  50. package/dist/{profile-update-CffmyW_4.js → profile-update-BkgWYDIp.js} +3 -2
  51. package/dist/{reaction-events-B00WSi98.js → reaction-events-nfUbFLdV.js} +9 -6
  52. package/dist/{recovery-key-store-BNnHvXdk.js → recovery-key-store-CognuDys.js} +5 -4
  53. package/dist/{resolve-targets-nm2L48sh.js → resolve-targets-BJAPY4Vb.js} +1 -1
  54. package/dist/{resolver.runtime-DS6uPaEe.js → resolver.runtime-LWJELYq5.js} +1 -1
  55. package/dist/{room-info-CAFZ3Z9J.js → room-info-BXQA7y10.js} +12 -3
  56. package/dist/runtime-api.js +6 -3
  57. package/dist/{sdk-bUytQHx7.js → sdk-DjaQFCHc.js} +229 -120
  58. package/dist/{secret-contract-M3hRmGO-.js → secret-contract-CufozuyJ.js} +1 -1
  59. package/dist/secret-contract-api.js +1 -1
  60. package/dist/{send-BldGYG8g.js → send-ZDLU4JTk.js} +1462 -1196
  61. package/dist/session-binding-contract-api.js +1 -1
  62. package/dist/{setup-bootstrap-Deleiibz.js → setup-bootstrap-BaXkhgJg.js} +4 -4
  63. package/dist/{setup-core-BVfBv2ai.js → setup-core-DQn6b5vi.js} +17 -266
  64. package/dist/setup-plugin-api.js +1 -44
  65. package/dist/{setup-surface-BBq3WeqX.js → setup-surface-DlhJdrNI.js} +9 -11
  66. package/dist/{startup-verification-wm3_i88s.js → startup-verification-Cs08ca8u.js} +3 -3
  67. package/dist/{storage-BvAkgzvT.js → storage-BsGuHngK.js} +3 -3
  68. package/dist/{thread-bindings-DGbl3vDW.js → thread-bindings-CcHZP3w2.js} +7 -4
  69. package/dist/{tool-actions.runtime-uu9blSqO.js → tool-actions.runtime-DmdRL0bN.js} +35 -18
  70. package/dist/{verification-DYMQ1QpI.js → verification-C7ig-bQE.js} +2 -2
  71. package/openclaw.plugin.json +54 -1
  72. package/package.json +4 -4
  73. package/dist/active-client-DXvz2gCo.js +0 -20
  74. package/dist/client-Dbso3-0V.js +0 -30
  75. package/dist/create-client-BmSU-q-D.js +0 -61
  76. package/dist/credentials-read-DVmFCm7X.js +0 -77
  77. package/dist/exec-approval-resolver-BCgqRtZS.js +0 -16
  78. package/dist/preflight-audio.runtime-BZxxQsxY.js +0 -11
  79. package/dist/probe.runtime-BuieqmS3.js +0 -3
  80. package/dist/record-shared-CWWx6fvI.js +0 -2
  81. package/dist/shared-CUMWxcMz.js +0 -673
  82. package/dist/sync-state-CWbp0QSY.js +0 -12
  83. package/dist/{runtime-api-D1xI_cvh.js → runtime-api-BGO16Fu8.js} +3 -3
@@ -1,23 +1,20 @@
1
- import { a as resolveMatrixDefaultOrOnlyAccountId, n as requiresExplicitMatrixDefaultAccount } from "./account-selection-kqquRzCf.js";
2
- import { t as isRecord$1 } from "./record-shared-CWWx6fvI.js";
1
+ import { a as resolveMatrixDefaultOrOnlyAccountId, n as requiresExplicitMatrixDefaultAccount } from "./account-selection-CWuhuFn1.js";
2
+ import { i as normalizeMatrixStoredCredentials, n as isMatrixCredentialRevocation, r as matrixCredentialsStoreKey, t as MATRIX_CREDENTIALS_NAMESPACE } from "./credentials-state-B3cuUFwZ.js";
3
3
  import { r as resolveMatrixCredentialsDir } from "./storage-paths-gUmSydsW.js";
4
- import { a as isMatrixCredentialRevocation, c as normalizeMatrixStoredCredentials, s as matrixCredentialsStoreKey, t as MATRIX_CREDENTIALS_NAMESPACE } from "./credentials-read-DVmFCm7X.js";
5
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-BMSJkkAE.js";
6
- import { C as readLegacyMatrixRecoveryKeyState, O as writeMatrixIdbSnapshotJsonToStore, T as readMatrixIdbSnapshotJsonFromStore, a as openMatrixStorageMetaStoreOptions, b as openMatrixRecoveryKeyStoreOptions, f as MATRIX_IDB_SNAPSHOT_FILENAME, g as hasMatrixRecoveryKeyStateInStore, h as hasMatrixLegacyCryptoMigrationStateInStore, i as normalizeMatrixStorageMetadata, j as writeMatrixRecoveryKeyStateToStore, k as writeMatrixLegacyCryptoMigrationStateToStore, m as MATRIX_RECOVERY_KEY_FILENAME, n as hasMatrixStorageMetaStateInStore, p as MATRIX_LEGACY_CRYPTO_MIGRATION_FILENAME, u as writeMatrixStorageMetaStateToStore, v as openMatrixIdbSnapshotStoreOptions, x as readLegacyMatrixLegacyCryptoMigrationState, y as openMatrixLegacyCryptoMigrationStoreOptions } from "./storage-BvAkgzvT.js";
7
- import { a as readLegacyMatrixSyncCacheState, i as openMatrixSyncCacheStoreOptions, o as writeMatrixSyncCacheStateToStore, r as hasMatrixSyncCacheStateInStore } from "./file-sync-store-DiiFsayX.js";
4
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-DEt4O4Zt.js";
5
+ import { C as readLegacyMatrixRecoveryKeyState, O as writeMatrixIdbSnapshotJsonToStore, T as readMatrixIdbSnapshotJsonFromStore, a as openMatrixStorageMetaStoreOptions, b as openMatrixRecoveryKeyStoreOptions, f as MATRIX_IDB_SNAPSHOT_FILENAME, g as hasMatrixRecoveryKeyStateInStore, h as hasMatrixLegacyCryptoMigrationStateInStore, i as normalizeMatrixStorageMetadata, j as writeMatrixRecoveryKeyStateToStore, k as writeMatrixLegacyCryptoMigrationStateToStore, m as MATRIX_RECOVERY_KEY_FILENAME, n as hasMatrixStorageMetaStateInStore, p as MATRIX_LEGACY_CRYPTO_MIGRATION_FILENAME, u as writeMatrixStorageMetaStateToStore, v as openMatrixIdbSnapshotStoreOptions, x as readLegacyMatrixLegacyCryptoMigrationState, y as openMatrixLegacyCryptoMigrationStoreOptions } from "./storage-BsGuHngK.js";
6
+ import { a as readLegacyMatrixSyncCacheState, i as openMatrixSyncCacheStoreOptions, o as writeMatrixSyncCacheStateToStore, r as hasMatrixSyncCacheStateInStore } from "./file-sync-store-DjskBX2o.js";
8
7
  import { n as MATRIX_IDB_SNAPSHOT_LOCK_OPTIONS } from "./idb-persistence-lock-DAJ49nZX.js";
9
- import { r as readLegacyMatrixIdbSnapshotStateUnlocked, t as isValidMatrixIdbSnapshotJson } from "./idb-persistence-B6x7dMsF.js";
10
- import { a as resolveMatrixInboundDedupeStateNamespace, n as MATRIX_INBOUND_DEDUPE_TTL_MS, r as buildMatrixInboundDedupeEventKey } from "./inbound-dedupe-CaBLblef.js";
8
+ import { r as readLegacyMatrixIdbSnapshotStateUnlocked, t as isValidMatrixIdbSnapshotJson } from "./idb-persistence-BM0P27Zg.js";
11
9
  import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
12
10
  import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
13
- import { archiveLegacyStateSource } from "openclaw/plugin-sdk/runtime-doctor";
14
11
  import { createHash, randomUUID } from "node:crypto";
15
12
  import path from "node:path";
13
+ import { archiveLegacyStateSource } from "openclaw/plugin-sdk/runtime-doctor-migrations";
16
14
  import fs from "node:fs";
17
15
  import fs$1 from "node:fs/promises";
18
16
  import { isDeepStrictEqual } from "node:util";
19
17
  import { withFileLock } from "openclaw/plugin-sdk/file-lock";
20
- import { createPersistentDedupeImportEntry } from "openclaw/plugin-sdk/persistent-dedupe";
21
18
  //#region extensions/matrix/src/matrix/crypto-snapshot-doctor.ts
22
19
  async function migrateLegacyMatrixIdbSnapshot(params) {
23
20
  const snapshotPath = path.join(params.storageRootDir, MATRIX_IDB_SNAPSHOT_FILENAME);
@@ -101,21 +98,55 @@ async function archiveLegacyMatrixIdbSnapshot(params) {
101
98
  //#endregion
102
99
  //#region extensions/matrix/src/matrix/monitor/inbound-dedupe-migration.ts
103
100
  const LEGACY_SQLITE_NAMESPACE = "inbound-dedupe";
104
- const LEGACY_SQLITE_MAX_ENTRIES = 2e4;
105
101
  const LEGACY_MARKERS_NAMESPACE = "inbound-dedupe-migrations";
106
- const LEGACY_MARKERS_MAX_ENTRIES = 1e3;
107
102
  const LEGACY_JSON_VERSION = 1;
103
+ const MATRIX_PLUGIN_ID = "matrix";
104
+ const MIGRATION_COMPLETION_NAMESPACE = "inbound-dedupe-migration-state";
105
+ const MIGRATION_COMPLETION_KEY = "sqlite-json-to-claimable-v1";
106
+ const STATE_DATABASE_RELATIVE_PATH = path.join("state", "openclaw.sqlite");
108
107
  const STORAGE_META_FILENAME = "storage-meta.json";
109
108
  const MATRIX_LEGACY_INBOUND_DEDUPE_FILENAME = "inbound-dedupe.json";
109
+ function openMatrixInboundDedupeMigrationCompletionStore(context, env) {
110
+ return context.openPluginStateKeyedStore({
111
+ namespace: MIGRATION_COMPLETION_NAMESPACE,
112
+ maxEntries: 4,
113
+ overflowPolicy: "reject-new",
114
+ env
115
+ });
116
+ }
117
+ async function hasCompletedMatrixInboundDedupeMigration(context, env) {
118
+ const marker = await openMatrixInboundDedupeMigrationCompletionStore(context, env).lookup(MIGRATION_COMPLETION_KEY);
119
+ return isRecord(marker) && marker.version === 1 && typeof marker.completedAt === "number" && Number.isFinite(marker.completedAt) && marker.completedAt >= 0;
120
+ }
121
+ async function recordMatrixInboundDedupeMigrationCompletion(context, env) {
122
+ await openMatrixInboundDedupeMigrationCompletionStore(context, env).register(MIGRATION_COMPLETION_KEY, {
123
+ version: 1,
124
+ completedAt: Date.now()
125
+ });
126
+ }
127
+ /**
128
+ * Reserves the durable completion row before any legacy source is changed.
129
+ * The invalid timestamp keeps detection active after an interrupted run, while
130
+ * updating this same key after retirement remains possible at plugin capacity.
131
+ */
132
+ async function reserveMatrixInboundDedupeMigrationCompletion(context, env) {
133
+ await openMatrixInboundDedupeMigrationCompletionStore(context, env).register(MIGRATION_COMPLETION_KEY, {
134
+ version: 1,
135
+ completedAt: -1
136
+ });
137
+ }
110
138
  async function collectMatrixInboundDedupeSources(stateDir) {
111
139
  const matrixRoot = path.join(stateDir, "matrix");
112
140
  const sqliteRoots = /* @__PURE__ */ new Set();
113
141
  const jsonRoots = /* @__PURE__ */ new Set();
114
- async function visit(dir) {
142
+ const warnings = [];
143
+ async function visit(dir, allowMissing = false) {
115
144
  let entries;
116
145
  try {
117
146
  entries = await fs$1.readdir(dir, { withFileTypes: true });
118
- } catch {
147
+ } catch (err) {
148
+ if (allowMissing && err.code === "ENOENT") return;
149
+ warnings.push(`Failed scanning Matrix inbound dedupe sources under ${dir}: ${String(err)}`);
119
150
  return;
120
151
  }
121
152
  for (const entry of entries) {
@@ -128,40 +159,41 @@ async function collectMatrixInboundDedupeSources(stateDir) {
128
159
  if (entry.isDirectory()) await visit(entryPath);
129
160
  }
130
161
  }
131
- await visit(matrixRoot);
162
+ await visit(matrixRoot, true);
132
163
  const matrixRootResolved = path.resolve(matrixRoot);
133
164
  const isAccountRoot = (root) => path.resolve(root) !== matrixRootResolved;
134
- return {
165
+ const roots = {
135
166
  sqliteRoots: [...sqliteRoots].filter(isAccountRoot).toSorted(),
136
167
  jsonRoots: [...jsonRoots].filter(isAccountRoot).toSorted()
137
168
  };
169
+ return warnings.length === 0 ? {
170
+ status: "complete",
171
+ ...roots
172
+ } : {
173
+ status: "incomplete",
174
+ ...roots,
175
+ warnings
176
+ };
138
177
  }
139
- function openLegacySqliteStore(io, storageRootDir) {
140
- return io.context.openPluginStateKeyedStore({
141
- namespace: LEGACY_SQLITE_NAMESPACE,
142
- maxEntries: LEGACY_SQLITE_MAX_ENTRIES,
143
- env: {
144
- ...io.env,
145
- OPENCLAW_STATE_DIR: storageRootDir
146
- }
147
- });
178
+ function selectLegacySqliteRows(db) {
179
+ if (!db.prepare(`SELECT 1 AS present
180
+ FROM sqlite_master
181
+ WHERE type = 'table' AND name = 'plugin_state_entries'`).get()) return [];
182
+ return db.prepare(`SELECT namespace, entry_key, value_json, expires_at
183
+ FROM plugin_state_entries
184
+ WHERE plugin_id = ? AND namespace IN (?, ?)
185
+ ORDER BY namespace ASC, created_at ASC, entry_key ASC`).all(MATRIX_PLUGIN_ID, LEGACY_SQLITE_NAMESPACE, LEGACY_MARKERS_NAMESPACE);
148
186
  }
149
- function openLegacyMarkersStore(io, storageRootDir) {
150
- return io.context.openPluginStateKeyedStore({
151
- namespace: LEGACY_MARKERS_NAMESPACE,
152
- maxEntries: LEGACY_MARKERS_MAX_ENTRIES,
153
- env: {
154
- ...io.env,
155
- OPENCLAW_STATE_DIR: storageRootDir
156
- }
157
- });
187
+ function isLegacySqliteRowExpired(row, now) {
188
+ if (typeof row.expires_at === "bigint") return row.expires_at <= BigInt(now);
189
+ return row.expires_at !== null && row.expires_at <= now;
158
190
  }
159
191
  function normalizeLegacyTimestamp(raw) {
160
192
  if (typeof raw !== "number" || !Number.isFinite(raw)) return null;
161
193
  return Math.max(0, Math.floor(raw));
162
194
  }
163
195
  function parseLegacySqliteRow(row) {
164
- const value = isRecord$1(row.value) ? row.value : {};
196
+ const value = isRecord(row.value) ? row.value : {};
165
197
  const roomId = typeof value.roomId === "string" ? value.roomId.trim() : "";
166
198
  const eventId = typeof value.eventId === "string" ? value.eventId.trim() : "";
167
199
  const ts = normalizeLegacyTimestamp(value.ts);
@@ -177,24 +209,63 @@ function parseLegacySqliteRow(row) {
177
209
  ts
178
210
  };
179
211
  }
180
- /** Reads one storage root's legacy SQLite dedupe rows; throws on store errors. */
181
- async function readLegacyInboundDedupeSqliteSource(io, storageRootDir) {
182
- const rows = await openLegacySqliteStore(io, storageRootDir).entries();
183
- const markerRows = await openLegacyMarkersStore(io, storageRootDir).entries();
184
- const markers = [];
185
- for (const row of rows) {
186
- const marker = parseLegacySqliteRow(row);
187
- if (marker) markers.push(marker);
212
+ /**
213
+ * Reads one storage root's legacy SQLite dedupe rows without opening it through
214
+ * the current state runtime. Historical per-account databases can predate the
215
+ * current core schema, and detection must not upgrade them merely to inspect
216
+ * these retired plugin-state namespaces.
217
+ */
218
+ async function readLegacyInboundDedupeSqliteSource(storageRootDir) {
219
+ const { openNodeSqliteDatabase } = await import("openclaw/plugin-sdk/sqlite-runtime");
220
+ const db = openNodeSqliteDatabase(path.join(storageRootDir, STATE_DATABASE_RELATIVE_PATH), { readOnly: true });
221
+ try {
222
+ const rows = selectLegacySqliteRows(db);
223
+ const markers = [];
224
+ const now = Date.now();
225
+ for (const row of rows) {
226
+ if (isLegacySqliteRowExpired(row, now)) continue;
227
+ const value = JSON.parse(row.value_json);
228
+ if (row.namespace !== LEGACY_SQLITE_NAMESPACE) continue;
229
+ const marker = parseLegacySqliteRow({
230
+ key: row.entry_key,
231
+ value
232
+ });
233
+ if (marker) markers.push(marker);
234
+ }
235
+ return {
236
+ markers,
237
+ legacyRowCount: rows.length
238
+ };
239
+ } finally {
240
+ db.close();
241
+ }
242
+ }
243
+ /** Deletes only the two retired Matrix namespaces after a successful import. */
244
+ async function retireLegacyInboundDedupeSqliteRows(storageRootDir) {
245
+ const { openNodeSqliteDatabase, runSqliteImmediateTransactionSync } = await import("openclaw/plugin-sdk/sqlite-runtime");
246
+ const db = openNodeSqliteDatabase(path.join(storageRootDir, STATE_DATABASE_RELATIVE_PATH));
247
+ try {
248
+ if (selectLegacySqliteRows(db).length === 0) return;
249
+ runSqliteImmediateTransactionSync(db, () => {
250
+ if (selectLegacySqliteRows(db).length === 0) return;
251
+ db.prepare(`DELETE FROM plugin_state_entries
252
+ WHERE plugin_id = ? AND namespace IN (?, ?)`).run(MATRIX_PLUGIN_ID, LEGACY_SQLITE_NAMESPACE, LEGACY_MARKERS_NAMESPACE);
253
+ });
254
+ } finally {
255
+ db.close();
188
256
  }
189
- return {
190
- markers,
191
- legacyRowCount: rows.length + markerRows.length
192
- };
193
257
  }
194
- /** Clears one storage root's retired legacy dedupe namespaces after import. */
195
- async function retireLegacyInboundDedupeSqliteRows(io, storageRootDir) {
196
- await openLegacySqliteStore(io, storageRootDir).clear();
197
- await openLegacyMarkersStore(io, storageRootDir).clear();
258
+ async function verifyMatrixInboundDedupeSourcesRetired(stateDir) {
259
+ const warnings = [];
260
+ const remaining = await collectMatrixInboundDedupeSources(stateDir);
261
+ if (remaining.status === "incomplete") warnings.push(...remaining.warnings);
262
+ for (const storageRootDir of remaining.sqliteRoots) try {
263
+ if ((await readLegacyInboundDedupeSqliteSource(storageRootDir)).legacyRowCount > 0) warnings.push(`Matrix inbound dedupe rows remain after retirement for ${storageRootDir}`);
264
+ } catch (err) {
265
+ warnings.push(`Failed verifying retired Matrix inbound dedupe rows for ${storageRootDir}: ${String(err)}`);
266
+ }
267
+ for (const storageRootDir of remaining.jsonRoots) warnings.push(`Matrix inbound dedupe JSON remains after retirement for ${storageRootDir}`);
268
+ return warnings;
198
269
  }
199
270
  async function resolveJsonRootAccountId(storageRootDir) {
200
271
  for (const filename of [STORAGE_META_FILENAME, `${STORAGE_META_FILENAME}.migrated`]) try {
@@ -217,11 +288,11 @@ async function readLegacyInboundDedupeJsonSource(storageRootDir) {
217
288
  } catch {
218
289
  return null;
219
290
  }
220
- if (!isRecord$1(parsed) || parsed.version !== LEGACY_JSON_VERSION || !Array.isArray(parsed.entries)) return null;
291
+ if (!isRecord(parsed) || parsed.version !== LEGACY_JSON_VERSION || !Array.isArray(parsed.entries)) return null;
221
292
  const accountId = await resolveJsonRootAccountId(storageRootDir);
222
293
  const markers = [];
223
294
  for (const entry of parsed.entries) {
224
- if (!isRecord$1(entry) || typeof entry.key !== "string") continue;
295
+ if (!isRecord(entry) || typeof entry.key !== "string") continue;
225
296
  const separator = entry.key.indexOf("|");
226
297
  if (separator <= 0) continue;
227
298
  const roomId = entry.key.slice(0, separator).trim();
@@ -239,14 +310,16 @@ async function readLegacyInboundDedupeJsonSource(storageRootDir) {
239
310
  }
240
311
  /**
241
312
  * Imports the globally newest legacy markers into the claimable-dedupe store.
242
- * Never exceeds capacity: eviction is by row creation time, so letting fresh
243
- * imports overflow the namespace would evict the newer rows the runtime
244
- * committed since the upgrade. Throws on store errors so the caller keeps the
245
- * legacy sources for the next doctor attempt.
313
+ * Never exceeds capacity, never replaces a post-upgrade destination row, and
314
+ * preserves source timestamps because eviction is ordered by row creation
315
+ * time. Throws on import or verification errors so the caller keeps the legacy
316
+ * sources for the next doctor attempt.
246
317
  */
247
318
  async function importNewestInboundDedupeMarkers(params) {
319
+ const { createPersistentDedupeImportEntry } = await import("openclaw/plugin-sdk/persistent-dedupe");
320
+ const { buildMatrixInboundDedupeEventKey, MATRIX_INBOUND_DEDUPE_STATE_MAX_ENTRIES, MATRIX_INBOUND_DEDUPE_TTL_MS, resolveMatrixInboundDedupeStateNamespace } = await import("./inbound-dedupe-RvSLqdsz.js");
248
321
  const now = params.now ?? Date.now();
249
- const stateMaxEntries = params.stateMaxEntries ?? 2e4;
322
+ const stateMaxEntries = params.stateMaxEntries ?? MATRIX_INBOUND_DEDUPE_STATE_MAX_ENTRIES;
250
323
  const newestByKey = /* @__PURE__ */ new Map();
251
324
  for (const marker of params.markers) {
252
325
  const key = buildMatrixInboundDedupeEventKey(marker);
@@ -264,24 +337,47 @@ async function importNewestInboundDedupeMarkers(params) {
264
337
  defaultTtlMs: MATRIX_INBOUND_DEDUPE_TTL_MS,
265
338
  env: params.io.env
266
339
  });
267
- let capacity = Math.max(0, stateMaxEntries - (await store.entries()).length);
268
- let imported = 0;
269
- for (const marker of markers) {
270
- if (capacity <= 0) break;
340
+ const existingEntries = await store.entries();
341
+ const existingKeys = new Set(existingEntries.map((entry) => entry.key));
342
+ const missingEntries = markers.flatMap((marker) => {
271
343
  const remainingTtlMs = MATRIX_INBOUND_DEDUPE_TTL_MS - (now - marker.ts);
272
- if (remainingTtlMs <= 0) continue;
344
+ if (remainingTtlMs <= 0) return [];
273
345
  const entry = createPersistentDedupeImportEntry({
274
346
  key: marker.key,
275
347
  seenAt: marker.ts,
276
348
  ttlMs: Math.max(1, Math.floor(remainingTtlMs))
277
349
  });
278
- if (await store.registerIfAbsent(entry.key, entry.value, { ttlMs: entry.ttlMs ?? 2592e6 })) {
279
- imported += 1;
280
- capacity -= 1;
281
- }
350
+ return existingKeys.has(entry.key) ? [] : [{
351
+ marker,
352
+ entry
353
+ }];
354
+ });
355
+ const namespaceCapacity = Math.max(0, stateMaxEntries - existingEntries.length);
356
+ const pluginCapacity = missingEntries.length > 0 ? params.io.context.getPluginStateCapacity?.() : void 0;
357
+ if (missingEntries.length > 0 && !pluginCapacity) throw new Error("plugin-wide Matrix inbound dedupe import capacity is unavailable");
358
+ const pluginRemainingCapacity = pluginCapacity ? Math.max(0, pluginCapacity.maxEntries - pluginCapacity.liveEntries) : namespaceCapacity;
359
+ const capacity = Math.min(namespaceCapacity, pluginRemainingCapacity);
360
+ const selectedEntries = missingEntries.slice(0, capacity);
361
+ if (selectedEntries.length > 0) {
362
+ const importEntries = params.io.context.importPluginStateEntries;
363
+ if (!importEntries) throw new Error("retention-aware Matrix inbound dedupe import is unavailable");
364
+ importEntries({
365
+ namespace: resolveMatrixInboundDedupeStateNamespace(),
366
+ maxEntries: stateMaxEntries,
367
+ defaultTtlMs: MATRIX_INBOUND_DEDUPE_TTL_MS,
368
+ env: params.io.env
369
+ }, selectedEntries.map(({ marker, entry }) => ({
370
+ key: entry.key,
371
+ value: entry.value,
372
+ createdAt: marker.ts,
373
+ ...entry.ttlMs != null ? { ttlMs: entry.ttlMs } : {}
374
+ })));
375
+ const importedKeys = new Set((await store.entries()).map((entry) => entry.key));
376
+ const missingKey = selectedEntries.find(({ entry }) => !importedKeys.has(entry.key))?.entry.key;
377
+ if (missingKey) throw new Error(`retention-aware Matrix inbound dedupe import did not persist ${missingKey}`);
282
378
  }
283
379
  return {
284
- imported,
380
+ imported: selectedEntries.length,
285
381
  total: markers.length
286
382
  };
287
383
  }
@@ -444,22 +540,7 @@ const stateMigrations = [
444
540
  id: "matrix-inbound-dedupe-to-claimable-dedupe",
445
541
  label: "Matrix inbound dedupe markers",
446
542
  async detectLegacyState(params) {
447
- const io = {
448
- context: params.context,
449
- env: params.env
450
- };
451
- const preview = [];
452
- const sources = await collectMatrixInboundDedupeSources(params.stateDir);
453
- for (const storageRootDir of sources.sqliteRoots) {
454
- try {
455
- if ((await readLegacyInboundDedupeSqliteSource(io, storageRootDir)).legacyRowCount === 0) continue;
456
- } catch {
457
- continue;
458
- }
459
- preview.push(`Matrix inbound dedupe rows can migrate to the claimable dedupe store: ${storageRootDir}`);
460
- }
461
- for (const storageRootDir of sources.jsonRoots) preview.push(`Matrix inbound dedupe JSON can migrate to the claimable dedupe store: ${path.join(storageRootDir, MATRIX_LEGACY_INBOUND_DEDUPE_FILENAME)}`);
462
- return preview.length > 0 ? { preview } : null;
543
+ return await hasCompletedMatrixInboundDedupeMigration(params.context, params.env) ? null : { preview: ["Matrix inbound dedupe legacy sources need a one-time migration scan"] };
463
544
  },
464
545
  async migrateLegacyState(params) {
465
546
  const io = {
@@ -468,11 +549,38 @@ const stateMigrations = [
468
549
  };
469
550
  const changes = [];
470
551
  const warnings = [];
552
+ if (await hasCompletedMatrixInboundDedupeMigration(params.context, params.env)) return {
553
+ changes,
554
+ warnings
555
+ };
556
+ try {
557
+ await reserveMatrixInboundDedupeMigrationCompletion(params.context, params.env);
558
+ } catch (err) {
559
+ warnings.push(`Failed reserving Matrix inbound dedupe migration completion: ${String(err)}; left legacy sources in place`);
560
+ return {
561
+ changes,
562
+ warnings
563
+ };
564
+ }
471
565
  const sources = await collectMatrixInboundDedupeSources(params.stateDir);
566
+ if (sources.status === "incomplete") warnings.push(...sources.warnings);
567
+ const recordCompletionIfClean = async (verifyRetirement = false) => {
568
+ if (warnings.length > 0) return;
569
+ if (verifyRetirement) {
570
+ warnings.push(...await verifyMatrixInboundDedupeSourcesRetired(params.stateDir));
571
+ if (warnings.length > 0) return;
572
+ }
573
+ try {
574
+ await recordMatrixInboundDedupeMigrationCompletion(params.context, params.env);
575
+ if (sources.sqliteRoots.length + sources.jsonRoots.length > 0) changes.push(`Recorded Matrix inbound dedupe migration completion (${sources.sqliteRoots.length} SQLite roots, ${sources.jsonRoots.length} JSON roots scanned)`);
576
+ } catch (err) {
577
+ warnings.push(`Failed recording Matrix inbound dedupe migration completion: ${String(err)}`);
578
+ }
579
+ };
472
580
  const gathered = [];
473
581
  const sqliteRootsToRetire = [];
474
582
  for (const storageRootDir of sources.sqliteRoots) try {
475
- const source = await readLegacyInboundDedupeSqliteSource(io, storageRootDir);
583
+ const source = await readLegacyInboundDedupeSqliteSource(storageRootDir);
476
584
  if (source.legacyRowCount === 0) continue;
477
585
  gathered.push(...source.markers);
478
586
  sqliteRootsToRetire.push(storageRootDir);
@@ -488,10 +596,13 @@ const stateMigrations = [
488
596
  } catch (err) {
489
597
  warnings.push(`Failed reading Matrix inbound dedupe JSON for ${storageRootDir}: ${String(err)}; left legacy file in place`);
490
598
  }
491
- if (sqliteRootsToRetire.length + jsonRootsToRetire.length === 0) return {
492
- changes,
493
- warnings
494
- };
599
+ if (sqliteRootsToRetire.length + jsonRootsToRetire.length === 0) {
600
+ await recordCompletionIfClean();
601
+ return {
602
+ changes,
603
+ warnings
604
+ };
605
+ }
495
606
  try {
496
607
  const result = await importNewestInboundDedupeMarkers({
497
608
  io,
@@ -506,7 +617,7 @@ const stateMigrations = [
506
617
  };
507
618
  }
508
619
  for (const storageRootDir of sqliteRootsToRetire) try {
509
- await retireLegacyInboundDedupeSqliteRows(io, storageRootDir);
620
+ await retireLegacyInboundDedupeSqliteRows(storageRootDir);
510
621
  changes.push(`Retired Matrix inbound dedupe rows for ${storageRootDir}`);
511
622
  } catch (err) {
512
623
  warnings.push(`Failed retiring Matrix inbound dedupe rows for ${storageRootDir}: ${String(err)}`);
@@ -518,6 +629,7 @@ const stateMigrations = [
518
629
  changes,
519
630
  warnings
520
631
  });
632
+ await recordCompletionIfClean(true);
521
633
  return {
522
634
  changes,
523
635
  warnings
@@ -1,5 +1,5 @@
1
- import { _ as MsgType, a as sendSingleTextMessageMatrix, t as editMessageMatrix, y as prepareMatrixSingleText } from "./send-BldGYG8g.js";
2
- import { createDraftStreamLoop } from "openclaw/plugin-sdk/channel-outbound";
1
+ import { E as MsgType, a as sendSingleTextMessageMatrix, l as prepareMatrixSingleText, t as editMessageMatrix } from "./send-ZDLU4JTk.js";
2
+ import { createFinalizableDraftStreamControlsForState } from "openclaw/plugin-sdk/channel-outbound";
3
3
  //#region extensions/matrix/src/matrix/draft-stream.ts
4
4
  const DEFAULT_THROTTLE_MS = 1e3;
5
5
  function resolveDraftPreviewOptions(mode) {
@@ -19,22 +19,26 @@ function createMatrixDraftStream(params) {
19
19
  let currentEventId;
20
20
  let lastSentText = "";
21
21
  let lastSentContent = "";
22
- let stopped = false;
22
+ const streamState = {
23
+ stopped: false,
24
+ final: false
25
+ };
23
26
  let sendFailed = false;
24
27
  let finalizeInPlaceBlocked = false;
25
28
  let liveFinalized = false;
26
29
  let replyToId = params.replyToId;
27
30
  const sendOrEdit = async (text) => {
28
31
  const trimmed = text.trimEnd();
29
- if (!trimmed) return false;
32
+ if (!trimmed.trim()) return false;
30
33
  const preparedText = prepareMatrixSingleText(trimmed, {
31
34
  cfg,
32
- accountId
35
+ accountId,
36
+ preserveWhitespace: true
33
37
  });
34
38
  if (!preparedText.fitsInSingleEvent) {
35
39
  finalizeInPlaceBlocked = true;
36
40
  if (!currentEventId) sendFailed = true;
37
- stopped = true;
41
+ streamState.stopped = true;
38
42
  log?.(`draft-stream: preview exceeded single-event limit (${preparedText.convertedText.length} > ${preparedText.singleEventLimit})`);
39
43
  return false;
40
44
  }
@@ -73,13 +77,13 @@ function createMatrixDraftStream(params) {
73
77
  log?.(`draft-stream: send/edit failed: ${String(err)}`);
74
78
  if (err instanceof Error && err.message.startsWith("Matrix single-message text exceeds limit")) finalizeInPlaceBlocked = true;
75
79
  if (!currentEventId) sendFailed = true;
76
- stopped = true;
80
+ streamState.stopped = true;
77
81
  return false;
78
82
  }
79
83
  };
80
- const loop = createDraftStreamLoop({
84
+ const { loop, update, stop: stopDraft, discardPending } = createFinalizableDraftStreamControlsForState({
81
85
  throttleMs: DEFAULT_THROTTLE_MS,
82
- isStopped: () => stopped,
86
+ state: streamState,
83
87
  sendOrEditStreamMessage: sendOrEdit
84
88
  });
85
89
  log?.(`draft-stream: ready (throttleMs=${DEFAULT_THROTTLE_MS})`);
@@ -107,21 +111,16 @@ function createMatrixDraftStream(params) {
107
111
  return true;
108
112
  };
109
113
  const stop = async () => {
110
- await loop.flush();
111
- stopped = true;
114
+ await stopDraft();
112
115
  return currentEventId;
113
116
  };
114
- const discardPending = async () => {
115
- stopped = true;
116
- loop.stop();
117
- await loop.waitForInFlight();
118
- };
119
117
  const reset = () => {
120
118
  replyToId = params.preserveReplyId ? params.replyToId : void 0;
121
119
  currentEventId = void 0;
122
120
  lastSentText = "";
123
121
  lastSentContent = "";
124
- stopped = false;
122
+ streamState.stopped = false;
123
+ streamState.final = false;
125
124
  sendFailed = false;
126
125
  finalizeInPlaceBlocked = false;
127
126
  liveFinalized = false;
@@ -129,10 +128,7 @@ function createMatrixDraftStream(params) {
129
128
  loop.resetThrottleWindow();
130
129
  };
131
130
  return {
132
- update: (text) => {
133
- if (stopped) return;
134
- loop.update(text);
135
- },
131
+ update,
136
132
  flush: loop.flush,
137
133
  stop,
138
134
  discardPending,
@@ -140,9 +136,10 @@ function createMatrixDraftStream(params) {
140
136
  reset,
141
137
  eventId: () => currentEventId,
142
138
  content: () => lastSentContent || void 0,
143
- matchesPreparedText: (text) => prepareMatrixSingleText(text, {
139
+ matchesPreparedText: (text) => prepareMatrixSingleText(text.trimEnd(), {
144
140
  cfg,
145
- accountId
141
+ accountId,
142
+ preserveWhitespace: true
146
143
  }).trimmedText === lastSentText,
147
144
  mustDeliverFinalNormally: () => sendFailed || finalizeInPlaceBlocked
148
145
  };
@@ -1,4 +1,4 @@
1
- import { a as resolveMatrixDefaultOrOnlyAccountId } from "./account-selection-kqquRzCf.js";
1
+ import { a as resolveMatrixDefaultOrOnlyAccountId } from "./account-selection-CWuhuFn1.js";
2
2
  import { t as resolveMatrixConfigFieldPath } from "./config-paths-CnREYb1Y.js";
3
3
  import { normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-id";
4
4
  //#region extensions/matrix/src/matrix/encryption-guidance.ts
@@ -1,8 +1,9 @@
1
- import { t as getMatrixScopedEnvVarNames } from "./env-vars-B0NxQnPk.js";
1
+ import { t as getMatrixScopedEnvVarNames } from "./env-vars-dGqak9dN.js";
2
+ import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
2
3
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
3
4
  //#region extensions/matrix/src/matrix/client/env-auth.ts
4
5
  function cleanEnv(value) {
5
- return typeof value === "string" ? value.trim() : "";
6
+ return normalizeOptionalString(value) ?? "";
6
7
  }
7
8
  function resolveGlobalMatrixEnvConfig(env) {
8
9
  return {
@@ -60,4 +61,4 @@ function resolveMatrixEnvAuthReadiness(accountId, env = process.env) {
60
61
  };
61
62
  }
62
63
  //#endregion
63
- export { resolveScopedMatrixEnvConfig as i, resolveGlobalMatrixEnvConfig as n, resolveMatrixEnvAuthReadiness as r, hasReadyMatrixEnvAuth as t };
64
+ export { resolveMatrixEnvAuthReadiness as n, resolveScopedMatrixEnvConfig as r, resolveGlobalMatrixEnvConfig as t };
@@ -31,7 +31,7 @@ function decodeMatrixEnvAccountToken(token) {
31
31
  if (hexEscape) {
32
32
  const hex = hexEscape[1];
33
33
  const codePoint = hex ? Number.parseInt(hex, 16) : NaN;
34
- if (!Number.isFinite(codePoint)) return;
34
+ if (!Number.isInteger(codePoint) || codePoint > 1114111) return;
35
35
  decoded += String.fromCodePoint(codePoint);
36
36
  index += hexEscape[0].length;
37
37
  continue;
@@ -1,11 +1,8 @@
1
1
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
2
2
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
3
3
  //#region extensions/matrix/src/matrix/errors.ts
4
- function formatMatrixErrorMessage(err) {
5
- return formatErrorMessage(err);
6
- }
7
4
  function formatMatrixErrorReason(err) {
8
- return normalizeLowercaseStringOrEmpty(formatMatrixErrorMessage(err));
5
+ return normalizeLowercaseStringOrEmpty(formatErrorMessage(err));
9
6
  }
10
7
  function isMatrixNotFoundError(err) {
11
8
  const errObj = err;
@@ -14,4 +11,4 @@ function isMatrixNotFoundError(err) {
14
11
  return message.includes("m_not_found") || message.includes("[404]") || message.includes("not found");
15
12
  }
16
13
  //#endregion
17
- export { formatMatrixErrorReason as n, isMatrixNotFoundError as r, formatMatrixErrorMessage as t };
14
+ export { isMatrixNotFoundError as n, formatMatrixErrorReason as t };
@@ -1,8 +1,8 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { t as isRecord } from "./record-shared-CWWx6fvI.js";
3
2
  import { t as getMatrixRuntime } from "./runtime-6S3DNFNv.js";
4
- import { M as resolveMatrixSqliteStateEnv, t as claimCurrentTokenStorageState } from "./storage-BvAkgzvT.js";
5
- import { n as LogService } from "./logger-D0GCSDQq.js";
3
+ import { M as resolveMatrixSqliteStateEnv, t as claimCurrentTokenStorageState } from "./storage-BsGuHngK.js";
4
+ import { n as LogService } from "./logger-B_71hN6W.js";
5
+ import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  import { createHash, randomUUID } from "node:crypto";
7
7
  import path from "node:path";
8
8
  import fs from "node:fs/promises";
@@ -108,6 +108,7 @@ var SqliteBackedMatrixSyncStore = class extends MemoryStore {
108
108
  this.savedSync = null;
109
109
  this.cleanShutdown = false;
110
110
  this.dirty = false;
111
+ this.frozen = false;
111
112
  this.persistTimer = null;
112
113
  this.persistPromise = null;
113
114
  this.stateKey = SYNC_CACHE_STATE_KEY;
@@ -154,6 +155,7 @@ var SqliteBackedMatrixSyncStore = class extends MemoryStore {
154
155
  return Promise.resolve(this.savedSync?.nextBatch ?? null);
155
156
  }
156
157
  setSyncData(syncData) {
158
+ if (this.frozen) return Promise.resolve();
157
159
  this.accumulator.accumulate(syncData);
158
160
  this.savedSync = this.accumulator.getJSON();
159
161
  this.markDirtyAndSchedulePersist();
@@ -163,6 +165,7 @@ var SqliteBackedMatrixSyncStore = class extends MemoryStore {
163
165
  return Promise.resolve(this.savedClientOptions ? cloneJson(this.savedClientOptions) : void 0);
164
166
  }
165
167
  storeClientOptions(options) {
168
+ if (this.frozen) return Promise.resolve();
166
169
  this.savedClientOptions = cloneJson(options);
167
170
  super.storeClientOptions(options);
168
171
  this.markDirtyAndSchedulePersist();
@@ -195,6 +198,23 @@ var SqliteBackedMatrixSyncStore = class extends MemoryStore {
195
198
  this.cleanShutdown = true;
196
199
  this.dirty = true;
197
200
  }
201
+ async freezeSyncCursorPersistence() {
202
+ this.frozen = true;
203
+ if (this.persistTimer) {
204
+ clearTimeout(this.persistTimer);
205
+ this.persistTimer = null;
206
+ }
207
+ await this.persistPromise;
208
+ }
209
+ discardPendingSyncCursorPersistence() {
210
+ this.frozen = true;
211
+ if (this.persistTimer) {
212
+ clearTimeout(this.persistTimer);
213
+ this.persistTimer = null;
214
+ }
215
+ this.cleanShutdown = false;
216
+ this.dirty = false;
217
+ }
198
218
  async flush() {
199
219
  if (this.persistTimer) {
200
220
  clearTimeout(this.persistTimer);
@@ -208,6 +228,7 @@ var SqliteBackedMatrixSyncStore = class extends MemoryStore {
208
228
  }
209
229
  }
210
230
  markDirtyAndSchedulePersist() {
231
+ if (this.frozen) return;
211
232
  this.cleanShutdown = false;
212
233
  this.dirty = true;
213
234
  if (this.persistTimer) return;