@openclaw/imessage 2026.9.6 → 2026.9.8

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 (81) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/.setup/{accounts-CUxZrTcY.mjs → accounts-nzcU0cq2.mjs} +3 -6
  3. package/dist/.setup/{actions.runtime-D0zG_96v.mjs → actions.runtime-DdBN29zj.mjs} +3 -5
  4. package/dist/.setup/{api-RZv6yXul.mjs → api-B9Qx21G-.mjs} +6 -5
  5. package/dist/.setup/{api-VLVcFh7r.mjs → api-D2S6FhGw.mjs} +7 -7
  6. package/dist/.setup/{approval-handler.runtime-Bv5_XgNr.mjs → approval-handler.runtime-DKSq0DWq.mjs} +8 -8
  7. package/dist/.setup/{approval-reactions-D6RFg6lU.mjs → approval-reactions-Bp33xc83.mjs} +5 -5
  8. package/dist/.setup/{channel-BLTaKdSK.mjs → channel-9fyvuN_p.mjs} +97 -152
  9. package/dist/.setup/channel.runtime-n-MPKL8k.mjs +92 -0
  10. package/dist/.setup/{config-schema-CCbTcJEV.mjs → config-schema-4Q3_EqYt.mjs} +9 -1
  11. package/dist/.setup/{group-policy-Cjztkquv.mjs → group-policy-90pw4tyG.mjs} +48 -334
  12. package/dist/.setup/{media-contract-DKJK7rh3.mjs → media-contract-CagwSMvo.mjs} +1 -1
  13. package/dist/.setup/{message-tool-api-pvlcOJPG.mjs → message-tool-api-D032l82H.mjs} +3 -18
  14. package/dist/.setup/{monitor-BJ3Ozi6I.mjs → monitor-fv9nRuTH.mjs} +211 -280
  15. package/dist/.setup/{question-reactions-lm8OxWbb.mjs → question-reactions-Cy-dmU_j.mjs} +1 -1
  16. package/dist/.setup/{remote-file-Bye99O-C.mjs → remote-file-DRPCaVmg.mjs} +70 -21
  17. package/dist/.setup/rolldown-runtime-BBjsoOtd.mjs +27 -0
  18. package/dist/.setup/{sanitize-outbound-sJPF2DVM.mjs → sanitize-outbound-3MLljgtY.mjs} +86 -24
  19. package/dist/.setup/{send-odAfTQ2J.mjs → send-Cu9nl757.mjs} +28 -112
  20. package/dist/.setup/setup-core-8MDdYHj5.mjs +226 -0
  21. package/dist/.setup/{channel.runtime---s2k4YN.mjs → setup-surface-tGXFDdWh.mjs} +6 -103
  22. package/dist/.setup/state-migrations-B-Q7bO0Y.mjs +47 -0
  23. package/dist/.setup/{targets-Cc9vthzI.mjs → targets-BicBiYsM.mjs} +10 -16
  24. package/dist/api.js +7 -7
  25. package/dist/channel-config-api.js +1 -1
  26. package/dist/channel-plugin-api.js +1 -1
  27. package/dist/contract-api.js +1 -1
  28. package/dist/doctor-contract-api.js +168 -7
  29. package/dist/media-contract-api.js +1 -1
  30. package/dist/message-tool-api.js +1 -1
  31. package/dist/runtime-api.js +6 -6
  32. package/dist/setup-entry.js +1 -1
  33. package/dist/src/chat-db.worker.js +6 -1
  34. package/node_modules/typebox/build/compile/code.mjs +1 -2
  35. package/node_modules/typebox/build/compile/validator.mjs +5 -1
  36. package/node_modules/typebox/build/format/iri.mjs +16 -9
  37. package/node_modules/typebox/build/guard/emit.mjs +2 -2
  38. package/node_modules/typebox/build/guard/guard.d.mts +7 -5
  39. package/node_modules/typebox/build/guard/guard.mjs +14 -9
  40. package/node_modules/typebox/build/guard/index.mjs +6 -0
  41. package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
  42. package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
  43. package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
  44. package/node_modules/typebox/build/schema/build.mjs +2 -3
  45. package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
  46. package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
  47. package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
  48. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
  49. package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
  50. package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
  51. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
  52. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
  53. package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
  54. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
  55. package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
  56. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
  57. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
  58. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
  59. package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
  60. package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
  61. package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
  62. package/node_modules/typebox/build/schema/parse.mjs +3 -0
  63. package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
  64. package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
  65. package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
  66. package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
  67. package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
  68. package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
  69. package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
  70. package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
  71. package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
  72. package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
  73. package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
  74. package/node_modules/typebox/package.json +1 -1
  75. package/node_modules/typebox/readme.md +3 -1
  76. package/openclaw.plugin.json +14 -0
  77. package/package.json +5 -5
  78. package/dist/.setup/rolldown-runtime-D7D4PA-g.mjs +0 -13
  79. package/dist/.setup/state-contract-DvahEY4X.mjs +0 -56
  80. package/dist/.setup/state-migrations-DIDzI99d.mjs +0 -277
  81. /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
