@cecwxf/wtt 0.1.7 → 0.1.9

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.
package/dist/channel.d.ts CHANGED
@@ -151,6 +151,9 @@ export type NormalizedInboundWsMessage = {
151
151
  to: string;
152
152
  from: string;
153
153
  conversationLabel: string;
154
+ /** Image URLs extracted from message content (HTML <img> or markdown ![](url)) */
155
+ mediaUrls: string[];
156
+ mediaTypes: string[];
154
157
  };
155
158
  export declare function normalizeInboundWsMessage(params: {
156
159
  msg: WsNewMessage;
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}
package/dist/channel.js CHANGED
@@ -18,7 +18,7 @@ import { WTTCloudClient } from "./ws-client.js";
18
18
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
19
19
  import { createRequire } from "node:module";
20
20
  import os from "node:os";
21
- import { dirname, join as joinPath } from "node:path";
21
+ import { dirname, extname, join as joinPath } from "node:path";
22
22
  import { randomBytes } from "node:crypto";
23
23
  import { pathToFileURL } from "node:url";
24
24
  const DEFAULT_ACCOUNT_ID = "default";
@@ -34,10 +34,15 @@ const DEFAULT_SLASH_COMPAT_ENABLED = false;
34
34
  const DEFAULT_SLASH_COMPAT_WTT_PREFIX_ONLY = true;
35
35
  const DEFAULT_SLASH_BYPASS_MENTION_GATE = false;
36
36
  const DEFAULT_NATURAL_BRIDGE_MIN_DOING_MS = 2500;
37
+ const DEFAULT_INBOUND_MEDIA_MAX_BYTES = 15 * 1024 * 1024;
38
+ const DEFAULT_INBOUND_MEDIA_MAX_PER_MESSAGE = 4;
39
+ const DEFAULT_INBOUND_MEDIA_FETCH_TIMEOUT_MS = 20_000;
37
40
  const hooks = { before: [], after: [] };
38
41
  const clients = new Map();
39
42
  const topicTypeCache = new Map();
43
+ const recentTopicMediaCache = new Map();
40
44
  const DEFAULT_P2P_E2E_ENABLED = true;
45
+ const RECENT_TOPIC_MEDIA_TTL_MS = 10 * 60_000;
41
46
  function registerHook(phase, fn) {
42
47
  if (phase === "before_tool_call")
43
48
  hooks.before.push(fn);
@@ -165,6 +170,73 @@ function rememberTopicType(topicId, topicType) {
165
170
  topicTypeCache.delete(oldest);
166
171
  }
167
172
  }
173
+ function recentTopicMediaKey(topicId, senderId) {
174
+ return `${topicId.trim()}::${senderId.trim()}`;
175
+ }
176
+ function rememberRecentTopicMedia(topicId, senderId, mediaUrls, mediaTypes) {
177
+ const topic = topicId.trim();
178
+ const sender = senderId.trim();
179
+ if (!topic || !sender || mediaUrls.length === 0)
180
+ return;
181
+ const dedupUrls = Array.from(new Set(mediaUrls.map((item) => String(item || "").trim()).filter(Boolean)));
182
+ const dedupTypes = mediaTypes.slice(0, dedupUrls.length);
183
+ if (dedupUrls.length === 0)
184
+ return;
185
+ recentTopicMediaCache.set(recentTopicMediaKey(topic, sender), {
186
+ at: Date.now(),
187
+ mediaUrls: dedupUrls,
188
+ mediaTypes: dedupTypes.length > 0 ? dedupTypes : new Array(dedupUrls.length).fill("image/png"),
189
+ });
190
+ while (recentTopicMediaCache.size > 5000) {
191
+ const oldest = recentTopicMediaCache.keys().next().value;
192
+ if (!oldest)
193
+ break;
194
+ recentTopicMediaCache.delete(oldest);
195
+ }
196
+ }
197
+ function readRecentTopicMedia(topicId, senderId) {
198
+ const now = Date.now();
199
+ const key = recentTopicMediaKey(topicId, senderId);
200
+ const direct = recentTopicMediaCache.get(key);
201
+ if (direct) {
202
+ if (now - direct.at <= RECENT_TOPIC_MEDIA_TTL_MS) {
203
+ return {
204
+ mediaUrls: direct.mediaUrls.slice(),
205
+ mediaTypes: direct.mediaTypes.slice(),
206
+ };
207
+ }
208
+ recentTopicMediaCache.delete(key);
209
+ }
210
+ // Fallback: some environments may surface a changing sender_id for HUMAN
211
+ // messages. In that case, use latest recent media within the same topic.
212
+ const prefix = `${topicId.trim()}::`;
213
+ let best = null;
214
+ for (const [entryKey, value] of recentTopicMediaCache.entries()) {
215
+ if (!entryKey.startsWith(prefix))
216
+ continue;
217
+ if (now - value.at > RECENT_TOPIC_MEDIA_TTL_MS) {
218
+ recentTopicMediaCache.delete(entryKey);
219
+ continue;
220
+ }
221
+ if (!best || value.at > best.at)
222
+ best = value;
223
+ }
224
+ if (!best)
225
+ return null;
226
+ return {
227
+ mediaUrls: best.mediaUrls.slice(),
228
+ mediaTypes: best.mediaTypes.slice(),
229
+ };
230
+ }
231
+ function looksLikeImageFollowupText(text) {
232
+ const raw = String(text || "").trim();
233
+ if (!raw)
234
+ return true;
235
+ const compact = raw.replace(/\s+/g, "");
236
+ if (compact.length <= 24)
237
+ return true;
238
+ return /(图片|图里|看图|识别|这是啥|这是什么|什么狗|啥狗|what\s+is\s+this|what\s+dog|identify)/i.test(raw);
239
+ }
168
240
  function isP2PTopicId(topicId) {
169
241
  const type = topicTypeCache.get(topicId.trim());
170
242
  return type === "p2p";
@@ -779,8 +851,285 @@ function sanitizeInboundText(raw) {
779
851
  let text = raw || "";
780
852
  // Strip WTT source marker banner block if present.
781
853
  text = text.replace(/┌─ 来源标识[\s\S]*?└[^\n]*\n?/g, "").trim();
854
+ // Remove inline image payload markers from text body.
855
+ // Why: markdown image syntax starts with `!`, which can be interpreted as
856
+ // shell-command prefix by OpenClaw slash command surface.
857
+ text = text.replace(/<img\b[^>]*>/gi, " ");
858
+ text = text.replace(/!\[[^\]]*\]\((https?:\/\/[^\s)]+)\)/gi, " ");
859
+ text = text.replace(/(?:^|\n)\s*https?:\/\/\S+\.(?:jpg|jpeg|png|gif|webp|svg)(?:\?[^\s]*)?\s*(?=\n|$)/gim, "\n");
860
+ text = text.replace(/\n{3,}/g, "\n\n").trim();
782
861
  return text;
783
862
  }
