@agent-wechat/agent-wechat 1.0.0-beta.6 → 1.0.0-beta.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.
- package/dist/index.js +134 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -377,6 +377,13 @@ var NativeWeChatClient = class {
|
|
|
377
377
|
throw new Error("Invalid native message identity");
|
|
378
378
|
return message;
|
|
379
379
|
}
|
|
380
|
+
/** Resolve only the stored original in this message's own conversation. */
|
|
381
|
+
async quotedMessage(accountId, messageId) {
|
|
382
|
+
const quote = await this.request(`v1/accounts/${id(accountId)}/messages/${id(messageId)}/quote`);
|
|
383
|
+
if (quote.accountId !== accountId || quote.messageId !== messageId || typeof quote.threadId !== "string" || !["resolved", "embedded", "missing", "invalid", "unsupported", "ambiguous"].includes(quote.state) || (quote.state === "resolved" ? !quote.message || quote.message.accountId !== accountId || quote.message.threadId !== quote.threadId || quote.message.id === messageId : quote.message != null))
|
|
384
|
+
throw new Error("Invalid native quote identity");
|
|
385
|
+
return quote;
|
|
386
|
+
}
|
|
380
387
|
/** Account-wide local search, with the daemon's query-bound opaque cursor. */
|
|
381
388
|
async searchMessages(accountId, options) {
|
|
382
389
|
const result = await this.request(`v1/accounts/${id(accountId)}/messages${listQuery(options)}`);
|
|
@@ -441,15 +448,16 @@ var NativeWeChatClient = class {
|
|
|
441
448
|
}
|
|
442
449
|
async mediaContent(accountId, assetId, options = {}) {
|
|
443
450
|
const max = options.maxBytes ?? 128 * 1024 * 1024;
|
|
444
|
-
if (!Number.isInteger(max) || max < 1 || max > 128 * 1024 * 1024 || options.range && !/^bytes=(?:\d+-\d*|-\d+)$/.test(options.range))
|
|
451
|
+
if (!Number.isInteger(max) || max < 1 || max > 128 * 1024 * 1024 || options.range && !/^bytes=(?:\d+-\d*|-\d+)$/.test(options.range) || options.representation != null && !["original", "preview"].includes(options.representation))
|
|
445
452
|
throw new Error("Invalid bounded content request");
|
|
446
|
-
const
|
|
453
|
+
const query = options.representation === "preview" ? "?representation=preview" : "";
|
|
454
|
+
const response = await this.response(`v1/accounts/${id(accountId)}/media/${id(assetId)}/content${query}`, { signal: options.signal, headers: options.range ? { Range: options.range } : {} });
|
|
447
455
|
const length = response.headers.get("content-length");
|
|
448
456
|
if (length && (!/^\d+$/.test(length) || Number(length) > max)) {
|
|
449
457
|
await response.body?.cancel();
|
|
450
458
|
throw new Error("Native media response exceeds its bound");
|
|
451
459
|
}
|
|
452
|
-
return { bytes: await this.readBounded(response, max), contentType: response.headers.get("content-type") ?? "application/octet-stream", etag: response.headers.get("etag"), contentRange: response.headers.get("content-range"), status: response.status };
|
|
460
|
+
return { bytes: await this.readBounded(response, max), contentType: response.headers.get("content-type") ?? "application/octet-stream", etag: response.headers.get("etag"), contentRange: response.headers.get("content-range"), sourceSha256: response.headers.get("x-source-sha256"), status: response.status };
|
|
453
461
|
}
|
|
454
462
|
markRead(accountId, threadId2, expectedVersion) {
|
|
455
463
|
if (typeof expectedVersion !== "string" || !expectedVersion || expectedVersion.length > 128)
|
|
@@ -1095,6 +1103,84 @@ async function prepareImageRepresentation(bytes, mime, cfg) {
|
|
|
1095
1103
|
}
|
|
1096
1104
|
throw Error("Native model image representation exceeds its bound");
|
|
1097
1105
|
}
|
|
1106
|
+
async function prepareNativeQuote(params) {
|
|
1107
|
+
const { client, account, message } = params;
|
|
1108
|
+
const guard = () => {
|
|
1109
|
+
params.signal?.throwIfAborted();
|
|
1110
|
+
params.assertCurrent?.();
|
|
1111
|
+
};
|
|
1112
|
+
guard();
|
|
1113
|
+
const comment = (message.body ?? "").slice(0, 16384);
|
|
1114
|
+
const unavailable = (state) => ({ body: `${comment}${comment ? "\n\n" : ""}[Quoted message ${state}; original lookup is confined to this chat]`, pending: false, retryable: state === "unavailable", quote: { state } });
|
|
1115
|
+
if (!message.quote) return unavailable("invalid");
|
|
1116
|
+
await params.beforeMediaAccess?.();
|
|
1117
|
+
guard();
|
|
1118
|
+
let resolution;
|
|
1119
|
+
try {
|
|
1120
|
+
resolution = await client.quotedMessage(account.native.accountId, message.id);
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
guard();
|
|
1123
|
+
if (!(error instanceof NativeApiError)) throw error;
|
|
1124
|
+
return unavailable("unavailable");
|
|
1125
|
+
}
|
|
1126
|
+
guard();
|
|
1127
|
+
if (resolution.accountId !== message.accountId || resolution.messageId !== message.id || resolution.threadId !== message.threadId) throw Error("Native quote identity does not match its message");
|
|
1128
|
+
if (resolution.state === "embedded") {
|
|
1129
|
+
if (resolution.message != null || (message.quote.type === 1 ? typeof resolution.embeddedText !== "string" || resolution.embeddedText.length > 16384 : message.quote.type !== 3 || resolution.embeddedImage !== true)) throw Error("Invalid embedded quote resolution");
|
|
1130
|
+
const author = { senderId: resolution.senderId ?? null, direction: null };
|
|
1131
|
+
if (params.allowQuotedMessage && !await params.allowQuotedMessage(author)) return unavailable("not permitted");
|
|
1132
|
+
guard();
|
|
1133
|
+
const quote2 = { state: "embedded", senderId: resolution.senderId ?? void 0, type: message.quote.type };
|
|
1134
|
+
const prefix2 = `${comment}${comment ? "\n\n" : ""}[Quoted ${message.quote.type === 3 ? "image" : "text"} from ${resolution.senderId ?? "unknown sender"}; embedded in quote, original not stored]
|
|
1135
|
+
`;
|
|
1136
|
+
if (message.quote.type === 1) return { body: `${prefix2}${resolution.embeddedText}
|
|
1137
|
+
[/Quoted message]`, pending: false, quote: quote2 };
|
|
1138
|
+
const beforeMediaAccess2 = async () => {
|
|
1139
|
+
await params.beforeMediaAccess?.();
|
|
1140
|
+
guard();
|
|
1141
|
+
if (params.allowQuotedMessage && !await params.allowQuotedMessage(author)) throw Error("Quoted message is no longer permitted");
|
|
1142
|
+
guard();
|
|
1143
|
+
};
|
|
1144
|
+
const prepared2 = await prepareNativeAttachment({ ...params, message: { ...message, type: 3, appMessageType: void 0, quote: void 0 }, beforeMediaAccess: beforeMediaAccess2 });
|
|
1145
|
+
return {
|
|
1146
|
+
...prepared2,
|
|
1147
|
+
body: `${prefix2}${prepared2.body}
|
|
1148
|
+
[/Quoted message]`,
|
|
1149
|
+
pending: false,
|
|
1150
|
+
quote: quote2,
|
|
1151
|
+
...prepared2.pending ? { attachment: { kind: "image", status: "pending", filename: "quoted-image" } } : {}
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
if (resolution.state !== "resolved") {
|
|
1155
|
+
if (!["missing", "invalid", "unsupported", "ambiguous"].includes(resolution.state) || resolution.message != null) throw Error("Invalid native quote resolution");
|
|
1156
|
+
return unavailable(resolution.state);
|
|
1157
|
+
}
|
|
1158
|
+
const original = resolution.message;
|
|
1159
|
+
if (!original || original.accountId !== message.accountId || original.threadId !== message.threadId || original.id === message.id || original.serverId !== message.quote.serverId || original.type !== message.quote.type || ![1, 3].includes(original.type)) throw Error("Native quoted original crosses its message boundary");
|
|
1160
|
+
if (params.allowQuotedMessage && !await params.allowQuotedMessage(original)) return unavailable("not permitted");
|
|
1161
|
+
guard();
|
|
1162
|
+
const quote = { state: "resolved", messageId: original.id, senderId: original.senderId ?? void 0, type: original.type };
|
|
1163
|
+
const prefix = `${comment}${comment ? "\n\n" : ""}[Quoted ${original.type === 3 ? "image" : "text"} from ${original.senderId ?? "unknown sender"}; message ${original.id}]
|
|
1164
|
+
`;
|
|
1165
|
+
if (original.type === 1) return { body: `${prefix}${(original.body ?? "").slice(0, 16384)}
|
|
1166
|
+
[/Quoted message]`, pending: false, quote };
|
|
1167
|
+
const beforeMediaAccess = async () => {
|
|
1168
|
+
await params.beforeMediaAccess?.();
|
|
1169
|
+
guard();
|
|
1170
|
+
if (params.allowQuotedMessage && !await params.allowQuotedMessage(original)) throw Error("Quoted message is no longer permitted");
|
|
1171
|
+
guard();
|
|
1172
|
+
};
|
|
1173
|
+
const prepared = await prepareNativeAttachment({ ...params, message: original, beforeMediaAccess });
|
|
1174
|
+
return {
|
|
1175
|
+
...prepared,
|
|
1176
|
+
body: `${prefix}${prepared.body}
|
|
1177
|
+
[/Quoted message]`,
|
|
1178
|
+
pending: false,
|
|
1179
|
+
quote,
|
|
1180
|
+
...prepared.pending ? { attachment: { kind: "image", status: "pending", filename: "quoted-image" } } : {},
|
|
1181
|
+
suppliedAssets: prepared.suppliedAssets?.map((asset) => ({ ...asset, sourceMessageId: original.id }))
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1098
1184
|
async function prepareNativeAttachment(params) {
|
|
1099
1185
|
const { client, account, message, core } = params;
|
|
1100
1186
|
const attachmentKind = kind(message.type);
|
|
@@ -1108,6 +1194,9 @@ async function prepareNativeAttachment(params) {
|
|
|
1108
1194
|
await params.beforeMediaAccess?.();
|
|
1109
1195
|
guard();
|
|
1110
1196
|
};
|
|
1197
|
+
if (message.type === 49 && message.appMessageType === 57) return prepareNativeQuote(params);
|
|
1198
|
+
if (message.type === 49 && message.appMessageType === 19) return { body: message.body?.slice(0, 65536) || "[Forwarded chat history unavailable]", pending: false };
|
|
1199
|
+
if (message.type === 49 && message.appMessageType != null && message.appMessageType !== 6) return { body: `[Unsupported WeChat app message subtype ${message.appMessageType}]`, pending: false, retryable: false };
|
|
1111
1200
|
if (!attachmentKind) return { body: message.type === 1 ? message.body ?? "" : `[Unsupported WeChat message type ${message.type}]`, pending: false };
|
|
1112
1201
|
const accountId = account.native.accountId;
|
|
1113
1202
|
const maxBytes = Math.min(account.mediaMaxMb * 1024 * 1024, 128 * 1024 * 1024);
|
|
@@ -1197,13 +1286,27 @@ async function prepareNativeAttachment(params) {
|
|
|
1197
1286
|
};
|
|
1198
1287
|
let suppliedPath = saved.path, suppliedMime = mime, suppliedHash = hash, suppliedLength = content.bytes.length;
|
|
1199
1288
|
let body = renderAttachmentBody("", attachment);
|
|
1200
|
-
if (attachmentKind === "image" && params.cfg) {
|
|
1201
|
-
|
|
1202
|
-
|
|
1289
|
+
if (attachmentKind === "image" && (params.cfg || mime === "image/x-wxgf")) {
|
|
1290
|
+
let imageBytes = Buffer.from(content.bytes), imageMime = mime;
|
|
1291
|
+
const wxgf = mime === "image/x-wxgf";
|
|
1292
|
+
if (wxgf) {
|
|
1293
|
+
const preview = asset.preview;
|
|
1294
|
+
if (!preview || preview.sourceSha256 !== hash || !/^[a-f0-9]{64}$/.test(preview.sha256) || !Number.isInteger(preview.byteLength) || preview.byteLength <= 0 || preview.byteLength > 16 * 1024 * 1024 || !["image/jpeg", "image/png"].includes(preview.contentType)) throw Error("Native WXGF preview metadata did not verify");
|
|
1295
|
+
await authorize();
|
|
1296
|
+
const derived = await client.mediaContent(accountId, asset.id, { maxBytes: preview.byteLength, representation: "preview", signal: params.signal });
|
|
1297
|
+
guard();
|
|
1298
|
+
const derivedMime = derived.contentType.split(";", 1)[0].trim().toLowerCase();
|
|
1299
|
+
if (derived.sourceSha256 !== hash || derivedMime !== preview.contentType || derived.bytes.length !== preview.byteLength || createHash4("sha256").update(derived.bytes).digest("hex") !== preview.sha256) throw Error("Native WXGF preview integrity did not verify");
|
|
1300
|
+
imageBytes = Buffer.from(derived.bytes);
|
|
1301
|
+
imageMime = derivedMime;
|
|
1302
|
+
}
|
|
1303
|
+
const representation = await prepareImageRepresentation(imageBytes, imageMime, params.cfg);
|
|
1304
|
+
if (wxgf || representation.preview) {
|
|
1305
|
+
const extension = representation.mime === "image/png" ? "png" : "jpg";
|
|
1203
1306
|
const normalized = await saveManagedAttachment({
|
|
1204
1307
|
data: representation.bytes.toString("base64"),
|
|
1205
1308
|
mime: representation.mime,
|
|
1206
|
-
filename: `${filename}.model
|
|
1309
|
+
filename: `${filename}.model.${extension}`,
|
|
1207
1310
|
maxBytes: 5 * 1024 * 1024,
|
|
1208
1311
|
saveMediaBuffer: core.channel.media.saveMediaBuffer
|
|
1209
1312
|
});
|
|
@@ -1211,7 +1314,8 @@ async function prepareNativeAttachment(params) {
|
|
|
1211
1314
|
suppliedMime = representation.mime;
|
|
1212
1315
|
suppliedHash = normalized.sha256;
|
|
1213
1316
|
suppliedLength = normalized.size;
|
|
1214
|
-
body += `
|
|
1317
|
+
body += wxgf ? `
|
|
1318
|
+
[Model image: WXGF still preview; original retained separately; animation and transparency are not represented]` : `
|
|
1215
1319
|
[Model image: prepared still preview; original retained separately]`;
|
|
1216
1320
|
}
|
|
1217
1321
|
}
|
|
@@ -1511,7 +1615,7 @@ function nativeDispatchManifest(params) {
|
|
|
1511
1615
|
const history = params.history ?? [];
|
|
1512
1616
|
if (history.length > 200 || history.some((message) => message.accountId !== params.message.accountId || message.threadId !== params.message.threadId || message.id === params.message.id)) throw Error("Invalid native history manifest");
|
|
1513
1617
|
const assets = Object.freeze((params.assets ?? []).map((asset) => {
|
|
1514
|
-
if (typeof asset.id !== "string" || !asset.id || asset.id.length > 2048 || !/^[a-f0-9]{64}$/.test(asset.sha256) || !Number.isSafeInteger(asset.byteLength) || asset.byteLength < 1 || asset.byteLength > 128 * 1024 * 1024 || typeof asset.path !== "string" || !isAbsolute3(asset.path) || asset.path.length > 4096 || typeof asset.mime !== "string" || asset.mime.length > 128 || !/^[a-z0-9.+-]+\/[a-z0-9.+-]+$/.test(asset.mime) || !["image", "audio", "video", "file"].includes(asset.kind) || !["model_image", "file_reference"].includes(asset.role) || asset.role === "model_image" && (asset.kind !== "image" || !asset.mime.startsWith("image/") || !asset.runtimeMedia) || !["trigger", "history"].includes(asset.contextRole) || asset.contextRole === "history" && !history.some((message) => message.id === asset.messageId)) throw Error("Invalid native supplied asset manifest");
|
|
1618
|
+
if (typeof asset.id !== "string" || !asset.id || asset.id.length > 2048 || !/^[a-f0-9]{64}$/.test(asset.sha256) || !Number.isSafeInteger(asset.byteLength) || asset.byteLength < 1 || asset.byteLength > 128 * 1024 * 1024 || typeof asset.path !== "string" || !isAbsolute3(asset.path) || asset.path.length > 4096 || typeof asset.mime !== "string" || asset.mime.length > 128 || !/^[a-z0-9.+-]+\/[a-z0-9.+-]+$/.test(asset.mime) || !["image", "audio", "video", "file"].includes(asset.kind) || !["model_image", "file_reference"].includes(asset.role) || asset.role === "model_image" && (asset.kind !== "image" || !asset.mime.startsWith("image/") || !asset.runtimeMedia) || !["trigger", "history"].includes(asset.contextRole) || asset.sourceMessageId != null && (typeof asset.sourceMessageId !== "string" || !asset.sourceMessageId || asset.sourceMessageId.length > 8192 || ![params.message, ...history].some((message) => message.id === asset.messageId && message.type === 49 && message.appMessageType === 57 && message.quote?.type === 3)) || asset.contextRole === "history" && !history.some((message) => message.id === asset.messageId)) throw Error("Invalid native supplied asset manifest");
|
|
1515
1619
|
return Object.freeze({ ...asset });
|
|
1516
1620
|
}));
|
|
1517
1621
|
const messageSid = `wechat-native:${params.message.accountId}:${params.message.id}`;
|
|
@@ -1537,14 +1641,16 @@ function nativeDispatchManifest(params) {
|
|
|
1537
1641
|
params.message.type,
|
|
1538
1642
|
params.message.body,
|
|
1539
1643
|
params.message.mentions,
|
|
1540
|
-
params.message.serverId
|
|
1644
|
+
params.message.serverId,
|
|
1645
|
+
params.message.appMessageType,
|
|
1646
|
+
params.message.quote
|
|
1541
1647
|
])),
|
|
1542
1648
|
role: "trigger"
|
|
1543
1649
|
})]),
|
|
1544
1650
|
...history.length ? { historyInputs: Object.freeze(history.map((message) => Object.freeze({
|
|
1545
1651
|
id: message.id,
|
|
1546
1652
|
version: message.version ?? "",
|
|
1547
|
-
contentSha256: digest(JSON.stringify([message.id, message.version, message.senderId, message.type, message.body, message.mentions, message.serverId])),
|
|
1653
|
+
contentSha256: digest(JSON.stringify([message.id, message.version, message.senderId, message.type, message.body, message.mentions, message.serverId, message.appMessageType, message.quote])),
|
|
1548
1654
|
role: "history"
|
|
1549
1655
|
}))) } : {},
|
|
1550
1656
|
assets,
|
|
@@ -1842,8 +1948,9 @@ async function dispatchNativeMessage(params) {
|
|
|
1842
1948
|
return "ignored";
|
|
1843
1949
|
}
|
|
1844
1950
|
let preparedMedia;
|
|
1951
|
+
const allowQuotedMessage = async (original) => original.direction === "outgoing" || !!original.senderId && (await senderAdmission(original.senderId)).ingress.admission === "dispatch";
|
|
1845
1952
|
try {
|
|
1846
|
-
preparedMedia = await (params.prepareAttachment ?? prepareNativeAttachment)({ client, account, message, core, cfg });
|
|
1953
|
+
preparedMedia = await (params.prepareAttachment ?? prepareNativeAttachment)({ client, account, message, core, cfg, allowQuotedMessage });
|
|
1847
1954
|
} catch {
|
|
1848
1955
|
state.update(message.id, { state: "needs_review" });
|
|
1849
1956
|
return "needs_review";
|
|
@@ -1856,7 +1963,7 @@ async function dispatchNativeMessage(params) {
|
|
|
1856
1963
|
if (item.message.senderId && (await senderAdmission(item.message.senderId)).ingress.admission === "dispatch") admitted.push(item.message);
|
|
1857
1964
|
}
|
|
1858
1965
|
const history = await prepareNativeHistory(admitted, async (historical) => transcribeNativeAttachment(
|
|
1859
|
-
await (params.prepareAttachment ?? prepareNativeAttachment)({ client, account, message: historical, core, cfg }),
|
|
1966
|
+
await (params.prepareAttachment ?? prepareNativeAttachment)({ client, account, message: historical, core, cfg, allowQuotedMessage }),
|
|
1860
1967
|
core,
|
|
1861
1968
|
cfg
|
|
1862
1969
|
));
|
|
@@ -1912,7 +2019,7 @@ async function dispatchNativeMessage(params) {
|
|
|
1912
2019
|
history: admitted
|
|
1913
2020
|
});
|
|
1914
2021
|
state.beginDispatch(message.id, dispatchManifest, buffered.map((item) => item.message.id));
|
|
1915
|
-
const callback = readPolicy !== "on_inbound" && (message.type === 1 || preparedMedia.attachment?.status === "ready" && preparedMedia.suppliedAssets?.length) ? state.preparedCallback([message.id], readPolicy) : void 0;
|
|
2022
|
+
const callback = readPolicy !== "on_inbound" && (message.type === 1 || message.type === 49 && message.appMessageType === 19 && !!message.body || preparedMedia.quote?.type === 1 && ["resolved", "embedded"].includes(preparedMedia.quote.state) || preparedMedia.attachment?.status === "ready" && preparedMedia.suppliedAssets?.length) ? state.preparedCallback([message.id], readPolicy) : void 0;
|
|
1916
2023
|
if (callback) trackNativePreparedInput(dispatchManifest, callback);
|
|
1917
2024
|
let failed = false;
|
|
1918
2025
|
let delivery = 0;
|
|
@@ -2590,8 +2697,11 @@ function threadId(value) {
|
|
|
2590
2697
|
return string(string(value, "thread ID").replace(/^(?:agent-)?wechat:(?:group:)?/i, ""), "thread ID");
|
|
2591
2698
|
}
|
|
2592
2699
|
function messageResult(message, retrievalTool = "wechat_media") {
|
|
2593
|
-
const
|
|
2594
|
-
const
|
|
2700
|
+
const isQuote = message.type === 49 && message.appMessageType === 57;
|
|
2701
|
+
const isForward = message.type === 49 && message.appMessageType === 19;
|
|
2702
|
+
const kind2 = isQuote ? "quote" : isForward ? "forwarded_history" : message.type === 3 ? "image" : message.type === 47 ? "sticker" : message.type === 34 ? "voice" : message.type === 43 ? "video" : message.type === 49 && (message.appMessageType == null || message.appMessageType === 6) ? "file" : "unsupported";
|
|
2703
|
+
const body = message.type === 1 || isForward ? message.body ?? "" : isQuote ? `${message.body ?? ""}
|
|
2704
|
+
[Quoted original not retrieved; use ${retrievalTool} with this message ID]` : `[WeChat ${kind2} message; attachment content has not been retrieved by this tool]`;
|
|
2595
2705
|
return {
|
|
2596
2706
|
id: message.id,
|
|
2597
2707
|
threadId: message.threadId,
|
|
@@ -2599,10 +2709,12 @@ function messageResult(message, retrievalTool = "wechat_media") {
|
|
|
2599
2709
|
senderId: message.senderId,
|
|
2600
2710
|
direction: message.direction,
|
|
2601
2711
|
type: message.type,
|
|
2712
|
+
appMessageType: message.appMessageType,
|
|
2713
|
+
quote: message.quote,
|
|
2602
2714
|
createdAt: message.createdAt,
|
|
2603
2715
|
body: body.slice(0, 4e3),
|
|
2604
2716
|
bodyTruncated: body.length > 4e3,
|
|
2605
|
-
attachment: message.type !== 1 && kind2 !== "unsupported" ? { kind: kind2, retrievalTool, messageId: message.id } : void 0
|
|
2717
|
+
attachment: message.type !== 1 && !isForward && kind2 !== "unsupported" ? { kind: kind2, retrievalTool, messageId: message.id } : void 0
|
|
2606
2718
|
};
|
|
2607
2719
|
}
|
|
2608
2720
|
function createNativeHistoryTools(context, dependencies = {}) {
|
|
@@ -2720,6 +2832,7 @@ function createLookupTools(context, dependencies, archive) {
|
|
|
2720
2832
|
waitMs: 2e3,
|
|
2721
2833
|
signal,
|
|
2722
2834
|
assertCurrent: guard,
|
|
2835
|
+
allowQuotedMessage: async (original) => original.direction === "outgoing" || await allowed(original.senderId ?? void 0),
|
|
2723
2836
|
beforeMediaAccess: async () => {
|
|
2724
2837
|
if (!await allowed(requesterSenderId) || message.direction !== "outgoing" && !await allowed(message.senderId ?? void 0)) throw Error("WeChat conversation access is no longer allowed");
|
|
2725
2838
|
}
|
|
@@ -2741,8 +2854,9 @@ function createLookupTools(context, dependencies, archive) {
|
|
|
2741
2854
|
const details = {
|
|
2742
2855
|
messageId: message.id,
|
|
2743
2856
|
threadId: message.threadId,
|
|
2744
|
-
status: prepared.pending ? "pending" : prepared.attachment?.status ?? "unsupported",
|
|
2857
|
+
status: prepared.pending ? "pending" : prepared.attachment?.status ?? prepared.quote?.state ?? "unsupported",
|
|
2745
2858
|
attachment: prepared.attachment,
|
|
2859
|
+
quote: prepared.quote,
|
|
2746
2860
|
variant: prepared.variant,
|
|
2747
2861
|
jobId: prepared.jobId,
|
|
2748
2862
|
retryable: prepared.retryable ?? false,
|
|
@@ -2786,13 +2900,13 @@ function createLookupTools(context, dependencies, archive) {
|
|
|
2786
2900
|
tool(archiveNames[1], "search", "Search locally stored messages across every thread in the configured WeChat account, including other senders. Optional threadId narrows the search. Follow nextCursor with the same query and thread selection; no remote backfill or read marking.", { query: querySchema, threadId: messageIdSchema, ...pageSchema }, ["query"]),
|
|
2787
2901
|
tool(archiveNames[2], "history", "Read the locally stored history of any thread in the configured WeChat account, newest first, without sender filtering or read marking. Use wechat_archive_threads to find thread IDs.", { threadId: messageIdSchema, ...pageSchema }, ["threadId"]),
|
|
2788
2902
|
tool(archiveNames[3], "message", "Look up a stored message in any thread of the configured WeChat account by its exact opaque message ID. Use wechat_archive_media to retrieve its attachment.", { messageId: messageIdSchema }, ["messageId"]),
|
|
2789
|
-
tool(archiveNames[4], "media", "Retrieve an attachment from any thread in the configured WeChat account by its message ID. Returns verified managed file paths and an image preview for vision. Cached bytes first; missing media requests a bounded server-owned download unless download=false. Preserve pending/expired/unavailable results and never automatically retry retryable=false. No sends or read marking.", mediaSchema, ["messageId"])
|
|
2903
|
+
tool(archiveNames[4], "media", "Retrieve an attachment or quoted text/image from any thread in the configured WeChat account by its message ID. Use the parent quote message ID for quotes. Returns verified managed file paths and an image preview for vision. Cached bytes first; missing media requests a bounded server-owned download unless download=false. Preserve pending/expired/unavailable results and never automatically retry retryable=false. No sends or read marking.", mediaSchema, ["messageId"])
|
|
2790
2904
|
];
|
|
2791
2905
|
return [
|
|
2792
2906
|
tool(names[0], "history", "Read the current WeChat conversation's locally stored messages, newest first, beyond the recent inbound buffer. Follow nextCursor for older history; no remote history backfill or read marking.", { ...pageSchema }),
|
|
2793
2907
|
tool(names[1], "search", "Search the current WeChat conversation's locally stored message text by case-insensitive substring, beyond buffered messages. Follow nextCursor with the same query in this conversation.", { query: querySchema, ...pageSchema }, ["query"]),
|
|
2794
2908
|
tool(names[2], "message", "Look up a stored message by its exact opaque message ID in the current WeChat conversation. Media is described without downloading it; use wechat_media to retrieve its attachment. Long text is bounded.", { messageId: messageIdSchema }, ["messageId"]),
|
|
2795
|
-
tool(names[3], "media", "Retrieve a message attachment from the current WeChat conversation. Returns a verified local file path and an image preview for vision when applicable. Uses cached bytes first; download defaults to true and requests a bounded server-owned transfer if missing. Pending results may be checked again; never automatically retry confirmed expiry or retryable=false. Does not send messages or mark the thread read.", mediaSchema, ["messageId"])
|
|
2909
|
+
tool(names[3], "media", "Retrieve a message attachment or quoted text/image from the current WeChat conversation. Use the parent quote message ID for quotes. Returns a verified local file path and an image preview for vision when applicable. Uses cached bytes first; download defaults to true and requests a bounded server-owned transfer if missing. Pending results may be checked again; never automatically retry confirmed expiry or retryable=false. Does not send messages or mark the thread read.", mediaSchema, ["messageId"])
|
|
2796
2910
|
];
|
|
2797
2911
|
}
|
|
2798
2912
|
function registerNativeHistoryTools(api) {
|