@@ -1,6 +1,6 @@
1
1
  // deno-fmt-ignore-file
2
- import { Settings } from '../../system/system.mjs';
3
- import { Arguments } from '../../system/arguments/index.mjs';
2
+ import { EnableParseErrors, DisableParseErrors } from '../../system/settings/internal.mjs';
3
+ import { Arguments, Settings } from '../../system/index.mjs';
4
4
  import { AssertError } from '../assert/index.mjs';
5
5
  import { Check } from '../check/index.mjs';
6
6
  import { Errors } from '../errors/index.mjs';
@@ -18,8 +18,11 @@ export class ParseError extends AssertError {
18
18
  }
19
19
  }
20
20
  function Assert(context, type, value) {
21
+ EnableParseErrors();
22
+ const errors = Errors(context, type, value);
23
+ DisableParseErrors();
21
24
  if (!Check(context, type, value))
22
- throw new ParseError(value, Errors(context, type, value));
25
+ throw new ParseError(value, errors);
23
26
  return value;
24
27
  }
25
28
  // ------------------------------------------------------------------
@@ -43,5 +46,8 @@ export function Parse(...args) {
43
46
  return value;
44
47
  if (Settings.Get().correctiveParse)
45
48
  return Parser(context, type, value);
46
- throw new ParseError(value, Errors(context, type, value));
49
+ EnableParseErrors();
50
+ const errors = Errors(context, type, value);
51
+ DisableParseErrors();
52
+ throw new ParseError(value, errors);
47
53
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "typebox",
3
3
  "description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
4
- "version": "1.3.30",
4
+ "version": "1.3.34",
5
5
  "keywords": [
6
6
  "typescript",
7
7
  "jsonschema"
@@ -166,9 +166,11 @@ import Schema from 'typebox/schema'
166
166
 
167
167
  ### Compile
168
168
 
169
- The compiler accepts both TypeBox types and plain JSON Schema objects. The Compile function will return a new Validator instance which can be used to check and parse values. The example below compiles a Script definition into a validator.
169
+ The compiler accepts TypeBox types as well as plain JSON Schema objects, and returns a Validator instance which can be used to check values. The following compiles a Vector type.
170
170
 
171
171
  ```typescript
172
+ import Schema from 'typebox/schema'
173
+
172
174
  // Compile
173
175
 
174
176
  const Vector = Schema.Compile(Type.Script(`{
@@ -423,6 +423,9 @@
423
423
  },
424
424
  "systemPrompt": {
425
425
  "type": "string"
426
+ },
427
+ "requireMentionInBotThreads": {
428
+ "type": "boolean"
426
429
  }
427
430
  },
428
431
  "additionalProperties": false
@@ -824,6 +827,9 @@
824
827
  },
825
828
  "systemPrompt": {
826
829
  "type": "string"
830
+ },
831
+ "requireMentionInBotThreads": {
832
+ "type": "boolean"
827
833
  }
828
834
  },
829
835
  "additionalProperties": false
@@ -867,6 +873,14 @@
867
873
  "groupAllowFrom": {
868
874
  "presentation": "phone-number"
869
875
  },