863
+ function extractInboundImageMedia(raw, rawMsg) {
864
+ const source = raw || "";
865
+ const mediaUrls = [];
866
+ const mediaTypes = [];
867
+ const imageExtRe = /\.(?:jpg|jpeg|png|gif|webp|bmp|svg|heic|heif)(?:\?|$)/i;
868
+ const imageHostHintRe = /(?:^|\/)media\/[0-9a-f-]{16,}(?:\.[a-z0-9]{2,8})?(?:\?|$)/i;
869
+ const pushUrl = (urlRaw) => {
870
+ const url = String(urlRaw || "").trim().replace(/[),.;]+$/, "");
871
+ if (!url)
872
+ return;
873
+ if (!/^https?:\/\//i.test(url) && !/^\/?.*media\/[0-9a-f-]{16,}/i.test(url))
874
+ return;
875
+ if (mediaUrls.includes(url))
876
+ return;
877
+ mediaUrls.push(url);
878
+ mediaTypes.push("image/png");
879
+ };
880
+ const scanStringForImageUrls = (input) => {
881
+ const text = String(input || "");
882
+ if (!text)
883
+ return;
884
+ // HTML <img src="url"> pattern (from rich editor)
885
+ const htmlImgRe = /<img\s[^>]*\bsrc\s*=\s*["']([^"']+)["']/gi;
886
+ let htmlMatch;
887
+ while ((htmlMatch = htmlImgRe.exec(text)) !== null) {
888
+ pushUrl(htmlMatch[1]);
889
+ }
890
+ // Markdown ![alt](url) pattern (accept absolute + relative media paths)
891
+ const mdImgRe = /!\[[^\]]*\]\(([^\s)]+)\)/gi;
892
+ let mdMatch;
893
+ while ((mdMatch = mdImgRe.exec(text)) !== null) {
894
+ pushUrl(mdMatch[1]);
895
+ }
896
+ // Generic URL scan (covers metadata/url fields and host URLs without explicit image token)
897
+ const genericUrlRe = /https?:\/\/[^\s"'<>\])]+/gi;
898
+ let urlMatch;
899
+ while ((urlMatch = genericUrlRe.exec(text)) !== null) {
900
+ const candidate = String(urlMatch[0] || "").replace(/[),.;]+$/, "");
901
+ if (!candidate)
902
+ continue;
903
+ if (imageExtRe.test(candidate) || imageHostHintRe.test(candidate)) {
904
+ pushUrl(candidate);
905
+ }
906
+ }
907
+ // Relative media URL scan (e.g. /media/<id> from backend metadata)
908
+ const relativeMediaRe = /(?:^|[\s(\["'])(\/?media\/[0-9a-f-]{16,}(?:\.[a-z0-9]{2,8})?(?:\?[^\s)"']*)?)/gi;
909
+ let relMatch;
910
+ while ((relMatch = relativeMediaRe.exec(text)) !== null) {
911
+ pushUrl(relMatch[1]);
912
+ }
913
+ // Bare image URLs on their own line (explicit fast path)
914
+ const bareImgRe = /^(https?:\/\/\S+\.(?:jpg|jpeg|png|gif|webp|svg)(?:\?[^\s]*)?)$/gim;
915
+ let bareMatch;
916
+ while ((bareMatch = bareImgRe.exec(text)) !== null) {
917
+ pushUrl(bareMatch[1]);
918
+ }
919
+ };
920
+ const walkUnknown = (value, depth = 0) => {
921
+ if (depth > 5 || value == null)
922
+ return;
923
+ if (typeof value === "string") {
924
+ scanStringForImageUrls(value);
925
+ return;
926
+ }
927
+ if (Array.isArray(value)) {
928
+ const upper = Math.min(value.length, 80);
929
+ for (let i = 0; i < upper; i += 1)
930
+ walkUnknown(value[i], depth + 1);
931
+ return;
932
+ }
933
+ if (typeof value !== "object")
934
+ return;
935
+ const obj = value;
936
+ for (const [k, v] of Object.entries(obj)) {
937
+ const key = k.toLowerCase();
938
+ if (typeof v === "string") {
939
+ // Prioritize likely media-related keys.
940
+ if (key.includes("image") || key.includes("media") || key.includes("url") || key.includes("file") || key.includes("asset") || key.includes("attach")) {
941
+ scanStringForImageUrls(v);
942
+ }
943
+ else if (/https?:\/\//i.test(v)) {
944
+ scanStringForImageUrls(v);
945
+ }
946
+ }
947
+ else {
948
+ walkUnknown(v, depth + 1);
949
+ }
950
+ }
951
+ };
952
+ scanStringForImageUrls(source);
953
+ if (rawMsg)
954
+ walkUnknown(rawMsg);
955
+ return { mediaUrls, mediaTypes };
956
+ }
957
+ function absolutizeInboundMediaUrls(mediaUrls, cloudUrl) {
958
+ const base = String(cloudUrl || "").trim().replace(/\/$/, "");
959
+ const originMatch = base.match(/^(https?:\/\/[^/]+)/i);
960
+ const origin = originMatch ? originMatch[1] : base;
961
+ const out = [];
962
+ for (const raw of mediaUrls) {
963
+ let url = String(raw || "").trim();
964
+ if (!url)
965
+ continue;
966
+ if (/^\/\//.test(url)) {
967
+ url = `https:${url}`;
968
+ }
969
+ else if (/^\//.test(url) && origin) {
970
+ url = `${origin}${url}`;
971
+ }
972
+ else if (/^media\//i.test(url) && origin) {
973
+ url = `${origin}/${url}`;
974
+ }
975
+ if (!/^https?:\/\//i.test(url))
976
+ continue;
977
+ if (out.includes(url))
978
+ continue;
979
+ out.push(url);
980
+ }
981
+ return out;
982
+ }
983
+ function resolveOpenClawHomeDir() {
984
+ const fromEnv = process.env.OPENCLAW_HOME?.trim();
985
+ if (fromEnv)
986
+ return fromEnv;
987
+ return dirname(openclawConfigPath());
988
+ }
989
+ function resolveInboundMediaDir() {
990
+ return joinPath(resolveOpenClawHomeDir(), "media", "inbound");
991
+ }
992
+ function extensionFromContentType(contentType) {
993
+ const normalized = String(contentType || "").toLowerCase();
994
+ if (!normalized)
995
+ return "";
996
+ if (normalized.includes("jpeg") || normalized.includes("jpg"))
997
+ return ".jpg";
998
+ if (normalized.includes("png"))
999
+ return ".png";
1000
+ if (normalized.includes("gif"))
1001
+ return ".gif";
1002
+ if (normalized.includes("webp"))
1003
+ return ".webp";
1004
+ if (normalized.includes("bmp"))
1005
+ return ".bmp";
1006
+ if (normalized.includes("svg"))
1007
+ return ".svg";
1008
+ if (normalized.includes("heic"))
1009
+ return ".heic";
1010
+ if (normalized.includes("heif"))
1011
+ return ".heif";
1012
+ if (normalized.includes("mp4"))
1013
+ return ".mp4";
1014
+ return "";
1015
+ }
1016
+ function extensionFromUrl(urlRaw) {
1017
+ try {
1018
+ const parsed = new URL(urlRaw);
1019
+ const ext = extname(parsed.pathname || "").toLowerCase();
1020
+ if (!ext)
1021
+ return "";
1022
+ if (/^\.[a-z0-9]{1,8}$/.test(ext))
1023
+ return ext;
1024
+ return "";
1025
+ }
1026
+ catch {
1027
+ const ext = extname(urlRaw).toLowerCase();
1028
+ if (!ext)
1029
+ return "";
1030
+ return /^\.[a-z0-9]{1,8}$/.test(ext) ? ext : "";
1031
+ }
1032
+ }
1033
+ async function downloadInboundMediaToLocal(params) {
1034
+ const headers = {
1035
+ Accept: "image/*,*/*;q=0.8",
1036
+ };
1037
+ if (params.account.token) {
1038
+ headers.Authorization = `Bearer ${params.account.token}`;
1039
+ headers["X-Agent-Token"] = params.account.token;
1040
+ }
1041
+ const controller = new AbortController();
1042
+ const timeout = setTimeout(() => controller.abort(), Math.max(1000, params.timeoutMs));
1043
+ let response;
1044
+ try {
1045
+ response = await fetch(params.url, {
1046
+ method: "GET",
1047
+ headers,
1048
+ redirect: "follow",
1049
+ signal: controller.signal,
1050
+ });
1051
+ }
1052
+ finally {
1053
+ clearTimeout(timeout);
1054
+ }
1055
+ if (!response.ok) {
1056
+ throw new Error(`http_${response.status}`);
1057
+ }
1058
+ const chunks = [];
1059
+ let total = 0;
1060
+ const reader = response.body?.getReader();
1061
+ if (reader) {
1062
+ while (true) {
1063
+ const { done, value } = await reader.read();
1064
+ if (done)
1065
+ break;
1066
+ if (!value)
1067
+ continue;
1068
+ total += value.byteLength;
1069
+ if (total > params.maxBytes) {
1070
+ throw new Error(`media_too_large_${total}`);
1071
+ }
1072
+ chunks.push(Buffer.from(value));
1073
+ }
1074
+ }
1075
+ else {
1076
+ const buf = Buffer.from(await response.arrayBuffer());
1077
+ total = buf.length;
1078
+ if (total > params.maxBytes) {
1079
+ throw new Error(`media_too_large_${total}`);
1080
+ }
1081
+ chunks.push(buf);
1082
+ }
1083
+ if (total <= 0) {
1084
+ throw new Error("empty_media");
1085
+ }
1086
+ const contentType = (response.headers.get("content-type") || "").split(";")[0]?.trim().toLowerCase() || undefined;
1087
+ const ext = extensionFromContentType(contentType) || extensionFromUrl(params.url) || ".bin";
1088
+ const fileName = `wtt-inbound-${Date.now()}-${randomBytes(6).toString("hex")}${ext}`;
1089
+ const mediaDir = resolveInboundMediaDir();
1090
+ await mkdir(mediaDir, { recursive: true });
1091
+ const filePath = joinPath(mediaDir, fileName);
1092
+ await writeFile(filePath, Buffer.concat(chunks));
1093
+ return {
1094
+ path: filePath,
1095
+ contentType,
1096
+ bytes: total,
1097
+ };
1098
+ }
1099
+ async function materializeInboundMediaForContext(params) {
1100
+ if (params.mediaUrls.length === 0) {
1101
+ return { mediaPaths: [], mediaTypes: [] };
1102
+ }
1103
+ const maxBytes = toPositiveInt(params.account.config.inboundMediaMaxBytes, DEFAULT_INBOUND_MEDIA_MAX_BYTES);
1104
+ const maxPerMessage = toPositiveInt(params.account.config.inboundMediaMaxPerMessage, DEFAULT_INBOUND_MEDIA_MAX_PER_MESSAGE);
1105
+ const timeoutMs = toPositiveInt(params.account.config.inboundMediaFetchTimeoutMs, DEFAULT_INBOUND_MEDIA_FETCH_TIMEOUT_MS);
1106
+ const sourceUrls = params.mediaUrls.slice(0, Math.max(1, maxPerMessage));
1107
+ const mediaPaths = [];
1108
+ const mediaTypes = [];
1109
+ for (let idx = 0; idx < sourceUrls.length; idx += 1) {
1110
+ const url = sourceUrls[idx] || "";
1111
+ if (!url)
1112
+ continue;
1113
+ try {
1114
+ const downloaded = await downloadInboundMediaToLocal({
1115
+ url,
1116
+ account: params.account,
1117
+ maxBytes,
1118
+ timeoutMs,
1119
+ });
1120
+ mediaPaths.push(downloaded.path);
1121
+ mediaTypes.push(downloaded.contentType || params.mediaTypes[idx] || "image/png");
1122
+ params.log?.("info", `[${params.accountId}] inbound media downloaded url=${url} path=${downloaded.path} bytes=${downloaded.bytes}`);
1123
+ }
1124
+ catch (err) {
1125
+ params.log?.("warn", `[${params.accountId}] inbound media download failed url=${url}`, err);
1126
+ }
1127
+ }
1128
+ return {
1129
+ mediaPaths,
1130
+ mediaTypes,
1131
+ };
1132
+ }
784
1133
  function isMeaningfulUserText(text) {
785
1134
  const t = (text || "").trim();
786
1135
  if (!t)
@@ -1113,9 +1462,17 @@ export function normalizeInboundWsMessage(params) {
1113
1462
  const topicId = resolveInboundTopicId(raw);
1114
1463
  const senderName = toOptionalString(raw.sender_display_name);
1115
1464
  const topicName = toOptionalString(raw.topic_name);
1116
- const content = sanitizeInboundText(params.decryptedContent ?? String(raw.content ?? ""));
1465
+ const rawContent = params.decryptedContent ?? String(raw.content ?? "");
1466
+ let content = sanitizeInboundText(rawContent);
1117
1467
  const messageId = toOptionalString(raw.id) ?? `${topicId || "no-topic"}:${senderId}:${Date.now()}`;
1118
1468
  const timestamp = toIsoTimestamp(raw.created_at);
1469
+ // Extract image URLs from original message body for OpenClaw media-understanding pipeline.
1470
+ const { mediaUrls, mediaTypes } = extractInboundImageMedia(rawContent, raw);
1471
+ // If message is image-only after sanitization, keep a minimal prompt so it
1472
+ // won't be dropped by empty-message gating.
1473
+ if (!content.trim() && mediaUrls.length > 0) {
1474
+ content = "请结合这张图片内容进行识别并回复。";
1475
+ }
1119
1476
  const isP2P = isLikelyP2PMessage(raw);
1120
1477
  const hasTopicId = Boolean(topicId);
1121
1478
  const chatType = isP2P ? "direct" : "group";
@@ -1142,6 +1499,8 @@ export function normalizeInboundWsMessage(params) {
1142
1499
  to,
1143
1500
  from,
1144
1501
  conversationLabel,
1502
+ mediaUrls,
1503
+ mediaTypes,
1145
1504
  };
1146
1505
  }
1147
1506
  export function createInboundMessageRelay(params) {
@@ -1277,11 +1636,19 @@ export async function routeInboundWsMessage(params) {
1277
1636
  decryptedContent = rawContent;
1278
1637
  }
1279
1638
  }
1280
- const normalized = normalizeInboundWsMessage({
1639
+ let normalized = normalizeInboundWsMessage({
1281
1640
  msg: params.msg,
1282
1641
  decryptedContent,
1283
1642
  });
1284
1643
  const rawMsg = params.msg.message;
1644
+ if (normalized.mediaUrls.length > 0) {
1645
+ const absMediaUrls = absolutizeInboundMediaUrls(normalized.mediaUrls, params.account.cloudUrl);
1646
+ normalized = {
1647
+ ...normalized,
1648
+ mediaUrls: absMediaUrls,
1649
+ mediaTypes: absMediaUrls.map(() => "image/png"),
1650
+ };
1651
+ }
1285
1652
  const inboundTaskId = toOptionalString(rawMsg.task_id);
1286
1653
  const inferredTopicType = String(rawMsg.topic_type ?? "").toLowerCase();
1287
1654
  if (normalized.topicId) {
@@ -1293,6 +1660,36 @@ export async function routeInboundWsMessage(params) {
1293
1660
  }
1294
1661
  }
1295
1662
  const typingTopicId = normalized.topicId?.trim() || "";
1663
+ const mentionMatch = resolveMentionMatch(String(rawMsg.content ?? ""), params.account.agentId, params.account.name);
1664
+ // Remember recent media from this sender/topic even when the message itself
1665
+ // does not trigger inference (for example: image first, @mention second).
1666
+ if (typingTopicId && normalized.mediaUrls.length > 0) {
1667
+ rememberRecentTopicMedia(typingTopicId, normalized.senderId, normalized.mediaUrls, normalized.mediaTypes);
1668
+ params.log?.("info", `[${params.accountId}] inbound media captured topic=${typingTopicId} sender=${normalized.senderId} count=${normalized.mediaUrls.length}`);
1669
+ }
1670
+ // If current @mention message carries no media, try to reuse sender's latest
1671
+ // media in the same topic so "image + @mention" split messages still work.
1672
+ if (typingTopicId
1673
+ && normalized.mediaUrls.length === 0
1674
+ && mentionMatch.matchesAgent
1675
+ && looksLikeImageFollowupText(normalized.text)) {
1676
+ const recentMedia = readRecentTopicMedia(typingTopicId, normalized.senderId);
1677
+ if (recentMedia && recentMedia.mediaUrls.length > 0) {
1678
+ normalized = {
1679
+ ...normalized,
1680
+ mediaUrls: recentMedia.mediaUrls,
1681
+ mediaTypes: recentMedia.mediaTypes,
1682
+ };
1683
+ params.log?.("info", `[${params.accountId}] inbound media hydrated from recent cache topic=${typingTopicId} sender=${normalized.senderId} count=${recentMedia.mediaUrls.length}`);
1684
+ }
1685
+ else {
1686
+ const rawKeys = Object.keys(rawMsg || {}).slice(0, 40).join(",");
1687
+ const md = parseInboundMetadata(rawMsg);
1688
+ const metadataKeys = md ? Object.keys(md).slice(0, 30).join(",") : "";
1689
+ const contentPreview = String(rawMsg.content ?? "").slice(0, 120).replace(/\s+/g, " ");
1690
+ params.log?.("info", `[${params.accountId}] inbound media cache_miss topic=${typingTopicId} sender=${normalized.senderId} raw_keys=${rawKeys} metadata_keys=${metadataKeys} content_preview=${contentPreview}`);
1691
+ }
1692
+ }
1296
1693
  const emitTypingSignal = async (state) => {
1297
1694
  if (!typingTopicId || !params.typingSignal)
1298
1695
  return;
@@ -1358,8 +1755,7 @@ export async function routeInboundWsMessage(params) {
1358
1755
  const isTaskLinkedTopic = Boolean(inboundTaskId || topicName.startsWith("TASK-"));
1359
1756
  const isSlashLike = /^\/\S+/.test((normalized.text || "").trim());
1360
1757
  const inboundMetadata = parseInboundMetadata(rawMsg);
1361
- const mentionTargetedDiscussion = topicType === "discussion"
1362
- && resolveMentionMatch(String(rawMsg.content ?? ""), params.account.agentId, params.account.name).matchesAgent;
1758
+ const mentionTargetedDiscussion = topicType === "discussion" && mentionMatch.matchesAgent;
1363
1759
  if (slashCompatEnabled) {
1364
1760
  if (topicType === "discussion" && !isTaskLinkedTopic && isSlashLike) {
1365
1761
  const targetAgentId = toOptionalString(inboundMetadata?.command_target_agent_id)
@@ -1413,6 +1809,21 @@ export async function routeInboundWsMessage(params) {
1413
1809
  else {
1414
1810
  params.log?.("info", `[${params.accountId}] inference_gate bypassed reason=slash_command topic_type=${String(rawMsg.topic_type)}`);
1415
1811
  }
1812
+ // Telegram-style media handling: download inbound media first, then pass
1813
+ // local file paths into MediaPath/MediaPaths for stable vision pipeline input.
1814
+ const downloadedInboundMedia = await materializeInboundMediaForContext({
1815
+ mediaUrls: normalized.mediaUrls,
1816
+ mediaTypes: normalized.mediaTypes,
1817
+ account: params.account,
1818
+ accountId: params.accountId,
1819
+ log: params.log,
1820
+ });
1821
+ const contextMediaPaths = downloadedInboundMedia.mediaPaths.length > 0
1822
+ ? downloadedInboundMedia.mediaPaths
1823
+ : normalized.mediaUrls;
1824
+ const contextMediaTypes = downloadedInboundMedia.mediaTypes.length > 0
1825
+ ? downloadedInboundMedia.mediaTypes
1826
+ : normalized.mediaTypes;
1416
1827
  const route = runtime.routing.resolveAgentRoute({
1417
1828
  cfg: params.cfg,
1418
1829
  channel: CHANNEL_ID,
@@ -1473,6 +1884,19 @@ export async function routeInboundWsMessage(params) {
1473
1884
  Timestamp: normalized.timestamp,
1474
1885
  OriginatingChannel: CHANNEL_ID,
1475
1886
  OriginatingTo: normalized.to,
1887
+ // Pass media as local paths when available (Telegram parity), fallback to
1888
+ // original URLs when download is unavailable.
1889
+ ...(contextMediaPaths.length > 0
1890
+ ? {
1891
+ MediaPath: contextMediaPaths[0],
1892
+ MediaPaths: contextMediaPaths,
1893
+ MediaUrl: contextMediaPaths[0],
1894
+ MediaUrls: contextMediaPaths,
1895
+ MediaType: contextMediaTypes[0],
1896
+ MediaTypes: contextMediaTypes,
1897
+ OriginalMediaUrls: normalized.mediaUrls.length > 0 ? normalized.mediaUrls : undefined,
1898
+ }
1899
+ : {}),
1476
1900
  });
1477
1901
  const taskExecutorScope = (params.account.config.taskExecutorScope ?? "all").toLowerCase();
1478
1902
  let naturalBridgeTaskStatus = "";