@cecwxf/wtt 0.1.12 → 0.1.14
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/bin/openclaw-wtt-topic-memory-backfill.mjs +420 -0
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +187 -57
- package/dist/channel.js.map +1 -1
- package/package.json +4 -2
package/dist/channel.js
CHANGED
|
@@ -244,6 +244,27 @@ function isP2PTopicId(topicId) {
|
|
|
244
244
|
const type = topicTypeCache.get(topicId.trim());
|
|
245
245
|
return type === "p2p";
|
|
246
246
|
}
|
|
247
|
+
function isDiscussionTopicMessage(raw, topicId) {
|
|
248
|
+
const topicType = String(raw.topic_type ?? "").trim().toLowerCase();
|
|
249
|
+
if (topicType === "discussion")
|
|
250
|
+
return true;
|
|
251
|
+
if (topicType === "p2p" || topicType === "broadcast" || topicType === "collaborative")
|
|
252
|
+
return false;
|
|
253
|
+
const cached = topicTypeCache.get(topicId.trim());
|
|
254
|
+
if (cached === "discussion")
|
|
255
|
+
return true;
|
|
256
|
+
if (cached === "p2p" || cached === "broadcast" || cached === "collaborative")
|
|
257
|
+
return false;
|
|
258
|
+
const metadata = parseInboundMetadata(raw);
|
|
259
|
+
const metadataType = String(metadata?.topic_type ?? metadata?.topicType ?? "").trim().toLowerCase();
|
|
260
|
+
if (metadataType === "discussion")
|
|
261
|
+
return true;
|
|
262
|
+
if (metadataType === "p2p" || metadataType === "broadcast" || metadataType === "collaborative")
|
|
263
|
+
return false;
|
|
264
|
+
// Heuristic fallback: most non-p2p group threads are discussion-like and
|
|
265
|
+
// should keep a local topic-memory file for retrieval.
|
|
266
|
+
return Boolean(topicId && !isLikelyP2PMessage(raw));
|
|
267
|
+
}
|
|
247
268
|
function isP2PE2EEnabled(account) {
|
|
248
269
|
const raw = account.config.p2pE2EEnabled;
|
|
249
270
|
return raw === undefined ? DEFAULT_P2P_E2E_ENABLED : raw !== false;
|
|
@@ -1156,9 +1177,9 @@ function compactDiscussionContent(raw) {
|
|
|
1156
1177
|
* Read and parse the local topic memory file into DiscussionHistoryMessage[].
|
|
1157
1178
|
* Returns empty array if the file doesn't exist or is malformed.
|
|
1158
1179
|
*
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1180
|
+
* Compatible formats:
|
|
1181
|
+
* - Legacy 2-line entries
|
|
1182
|
+
* - Extended multi-line entries with text/media/reply_excerpt blocks
|
|
1162
1183
|
*/
|
|
1163
1184
|
async function readLocalTopicMemory(params) {
|
|
1164
1185
|
if (!params.topicId)
|
|
@@ -1173,25 +1194,71 @@ async function readLocalTopicMemory(params) {
|
|
|
1173
1194
|
}
|
|
1174
1195
|
const messages = [];
|
|
1175
1196
|
const lines = raw.split("\n");
|
|
1176
|
-
// Pattern: - [timestamp] type:senderId(displayName) id=xxx [reply_to=xxx]
|
|
1177
|
-
const entryRe = /^- \[([^\]]*)\]\s+([\w]+):(\S+?)(?:\(([^)]*)\))?\s+id=(\S+?)(?:\s+reply_to=(\S+))?$/;
|
|
1178
|
-
|
|
1197
|
+
// Pattern: - [timestamp] type:senderId(displayName) id=xxx [reply_to=xxx] [...]
|
|
1198
|
+
const entryRe = /^- \[([^\]]*)\]\s+([\w]+):(\S+?)(?:\(([^)]*)\))?\s+id=(\S+?)(?:\s+reply_to=(\S+))?(?:\s+.*)?$/;
|
|
1199
|
+
let i = 0;
|
|
1200
|
+
while (i < lines.length) {
|
|
1179
1201
|
const match = lines[i].match(entryRe);
|
|
1180
|
-
if (!match)
|
|
1202
|
+
if (!match) {
|
|
1203
|
+
i += 1;
|
|
1181
1204
|
continue;
|
|
1205
|
+
}
|
|
1182
1206
|
const [, createdAt, senderType, senderId, displayName, id, replyTo] = match;
|
|
1183
|
-
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1186
|
-
|
|
1207
|
+
i += 1;
|
|
1208
|
+
const bodyLines = [];
|
|
1209
|
+
while (i < lines.length && !entryRe.test(lines[i])) {
|
|
1210
|
+
if (lines[i].startsWith(" "))
|
|
1211
|
+
bodyLines.push(lines[i].slice(2));
|
|
1212
|
+
i += 1;
|
|
1213
|
+
}
|
|
1214
|
+
let content = "";
|
|
1215
|
+
const mediaPaths = [];
|
|
1216
|
+
const mediaUrls = [];
|
|
1217
|
+
let replyExcerpt;
|
|
1218
|
+
for (const row of bodyLines) {
|
|
1219
|
+
const line = row.trim();
|
|
1220
|
+
if (!line)
|
|
1221
|
+
continue;
|
|
1222
|
+
if (line.startsWith("text:")) {
|
|
1223
|
+
content = line.slice("text:".length).trim();
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (line.startsWith("media_paths:")) {
|
|
1227
|
+
const payload = line.slice("media_paths:".length).trim();
|
|
1228
|
+
for (const part of payload.split("|").map((item) => item.trim()).filter(Boolean)) {
|
|
1229
|
+
mediaPaths.push(part);
|
|
1230
|
+
}
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
if (line.startsWith("media_urls:")) {
|
|
1234
|
+
const payload = line.slice("media_urls:".length).trim();
|
|
1235
|
+
for (const part of payload.split("|").map((item) => item.trim()).filter(Boolean)) {
|
|
1236
|
+
mediaUrls.push(part);
|
|
1237
|
+
}
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (line.startsWith("reply_excerpt:")) {
|
|
1241
|
+
replyExcerpt = line.slice("reply_excerpt:".length).trim() || undefined;
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
if (!content) {
|
|
1245
|
+
content = line;
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
content += ` ${line}`;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1187
1251
|
messages.push({
|
|
1188
1252
|
id,
|
|
1189
1253
|
senderId,
|
|
1190
1254
|
senderDisplayName: displayName || undefined,
|
|
1191
1255
|
senderType: senderType || undefined,
|
|
1192
|
-
content:
|
|
1256
|
+
content: content.trim(),
|
|
1193
1257
|
createdAt: createdAt || undefined,
|
|
1194
1258
|
replyTo: replyTo || undefined,
|
|
1259
|
+
mediaPaths,
|
|
1260
|
+
mediaUrls,
|
|
1261
|
+
replyExcerpt,
|
|
1195
1262
|
});
|
|
1196
1263
|
}
|
|
1197
1264
|
return messages;
|
|
@@ -1257,7 +1324,9 @@ async function persistDiscussionTopicMemory(params) {
|
|
|
1257
1324
|
const path = joinPath(dir, `topic_id_${params.topicId}.md`);
|
|
1258
1325
|
const lines = [];
|
|
1259
1326
|
lines.push(`# topic_id_${params.topicId}`);
|
|
1260
|
-
|
|
1327
|
+
if (params.topicName?.trim()) {
|
|
1328
|
+
lines.push(`topic_name: ${params.topicName.trim()}`);
|
|
1329
|
+
}
|
|
1261
1330
|
lines.push(`updated_at: ${new Date().toISOString()}`);
|
|
1262
1331
|
lines.push("");
|
|
1263
1332
|
for (const msg of params.messages) {
|
|
@@ -1265,9 +1334,17 @@ async function persistDiscussionTopicMemory(params) {
|
|
|
1265
1334
|
const nameTag = msg.senderDisplayName ? `(${msg.senderDisplayName})` : "";
|
|
1266
1335
|
const who = `${msg.senderType || "unknown"}:${msg.senderId}${nameTag}`;
|
|
1267
1336
|
const content = compactDiscussionContent(msg.content).replace(/\n/g, " ").trim();
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1337
|
+
const mediaCount = (msg.mediaPaths?.length ?? 0) + (msg.mediaUrls?.length ?? 0);
|
|
1338
|
+
lines.push(`- [${ts}] ${who} id=${msg.id}${msg.replyTo ? ` reply_to=${msg.replyTo}` : ""}${mediaCount > 0 ? ` media_count=${mediaCount}` : ""}`);
|
|
1339
|
+
lines.push(` text: ${content}`);
|
|
1340
|
+
if (msg.mediaPaths && msg.mediaPaths.length > 0) {
|
|
1341
|
+
lines.push(` media_paths: ${msg.mediaPaths.join(" | ")}`);
|
|
1342
|
+
}
|
|
1343
|
+
if (msg.mediaUrls && msg.mediaUrls.length > 0) {
|
|
1344
|
+
lines.push(` media_urls: ${msg.mediaUrls.join(" | ")}`);
|
|
1345
|
+
}
|
|
1346
|
+
if (msg.replyExcerpt) {
|
|
1347
|
+
lines.push(` reply_excerpt: ${msg.replyExcerpt}`);
|
|
1271
1348
|
}
|
|
1272
1349
|
}
|
|
1273
1350
|
await writeFile(path, `${lines.join("\n")}\n`, "utf8");
|
|
@@ -1299,22 +1376,45 @@ async function appendDiscussionTopicMemory(params) {
|
|
|
1299
1376
|
const idMarker = `id=${params.msg.id}`;
|
|
1300
1377
|
if (existing.includes(idMarker))
|
|
1301
1378
|
return;
|
|
1379
|
+
const history = existing ? await readLocalTopicMemory({ topicId: params.topicId }) : [];
|
|
1380
|
+
const replyTarget = params.msg.replyTo
|
|
1381
|
+
? history.find((item) => item.id === params.msg.replyTo)
|
|
1382
|
+
: undefined;
|
|
1383
|
+
const replyExcerpt = replyTarget
|
|
1384
|
+
? compactDiscussionContent(replyTarget.content).slice(0, 220)
|
|
1385
|
+
: undefined;
|
|
1302
1386
|
const ts = params.msg.createdAt ?? new Date().toISOString();
|
|
1303
1387
|
const nameTag = params.msg.senderDisplayName ? `(${params.msg.senderDisplayName})` : "";
|
|
1304
1388
|
const who = `${params.msg.senderType || "unknown"}:${params.msg.senderId}${nameTag}`;
|
|
1305
1389
|
const content = compactDiscussionContent(params.msg.content).replace(/\n/g, " ").trim();
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1390
|
+
const mediaPaths = Array.from(new Set((params.msg.mediaPaths ?? []).map((v) => String(v || "").trim()).filter(Boolean)));
|
|
1391
|
+
const mediaUrls = Array.from(new Set((params.msg.mediaUrls ?? []).map((v) => String(v || "").trim()).filter(Boolean)));
|
|
1392
|
+
const mediaCount = mediaPaths.length + mediaUrls.length;
|
|
1393
|
+
const lines = [];
|
|
1394
|
+
lines.push(`- [${ts}] ${who} ${idMarker}${params.msg.replyTo ? ` reply_to=${params.msg.replyTo}` : ""}${mediaCount > 0 ? ` media_count=${mediaCount}` : ""}`);
|
|
1395
|
+
lines.push(` text: ${content}`);
|
|
1396
|
+
if (mediaPaths.length > 0) {
|
|
1397
|
+
lines.push(` media_paths: ${mediaPaths.join(" | ")}`);
|
|
1398
|
+
}
|
|
1399
|
+
if (mediaUrls.length > 0) {
|
|
1400
|
+
lines.push(` media_urls: ${mediaUrls.join(" | ")}`);
|
|
1401
|
+
}
|
|
1402
|
+
if (replyExcerpt) {
|
|
1403
|
+
lines.push(` reply_excerpt: ${replyExcerpt}`);
|
|
1309
1404
|
}
|
|
1310
1405
|
if (!existing) {
|
|
1311
1406
|
// Create file with header
|
|
1312
|
-
const
|
|
1313
|
-
|
|
1407
|
+
const headerLines = [`# topic_id_${params.topicId}`];
|
|
1408
|
+
if (params.topicName?.trim()) {
|
|
1409
|
+
headerLines.push(`topic_name: ${params.topicName.trim()}`);
|
|
1410
|
+
}
|
|
1411
|
+
headerLines.push(`updated_at: ${new Date().toISOString()}`);
|
|
1412
|
+
headerLines.push("");
|
|
1413
|
+
await writeFile(path, `${headerLines.join("\n")}\n${lines.join("\n")}\n`, "utf8");
|
|
1314
1414
|
}
|
|
1315
1415
|
else {
|
|
1316
1416
|
// Append to existing file
|
|
1317
|
-
await writeFile(path, existing.trimEnd()
|
|
1417
|
+
await writeFile(path, `${existing.trimEnd()}\n${lines.join("\n")}\n`, "utf8");
|
|
1318
1418
|
}
|
|
1319
1419
|
}
|
|
1320
1420
|
catch (err) {
|
|
@@ -1336,15 +1436,26 @@ function buildDiscussionContextBlock(params) {
|
|
|
1336
1436
|
const targetName = target.senderDisplayName ? `(${target.senderDisplayName})` : "";
|
|
1337
1437
|
lines.push(`[被回复消息] id=${target.id} sender=${target.senderId}${targetName}`);
|
|
1338
1438
|
lines.push(compactDiscussionContent(target.content).slice(0, 1200));
|
|
1439
|
+
if (target.mediaPaths && target.mediaPaths.length > 0) {
|
|
1440
|
+
lines.push(`media_paths: ${target.mediaPaths.join(" | ")}`);
|
|
1441
|
+
}
|
|
1442
|
+
if (target.mediaUrls && target.mediaUrls.length > 0) {
|
|
1443
|
+
lines.push(`media_urls: ${target.mediaUrls.join(" | ")}`);
|
|
1444
|
+
}
|
|
1339
1445
|
lines.push("");
|
|
1340
1446
|
}
|
|
1341
1447
|
}
|
|
1342
1448
|
for (const msg of focus) {
|
|
1343
1449
|
const compact = compactDiscussionContent(msg.content);
|
|
1344
|
-
|
|
1450
|
+
const hasMedia = Boolean((msg.mediaPaths && msg.mediaPaths.length > 0) || (msg.mediaUrls && msg.mediaUrls.length > 0));
|
|
1451
|
+
if (!compact && !hasMedia)
|
|
1345
1452
|
continue;
|
|
1346
1453
|
const nameTag = msg.senderDisplayName ? `(${msg.senderDisplayName})` : "";
|
|
1347
|
-
|
|
1454
|
+
const mediaTag = (msg.mediaPaths && msg.mediaPaths.length > 0)
|
|
1455
|
+
? ` media_paths=${msg.mediaPaths.join("|")}`
|
|
1456
|
+
: ((msg.mediaUrls && msg.mediaUrls.length > 0) ? ` media_urls=${msg.mediaUrls.join("|")}` : "");
|
|
1457
|
+
const replyExcerptTag = msg.replyExcerpt ? ` reply_excerpt=${msg.replyExcerpt}` : "";
|
|
1458
|
+
lines.push(`- id=${msg.id} sender=${msg.senderId}${nameTag}${msg.replyTo ? ` reply_to=${msg.replyTo}` : ""}${mediaTag}${replyExcerptTag}: ${compact || "[media_message]"}`);
|
|
1348
1459
|
}
|
|
1349
1460
|
let block = lines.join("\n").trim();
|
|
1350
1461
|
if (block.length > params.maxChars) {
|
|
@@ -1886,11 +1997,13 @@ export async function routeInboundWsMessage(params) {
|
|
|
1886
1997
|
}
|
|
1887
1998
|
const typingTopicId = normalized.topicId?.trim() || "";
|
|
1888
1999
|
const mentionMatch = resolveMentionMatch(String(rawMsg.content ?? ""), params.account.agentId, params.account.name);
|
|
2000
|
+
const originalMediaUrls = normalized.mediaUrls.slice();
|
|
2001
|
+
const originalMediaTypes = normalized.mediaTypes.slice();
|
|
1889
2002
|
// Remember recent media from this sender/topic even when the message itself
|
|
1890
2003
|
// does not trigger inference (for example: image first, @mention second).
|
|
1891
|
-
if (typingTopicId &&
|
|
1892
|
-
rememberRecentTopicMedia(typingTopicId, normalized.senderId,
|
|
1893
|
-
params.log?.("info", `[${params.accountId}] inbound media captured topic=${typingTopicId} sender=${normalized.senderId} count=${
|
|
2004
|
+
if (typingTopicId && originalMediaUrls.length > 0) {
|
|
2005
|
+
rememberRecentTopicMedia(typingTopicId, normalized.senderId, originalMediaUrls, originalMediaTypes);
|
|
2006
|
+
params.log?.("info", `[${params.accountId}] inbound media captured topic=${typingTopicId} sender=${normalized.senderId} count=${originalMediaUrls.length}`);
|
|
1894
2007
|
}
|
|
1895
2008
|
// If current @mention message carries no media, try to reuse sender's latest
|
|
1896
2009
|
// media in the same topic so "image + @mention" split messages still work.
|
|
@@ -1915,24 +2028,6 @@ export async function routeInboundWsMessage(params) {
|
|
|
1915
2028
|
params.log?.("info", `[${params.accountId}] inbound media cache_miss topic=${typingTopicId} sender=${normalized.senderId} raw_keys=${rawKeys} metadata_keys=${metadataKeys} content_preview=${contentPreview}`);
|
|
1916
2029
|
}
|
|
1917
2030
|
}
|
|
1918
|
-
// Incrementally record every discussion message to the topic memory file.
|
|
1919
|
-
// This runs BEFORE inference gating, so even messages that don't trigger
|
|
1920
|
-
// inference (no @mention) are persisted for later context retrieval.
|
|
1921
|
-
if (inferredTopicType === "discussion" && typingTopicId && normalized.text.trim()) {
|
|
1922
|
-
appendDiscussionTopicMemory({
|
|
1923
|
-
topicId: typingTopicId,
|
|
1924
|
-
msg: {
|
|
1925
|
-
id: normalized.messageId,
|
|
1926
|
-
senderId: normalized.senderId,
|
|
1927
|
-
senderDisplayName: normalized.senderName ?? toOptionalString(rawMsg.sender_display_name),
|
|
1928
|
-
senderType: String(rawMsg.sender_type ?? "unknown"),
|
|
1929
|
-
content: normalized.text,
|
|
1930
|
-
createdAt: normalized.timestamp,
|
|
1931
|
-
replyTo: toOptionalString(rawMsg.reply_to),
|
|
1932
|
-
},
|
|
1933
|
-
log: params.log,
|
|
1934
|
-
}).catch(() => { });
|
|
1935
|
-
}
|
|
1936
2031
|
const emitTypingSignal = async (state) => {
|
|
1937
2032
|
if (!typingTopicId || !params.typingSignal)
|
|
1938
2033
|
return;
|
|
@@ -1995,12 +2090,43 @@ export async function routeInboundWsMessage(params) {
|
|
|
1995
2090
|
const slashBypassMentionGate = params.account.config.slashBypassMentionGate ?? DEFAULT_SLASH_BYPASS_MENTION_GATE;
|
|
1996
2091
|
const topicType = String(rawMsg.topic_type ?? "").toLowerCase();
|
|
1997
2092
|
const topicName = String(rawMsg.topic_name ?? "");
|
|
2093
|
+
const isDiscussionTopic = isDiscussionTopicMessage(rawMsg, typingTopicId);
|
|
1998
2094
|
const isTaskLinkedTopic = Boolean(inboundTaskId || topicName.startsWith("TASK-"));
|
|
1999
2095
|
const isSlashLike = /^\/\S+/.test((normalized.text || "").trim());
|
|
2000
2096
|
const inboundMetadata = parseInboundMetadata(rawMsg);
|
|
2001
|
-
const mentionTargetedDiscussion =
|
|
2097
|
+
const mentionTargetedDiscussion = isDiscussionTopic && mentionMatch.matchesAgent;
|
|
2098
|
+
// For discussion topics, keep local topic memory complete even when inference
|
|
2099
|
+
// is not triggered. Download image media and index local paths/urls.
|
|
2100
|
+
let indexedTopicMedia = { mediaPaths: [], mediaTypes: [] };
|
|
2101
|
+
if (isDiscussionTopic && typingTopicId && (normalized.text.trim() || originalMediaUrls.length > 0)) {
|
|
2102
|
+
if (originalMediaUrls.length > 0) {
|
|
2103
|
+
indexedTopicMedia = await materializeInboundMediaForContext({
|
|
2104
|
+
mediaUrls: originalMediaUrls,
|
|
2105
|
+
mediaTypes: originalMediaTypes,
|
|
2106
|
+
account: params.account,
|
|
2107
|
+
accountId: params.accountId,
|
|
2108
|
+
log: params.log,
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
await appendDiscussionTopicMemory({
|
|
2112
|
+
topicId: typingTopicId,
|
|
2113
|
+
topicName: normalized.topicName ?? topicName,
|
|
2114
|
+
msg: {
|
|
2115
|
+
id: normalized.messageId,
|
|
2116
|
+
senderId: normalized.senderId,
|
|
2117
|
+
senderDisplayName: normalized.senderName ?? toOptionalString(rawMsg.sender_display_name),
|
|
2118
|
+
senderType: String(rawMsg.sender_type ?? "unknown"),
|
|
2119
|
+
content: normalized.text,
|
|
2120
|
+
createdAt: normalized.timestamp,
|
|
2121
|
+
replyTo: toOptionalString(rawMsg.reply_to),
|
|
2122
|
+
mediaPaths: indexedTopicMedia.mediaPaths,
|
|
2123
|
+
mediaUrls: originalMediaUrls,
|
|
2124
|
+
},
|
|
2125
|
+
log: params.log,
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2002
2128
|
if (slashCompatEnabled) {
|
|
2003
|
-
if (
|
|
2129
|
+
if (isDiscussionTopic && !isTaskLinkedTopic && isSlashLike) {
|
|
2004
2130
|
const targetAgentId = toOptionalString(inboundMetadata?.command_target_agent_id)
|
|
2005
2131
|
?? toOptionalString(inboundMetadata?.commandTargetAgentId);
|
|
2006
2132
|
// For non-task discuss slash commands: execute only on explicitly targeted agent.
|
|
@@ -2054,13 +2180,16 @@ export async function routeInboundWsMessage(params) {
|
|
|
2054
2180
|
}
|
|
2055
2181
|
// Telegram-style media handling: download inbound media first, then pass
|
|
2056
2182
|
// local file paths into MediaPath/MediaPaths for stable vision pipeline input.
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2183
|
+
// Reuse files already downloaded for topic-memory indexing when available.
|
|
2184
|
+
const downloadedInboundMedia = indexedTopicMedia.mediaPaths.length > 0
|
|
2185
|
+
? indexedTopicMedia
|
|
2186
|
+
: await materializeInboundMediaForContext({
|
|
2187
|
+
mediaUrls: normalized.mediaUrls,
|
|
2188
|
+
mediaTypes: normalized.mediaTypes,
|
|
2189
|
+
account: params.account,
|
|
2190
|
+
accountId: params.accountId,
|
|
2191
|
+
log: params.log,
|
|
2192
|
+
});
|
|
2064
2193
|
const contextMediaPaths = downloadedInboundMedia.mediaPaths.length > 0
|
|
2065
2194
|
? downloadedInboundMedia.mediaPaths
|
|
2066
2195
|
: normalized.mediaUrls;
|
|
@@ -2099,12 +2228,12 @@ export async function routeInboundWsMessage(params) {
|
|
|
2099
2228
|
?? toOptionalString(rawMsg.taskTitle)
|
|
2100
2229
|
?? toOptionalString(rawMsg.title)
|
|
2101
2230
|
?? (normalized.topicName && !normalized.topicName.startsWith("TASK-") ? normalized.topicName : undefined);
|
|
2102
|
-
const mentionDirective = (
|
|
2231
|
+
const mentionDirective = (isDiscussionTopic && mentionTargetedDiscussion)
|
|
2103
2232
|
? "注意:这是讨论话题中明确 @ 你的消息,必须直接回应用户问题,禁止输出 NO_REPLY。\n\n"
|
|
2104
2233
|
: "";
|
|
2105
2234
|
const replyToId = toOptionalString(rawMsg.reply_to);
|
|
2106
2235
|
let discussionContextBlock = "";
|
|
2107
|
-
if (
|
|
2236
|
+
if (isDiscussionTopic && normalized.topicId && (mentionTargetedDiscussion || Boolean(replyToId))) {
|
|
2108
2237
|
const contextWindow = toPositiveInt(params.account.config.discussionContextWindow, DEFAULT_DISCUSSION_CONTEXT_WINDOW);
|
|
2109
2238
|
const contextMaxChars = toPositiveInt(params.account.config.discussionContextMaxChars, DEFAULT_DISCUSSION_CONTEXT_MAX_CHARS);
|
|
2110
2239
|
// Local-first: read from local memory file (kept up-to-date by incremental append).
|
|
@@ -2126,6 +2255,7 @@ export async function routeInboundWsMessage(params) {
|
|
|
2126
2255
|
if (discussionMessages.length > 0) {
|
|
2127
2256
|
await persistDiscussionTopicMemory({
|
|
2128
2257
|
topicId: normalized.topicId,
|
|
2258
|
+
topicName: normalized.topicName,
|
|
2129
2259
|
messages: discussionMessages,
|
|
2130
2260
|
log: params.log,
|
|
2131
2261
|
});
|
|
@@ -2281,7 +2411,7 @@ export async function routeInboundWsMessage(params) {
|
|
|
2281
2411
|
finally {
|
|
2282
2412
|
await emitTypingSignal("stop");
|
|
2283
2413
|
}
|
|
2284
|
-
const shouldForceMentionAck =
|
|
2414
|
+
const shouldForceMentionAck = isDiscussionTopic
|
|
2285
2415
|
&& Boolean(inferDecision?.trigger)
|
|
2286
2416
|
&& (mentionTargetedDiscussion || inferDecision?.reason === "discussion_runner_match");
|
|
2287
2417
|
if (!dispatchProducedOutput && shouldForceMentionAck) {
|