876
+ "groups.*.requireMentionInBotThreads": {
877
+ "label": "iMessage Bot Thread Mention Requirement",
878
+ "help": "Override mention gating in native reply threads started by this account. False allows unmentioned replies; true requires a mention and keeps threads quiet when mention patterns are disabled. Omit to preserve normal gating. Ownership uses the bounded cache of messages OpenClaw sent; sender restrictions still apply."
879
+ },
880
+ "accounts.*.groups.*.requireMentionInBotThreads": {
881
+ "label": "iMessage Account Bot Thread Mention Requirement",
882
+ "help": "Override mention gating in native reply threads whose root this account sent. Unknown or evicted roots keep the group's normal mention requirement."
883
+ },
870
884
  "accounts.*.allowFrom.*": {
871
885
  "presentation": "phone-number"
872
886
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/imessage",
3
- "version": "2026.9.6",
3
+ "version": "2026.9.8",
4
4
  "description": "OpenClaw iMessage channel plugin using imsg on a signed-in Mac",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,11 +8,11 @@
8
8
  },
9
9
  "type": "module",
10
10
  "dependencies": {
11
- "typebox": "1.3.30",
11
+ "typebox": "1.3.34",
12
12
  "zod": "4.6.5"
13
13
  },
14
14
  "peerDependencies": {
15
- "openclaw": ">=2026.9.6"
15
+ "openclaw": ">=2026.9.8"
16
16
  },
