@openclaw/imessage 2026.10.10 → 2026.11.5-beta.1

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 (137) hide show
  1. package/dist/.setup/{accounts-BH0coL46.mjs → accounts-C1Ypv3Wn.mjs} +6 -7
  2. package/dist/.setup/{actions-CW7XYYF4.mjs → actions-qOuKy8To.mjs} +360 -108
  3. package/dist/.setup/{actions.runtime-B2ZHQ3TJ.mjs → actions.runtime-Ccg9CARL.mjs} +10 -56
  4. package/dist/.setup/{api-6xi-NBrt.mjs → api-BXXuQQCS.mjs} +7 -7
  5. package/dist/.setup/{api-B0ih8Iof.mjs → api-DTbxSOPc.mjs} +8 -8
  6. package/dist/.setup/{approval-handler.runtime-BKmqba3L.mjs → approval-handler.runtime-B6vrFfZx.mjs} +112 -131
  7. package/dist/.setup/{approval-reactions-B9CJS0NT.mjs → approval-reactions-DBBsqbRC.mjs} +152 -72
  8. package/dist/.setup/{channel-6kcKpSRw.mjs → channel-CLaN4mQn.mjs} +142 -119
  9. package/dist/.setup/{channel.runtime-BW4A0m4h.mjs → channel.runtime-XJGx1MIH.mjs} +205 -363
  10. package/dist/.setup/chat-context-fTfJVqAi.mjs +77 -0
  11. package/dist/.setup/chat-lookup-xtPsOBkl.mjs +50 -0
  12. package/dist/.setup/client-D_BaG8sC.mjs +367 -0
  13. package/dist/.setup/{config-schema-DRkdRC21.mjs → config-schema-BjkQV5P5.mjs} +1 -2
  14. package/dist/.setup/{conversation-bindings-vZFn6sN2.mjs → conversation-bindings-8DjxoIYU.mjs} +9 -6
  15. package/dist/.setup/{media-contract-CoixpXRX.mjs → media-contract-BErFBKV9.mjs} +1 -1
  16. package/dist/.setup/{message-tool-api-CxSY_rh7.mjs → message-tool-api-DtZ1wKbT.mjs} +3 -6
  17. package/dist/.setup/{remote-file-CCgMYZ-u.mjs → remote-file-DpK3GQFD.mjs} +8 -22
  18. package/dist/.setup/{sanitize-outbound-C02fJrn6.mjs → sanitize-outbound-Dhjhz0a-.mjs} +23 -27
  19. package/dist/.setup/{send-D98hQFdv.mjs → send-DhEW0gfe.mjs} +150 -226
  20. package/dist/.setup/{setup-core-BWnW0czE.mjs → setup-core-B1_P_t4T.mjs} +2 -2
  21. package/dist/.setup/{setup-surface-aRZhAzzQ.mjs → setup-surface-CD28mXWK.mjs} +11 -25
  22. package/dist/.setup/{targets-CIx4BKqL.mjs → targets-C7CIWJ0k.mjs} +12 -3
  23. package/dist/.source-module-urls/929c518fa2986b0f85f66bceefead22868db0ab452dee914dc6a2bb86b2e60ad.json +1 -1
  24. package/dist/api.js +8 -8
  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/media-contract-api.js +1 -1
  29. package/dist/message-tool-api.js +1 -1
  30. package/dist/runtime-api.js +1 -3
  31. package/dist/session-binding-contract-api.js +1 -1
  32. package/dist/setup-entry.js +1 -1
  33. package/dist/src/chat-db.worker.js +1 -1
  34. package/node_modules/typebox/build/format/duration.d.mts +2 -2
  35. package/node_modules/typebox/build/format/duration.mjs +2 -2
  36. package/node_modules/typebox/build/format/idn_email.d.mts +1 -0
  37. package/node_modules/typebox/build/format/idn_email.mjs +1 -0
  38. package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -1
  39. package/node_modules/typebox/build/format/idn_hostname.mjs +1 -1
  40. package/node_modules/typebox/build/format/ipv4.d.mts +2 -2
  41. package/node_modules/typebox/build/format/ipv4.mjs +2 -2
  42. package/node_modules/typebox/build/format/ipv6.d.mts +1 -1
  43. package/node_modules/typebox/build/format/ipv6.mjs +1 -1
  44. package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
  45. package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
  46. package/node_modules/typebox/build/schema/build.mjs +1 -0
  47. package/node_modules/typebox/build/schema/engine/_unique.d.mts +4 -2
  48. package/node_modules/typebox/build/schema/engine/_unique.mjs +7 -3
  49. package/node_modules/typebox/build/schema/engine/additionalItems.mjs +3 -3
  50. package/node_modules/typebox/build/schema/engine/additionalProperties.d.mts +0 -3
  51. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +15 -17
  52. package/node_modules/typebox/build/schema/engine/contains.mjs +3 -3
  53. package/node_modules/typebox/build/schema/engine/index.d.mts +1 -0
  54. package/node_modules/typebox/build/schema/engine/index.mjs +1 -0
  55. package/node_modules/typebox/build/schema/engine/items.mjs +7 -4
  56. package/node_modules/typebox/build/schema/engine/maxContains.mjs +2 -2
  57. package/node_modules/typebox/build/schema/engine/minContains.mjs +3 -3
  58. package/node_modules/typebox/build/schema/engine/oneOf.mjs +2 -2
  59. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +2 -2
  60. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -2
  61. package/node_modules/typebox/build/schema/engine/unevaluatedItems.mjs +2 -2
  62. package/node_modules/typebox/build/schema/engine/unevaluatedProperties.mjs +2 -2
  63. package/node_modules/typebox/build/system/hashing/hash.mjs +63 -34
  64. package/node_modules/typebox/build/system/locale/en_US.mjs +2 -1
  65. package/node_modules/typebox/build/system/memory/assign.d.mts +2 -8
  66. package/node_modules/typebox/build/system/memory/assign.mjs +13 -6
  67. package/node_modules/typebox/build/system/memory/clone.d.mts +4 -5
  68. package/node_modules/typebox/build/system/memory/clone.mjs +8 -96
  69. package/node_modules/typebox/build/system/memory/create.d.mts +1 -5
  70. package/node_modules/typebox/build/system/memory/create.mjs +18 -13
  71. package/node_modules/typebox/build/system/memory/deep.d.mts +2 -0
  72. package/node_modules/typebox/build/system/memory/deep.mjs +108 -0
  73. package/node_modules/typebox/build/system/memory/discard.d.mts +1 -1
  74. package/node_modules/typebox/build/system/memory/discard.mjs +9 -8
  75. package/node_modules/typebox/build/system/memory/finalize.d.mts +4 -0
  76. package/node_modules/typebox/build/system/memory/finalize.mjs +30 -0
  77. package/node_modules/typebox/build/system/memory/keys.d.mts +2 -0
  78. package/node_modules/typebox/build/system/memory/keys.mjs +8 -0
  79. package/node_modules/typebox/build/system/memory/update.d.mts +1 -4
  80. package/node_modules/typebox/build/system/memory/update.mjs +43 -16
  81. package/node_modules/typebox/build/type/engine/call/index.d.mts +1 -0
  82. package/node_modules/typebox/build/type/engine/call/index.mjs +1 -0
  83. package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +2 -24
  84. package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +2 -29
  85. package/node_modules/typebox/build/type/engine/evaluate/index.d.mts +1 -0
  86. package/node_modules/typebox/build/type/engine/evaluate/index.mjs +1 -0
  87. package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +5 -1
  88. package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +6 -1
  89. package/node_modules/typebox/build/type/engine/evaluate/property.d.mts +30 -0
  90. package/node_modules/typebox/build/type/engine/evaluate/property.mjs +29 -0
  91. package/node_modules/typebox/build/type/engine/immutable/instantiate_add.d.mts +1 -3
  92. package/node_modules/typebox/build/type/engine/immutable/instantiate_add.mjs +1 -4
  93. package/node_modules/typebox/build/type/engine/object/from_intersect.d.mts +2 -2
  94. package/node_modules/typebox/build/type/engine/object/from_intersect.mjs +2 -2
  95. package/node_modules/typebox/build/type/engine/object/from_union.d.mts +2 -2
  96. package/node_modules/typebox/build/type/engine/object/from_union.mjs +2 -2
  97. package/node_modules/typebox/build/type/engine/optional/instantiate_add.d.mts +1 -3
  98. package/node_modules/typebox/build/type/engine/optional/instantiate_add.mjs +1 -4
  99. package/node_modules/typebox/build/type/engine/readonly/instantiate_add.d.mts +1 -3
  100. package/node_modules/typebox/build/type/engine/readonly/instantiate_add.mjs +1 -4
  101. package/node_modules/typebox/build/type/engine/this/index.d.mts +1 -0
  102. package/node_modules/typebox/build/type/engine/this/index.mjs +1 -0
  103. package/node_modules/typebox/build/value/clone/clone.d.mts +4 -3
  104. package/node_modules/typebox/build/value/clone/clone.mjs +5 -7
  105. package/node_modules/typebox/build/value/repair/from_cyclic.d.mts +2 -0
  106. package/node_modules/typebox/build/value/repair/from_cyclic.mjs +6 -0
  107. package/node_modules/typebox/build/value/shared/union_priority_sort.mjs +25 -14
  108. package/node_modules/typebox/package.json +1 -1
  109. package/node_modules/typebox/readme.md +1 -1
  110. package/node_modules/zod/package.json +1 -1
  111. package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
  112. package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
  113. package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
  114. package/node_modules/zod/src/v4/core/compile.ts +9 -0
  115. package/node_modules/zod/src/v4/core/schemas.ts +35 -1
  116. package/node_modules/zod/src/v4/core/util.ts +13 -0
  117. package/node_modules/zod/src/v4/core/versions.ts +1 -1
  118. package/node_modules/zod/v4/core/compile.cjs +8 -0
  119. package/node_modules/zod/v4/core/compile.js +8 -0
  120. package/node_modules/zod/v4/core/schemas.cjs +30 -0
  121. package/node_modules/zod/v4/core/schemas.d.cts +3 -1
  122. package/node_modules/zod/v4/core/schemas.d.ts +3 -1
  123. package/node_modules/zod/v4/core/schemas.js +30 -0
  124. package/node_modules/zod/v4/core/util.cjs +15 -0
  125. package/node_modules/zod/v4/core/util.d.cts +2 -0
  126. package/node_modules/zod/v4/core/util.d.ts +2 -0
  127. package/node_modules/zod/v4/core/util.js +14 -0
  128. package/node_modules/zod/v4/core/versions.cjs +1 -1
  129. package/node_modules/zod/v4/core/versions.js +1 -1
  130. package/openclaw.plugin.json +0 -30
  131. package/package.json +6 -6
  132. package/dist/.setup/client-C3e7ZLqA.mjs +0 -739
  133. package/dist/.setup/question-reactions-DTj-VH3N.mjs +0 -67
  134. package/dist/.setup/reaction-context-Bw00FkmU.mjs +0 -105
  135. package/dist/.setup/runtime-BRQCPuAD.mjs +0 -7
  136. package/node_modules/typebox/build/system/memory/freeze.d.mts +0 -4
  137. package/node_modules/typebox/build/system/memory/freeze.mjs +0 -6
@@ -1,7 +1,7 @@
1
1
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
2
2
  import { normalizeAccountId, resolveAccountEntry } from "openclaw/plugin-sdk/account-resolution";
3
3
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
4
- import { accessSync, constants, readFileSync, realpathSync, statSync } from "node:fs";
4
+ import { accessSync, constants, readFileSync, statSync } from "node:fs";
5
5
  import path from "node:path";
6
6
  import { createAccountListHelpers } from "openclaw/plugin-sdk/account-helpers";
7
7
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
@@ -49,7 +49,7 @@ function resolveIMessageExecutable(cliPath) {
49
49
  }
50
50
  function isMachOExecutable(filePath) {
51
51
  try {
52
- return MACH_O_MAGICS.has(readFileSync(realpathSync(filePath)).subarray(0, 4).toString("hex"));
52
+ return MACH_O_MAGICS.has(readFileSync(filePath).subarray(0, 4).toString("hex"));
53
53
  } catch {
54
54
  return false;
55
55
  }
@@ -63,7 +63,7 @@ function isProvenLocalIMessageCliPath(params) {
63
63
  const executable = resolveIMessageExecutable(cliPath);
64
64
  let local = executable ? isMachOExecutable(executable) : false;
65
65
  if (executable && !local) try {
66
- const match = readFileSync(realpathSync(executable), "utf8").match(/^#![^\r\n]+\r?\n\s*exec\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s+"\$@"\s*$/u);
66
+ const match = readFileSync(executable, "utf8").match(/^#![^\r\n]+\r?\n\s*exec\s+(?:"([^"]+)"|'([^']+)'|(\S+))\s+"\$@"\s*$/u);
67
67
  const target = match?.[1] ?? match?.[2] ?? match?.[3];
68
68
  local = Boolean(target && path.isAbsolute(target) && isMachOExecutable(target));
69
69
  } catch {
@@ -148,8 +148,8 @@ async function resolveIMessageRemoteHost(params) {
148
148
  const { logVerbose } = await import("openclaw/plugin-sdk/runtime-env");
149
149
  if (params.remoteHost?.trim()) logVerbose("imessage: ignoring unsafe configured remoteHost value");
150
150
  const key = cacheKey(params.cliPath);
151
- if (detectedRemoteHosts.has(key)) {
152
- const cached = detectedRemoteHosts.get(key);
151
+ const cached = detectedRemoteHosts.get(key);
152
+ if (cached !== void 0) {
153
153
  if (cached === ambiguousSshWrapper) throwAmbiguousSshWrapper();
154
154
  return cached ?? void 0;
155
155
  }
@@ -171,7 +171,6 @@ async function resolveIMessageRemoteHost(params) {
171
171
  detectedRemoteHosts.set(key, ambiguousSshWrapper);
172
172
  throwAmbiguousSshWrapper();
173
173
  }
174
- if (detected) logVerbose(`imessage: detected remoteHost=${detected} from cliPath`);
175
174
  detectedRemoteHosts.set(key, detected ?? null);
176
175
  return detected;
177
176
  })().finally(() => remoteHostLookups.delete(key));
@@ -211,7 +210,7 @@ function resolveIMessageAccount(params) {
211
210
  const baseEnabled = params.cfg.channels?.imessage?.enabled !== false;
212
211
  const merged = mergeIMessageAccountConfig(params.cfg, accountId);
213
212
  const accountEnabled = merged.enabled !== false;
214
- const configured = Boolean(merged.enabled === true || merged.cliPath?.trim() || merged.dbPath?.trim() || merged.service || merged.sendTransport || merged.region?.trim() || merged.allowFrom && merged.allowFrom.length > 0 || merged.groupAllowFrom && merged.groupAllowFrom.length > 0 || merged.dmPolicy || merged.groupPolicy || typeof merged.includeAttachments === "boolean" || merged.attachmentRoots && merged.attachmentRoots.length > 0 || merged.remoteAttachmentRoots && merged.remoteAttachmentRoots.length > 0 || typeof merged.mediaMaxMb === "number" || typeof merged.textChunkLimit === "number" || merged.groups && Object.keys(merged.groups).length > 0);
213
+ const configured = Boolean(merged.enabled === true || merged.cliPath?.trim() || merged.dbPath?.trim() || merged.service || merged.sendTransport || merged.region?.trim() || merged.allowFrom && merged.allowFrom.length > 0 || merged.groupAllowFrom && merged.groupAllowFrom.length > 0 || merged.dmPolicy || merged.groupPolicy || merged.includeAttachments !== void 0 || merged.attachmentRoots && merged.attachmentRoots.length > 0 || merged.remoteAttachmentRoots && merged.remoteAttachmentRoots.length > 0 || merged.mediaMaxMb !== void 0 || merged.textChunkLimit !== void 0 || merged.groups && Object.keys(merged.groups).length > 0);
215
214
  return {
216
215
  accountId,
217
216
  enabled: baseEnabled && accountEnabled,
@@ -1,18 +1,20 @@
1
- import { c as parseIMessageTarget } from "./targets-CIx4BKqL.mjs";
2
- import { l as resolveIMessageRemoteHost, n as hasExclusiveIMessageLocalDatabase, o as resolveIMessageAccount, u as expandIMessageUserPath } from "./accounts-BH0coL46.mjs";
3
- import { a as setCachedIMessagePrivateApiStatus, n as getCachedIMessagePrivateApiStatus, o as IMESSAGE_ACTIONS, r as imessageRpcSupportsMethod, s as IMESSAGE_ACTION_NAMES, t as describeIMessageMessageTool } from "./message-tool-api-CxSY_rh7.mjs";
4
- import { a as isIMessageCurrentMessageInChatAsync, i as findLatestIMessageEntryForChat, n as createIMessageRpcClient, s as rememberIMessageReplyCache, y as chatContextFromIMessageTarget } from "./client-C3e7ZLqA.mjs";
5
- import { l as isAutoManagedIMessageCliPath, n as IMESSAGE_UPDATE_COMMAND, t as IMESSAGE_INSTALL_COMMAND } from "./setup-core-BWnW0czE.mjs";
6
- import { filterStringEntries, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
1
+ import { l as parseIMessageTarget } from "./targets-C7CIWJ0k.mjs";
2
+ import { l as resolveIMessageRemoteHost, n as hasExclusiveIMessageLocalDatabase, o as resolveIMessageAccount, u as expandIMessageUserPath } from "./accounts-C1Ypv3Wn.mjs";
3
+ import { a as setCachedIMessagePrivateApiStatus, n as getCachedIMessagePrivateApiStatus, o as IMESSAGE_ACTIONS, r as imessageRpcSupportsMethod, s as IMESSAGE_ACTION_NAMES, t as describeIMessageMessageTool } from "./message-tool-api-DtZ1wKbT.mjs";
4
+ import { i as resolveIMessageChatMatch, t as chatContextFromIMessageTarget } from "./chat-context-fTfJVqAi.mjs";
5
+ import { n as createIMessageRpcClient } from "./client-D_BaG8sC.mjs";
6
+ import { l as isAutoManagedIMessageCliPath, n as IMESSAGE_UPDATE_COMMAND, t as IMESSAGE_INSTALL_COMMAND } from "./setup-core-B1_P_t4T.mjs";
7
+ import { filterStringEntries, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
7
8
  import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
8
9
  import path from "node:path";
9
10
  import { readBooleanParam } from "openclaw/plugin-sdk/boolean-param";
10
11
  import { createActionGate, jsonResult, readNonNegativeIntegerParam, readPositiveIntegerParam, readReactionParams, readStringArrayParam, readStringParam } from "openclaw/plugin-sdk/channel-actions";
11
12
  import { canonicalizeBase64 } from "openclaw/plugin-sdk/media-runtime";
12
13
  import { normalizePollInput } from "openclaw/plugin-sdk/poll-runtime";
13
- import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
14
+ import { createSubsystemLogger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
14
15
  import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
15
- import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
16
+ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
17
+ import { createHash } from "node:crypto";
16
18
  import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
17
19
  import { captureRuntimeConfigAsyncReader } from "openclaw/plugin-sdk/runtime-config-snapshot";
18
20
  import { detectBinary } from "openclaw/plugin-sdk/setup";
@@ -20,7 +22,6 @@ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
20
22
  // extensions/imessage/src/message-action-reference.ts
21
23
  async function resolveAuthorizedIMessageActionReference(params) {
22
24
  const options = {
23
- requireKnownShortId: true,
24
25
  chatContext: params.inputChatContext,
25
26
  ...params.requireFromMe ? { requireFromMe: true } : {}
26
27
  };
@@ -34,10 +35,7 @@ async function resolveAuthorizedIMessageActionReference(params) {
34
35
  await authorize(messageId, params.inputChatContext);
35
36
  const chatGuid = await params.resolveChatGuid();
36
37
  const chatContext = { chatGuid };
37
- const resolvedMessageId = await params.resolveMessageId(messageId, {
38
- requireKnownShortId: true,
39
- chatContext
40
- });
38
+ const resolvedMessageId = await params.resolveMessageId(messageId, { chatContext });
41
39
  await authorize(resolvedMessageId, chatContext);
42
40
  return {
43
41
  messageId: resolvedMessageId,
@@ -54,31 +52,312 @@ function normalizeIMessageMessageId(value) {
54
52
  const id = value?.trim();
55
53
  return id && id !== "ok" && id !== "unknown" ? id : void 0;
56
54
  }
55
+ // extensions/imessage/src/persistence-failure.ts
56
+ function createPersistenceFailureReporter(cache) {
57
+ let reported = false;
58
+ return (scope, err) => {
59
+ if (reported) return;
60
+ reported = true;
61
+ logVerbose(`imessage ${cache}: ${scope} disabled after first failure: ${String(err)}`);
62
+ };
63
+ }
64
+ // extensions/imessage/src/runtime.ts
65
+ const { getRuntime: getIMessageRuntime, setRuntime: setIMessageRuntime, tryGetRuntime: getOptionalIMessageRuntime } = createPluginRuntimeStore({
66
+ pluginId: "imessage",
67
+ errorMessage: "iMessage runtime not initialized"
68
+ });
69
+ // extensions/imessage/src/state-contract.ts
70
+ const IMESSAGE_REPLY_CACHE_NAMESPACE = "imessage.reply-cache";
71
+ const IMESSAGE_REPLY_CACHE_MAX_ENTRIES = 2e3;
72
+ const IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE = "imessage.reply-cache-counter";
73
+ const IMESSAGE_REPLY_CACHE_COUNTER_KEY = "short-id-counter";
74
+ const IMESSAGE_REPLY_CACHE_TTL_MS = 216e5;
75
+ function resolveIMessageReplyCacheEntryKey(messageId) {
76
+ return createHash("sha256").update(messageId, "utf8").digest("hex").slice(0, 32);
77
+ }
78
+ const MAX_FAILURE_RETRY_MAP_SIZE = 512;
79
+ const textEncoder = new TextEncoder();
80
+ const IMESSAGE_CATCHUP_CURSOR_NAMESPACE = "imessage.catchup-cursors";
81
+ function resolveIMessageCatchupCursorKey(accountId) {
82
+ return createHash("sha256").update(accountId, "utf8").digest("hex").slice(0, 32);
83
+ }
84
+ /**
85
+ * Bound the retry map so a pathological storm of unique failing GUIDs
86
+ * cannot grow the cursor value without limit. Keeps the `maxSize` entries
87
+ * with the highest counts (closest to give-up) when over the bound.
88
+ */
89
+ function capFailureRetriesMap(map, maxSize = MAX_FAILURE_RETRY_MAP_SIZE) {
90
+ const maxBytes = 48e3;
91
+ const entries = Object.entries(map);
92
+ if (entries.length <= maxSize && textEncoder.encode(JSON.stringify(map)).byteLength <= maxBytes) return map;
93
+ entries.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
94
+ const capped = {};
95
+ for (const [guid, count] of entries.slice(0, maxSize)) {
96
+ capped[guid] = count;
97
+ if (textEncoder.encode(JSON.stringify(capped)).byteLength > maxBytes) {
98
+ delete capped[guid];
99
+ break;
100
+ }
101
+ }
102
+ return capped;
103
+ }
104
+ const IMESSAGE_SENT_ECHOES_TTL_MS = 432e5;
105
+ const IMESSAGE_SENT_ECHOES_NAMESPACE = "imessage.sent-echoes";
106
+ function resolveIMessageSentEchoEntryKey(entry) {
107
+ return createHash("sha256").update(JSON.stringify([
108
+ entry.scope,
109
+ entry.text ?? "",
110
+ resolveIMessageEchoMediaKey(entry.media) ?? "",
111
+ entry.messageId ?? "",
112
+ entry.timestamp
113
+ ])).digest("hex").slice(0, 32);
114
+ }
115
+ function resolveIMessageEchoMediaKey(media) {
116
+ const contentType = media?.contentType?.trim().toLowerCase() || void 0;
117
+ const kind = media?.kind && media.kind !== "unknown" ? media.kind : void 0;
118
+ if (kind) return `kind:${kind}`;
119
+ const normalizedContentType = contentType?.split(";", 1)[0]?.trim();
120
+ return normalizedContentType ? `mime:${normalizedContentType}` : void 0;
121
+ }
122
+ // extensions/imessage/src/monitor-reply-cache.ts
123
+ /** Recency window for the "react to the latest message" fallback. */
124
+ const LATEST_FALLBACK_MS = 6e5;
125
+ const reportPersistenceFailure = createPersistenceFailureReporter("reply-cache");
126
+ const imessageReplyCacheByMessageId = /* @__PURE__ */ new Map();
127
+ const imessageShortIdToUuid = /* @__PURE__ */ new Map();
128
+ let imessageShortIdCounter = 0;
129
+ function openReplyCacheStore() {
130
+ return getIMessageRuntime().state.openKeyedStoreV2({
131
+ namespace: IMESSAGE_REPLY_CACHE_NAMESPACE,
132
+ maxEntries: IMESSAGE_REPLY_CACHE_MAX_ENTRIES
133
+ });
134
+ }
135
+ function openReplyCacheCounterStore() {
136
+ return getIMessageRuntime().state.openKeyedStoreV2({
137
+ namespace: IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE,
138
+ maxEntries: 1
139
+ });
140
+ }
141
+ function remainingTtlMs(timestamp) {
142
+ const remaining = IMESSAGE_REPLY_CACHE_TTL_MS - Math.max(0, Date.now() - timestamp);
143
+ return remaining > 0 ? remaining : void 0;
144
+ }
145
+ let hydrated = false;
146
+ let hydration;
147
+ let persistence = Promise.resolve();
148
+ function hydrateCounter(counter) {
149
+ if (counter && Number.isSafeInteger(counter.counter) && counter.counter > 0) imessageShortIdCounter = Math.max(imessageShortIdCounter, counter.counter);
150
+ }
151
+ function hydrateRows(entries) {
152
+ const cutoff = Date.now() - IMESSAGE_REPLY_CACHE_TTL_MS;
153
+ for (const entry of entries.filter((cached) => cached.timestamp >= cutoff).toSorted((a, b) => a.timestamp - b.timestamp).slice(-2e3)) {
154
+ const numeric = Number.parseInt(entry.shortId, 10);
155
+ if (Number.isFinite(numeric) && numeric > imessageShortIdCounter) imessageShortIdCounter = numeric;
156
+ imessageReplyCacheByMessageId.set(entry.messageId, entry);
157
+ imessageShortIdToUuid.set(entry.shortId, entry.messageId);
158
+ }
159
+ }
160
+ async function hydrateFromStoreOnce() {
161
+ if (hydrated) return;
162
+ hydration ??= (async () => {
163
+ try {
164
+ hydrateCounter(await openReplyCacheCounterStore().lookup(IMESSAGE_REPLY_CACHE_COUNTER_KEY));
165
+ hydrateRows((await openReplyCacheStore().entries()).map(({ value }) => value));
166
+ } catch (err) {
167
+ reportPersistenceFailure("read", err);
168
+ } finally {
169
+ hydrated = true;
170
+ }
171
+ })();
172
+ await hydration;
173
+ }
174
+ async function persistReplyCacheEntry(entry) {
175
+ const ttlMs = remainingTtlMs(entry.timestamp);
176
+ if (!ttlMs) return;
177
+ try {
178
+ await openReplyCacheStore().register(resolveIMessageReplyCacheEntryKey(entry.messageId), entry, { ttlMs });
179
+ } catch (err) {
180
+ reportPersistenceFailure("write", err);
181
+ }
182
+ }
183
+ async function deleteReplyCacheEntry(messageId) {
184
+ try {
185
+ await openReplyCacheStore().delete(resolveIMessageReplyCacheEntryKey(messageId));
186
+ } catch (err) {
187
+ reportPersistenceFailure("delete", err);
188
+ }
189
+ }
190
+ async function persistReplyCacheCounter(counter) {
191
+ try {
192
+ await openReplyCacheCounterStore().register(IMESSAGE_REPLY_CACHE_COUNTER_KEY, { counter });
193
+ } catch (err) {
194
+ reportPersistenceFailure("counter", err);
195
+ }
196
+ }
197
+ function buildReplyCacheEntry(entry, messageId, shortId) {
198
+ return {
199
+ accountId: entry.accountId,
200
+ messageId,
201
+ shortId,
202
+ timestamp: entry.timestamp,
203
+ ...entry.chatGuid !== void 0 ? { chatGuid: entry.chatGuid } : {},
204
+ ...entry.chatIdentifier !== void 0 ? { chatIdentifier: entry.chatIdentifier } : {},
205
+ ...entry.chatId !== void 0 ? { chatId: entry.chatId } : {},
206
+ ...entry.isFromMe !== void 0 ? { isFromMe: entry.isFromMe } : {}
207
+ };
208
+ }
209
+ async function rememberIMessageReplyCache(entry) {
210
+ await hydrateFromStoreOnce();
211
+ const messageId = entry.messageId.trim();
212
+ if (!messageId) return {
213
+ ...entry,
214
+ shortId: ""
215
+ };
216
+ let shortId = imessageReplyCacheByMessageId.get(messageId)?.shortId;
217
+ const isNewMessage = !shortId;
218
+ if (!shortId) {
219
+ shortId = String(++imessageShortIdCounter);
220
+ imessageShortIdToUuid.set(shortId, messageId);
221
+ }
222
+ const fullEntry = buildReplyCacheEntry(entry, messageId, shortId);
223
+ imessageReplyCacheByMessageId.delete(messageId);
224
+ imessageReplyCacheByMessageId.set(messageId, fullEntry);
225
+ const cutoff = Date.now() - IMESSAGE_REPLY_CACHE_TTL_MS;
226
+ const deletedMessageIds = [];
227
+ const pruneUntil = (retain) => {
228
+ for (const [key, cached] of imessageReplyCacheByMessageId) {
229
+ if (retain(key, cached)) break;
230
+ imessageReplyCacheByMessageId.delete(key);
231
+ deletedMessageIds.push(key);
232
+ if (cached.shortId) imessageShortIdToUuid.delete(cached.shortId);
233
+ }
234
+ };
235
+ pruneUntil((_key, cached) => cached.timestamp >= cutoff);
236
+ pruneUntil((key) => !key || imessageReplyCacheByMessageId.size <= 2e3);
237
+ const counter = imessageShortIdCounter;
238
+ persistence = persistence.then(async () => {
239
+ if (isNewMessage) await persistReplyCacheCounter(counter);
240
+ for (const messageIdToDelete of deletedMessageIds) await deleteReplyCacheEntry(messageIdToDelete);
241
+ await persistReplyCacheEntry(fullEntry);
242
+ });
243
+ await persistence;
244
+ return fullEntry;
245
+ }
246
+ function hasChatScope(ctx) {
247
+ if (!ctx) return false;
248
+ return Boolean(normalizeOptionalString(ctx.chatGuid) || normalizeOptionalString(ctx.chatIdentifier) || typeof ctx.chatId === "number");
249
+ }
250
+ function describeChatForError(values) {
251
+ const parts = [];
252
+ if (normalizeOptionalString(values.chatGuid)) parts.push("chatGuid=<redacted>");
253
+ if (normalizeOptionalString(values.chatIdentifier)) parts.push("chatIdentifier=<redacted>");
254
+ if (typeof values.chatId === "number") parts.push("chatId=<redacted>");
255
+ return parts.length === 0 ? "<unknown chat>" : parts.join(", ");
256
+ }
257
+ function describeMessageIdForError(inputId, inputKind) {
258
+ if (inputKind === "short") return `<short:${inputId.length}-digit>`;
259
+ return `<uuid:${inputId.slice(0, 8)}...>`;
260
+ }
261
+ function buildCrossChatError(inputId, inputKind, cached, ctx) {
262
+ const remediation = inputKind === "short" ? "Use a message ID from the current chat target; MessageSidFull from another chat is rejected." : "Retry with the correct chat target.";
263
+ return /* @__PURE__ */ new Error(`iMessage message id ${describeMessageIdForError(inputId, inputKind)} belongs to a different chat (${describeChatForError(cached)}) than the current call target (${describeChatForError(ctx)}). ${remediation}`);
264
+ }
265
+ async function resolveIMessageMessageId(shortOrUuid, opts) {
266
+ const trimmed = shortOrUuid.trim();
267
+ if (!trimmed) return trimmed;
268
+ await hydrateFromStoreOnce();
269
+ const inputKind = /^\d+$/.test(trimmed) ? "short" : "uuid";
270
+ const messageId = inputKind === "short" ? imessageShortIdToUuid.get(trimmed) : trimmed;
271
+ if (!messageId) {
272
+ if (!hasChatScope(opts?.chatContext)) throw new Error(`iMessage short message id ${describeMessageIdForError(trimmed, "short")} requires a chat scope (chatGuid / chatIdentifier / chatId or a target).`);
273
+ throw new Error(`iMessage short message id ${describeMessageIdForError(trimmed, "short")} is no longer available. Use MessageSidFull.`);
274
+ }
275
+ const cached = imessageReplyCacheByMessageId.get(messageId);
276
+ if (cached && opts?.chatContext && (inputKind === "uuid" || hasChatScope(opts.chatContext)) && resolveIMessageChatMatch(cached, opts.chatContext) === "mismatch") throw buildCrossChatError(trimmed, inputKind, cached, opts.chatContext);
277
+ if (opts?.requireFromMe && cached?.isFromMe !== true) throw buildFromMeError(trimmed, inputKind);
278
+ return messageId;
279
+ }
280
+ async function isKnownFromMeIMessageMessageId(messageId, ctx) {
281
+ const trimmed = normalizeOptionalString(messageId);
282
+ if (!trimmed || !ctx.accountId || !hasChatScope(ctx)) return false;
283
+ await hydrateFromStoreOnce();
284
+ const cached = imessageReplyCacheByMessageId.get(trimmed);
285
+ if (!cached || cached.isFromMe !== true) return false;
286
+ return resolveCachedResourceBinding(trimmed, {
287
+ ...ctx,
288
+ accountId: ctx.accountId
289
+ }) === "match";
290
+ }
291
+ async function isKnownFromMeIMessageTarget(params) {
292
+ const { accountId, chatId, chatGuid, chatIdentifier } = params;
293
+ const ctx = {
294
+ accountId,
295
+ chatId,
296
+ chatGuid,
297
+ chatIdentifier
298
+ };
299
+ for (const messageId of params.messageIds) if (await isKnownFromMeIMessageMessageId(messageId, ctx)) return true;
300
+ return false;
301
+ }
302
+ function buildFromMeError(inputId, inputKind) {
303
+ return /* @__PURE__ */ new Error(`iMessage message id ${describeMessageIdForError(inputId, inputKind)} is not one this agent sent. edit and unsend can only target messages the gateway delivered itself; messages received from other participants cannot be modified.`);
304
+ }
305
+ /** Latest recent entry with a positive same-account conversation match; never guess a chat. */
306
+ function findLatestIMessageEntryForChat(ctx) {
307
+ if (!hasChatScope(ctx) || !ctx.accountId) return;
308
+ const cutoff = Date.now() - LATEST_FALLBACK_MS;
309
+ let best;
310
+ for (const entry of imessageReplyCacheByMessageId.values()) {
311
+ if (entry.accountId !== ctx.accountId) continue;
312
+ if (entry.timestamp < cutoff) continue;
313
+ if (resolveIMessageChatMatch(entry, ctx) !== "match") continue;
314
+ if (!best || entry.timestamp > best.timestamp) best = entry;
315
+ }
316
+ return best;
317
+ }
318
+ async function resolveIMessageCachedResourceBinding(messageId, ctx) {
319
+ await hydrateFromStoreOnce();
320
+ return resolveCachedResourceBinding(messageId, ctx);
321
+ }
322
+ function resolveCachedResourceBinding(messageId, ctx) {
323
+ const entry = imessageReplyCacheByMessageId.get(messageId.trim());
324
+ if (!entry) return "unknown";
325
+ if (Date.now() - entry.timestamp > 216e5) return "unknown";
326
+ if (entry.accountId !== ctx.accountId) return "mismatch";
327
+ return resolveIMessageChatMatch(entry, ctx);
328
+ }
329
+ async function isIMessageCurrentMessageInChatAsync(params) {
330
+ await hydrateFromStoreOnce();
331
+ return isCurrentMessageInChat(params);
332
+ }
333
+ function isCurrentMessageInChat(params) {
334
+ if (!params.accountId || !hasChatScope(params.chatContext)) return false;
335
+ const currentMessageId = normalizeOptionalString(String(params.currentMessageId));
336
+ if (!currentMessageId) return false;
337
+ const fullMessageId = /^\d+$/.test(currentMessageId) ? imessageShortIdToUuid.get(currentMessageId) : currentMessageId;
338
+ if (!fullMessageId) return false;
339
+ return resolveCachedResourceBinding(fullMessageId, {
340
+ ...params.chatContext,
341
+ accountId: params.accountId
342
+ }) === "match";
343
+ }
57
344
  // extensions/imessage/src/probe.ts
58
345
  const RPC_SUPPORT_CACHE_TTL_MS = 3e5;
59
346
  const PRIVATE_API_NEGATIVE_TTL_MS = 1e4;
60
347
  const rpcSupportCache = /* @__PURE__ */ new Map();
61
348
  function cacheIMessagePrivateApiStatus(cliPath, status) {
62
- if (status.available) {
63
- setCachedIMessagePrivateApiStatus(cliPath, status, 0);
64
- return;
65
- }
66
- const expiresAt = resolveExpiresAtMsFromDurationMs(PRIVATE_API_NEGATIVE_TTL_MS);
67
- if (expiresAt !== void 0) setCachedIMessagePrivateApiStatus(cliPath, status, expiresAt);
349
+ setCachedIMessagePrivateApiStatus(cliPath, status, status.available ? 0 : Date.now() + PRIVATE_API_NEGATIVE_TTL_MS);
68
350
  }
69
351
  function setCachedRpcSupport(cliPath, result) {
70
- const expiresAt = resolveExpiresAtMsFromDurationMs(RPC_SUPPORT_CACHE_TTL_MS);
71
- if (expiresAt === void 0) return;
72
352
  rpcSupportCache.set(cliPath, {
73
353
  result,
74
- expiresAt
354
+ expiresAt: Date.now() + RPC_SUPPORT_CACHE_TTL_MS
75
355
  });
76
356
  }
77
357
  async function probeRpcSupport(cliPath, timeoutMs) {
78
358
  const cached = rpcSupportCache.get(cliPath);
79
359
  if (cached) {
80
- const now = asDateTimestampMs(Date.now());
81
- if (now !== void 0 && cached.expiresAt > now) return cached.result;
360
+ if (cached.expiresAt > Date.now()) return cached.result;
82
361
  rpcSupportCache.delete(cliPath);
83
362
  }
84
363
  try {
@@ -134,6 +413,21 @@ async function probeIMessagePrivateApi(cliPath, timeoutMs, options = {}) {
134
413
  const cached = getCachedIMessagePrivateApiStatus(key);
135
414
  if (cached) return cached;
136
415
  }
416
+ const unavailable = (error) => {
417
+ const status = {
418
+ available: false,
419
+ v2Ready: false,
420
+ selectors: {},
421
+ rpcMethods: [],
422
+ cliCapabilities: {
423
+ sendRichSupportsAttachment: false,
424
+ pollSendSupportsNoComment: false
425
+ },
426
+ error
427
+ };
428
+ cacheIMessagePrivateApiStatus(key, status);
429
+ return status;
430
+ };
137
431
  try {
138
432
  const result = await runCommandWithTimeout([
139
433
  expandIMessageUserPath(key),
@@ -142,21 +436,7 @@ async function probeIMessagePrivateApi(cliPath, timeoutMs, options = {}) {
142
436
  ], { timeoutMs });
143
437
  const combined = `${result.stdout}\n${result.stderr}`.trim();
144
438
  const normalized = normalizeLowercaseStringOrEmpty(combined);
145
- if (result.code !== 0 && normalized.includes("unknown subcommand") && normalized.includes("status")) {
146
- const status = {
147
- available: false,
148
- v2Ready: false,
149
- selectors: {},
150
- rpcMethods: [],
151
- cliCapabilities: {
152
- sendRichSupportsAttachment: false,
153
- pollSendSupportsNoComment: false
154
- },
155
- error: `imsg CLI does not support the "status" subcommand. Update imsg on the Messages Mac: ${IMESSAGE_UPDATE_COMMAND}`
156
- };
157
- cacheIMessagePrivateApiStatus(key, status);
158
- return status;
159
- }
439
+ if (result.code !== 0 && normalized.includes("unknown subcommand") && normalized.includes("status")) return unavailable(`imsg CLI does not support the "status" subcommand. Update imsg on the Messages Mac: ${IMESSAGE_UPDATE_COMMAND}`);
160
440
  const lines = normalizeStringEntries(result.stdout.split(/\r?\n/));
161
441
  let payload = null;
162
442
  for (const line of lines.toReversed()) try {
@@ -193,19 +473,7 @@ async function probeIMessagePrivateApi(cliPath, timeoutMs, options = {}) {
193
473
  cacheIMessagePrivateApiStatus(key, status);
194
474
  return status;
195
475
  } catch (err) {
196
- const status = {
197
- available: false,
198
- v2Ready: false,
199
- selectors: {},
200
- rpcMethods: [],
201
- cliCapabilities: {
202
- sendRichSupportsAttachment: false,
203
- pollSendSupportsNoComment: false
204
- },
205
- error: String(err)
206
- };
207
- cacheIMessagePrivateApiStatus(key, status);
208
- return status;
476
+ return unavailable(String(err));
209
477
  }
210
478
  }
211
479
  async function probeIMessage(timeoutMs, opts = {}) {
@@ -257,7 +525,7 @@ async function probeIMessage(timeoutMs, opts = {}) {
257
525
  }
258
526
  }
259
527
  // extensions/imessage/src/actions.ts
260
- const loadIMessageActionsRuntime = createLazyRuntimeNamedExport(() => import("./actions.runtime-B2ZHQ3TJ.mjs"), "imessageActionsRuntime");
528
+ const loadIMessageActionsRuntime = createLazyRuntimeNamedExport(() => import("./actions.runtime-Ccg9CARL.mjs"), "imessageActionsRuntime");
261
529
  const log = createSubsystemLogger("channels/imessage");
262
530
  const providerId = "imessage";
263
531
  const SUPPORTED_ACTIONS = /* @__PURE__ */ new Set([...IMESSAGE_ACTION_NAMES, "upload-file"]);
@@ -329,32 +597,9 @@ function readMessageIdWithChatFallback(params, chatContext) {
329
597
  if (latest?.messageId) return latest.messageId;
330
598
  return readStringParam(params, "messageId", { required: true });
331
599
  }
332
- async function resolveChatGuid(params) {
333
- const target = resolveIMessageActionTarget(params);
334
- if (!target) throw new Error(`iMessage ${params.action} requires chatGuid, chatId, chatIdentifier, or a chat target.`);
335
- if (target.kind === "chat_guid") return target.chatGuid;
336
- const synthesizedIdentifier = target.kind === "handle" ? `${target.service === "sms" ? "SMS" : "iMessage"};-;${target.to}` : "";
337
- const lookupTarget = target.kind === "handle" ? {
338
- kind: "chat_identifier",
339
- chatIdentifier: synthesizedIdentifier
340
- } : target;
341
- const resolved = await params.runtime.resolveChatGuidForTarget({
342
- target: lookupTarget,
343
- options: params.options,
344
- conversationReadOrigin: params.conversationReadOrigin
345
- });
346
- if (resolved) return resolved;
347
- if (target.kind !== "handle") throw new Error(`iMessage ${params.action} failed: chatGuid not found for ${formatUnresolvedTarget(target)}.`);
348
- if (params.action === "react" || params.action === "edit" || params.action === "unsend") throw new Error(`iMessage ${params.action} requires a known chat. No registered chat for the supplied target; send a message first or pass an explicit chatGuid.`);
349
- return synthesizedIdentifier;
350
- }
351
600
  function formatUnresolvedTarget(target) {
352
601
  return target.kind === "chat_id" ? "chat_id:<redacted>" : "chat_identifier:<redacted>";
353
602
  }
354
- function buildChatContextFromActionParams(params) {
355
- const target = resolveIMessageActionTarget(params);
356
- return target ? chatContextFromIMessageTarget(target, params.service) : {};
357
- }
358
603
  function mapTapbackReaction(emoji) {
359
604
  const value = normalizeOptionalLowercaseString(emoji)?.replace(/\ufe0f/g, "");
360
605
  return value ? [
@@ -399,7 +644,7 @@ function decodeBase64Buffer(params, action) {
399
644
  if (!base64Buffer) throw new Error(`iMessage ${action} requires buffer (base64) parameter.`);
400
645
  const canonical = canonicalizeBase64(base64Buffer.replaceAll("-", "+").replaceAll("_", "/"));
401
646
  if (!canonical) throw new Error(`iMessage ${action} buffer must be valid base64.`);
402
- return Uint8Array.from(Buffer.from(canonical, "base64"));
647
+ return Buffer.from(canonical, "base64");
403
648
  }
404
649
  const REPLY_ATTACHMENT_PATH_PARAM_NAMES = [
405
650
  "filePath",
@@ -408,21 +653,6 @@ const REPLY_ATTACHMENT_PATH_PARAM_NAMES = [
408
653
  "mediaUrl",
409
654
  "fileUrl"
410
655
  ];
411
- function extractReplyAttachment(params) {
412
- if (readStringParam(params, "buffer")) {
413
- const filename = readStringParam(params, "filename") ?? "attachment.bin";
414
- return { spec: {
415
- kind: "buffer",
416
- buffer: decodeBase64Buffer(params, "reply attachment"),
417
- filename
418
- } };
419
- }
420
- for (const name of REPLY_ATTACHMENT_PATH_PARAM_NAMES) if (readStringParam(params, name)) return {
421
- spec: null,
422
- bypassParam: name
423
- };
424
- return null;
425
- }
426
656
  const EFFECT_ALIASES = {
427
657
  slam: "com.apple.MobileSMS.expressivesend.impact",
428
658
  impact: "com.apple.MobileSMS.expressivesend.impact",
@@ -509,20 +739,35 @@ const imessageMessageActions = {
509
739
  timeoutMs: account.config.probeTimeoutMs
510
740
  };
511
741
  const attestedConversationReadOrigin = conversationReadOrigin ?? "delegated";
512
- const chatGuid = async () => await resolveChatGuid({
513
- action,
514
- actionParams: params,
515
- currentChannelId: toolContext?.currentChannelId,
516
- conversationReadOrigin: attestedConversationReadOrigin,
517
- runtime,
518
- options: opts
519
- });
742
+ const chatGuid = async () => {
743
+ const target = resolveIMessageActionTarget({
744
+ actionParams: params,
745
+ currentChannelId: toolContext?.currentChannelId
746
+ });
747
+ if (!target) throw new Error(`iMessage ${action} requires chatGuid, chatId, chatIdentifier, or a chat target.`);
748
+ if (target.kind === "chat_guid") return target.chatGuid;
749
+ const synthesizedIdentifier = target.kind === "handle" ? `${target.service === "sms" ? "SMS" : "iMessage"};-;${target.to}` : "";
750
+ const lookupTarget = target.kind === "handle" ? {
751
+ kind: "chat_identifier",
752
+ chatIdentifier: synthesizedIdentifier
753
+ } : target;
754
+ const resolved = await runtime.resolveChatGuidForTarget({
755
+ target: lookupTarget,
756
+ options: opts,
757
+ conversationReadOrigin: attestedConversationReadOrigin
758
+ });
759
+ if (resolved) return resolved;
760
+ if (target.kind !== "handle") throw new Error(`iMessage ${action} failed: chatGuid not found for ${formatUnresolvedTarget(target)}.`);
761
+ if (action === "react" || action === "edit" || action === "unsend") throw new Error(`iMessage ${action} requires a known chat. No registered chat for the supplied target; send a message first or pass an explicit chatGuid.`);
762
+ return synthesizedIdentifier;
763
+ };
520
764
  const messageReference = async (input) => {
521
- const inputChatContext = buildChatContextFromActionParams({
765
+ const service = account.config.service;
766
+ const target = resolveIMessageActionTarget({
522
767
  actionParams: params,
523
- currentChannelId: toolContext?.currentChannelId,
524
- service: account.config.service
768
+ currentChannelId: toolContext?.currentChannelId
525
769
  });
770
+ const inputChatContext = target ? chatContextFromIMessageTarget(target, service) : {};
526
771
  return await resolveAuthorizedIMessageActionReference({
527
772
  messageId: input?.messageId,
528
773
  inputChatContext,
@@ -580,7 +825,7 @@ const imessageMessageActions = {
580
825
  if (!remove && (isEmpty || !reaction)) throw new Error("iMessage react supports love, like, dislike, laugh, emphasize, and question tapbacks.");
581
826
  const partIndex = readNonNegativeIntegerParam(params, "partIndex");
582
827
  const reference = await messageReference();
583
- const reactionsToSend = remove && !reaction ? [...TAPBACK_KINDS] : reaction ? [reaction] : [];
828
+ const reactionsToSend = remove && !reaction ? TAPBACK_KINDS : reaction ? [reaction] : [];
584
829
  for (const kind of reactionsToSend) await runtime.sendReaction({
585
830
  chatGuid: reference.chatGuid,
586
831
  messageId: reference.messageId,
@@ -631,9 +876,16 @@ const imessageMessageActions = {
631
876
  const text = readMessageText(params);
632
877
  if (!text) throw new Error("iMessage reply requires text or message.");
633
878
  const reference = await messageReference();
634
- const attachment = extractReplyAttachment(params);
879
+ let attachment;
880
+ if (readStringParam(params, "buffer")) {
881
+ const filename = readStringParam(params, "filename") ?? "attachment.bin";
882
+ attachment = {
883
+ kind: "buffer",
884
+ buffer: decodeBase64Buffer(params, "reply attachment"),
885
+ filename
886
+ };
887
+ } else for (const name of REPLY_ATTACHMENT_PATH_PARAM_NAMES) if (readStringParam(params, name)) throw new Error(`iMessage reply rejected \`${name}\` because it did not pass through the outbound media resolver. Pass a base64 \`buffer\` + \`filename\` directly, or invoke message(action: "reply") through the runner so the resolver can validate the path against mediaLocalRoots/sandbox/size before sending.`);
635
888
  if (attachment) {
636
- if (attachment.spec === null) throw new Error(`iMessage reply rejected \`${attachment.bypassParam}\` because it did not pass through the outbound media resolver. Pass a base64 \`buffer\` + \`filename\` directly, or invoke message(action: "reply") through the runner so the resolver can validate the path against mediaLocalRoots/sandbox/size before sending.`);
637
889
  if (!opts.remoteHost && privateApiStatus?.cliCapabilities?.sendRichSupportsAttachment !== true) throw new Error("iMessage reply with an attachment needs an imsg build that exposes `send-rich --file` (openclaw/imsg#114). Upgrade imsg, or use action 'upload-file' (with filePath/filename) or action 'send' (with media) to deliver the file plus a separate 'reply' for any text.");
638
890
  }
639
891
  const partIndex = readNonNegativeIntegerParam(params, "partIndex");
@@ -642,7 +894,7 @@ const imessageMessageActions = {
642
894
  text,
643
895
  replyToMessageId: reference.messageId,
644
896
  partIndex,
645
- attachment: attachment?.spec ?? void 0,
897
+ attachment,
646
898
  options: opts
647
899
  }), reference.chatGuid, { repliedTo: reference.messageId });
648
900
  }
@@ -773,4 +1025,4 @@ const imessageMessageActions = {
773
1025
  throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
774
1026
  }
775
1027
  };
776
- export { normalizeIMessageMessageId as a, normalizeIMessageGuid as i, probeIMessage as n, probeIMessagePrivateApi as r, imessageMessageActions as t };
1028
+ export { setIMessageRuntime as _, rememberIMessageReplyCache as a, normalizeIMessageMessageId as b, IMESSAGE_CATCHUP_CURSOR_NAMESPACE as c, capFailureRetriesMap as d, resolveIMessageCatchupCursorKey as f, getOptionalIMessageRuntime as g, getIMessageRuntime as h, isKnownFromMeIMessageTarget as i, IMESSAGE_SENT_ECHOES_NAMESPACE as l, resolveIMessageSentEchoEntryKey as m, probeIMessage as n, resolveIMessageCachedResourceBinding as o, resolveIMessageEchoMediaKey as p, probeIMessagePrivateApi as r, resolveIMessageMessageId as s, imessageMessageActions as t, IMESSAGE_SENT_ECHOES_TTL_MS as u, createPersistenceFailureReporter as v, normalizeIMessageGuid as y };