17
17
  "peerDependenciesMeta": {
18
18
  "openclaw": {
@@ -92,10 +92,10 @@
92
92
  "allowInvalidConfigRecovery": true
93
93
  },
94
94
  "compat": {
95
- "pluginApi": ">=2026.9.6"
95
+ "pluginApi": ">=2026.9.8"
96
96
  },
97
97
  "build": {
98
- "openclawVersion": "2026.9.6",
98
+ "openclawVersion": "2026.9.8",
99
99
  "bundledDist": false,
100
100
  "workerEntries": [
101
101
  "./src/chat-db.worker.ts"
@@ -1,13 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __defProp = Object.defineProperty;
3
- var __exportAll = (all, no_symbols) => {
4
- let target = {};
5
- for (var name in all) __defProp(target, name, {
6
- get: all[name],
7
- enumerable: true
8
- });
9
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
- return target;
11
- };
12
- //#endregion
13
- export { __exportAll as t };
@@ -1,56 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- //#region extensions/imessage/src/state-contract.ts
3
- const IMESSAGE_REPLY_CACHE_NAMESPACE = "imessage.reply-cache";
4
- const IMESSAGE_REPLY_CACHE_MAX_ENTRIES = 2e3;
5
- const IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE = "imessage.reply-cache-counter";
6
- const IMESSAGE_REPLY_CACHE_COUNTER_KEY = "short-id-counter";
7
- const IMESSAGE_REPLY_CACHE_TTL_MS = 216e5;
8
- function resolveIMessageReplyCacheEntryKey(messageId) {
9
- return createHash("sha256").update(messageId, "utf8").digest("hex").slice(0, 32);
10
- }
11
- const MAX_FAILURE_RETRY_MAP_SIZE = 512;
12
- const MAX_FAILURE_RETRY_MAP_JSON_BYTES = 48e3;
13
- const textEncoder = new TextEncoder();
14
- const IMESSAGE_CATCHUP_CURSOR_NAMESPACE = "imessage.catchup-cursors";
15
- function resolveIMessageCatchupCursorKey(accountId) {
16
- return createHash("sha256").update(accountId, "utf8").digest("hex").slice(0, 32);
17
- }
18
- /**
19
- * Bound the retry map so a pathological storm of unique failing GUIDs
20
- * cannot grow the cursor value without limit. Keeps the `maxSize` entries
21
- * with the highest counts (closest to give-up) when over the bound.
22
- */
23
- function capFailureRetriesMap(map, maxSize = MAX_FAILURE_RETRY_MAP_SIZE, maxBytes = MAX_FAILURE_RETRY_MAP_JSON_BYTES) {
24
- const entries = Object.entries(map);
25
- if (entries.length <= maxSize && textEncoder.encode(JSON.stringify(map)).byteLength <= maxBytes) return map;
26
- entries.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
27
- const capped = {};
28
- for (const [guid, count] of entries.slice(0, maxSize)) {
29
- capped[guid] = count;
30
- if (textEncoder.encode(JSON.stringify(capped)).byteLength > maxBytes) {
31
- delete capped[guid];
32
- break;
33
- }
34
- }
35
- return capped;
36
- }
37
- const IMESSAGE_SENT_ECHOES_TTL_MS = 432e5;
38
- const IMESSAGE_SENT_ECHOES_NAMESPACE = "imessage.sent-echoes";
39
- function resolveIMessageSentEchoEntryKey(entry) {
40
- return createHash("sha256").update(JSON.stringify([
41
- entry.scope,
42
- entry.text ?? "",
43
- resolveIMessageEchoMediaKey(entry.media) ?? "",
44
- entry.messageId ?? "",
45
- entry.timestamp
46
- ])).digest("hex").slice(0, 32);
47
- }
48
- function resolveIMessageEchoMediaKey(media) {
49
- const contentType = media?.contentType?.trim().toLowerCase() || void 0;
50
- const kind = media?.kind && media.kind !== "unknown" ? media.kind : void 0;
51
- if (kind) return `kind:${kind}`;
52
- const normalizedContentType = contentType?.split(";", 1)[0]?.trim();
53
- return normalizedContentType ? `mime:${normalizedContentType}` : void 0;
54
- }
55
- //#endregion
56
- export { IMESSAGE_REPLY_CACHE_NAMESPACE as a, IMESSAGE_SENT_ECHOES_TTL_MS as c, resolveIMessageEchoMediaKey as d, resolveIMessageReplyCacheEntryKey as f, IMESSAGE_REPLY_CACHE_MAX_ENTRIES as i, capFailureRetriesMap as l, IMESSAGE_REPLY_CACHE_COUNTER_KEY as n, IMESSAGE_REPLY_CACHE_TTL_MS as o, resolveIMessageSentEchoEntryKey as p, IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE as r, IMESSAGE_SENT_ECHOES_NAMESPACE as s, IMESSAGE_CATCHUP_CURSOR_NAMESPACE as t, resolveIMessageCatchupCursorKey as u };
@@ -1,277 +0,0 @@
1
- import { a as resolveDefaultIMessageAccountId, i as listIMessageAccountIds, o as resolveIMessageAccount } from "./accounts-CUxZrTcY.mjs";
2
- import { a as IMESSAGE_REPLY_CACHE_NAMESPACE, c as IMESSAGE_SENT_ECHOES_TTL_MS, f as resolveIMessageReplyCacheEntryKey, i as IMESSAGE_REPLY_CACHE_MAX_ENTRIES, l as capFailureRetriesMap, n as IMESSAGE_REPLY_CACHE_COUNTER_KEY, o as IMESSAGE_REPLY_CACHE_TTL_MS, p as resolveIMessageSentEchoEntryKey, r as IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE, s as IMESSAGE_SENT_ECHOES_NAMESPACE, t as IMESSAGE_CATCHUP_CURSOR_NAMESPACE, u as resolveIMessageCatchupCursorKey } from "./state-contract-DvahEY4X.mjs";
3
- import { asFiniteNumber, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
4
- import fs from "node:fs";
5
- import path from "node:path";
6
- import { createHash } from "node:crypto";
7
- import { fileExists } from "openclaw/plugin-sdk/file-access-runtime";
8
- import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
9
- //#region extensions/imessage/src/state-migrations.ts
10
- function resolveMigrationStateDir(params) {
11
- return params.stateDir ?? resolveStateDir(params.env);
12
- }
13
- function remainingTtlMs(timestamp, ttlMs) {
14
- const remaining = ttlMs - Math.max(0, Date.now() - timestamp);
15
- return remaining > 0 ? remaining : void 0;
16
- }
17
- function readJsonl(pathValue) {
18
- try {
19
- return fs.readFileSync(pathValue, "utf8").split(/\n+/).flatMap((line) => {
20
- if (!line) return [];
21
- try {
22
- return [JSON.parse(line)];
23
- } catch {
24
- return [];
25
- }
26
- });
27
- } catch (err) {
28
- throw new Error(`Failed reading ${pathValue}: ${String(err)}`, { cause: err });
29
- }
30
- }
31
- function parseReplyCacheEntry(raw) {
32
- if (!raw || typeof raw !== "object") return null;
33
- const parsed = raw;
34
- if (typeof parsed.accountId !== "string" || typeof parsed.messageId !== "string" || typeof parsed.shortId !== "string" || typeof parsed.timestamp !== "number") return null;
35
- return {
36
- accountId: parsed.accountId,
37
- messageId: parsed.messageId,
38
- shortId: parsed.shortId,
39
- timestamp: parsed.timestamp,
40
- ...typeof parsed.chatGuid === "string" ? { chatGuid: parsed.chatGuid } : {},
41
- ...typeof parsed.chatIdentifier === "string" ? { chatIdentifier: parsed.chatIdentifier } : {},
42
- ...typeof parsed.chatId === "number" ? { chatId: parsed.chatId } : {},
43
- ...typeof parsed.isFromMe === "boolean" ? { isFromMe: parsed.isFromMe } : {}
44
- };
45
- }
46
- function readReplyCacheMaxShortId(sourcePath) {
47
- let max = 0;
48
- for (const raw of readJsonl(sourcePath)) {
49
- if (!raw || typeof raw !== "object") continue;
50
- const shortId = raw.shortId;
51
- if (typeof shortId !== "string") continue;
52
- const numeric = Number.parseInt(shortId, 10);
53
- if (Number.isFinite(numeric) && numeric > max) max = numeric;
54
- }
55
- return max;
56
- }
57
- function readReplyCounterValue(value) {
58
- if (!value || typeof value !== "object") return null;
59
- const counter = value.counter;
60
- return asFiniteNumber(counter) ?? null;
61
- }
62
- function shouldReplaceReplyCounter(existingValue, incomingValue) {
63
- const incomingCounter = readReplyCounterValue(incomingValue);
64
- if (incomingCounter === null) return false;
65
- const existingCounter = readReplyCounterValue(existingValue);
66
- return existingCounter === null || incomingCounter > existingCounter;
67
- }
68
- function parseSentEchoEntry(raw) {
69
- if (!raw || typeof raw !== "object") return null;
70
- const parsed = raw;
71
- if (typeof parsed.scope !== "string" || typeof parsed.timestamp !== "number") return null;
72
- return {
73
- scope: parsed.scope,
74
- timestamp: parsed.timestamp,
75
- ...typeof parsed.text === "string" ? { text: parsed.text } : {},
76
- ...typeof parsed.messageId === "string" ? { messageId: parsed.messageId } : {}
77
- };
78
- }
79
- function listReplyCacheEntries(sourcePath) {
80
- const entriesByKey = /* @__PURE__ */ new Map();
81
- for (const entry of readJsonl(sourcePath).map(parseReplyCacheEntry)) {
82
- if (!entry) continue;
83
- const ttlMs = remainingTtlMs(entry.timestamp, IMESSAGE_REPLY_CACHE_TTL_MS);
84
- if (!ttlMs) continue;
85
- const key = resolveIMessageReplyCacheEntryKey(entry.messageId);
86
- entriesByKey.delete(key);
87
- entriesByKey.set(key, {
88
- value: entry,
89
- ttlMs
90
- });
91
- }
92
- return [...entriesByKey.entries()].slice(-IMESSAGE_REPLY_CACHE_MAX_ENTRIES).map(([key, entry]) => ({
93
- key,
94
- value: entry.value,
95
- ttlMs: entry.ttlMs
96
- }));
97
- }
98
- function listSentEchoEntries(sourcePath) {
99
- return readJsonl(sourcePath).map(parseSentEchoEntry).filter((entry) => Boolean(entry)).slice(-256).flatMap((entry) => {
100
- const ttlMs = remainingTtlMs(entry.timestamp, IMESSAGE_SENT_ECHOES_TTL_MS);
101
- if (!ttlMs) return [];
102
- return [{
103
- key: resolveIMessageSentEchoEntryKey(entry),
104
- value: entry,
105
- ttlMs
106
- }];
107
- });
108
- }
109
- function resolveLegacyCatchupCursorPath(stateDir, accountId) {
110
- const safePrefix = accountId.replace(/[^a-zA-Z0-9_-]/g, "_") || "account";
111
- const hash = createHash("sha256").update(accountId, "utf8").digest("hex").slice(0, 12);
112
- return path.join(stateDir, "imessage", "catchup", `${safePrefix}__${hash}.json`);
113
- }
114
- function listLegacyCatchupCursorPaths(stateDir) {
115
- const dir = path.join(stateDir, "imessage", "catchup");
116
- try {
117
- return fs.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path.join(dir, entry.name));
118
- } catch {
119
- return [];
120
- }
121
- }
122
- function normalizeCatchupCursor(raw) {
123
- if (!raw || typeof raw !== "object") return null;
124
- const value = raw;
125
- if (typeof value.lastSeenMs !== "number" || !Number.isFinite(value.lastSeenMs) || typeof value.lastSeenRowid !== "number" || !Number.isFinite(value.lastSeenRowid)) return null;
126
- const failureRetries = sanitizeCatchupFailureRetries(value.failureRetries);
127
- const hasRetries = Object.keys(failureRetries).length > 0;
128
- return {
129
- lastSeenMs: value.lastSeenMs,
130
- lastSeenRowid: value.lastSeenRowid,
131
- updatedAt: typeof value.updatedAt === "number" ? value.updatedAt : 0,
132
- ...hasRetries ? { failureRetries } : {}
133
- };
134
- }
135
- function readCatchupCursor(sourcePath) {
136
- let parsed;
137
- try {
138
- parsed = JSON.parse(fs.readFileSync(sourcePath, "utf8"));
139
- } catch (err) {
140
- throw new Error(`Failed reading ${sourcePath}: ${String(err)}`, { cause: err });
141
- }
142
- const cursor = normalizeCatchupCursor(parsed);
143
- if (!cursor) throw new Error(`Invalid iMessage catchup cursor: ${sourcePath}`);
144
- return cursor;
145
- }
146
- function sanitizeCatchupFailureRetries(raw) {
147
- if (!raw || typeof raw !== "object") return {};
148
- const out = {};
149
- for (const [guid, count] of Object.entries(raw)) if (typeof count === "number" && Number.isFinite(count) && count > 0) out[guid] = Math.floor(count);
150
- return capFailureRetriesMap(out);
151
- }
152
- function shouldReplaceCatchupCursor(existingValue, incomingValue) {
153
- const incoming = normalizeCatchupCursor(incomingValue);
154
- if (!incoming) return false;
155
- const existing = normalizeCatchupCursor(existingValue);
156
- return !existing || incoming.lastSeenRowid > existing.lastSeenRowid || incoming.lastSeenRowid === existing.lastSeenRowid && incoming.lastSeenMs > existing.lastSeenMs;
157
- }
158
- function detectReplyCacheMigration(params) {
159
- const stateDir = resolveMigrationStateDir(params);
160
- const sourcePath = path.join(stateDir, "imessage", "reply-cache.jsonl");
161
- if (!fileExists(sourcePath)) return [];
162
- const plans = [];
163
- plans.push({
164
- kind: "plugin-state-import",
165
- label: "iMessage reply short-id counter",
166
- sourcePath,
167
- targetPath: `plugin state:${IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE}`,
168
- pluginId: "imessage",
169
- namespace: IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE,
170
- maxEntries: 1,
171
- scopeKey: "",
172
- stateDir,
173
- preview: `- iMessage reply short-id counter: ${sourcePath} → plugin state (${IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE})`,
174
- readEntries: () => {
175
- const maxShortId = readReplyCacheMaxShortId(sourcePath);
176
- return maxShortId > 0 ? [{
177
- key: IMESSAGE_REPLY_CACHE_COUNTER_KEY,
178
- value: { counter: maxShortId }
179
- }] : [];
180
- },
181
- shouldReplaceExistingEntry: ({ existingValue, incomingValue }) => shouldReplaceReplyCounter(existingValue, incomingValue)
182
- });
183
- plans.push({
184
- kind: "plugin-state-import",
185
- label: "iMessage reply short-id cache",
186
- sourcePath,
187
- targetPath: `plugin state:${IMESSAGE_REPLY_CACHE_NAMESPACE}`,
188
- pluginId: "imessage",
189
- namespace: IMESSAGE_REPLY_CACHE_NAMESPACE,
190
- maxEntries: IMESSAGE_REPLY_CACHE_MAX_ENTRIES,
191
- scopeKey: "",
192
- stateDir,
193
- cleanupSource: "rename",
194
- cleanupWhenEmpty: true,
195
- preview: `- iMessage reply short-id cache: ${sourcePath} → plugin state (${IMESSAGE_REPLY_CACHE_NAMESPACE})`,
196
- readEntries: () => listReplyCacheEntries(sourcePath)
197
- });
198
- return plans;
199
- }
200
- function detectSentEchoMigration(params) {
201
- const stateDir = resolveMigrationStateDir(params);
202
- const sourcePath = path.join(stateDir, "imessage", "sent-echoes.jsonl");
203
- if (!fileExists(sourcePath)) return [];
204
- return [{
205
- kind: "plugin-state-import",
206
- label: "iMessage sent-echo dedupe cache",
207
- sourcePath,
208
- targetPath: `plugin state:${IMESSAGE_SENT_ECHOES_NAMESPACE}`,
209
- pluginId: "imessage",
210
- namespace: IMESSAGE_SENT_ECHOES_NAMESPACE,
211
- maxEntries: 256,
212
- scopeKey: "",
213
- stateDir,
214
- cleanupSource: "rename",
215
- cleanupWhenEmpty: true,
216
- preview: `- iMessage sent-echo dedupe cache: ${sourcePath} → plugin state (${IMESSAGE_SENT_ECHOES_NAMESPACE})`,
217
- readEntries: () => listSentEchoEntries(sourcePath)
218
- }];
219
- }
220
- function detectCatchupCursorMigrations(params) {
221
- const stateDir = resolveMigrationStateDir(params);
222
- const accountIds = uniqueStrings([resolveDefaultIMessageAccountId(params.cfg), ...listIMessageAccountIds(params.cfg)].map((accountId) => resolveIMessageAccount({
223
- cfg: params.cfg,
224
- accountId
225
- }).accountId));
226
- const configuredPaths = new Set(accountIds.map((accountId) => resolveLegacyCatchupCursorPath(stateDir, accountId)));
227
- const configuredPlans = accountIds.flatMap((accountId) => {
228
- const sourcePath = resolveLegacyCatchupCursorPath(stateDir, accountId);
229
- if (!fileExists(sourcePath)) return [];
230
- return {
231
- kind: "plugin-state-import",
232
- label: "iMessage catchup cursor",
233
- sourcePath,
234
- targetPath: `plugin state:${IMESSAGE_CATCHUP_CURSOR_NAMESPACE}`,
235
- pluginId: "imessage",
236
- namespace: IMESSAGE_CATCHUP_CURSOR_NAMESPACE,
237
- maxEntries: 256,
238
- scopeKey: "",
239
- stateDir,
240
- cleanupSource: "rename",
241
- preview: `- iMessage catchup cursor: ${sourcePath} → plugin state (${IMESSAGE_CATCHUP_CURSOR_NAMESPACE})`,
242
- readEntries: () => {
243
- const cursor = readCatchupCursor(sourcePath);
244
- return [{
245
- key: resolveIMessageCatchupCursorKey(accountId),
246
- value: cursor
247
- }];
248
- },
249
- shouldReplaceExistingEntry: (replaceParams) => shouldReplaceCatchupCursor(replaceParams.existingValue, replaceParams.incomingValue)
250
- };
251
- });
252
- const orphanPlans = listLegacyCatchupCursorPaths(stateDir).filter((sourcePath) => !configuredPaths.has(sourcePath)).map((sourcePath) => ({
253
- kind: "plugin-state-import",
254
- label: "iMessage orphan catchup cursor",
255
- sourcePath,
256
- targetPath: `plugin state:${IMESSAGE_CATCHUP_CURSOR_NAMESPACE}`,
257
- pluginId: "imessage",
258
- namespace: IMESSAGE_CATCHUP_CURSOR_NAMESPACE,
259
- maxEntries: 256,
260
- scopeKey: "",
261
- stateDir,
262
- cleanupSource: "rename",
263
- cleanupWhenEmpty: true,
264
- preview: `- iMessage orphan catchup cursor: ${sourcePath} → archived legacy state`,
265
- readEntries: () => []
266
- }));
267
- return [...configuredPlans, ...orphanPlans];
268
- }
269
- async function detectIMessageLegacyStateMigrations(params) {
270
- return [
271
- ...detectCatchupCursorMigrations(params),
272
- ...detectReplyCacheMigration(params),
273
- ...detectSentEchoMigration(params)
274
- ];
275
- }
276
- //#endregion
277
- export { detectIMessageLegacyStateMigrations };