@ycodium-ai/plugin-im 0.2.2634 → 0.2.2664
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/README.md +52 -1
- package/dist/index.js +1291 -283
- package/locales/en.json +20 -1
- package/locales/zh-CN.json +20 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -23360,7 +23360,7 @@ var require_websocket = __commonJS({
|
|
|
23360
23360
|
var http = __require("http");
|
|
23361
23361
|
var net = __require("net");
|
|
23362
23362
|
var tls = __require("tls");
|
|
23363
|
-
var { randomBytes, createHash } = __require("crypto");
|
|
23363
|
+
var { randomBytes, createHash: createHash2 } = __require("crypto");
|
|
23364
23364
|
var { Duplex, Readable } = __require("stream");
|
|
23365
23365
|
var { URL: URL2 } = __require("url");
|
|
23366
23366
|
var PerMessageDeflate = require_permessage_deflate();
|
|
@@ -24028,7 +24028,7 @@ var require_websocket = __commonJS({
|
|
|
24028
24028
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
24029
24029
|
return;
|
|
24030
24030
|
}
|
|
24031
|
-
const digest =
|
|
24031
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
24032
24032
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
24033
24033
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
24034
24034
|
return;
|
|
@@ -24397,7 +24397,7 @@ var require_websocket_server = __commonJS({
|
|
|
24397
24397
|
var EventEmitter = __require("events");
|
|
24398
24398
|
var http = __require("http");
|
|
24399
24399
|
var { Duplex } = __require("stream");
|
|
24400
|
-
var { createHash } = __require("crypto");
|
|
24400
|
+
var { createHash: createHash2 } = __require("crypto");
|
|
24401
24401
|
var extension = require_extension();
|
|
24402
24402
|
var PerMessageDeflate = require_permessage_deflate();
|
|
24403
24403
|
var subprotocol = require_subprotocol();
|
|
@@ -24704,7 +24704,7 @@ var require_websocket_server = __commonJS({
|
|
|
24704
24704
|
);
|
|
24705
24705
|
}
|
|
24706
24706
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
24707
|
-
const digest =
|
|
24707
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
24708
24708
|
const headers = [
|
|
24709
24709
|
"HTTP/1.1 101 Switching Protocols",
|
|
24710
24710
|
"Upgrade: websocket",
|
|
@@ -25198,9 +25198,9 @@ var require_float = __commonJS({
|
|
|
25198
25198
|
var require_utf8 = __commonJS({
|
|
25199
25199
|
"../../node_modules/.pnpm/@protobufjs+utf8@1.1.2/node_modules/@protobufjs/utf8/index.js"(exports) {
|
|
25200
25200
|
"use strict";
|
|
25201
|
-
var
|
|
25201
|
+
var utf83 = exports;
|
|
25202
25202
|
var replacementCharCode = 65533;
|
|
25203
|
-
|
|
25203
|
+
utf83.length = function utf8_length(string) {
|
|
25204
25204
|
var len = 0, c = 0;
|
|
25205
25205
|
for (var i = 0; i < string.length; ++i) {
|
|
25206
25206
|
c = string.charCodeAt(i);
|
|
@@ -25216,7 +25216,7 @@ var require_utf8 = __commonJS({
|
|
|
25216
25216
|
}
|
|
25217
25217
|
return len;
|
|
25218
25218
|
};
|
|
25219
|
-
|
|
25219
|
+
utf83.read = function utf8_read(buffer, start, end) {
|
|
25220
25220
|
if (end - start < 1)
|
|
25221
25221
|
return "";
|
|
25222
25222
|
var parts = null, chunk = [], i = 0, t, t2, c2, c3;
|
|
@@ -25252,7 +25252,7 @@ var require_utf8 = __commonJS({
|
|
|
25252
25252
|
}
|
|
25253
25253
|
return String.fromCharCode.apply(String, chunk.slice(0, i));
|
|
25254
25254
|
};
|
|
25255
|
-
|
|
25255
|
+
utf83.write = function utf8_write(string, buffer, offset) {
|
|
25256
25256
|
var start = offset, c12, c2;
|
|
25257
25257
|
for (var i = 0; i < string.length; ++i) {
|
|
25258
25258
|
c12 = string.charCodeAt(i);
|
|
@@ -26595,7 +26595,7 @@ var require_writer = __commonJS({
|
|
|
26595
26595
|
var BufferWriter;
|
|
26596
26596
|
var LongBits = util.LongBits;
|
|
26597
26597
|
var base64 = util.base64;
|
|
26598
|
-
var
|
|
26598
|
+
var utf83 = util.utf8;
|
|
26599
26599
|
function Op(fn, len, val) {
|
|
26600
26600
|
this.fn = fn;
|
|
26601
26601
|
this.len = len;
|
|
@@ -26730,8 +26730,8 @@ var require_writer = __commonJS({
|
|
|
26730
26730
|
return this.uint32(len)._push(writeBytes, len, value);
|
|
26731
26731
|
};
|
|
26732
26732
|
Writer.prototype.string = function write_string(value) {
|
|
26733
|
-
var len =
|
|
26734
|
-
return len ? this.uint32(len)._push(
|
|
26733
|
+
var len = utf83.length(value);
|
|
26734
|
+
return len ? this.uint32(len)._push(utf83.write, len, value) : this._push(writeByte, 1, 0);
|
|
26735
26735
|
};
|
|
26736
26736
|
Writer.prototype.fork = function fork() {
|
|
26737
26737
|
this.states = new State(this);
|
|
@@ -26836,7 +26836,7 @@ var require_reader = __commonJS({
|
|
|
26836
26836
|
var util = require_minimal();
|
|
26837
26837
|
var BufferReader;
|
|
26838
26838
|
var LongBits = util.LongBits;
|
|
26839
|
-
var
|
|
26839
|
+
var utf83 = util.utf8;
|
|
26840
26840
|
function indexOutOfRange(reader, writeLength) {
|
|
26841
26841
|
return RangeError("index out of range: " + reader.pos + " + " + (writeLength || 1) + " > " + reader.len);
|
|
26842
26842
|
}
|
|
@@ -27001,7 +27001,7 @@ var require_reader = __commonJS({
|
|
|
27001
27001
|
};
|
|
27002
27002
|
Reader.prototype.string = function read_string() {
|
|
27003
27003
|
var bytes = this.bytes();
|
|
27004
|
-
return
|
|
27004
|
+
return utf83.read(bytes, 0, bytes.length);
|
|
27005
27005
|
};
|
|
27006
27006
|
Reader.prototype.skip = function skip(length) {
|
|
27007
27007
|
if (typeof length === "number") {
|
|
@@ -123921,13 +123921,13 @@ ${block2}
|
|
|
123921
123921
|
throw err;
|
|
123922
123922
|
}
|
|
123923
123923
|
const delay = base * Math.pow(3, attempt - 1);
|
|
123924
|
-
yield
|
|
123924
|
+
yield sleep2(delay);
|
|
123925
123925
|
}
|
|
123926
123926
|
}
|
|
123927
123927
|
throw lastErr;
|
|
123928
123928
|
});
|
|
123929
123929
|
}
|
|
123930
|
-
function
|
|
123930
|
+
function sleep2(ms) {
|
|
123931
123931
|
return new Promise((r) => setTimeout(r, ms));
|
|
123932
123932
|
}
|
|
123933
123933
|
var DEFAULT_CHUNK_LIMIT = 3500;
|
|
@@ -126167,6 +126167,151 @@ import {
|
|
|
126167
126167
|
USER_INPUT_PENDING_TRIGGER_KIND
|
|
126168
126168
|
} from "@ycodium-ai/plugin-api";
|
|
126169
126169
|
|
|
126170
|
+
// src/attachments.ts
|
|
126171
|
+
var IMAGE_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
126172
|
+
var FILE_LIMIT_BYTES = 25 * 1024 * 1024;
|
|
126173
|
+
var MAX_ATTACHMENTS_PER_TURN = 20;
|
|
126174
|
+
var INLINE_IMAGE_MIMES = /* @__PURE__ */ new Set([
|
|
126175
|
+
"image/gif",
|
|
126176
|
+
"image/jpeg",
|
|
126177
|
+
"image/png",
|
|
126178
|
+
"image/webp"
|
|
126179
|
+
]);
|
|
126180
|
+
var isInlineImageMime = (mimeType) => INLINE_IMAGE_MIMES.has(mimeType.toLowerCase());
|
|
126181
|
+
var limitForMime = (mimeType) => isInlineImageMime(mimeType) ? IMAGE_LIMIT_BYTES : FILE_LIMIT_BYTES;
|
|
126182
|
+
var limitForKind = (kind) => kind === "image" ? IMAGE_LIMIT_BYTES : FILE_LIMIT_BYTES;
|
|
126183
|
+
var formatMegabytes = (bytes) => {
|
|
126184
|
+
const megabytes = bytes / (1024 * 1024);
|
|
126185
|
+
return `${Number.isInteger(megabytes) ? String(megabytes) : megabytes.toFixed(1)} MB`;
|
|
126186
|
+
};
|
|
126187
|
+
var ImResourceTooLargeError = class extends Error {
|
|
126188
|
+
limitBytes;
|
|
126189
|
+
constructor(limitBytes) {
|
|
126190
|
+
super(`the resource is over the ${String(limitBytes)} byte limit`);
|
|
126191
|
+
this.name = "ImResourceTooLargeError";
|
|
126192
|
+
this.limitBytes = limitBytes;
|
|
126193
|
+
}
|
|
126194
|
+
};
|
|
126195
|
+
var readBodyLimited = async (response, maxBytes) => {
|
|
126196
|
+
const declared = Number(response.headers.get("content-length"));
|
|
126197
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
126198
|
+
await response.body?.cancel().catch(() => void 0);
|
|
126199
|
+
throw new ImResourceTooLargeError(maxBytes);
|
|
126200
|
+
}
|
|
126201
|
+
const reader = response.body?.getReader();
|
|
126202
|
+
if (reader === void 0) return new Uint8Array(0);
|
|
126203
|
+
const chunks = [];
|
|
126204
|
+
let total = 0;
|
|
126205
|
+
for (; ; ) {
|
|
126206
|
+
const { done, value } = await reader.read();
|
|
126207
|
+
if (done) break;
|
|
126208
|
+
total += value.byteLength;
|
|
126209
|
+
if (total > maxBytes) {
|
|
126210
|
+
await reader.cancel().catch(() => void 0);
|
|
126211
|
+
throw new ImResourceTooLargeError(maxBytes);
|
|
126212
|
+
}
|
|
126213
|
+
chunks.push(value);
|
|
126214
|
+
}
|
|
126215
|
+
const bytes = new Uint8Array(total);
|
|
126216
|
+
let offset = 0;
|
|
126217
|
+
for (const chunk of chunks) {
|
|
126218
|
+
bytes.set(chunk, offset);
|
|
126219
|
+
offset += chunk.byteLength;
|
|
126220
|
+
}
|
|
126221
|
+
return bytes;
|
|
126222
|
+
};
|
|
126223
|
+
var startsWith = (bytes, signature, offset = 0) => signature.every((byte, index) => bytes[offset + index] === byte);
|
|
126224
|
+
var ascii = (bytes, from, to) => String.fromCharCode(...bytes.subarray(from, to));
|
|
126225
|
+
var sniffMime = (bytes) => {
|
|
126226
|
+
if (startsWith(bytes, [137, 80, 78, 71, 13, 10, 26, 10])) return "image/png";
|
|
126227
|
+
if (startsWith(bytes, [255, 216, 255])) return "image/jpeg";
|
|
126228
|
+
const head = ascii(bytes, 0, 6);
|
|
126229
|
+
if (head === "GIF87a" || head === "GIF89a") return "image/gif";
|
|
126230
|
+
if (startsWith(bytes, [37, 80, 68, 70])) return "application/pdf";
|
|
126231
|
+
if (startsWith(bytes, [80, 75, 3, 4]) || startsWith(bytes, [80, 75, 5, 6])) {
|
|
126232
|
+
return "application/zip";
|
|
126233
|
+
}
|
|
126234
|
+
if (ascii(bytes, 0, 4) === "RIFF") {
|
|
126235
|
+
const form = ascii(bytes, 8, 12);
|
|
126236
|
+
if (form === "WEBP") return "image/webp";
|
|
126237
|
+
if (form === "AVI ") return "video/x-msvideo";
|
|
126238
|
+
if (form === "WAVE") return "audio/wav";
|
|
126239
|
+
}
|
|
126240
|
+
if (ascii(bytes, 4, 8) === "ftyp") {
|
|
126241
|
+
const brand = ascii(bytes, 8, 12);
|
|
126242
|
+
if (brand === "qt ") return "video/quicktime";
|
|
126243
|
+
if (brand === "M4A ") return "audio/mp4";
|
|
126244
|
+
return "video/mp4";
|
|
126245
|
+
}
|
|
126246
|
+
if (startsWith(bytes, [26, 69, 223, 163])) return "video/webm";
|
|
126247
|
+
if (ascii(bytes, 0, 4) === "OggS") return "audio/ogg";
|
|
126248
|
+
if (ascii(bytes, 0, 3) === "ID3") return "audio/mpeg";
|
|
126249
|
+
return void 0;
|
|
126250
|
+
};
|
|
126251
|
+
var resolveMime = (claimed, bytes) => {
|
|
126252
|
+
const sniffed = sniffMime(bytes);
|
|
126253
|
+
const generic = claimed === "" || claimed === "application/octet-stream";
|
|
126254
|
+
if (sniffed === void 0) {
|
|
126255
|
+
return isInlineImageMime(claimed) ? "application/octet-stream" : claimed || "application/octet-stream";
|
|
126256
|
+
}
|
|
126257
|
+
if (isInlineImageMime(sniffed) || generic || isInlineImageMime(claimed)) return sniffed;
|
|
126258
|
+
return claimed;
|
|
126259
|
+
};
|
|
126260
|
+
var IMAGE_EXTENSIONS = {
|
|
126261
|
+
"image/png": ["png"],
|
|
126262
|
+
"image/jpeg": ["jpg", "jpeg"],
|
|
126263
|
+
"image/gif": ["gif"],
|
|
126264
|
+
"image/webp": ["webp"]
|
|
126265
|
+
};
|
|
126266
|
+
var withImageExtension = (name, mimeType) => {
|
|
126267
|
+
const extensions = IMAGE_EXTENSIONS[mimeType.toLowerCase()];
|
|
126268
|
+
if (extensions === void 0) return name;
|
|
126269
|
+
const dot = name.lastIndexOf(".");
|
|
126270
|
+
const current = dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
126271
|
+
if (extensions.includes(current)) return name;
|
|
126272
|
+
const stem = dot > 0 ? name.slice(0, dot) : name;
|
|
126273
|
+
return `${stem}.${extensions[0] ?? "bin"}`;
|
|
126274
|
+
};
|
|
126275
|
+
var NAME_MAX_BYTES = 200;
|
|
126276
|
+
var utf8 = new TextEncoder();
|
|
126277
|
+
var sanitizeAttachmentName = (name, fallback) => {
|
|
126278
|
+
const cleaned = name.replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}/\\<>:"|?*]/gu, "_").replace(/_+/g, "_").trim();
|
|
126279
|
+
if (cleaned === "" || /^\.+$/.test(cleaned) || cleaned === "_") return fallback;
|
|
126280
|
+
if (utf8.encode(cleaned).byteLength <= NAME_MAX_BYTES) return cleaned;
|
|
126281
|
+
const dot = cleaned.lastIndexOf(".");
|
|
126282
|
+
const extension = dot > 0 && cleaned.length - dot <= 17 ? cleaned.slice(dot) : "";
|
|
126283
|
+
const stem = [...extension === "" ? cleaned : cleaned.slice(0, dot)];
|
|
126284
|
+
while (stem.length > 1 && utf8.encode(stem.join("") + extension).byteLength > NAME_MAX_BYTES) {
|
|
126285
|
+
stem.pop();
|
|
126286
|
+
}
|
|
126287
|
+
return stem.join("") + extension;
|
|
126288
|
+
};
|
|
126289
|
+
var mimeFromName = (name) => {
|
|
126290
|
+
const ext = name.slice(name.lastIndexOf(".") + 1).toLowerCase();
|
|
126291
|
+
const known = {
|
|
126292
|
+
png: "image/png",
|
|
126293
|
+
jpg: "image/jpeg",
|
|
126294
|
+
jpeg: "image/jpeg",
|
|
126295
|
+
gif: "image/gif",
|
|
126296
|
+
webp: "image/webp",
|
|
126297
|
+
pdf: "application/pdf",
|
|
126298
|
+
txt: "text/plain",
|
|
126299
|
+
md: "text/markdown",
|
|
126300
|
+
json: "application/json",
|
|
126301
|
+
csv: "text/csv",
|
|
126302
|
+
zip: "application/zip",
|
|
126303
|
+
mp4: "video/mp4",
|
|
126304
|
+
mov: "video/quicktime",
|
|
126305
|
+
webm: "video/webm",
|
|
126306
|
+
mp3: "audio/mpeg",
|
|
126307
|
+
wav: "audio/wav",
|
|
126308
|
+
ogg: "audio/ogg",
|
|
126309
|
+
opus: "audio/ogg",
|
|
126310
|
+
m4a: "audio/mp4"
|
|
126311
|
+
};
|
|
126312
|
+
return known[ext] ?? "application/octet-stream";
|
|
126313
|
+
};
|
|
126314
|
+
|
|
126170
126315
|
// src/chatCommand.ts
|
|
126171
126316
|
var teamArgument = (text2, chinese) => {
|
|
126172
126317
|
const match = text2.match(/^\/team(?:\s+([\s\S]*))?$/i);
|
|
@@ -126371,11 +126516,13 @@ var readQuestionOptions = (value) => {
|
|
|
126371
126516
|
const token = nonEmpty(entry["token"]);
|
|
126372
126517
|
const optionValue = nonEmpty(entry["value"]);
|
|
126373
126518
|
if (token === null || optionValue === null || typeof entry["label"] !== "string") return null;
|
|
126519
|
+
const planAction = entry["planAction"];
|
|
126374
126520
|
options.push({
|
|
126375
126521
|
token,
|
|
126376
126522
|
value: optionValue,
|
|
126377
126523
|
label: entry["label"],
|
|
126378
|
-
description: typeof entry["description"] === "string" ? entry["description"] : ""
|
|
126524
|
+
description: typeof entry["description"] === "string" ? entry["description"] : "",
|
|
126525
|
+
...planAction === "approve" || planAction === "revise" || planAction === "stop" ? { planAction } : {}
|
|
126379
126526
|
});
|
|
126380
126527
|
}
|
|
126381
126528
|
return options.length === 0 ? null : options;
|
|
@@ -126807,6 +126954,39 @@ var uploadImage = async (input) => {
|
|
|
126807
126954
|
}
|
|
126808
126955
|
return body.data.image_key;
|
|
126809
126956
|
};
|
|
126957
|
+
var uploadFile = async (input) => {
|
|
126958
|
+
const form = new FormData();
|
|
126959
|
+
form.set("file_type", "stream");
|
|
126960
|
+
form.set("file_name", input.name);
|
|
126961
|
+
form.set("file", new Blob([new Uint8Array(input.bytes)], { type: input.mimeType }), input.name);
|
|
126962
|
+
const response = await input.http(`${LARK_BASE}/im/v1/files`, {
|
|
126963
|
+
method: "POST",
|
|
126964
|
+
// 不设 content-type:multipart 的 boundary 要让 fetch 自己写。
|
|
126965
|
+
headers: { authorization: `Bearer ${input.token}` },
|
|
126966
|
+
body: form
|
|
126967
|
+
});
|
|
126968
|
+
const body = await readEnvelope("files.create", response);
|
|
126969
|
+
if (typeof body.data?.file_key !== "string") {
|
|
126970
|
+
throw new LarkApiError("files.create", 0, "the response carried no file_key");
|
|
126971
|
+
}
|
|
126972
|
+
return body.data.file_key;
|
|
126973
|
+
};
|
|
126974
|
+
var sendChatFile = async (input) => {
|
|
126975
|
+
const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=chat_id`, {
|
|
126976
|
+
method: "POST",
|
|
126977
|
+
headers: {
|
|
126978
|
+
authorization: `Bearer ${input.token}`,
|
|
126979
|
+
"content-type": "application/json; charset=utf-8"
|
|
126980
|
+
},
|
|
126981
|
+
body: JSON.stringify({
|
|
126982
|
+
receive_id: input.chatId,
|
|
126983
|
+
msg_type: "file",
|
|
126984
|
+
content: JSON.stringify({ file_key: input.fileKey })
|
|
126985
|
+
})
|
|
126986
|
+
});
|
|
126987
|
+
const body = await readEnvelope("send.file", response);
|
|
126988
|
+
return body.data?.message_id ?? null;
|
|
126989
|
+
};
|
|
126810
126990
|
var sendChatImage = async (input) => {
|
|
126811
126991
|
const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=chat_id`, {
|
|
126812
126992
|
method: "POST",
|
|
@@ -132280,82 +132460,171 @@ var buildChoiceCard = (card) => {
|
|
|
132280
132460
|
|
|
132281
132461
|
// src/larkInbound.ts
|
|
132282
132462
|
var import_node_sdk = __toESM(require_lib2(), 1);
|
|
132283
|
-
|
|
132284
|
-
|
|
132285
|
-
)
|
|
132286
|
-
|
|
132287
|
-
|
|
132288
|
-
|
|
132289
|
-
|
|
132290
|
-
|
|
132291
|
-
|
|
132292
|
-
gif: "image/gif",
|
|
132293
|
-
webp: "image/webp",
|
|
132294
|
-
pdf: "application/pdf",
|
|
132295
|
-
txt: "text/plain",
|
|
132296
|
-
md: "text/markdown",
|
|
132297
|
-
json: "application/json",
|
|
132298
|
-
csv: "text/csv"
|
|
132299
|
-
};
|
|
132300
|
-
return known[ext] ?? "application/octet-stream";
|
|
132463
|
+
|
|
132464
|
+
// src/larkPost.ts
|
|
132465
|
+
var styledText = (text2, style) => {
|
|
132466
|
+
let out = text2;
|
|
132467
|
+
if (style?.includes("bold")) out = `**${out}**`;
|
|
132468
|
+
if (style?.includes("italic")) out = `*${out}*`;
|
|
132469
|
+
if (style?.includes("lineThrough")) out = `~~${out}~~`;
|
|
132470
|
+
if (style?.includes("underline")) out = `<u>${out}</u>`;
|
|
132471
|
+
return out;
|
|
132301
132472
|
};
|
|
132302
|
-
var
|
|
132303
|
-
|
|
132473
|
+
var nonEmpty2 = (value) => typeof value === "string" && value !== "";
|
|
132474
|
+
var resourceOf = (element, index) => {
|
|
132475
|
+
if (element.tag === "img" && nonEmpty2(element.image_key)) {
|
|
132476
|
+
return {
|
|
132477
|
+
fileKey: element.image_key,
|
|
132478
|
+
kind: "image",
|
|
132479
|
+
name: `image-${String(index)}.png`,
|
|
132480
|
+
mimeType: "image/png"
|
|
132481
|
+
};
|
|
132482
|
+
}
|
|
132483
|
+
if ((element.tag === "media" || element.tag === "file") && nonEmpty2(element.file_key)) {
|
|
132484
|
+
const video = element.tag === "media";
|
|
132485
|
+
const name = nonEmpty2(element.file_name) ? element.file_name : video ? `video-${String(index)}.mp4` : `file-${String(index)}`;
|
|
132486
|
+
return {
|
|
132487
|
+
fileKey: element.file_key,
|
|
132488
|
+
kind: "file",
|
|
132489
|
+
name,
|
|
132490
|
+
mimeType: video && !nonEmpty2(element.file_name) ? "video/mp4" : mimeFromName(name)
|
|
132491
|
+
};
|
|
132492
|
+
}
|
|
132493
|
+
return null;
|
|
132494
|
+
};
|
|
132495
|
+
var placeholderOf = (element, index) => `<${element.tag === "img" ? "image" : element.tag === "media" ? "video" : "file"} ${String(index)}>`;
|
|
132496
|
+
var serializeElement = (element, index, botIds) => {
|
|
132497
|
+
switch (element.tag) {
|
|
132498
|
+
case "text":
|
|
132499
|
+
return styledText(element.text ?? "", element.style);
|
|
132500
|
+
case "a": {
|
|
132501
|
+
const label = element.text ?? element.href ?? "";
|
|
132502
|
+
return nonEmpty2(element.href) ? `[${label}](${element.href})` : label;
|
|
132503
|
+
}
|
|
132504
|
+
case "at": {
|
|
132505
|
+
if (element.user_id !== void 0 && botIds.has(element.user_id)) return "";
|
|
132506
|
+
return nonEmpty2(element.user_name) ? `@${element.user_name}` : "";
|
|
132507
|
+
}
|
|
132508
|
+
case "code_block":
|
|
132509
|
+
return `\`\`\`${element.language ?? ""}
|
|
132510
|
+
${element.text ?? ""}
|
|
132511
|
+
\`\`\``;
|
|
132512
|
+
case "hr":
|
|
132513
|
+
return "---";
|
|
132514
|
+
case "emotion":
|
|
132515
|
+
return element.emoji_type ?? "";
|
|
132516
|
+
case "img":
|
|
132517
|
+
case "media":
|
|
132518
|
+
case "file":
|
|
132519
|
+
return index === null ? "" : placeholderOf(element, index);
|
|
132520
|
+
default:
|
|
132521
|
+
return "";
|
|
132522
|
+
}
|
|
132523
|
+
};
|
|
132524
|
+
var serializePost = (content, botIds = /* @__PURE__ */ new Set()) => {
|
|
132304
132525
|
let parsed;
|
|
132305
132526
|
try {
|
|
132306
132527
|
parsed = JSON.parse(content);
|
|
132307
132528
|
} catch {
|
|
132308
|
-
return
|
|
132529
|
+
return void 0;
|
|
132309
132530
|
}
|
|
132310
|
-
const
|
|
132311
|
-
const
|
|
132312
|
-
|
|
132313
|
-
for (const
|
|
132314
|
-
const
|
|
132315
|
-
|
|
132316
|
-
|
|
132317
|
-
|
|
132318
|
-
imageKey = segment.image_key;
|
|
132319
|
-
}
|
|
132320
|
-
}
|
|
132321
|
-
const line = parts.join("").trim();
|
|
132322
|
-
if (line.length > 0) lines.push(line);
|
|
132531
|
+
const elements = (parsed.content ?? []).flat();
|
|
132532
|
+
const resources = [];
|
|
132533
|
+
const indexOf = /* @__PURE__ */ new Map();
|
|
132534
|
+
for (const element of elements) {
|
|
132535
|
+
const resource = resourceOf(element, resources.length + 1);
|
|
132536
|
+
if (resource === null) continue;
|
|
132537
|
+
resources.push(resource);
|
|
132538
|
+
indexOf.set(element, resources.length);
|
|
132323
132539
|
}
|
|
132324
|
-
|
|
132325
|
-
|
|
132326
|
-
|
|
132327
|
-
|
|
132540
|
+
const md2 = parsed.md;
|
|
132541
|
+
if (typeof md2 === "string" && md2.trim() !== "") {
|
|
132542
|
+
const placeholderByKey = /* @__PURE__ */ new Map();
|
|
132543
|
+
for (const element of elements) {
|
|
132544
|
+
const index = indexOf.get(element);
|
|
132545
|
+
const key = element.tag === "img" ? element.image_key : element.file_key;
|
|
132546
|
+
if (index !== void 0 && nonEmpty2(key)) {
|
|
132547
|
+
placeholderByKey.set(key, placeholderOf(element, index));
|
|
132548
|
+
}
|
|
132549
|
+
}
|
|
132550
|
+
const text3 = md2.replace(
|
|
132551
|
+
/!\[(?:img|video|file)\]\(([^)]+)\)/g,
|
|
132552
|
+
(whole, rawKey) => placeholderByKey.get(rawKey.trim()) ?? whole
|
|
132553
|
+
);
|
|
132554
|
+
return { text: text3, resources };
|
|
132555
|
+
}
|
|
132556
|
+
const paragraphs = (parsed.content ?? []).map(
|
|
132557
|
+
(row) => row.map((element) => serializeElement(element, indexOf.get(element) ?? null, botIds)).join("")
|
|
132558
|
+
);
|
|
132559
|
+
const text2 = paragraphs.map((line) => line.trim()).filter((line) => line !== "").join("\n");
|
|
132560
|
+
if (text2 === "" && resources.length === 0) return void 0;
|
|
132561
|
+
return { text: text2, resources };
|
|
132328
132562
|
};
|
|
132329
|
-
|
|
132563
|
+
|
|
132564
|
+
// src/larkInbound.ts
|
|
132565
|
+
var mentionsApp = (event, appId) => (event.message?.mentions ?? []).some(
|
|
132566
|
+
(mention) => mention.id?.app_id === appId || mention.mentioned_type === "bot"
|
|
132567
|
+
);
|
|
132568
|
+
var RESOURCE_MESSAGE_TYPES = /* @__PURE__ */ new Set(["image", "file", "audio", "media"]);
|
|
132569
|
+
var UNSUPPORTED_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
132570
|
+
"sticker",
|
|
132571
|
+
"folder",
|
|
132572
|
+
"share_chat",
|
|
132573
|
+
"share_user",
|
|
132574
|
+
"system",
|
|
132575
|
+
"merge",
|
|
132576
|
+
"interactive"
|
|
132577
|
+
]);
|
|
132578
|
+
var botIdsOf = (mentions, appId) => {
|
|
132579
|
+
const ids = /* @__PURE__ */ new Set();
|
|
132580
|
+
for (const mention of mentions) {
|
|
132581
|
+
if (mention.id?.app_id !== appId && mention.mentioned_type !== "bot") continue;
|
|
132582
|
+
ids.add(mention.key);
|
|
132583
|
+
for (const id of [mention.id?.open_id, mention.id?.app_id]) if (id !== void 0) ids.add(id);
|
|
132584
|
+
}
|
|
132585
|
+
return ids;
|
|
132586
|
+
};
|
|
132587
|
+
var readJson = (content) => {
|
|
132330
132588
|
if (content === void 0) return null;
|
|
132331
|
-
let parsed;
|
|
132332
132589
|
try {
|
|
132333
|
-
parsed = JSON.parse(content);
|
|
132590
|
+
const parsed = JSON.parse(content);
|
|
132591
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
132334
132592
|
} catch {
|
|
132335
132593
|
return null;
|
|
132336
132594
|
}
|
|
132595
|
+
};
|
|
132596
|
+
var readResource = (messageType, content) => {
|
|
132597
|
+
const parsed = readJson(content);
|
|
132598
|
+
if (parsed === null) return null;
|
|
132599
|
+
const key = (field) => {
|
|
132600
|
+
const value = parsed[field];
|
|
132601
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
132602
|
+
};
|
|
132337
132603
|
if (messageType === "image") {
|
|
132338
|
-
const
|
|
132339
|
-
return
|
|
132604
|
+
const imageKey = key("image_key");
|
|
132605
|
+
return imageKey === null ? null : { fileKey: imageKey, kind: "image", name: "image.png", mimeType: "image/png" };
|
|
132340
132606
|
}
|
|
132341
132607
|
if (messageType === "file") {
|
|
132342
|
-
const
|
|
132343
|
-
if (
|
|
132344
|
-
const
|
|
132345
|
-
|
|
132346
|
-
|
|
132608
|
+
const fileKey = key("file_key");
|
|
132609
|
+
if (fileKey === null) return null;
|
|
132610
|
+
const name = key("file_name") ?? "file";
|
|
132611
|
+
return { fileKey, kind: "file", name, mimeType: mimeFromName(name) };
|
|
132612
|
+
}
|
|
132613
|
+
if (messageType === "audio") {
|
|
132614
|
+
const fileKey = key("file_key");
|
|
132615
|
+
return fileKey === null ? null : { fileKey, kind: "file", name: "voice.opus", mimeType: "audio/ogg" };
|
|
132616
|
+
}
|
|
132617
|
+
if (messageType === "media") {
|
|
132618
|
+
const fileKey = key("file_key");
|
|
132619
|
+
if (fileKey === null) return null;
|
|
132620
|
+
const name = key("file_name") ?? "video.mp4";
|
|
132621
|
+
return { fileKey, kind: "file", name, mimeType: mimeFromName(name) };
|
|
132347
132622
|
}
|
|
132348
132623
|
return null;
|
|
132349
132624
|
};
|
|
132350
132625
|
var readText = (content) => {
|
|
132351
|
-
|
|
132352
|
-
|
|
132353
|
-
const parsed = JSON.parse(content);
|
|
132354
|
-
const text2 = parsed.text;
|
|
132355
|
-
return typeof text2 === "string" ? text2 : null;
|
|
132356
|
-
} catch {
|
|
132357
|
-
return null;
|
|
132358
|
-
}
|
|
132626
|
+
const text2 = readJson(content)?.["text"];
|
|
132627
|
+
return typeof text2 === "string" ? text2 : null;
|
|
132359
132628
|
};
|
|
132360
132629
|
var parseInboundMessage = (event, appId) => {
|
|
132361
132630
|
if (event.sender?.sender_type === "app") return null;
|
|
@@ -132364,33 +132633,52 @@ var parseInboundMessage = (event, appId) => {
|
|
|
132364
132633
|
const { message_id: messageId, chat_id: chatId } = message;
|
|
132365
132634
|
if (typeof messageId !== "string" || typeof chatId !== "string") return null;
|
|
132366
132635
|
const chatType = message.chat_type ?? "group";
|
|
132367
|
-
if (chatType !== "p2p" && !mentionsApp(event, appId)) return null;
|
|
132368
132636
|
const messageType = message.message_type ?? "";
|
|
132369
|
-
|
|
132370
|
-
|
|
132371
|
-
|
|
132372
|
-
const
|
|
132373
|
-
if (
|
|
132374
|
-
|
|
132375
|
-
|
|
132376
|
-
|
|
132377
|
-
|
|
132378
|
-
|
|
132379
|
-
|
|
132380
|
-
|
|
132381
|
-
|
|
132637
|
+
if (chatType !== "p2p" && !mentionsApp(event, appId) && !RESOURCE_MESSAGE_TYPES.has(messageType)) {
|
|
132638
|
+
return null;
|
|
132639
|
+
}
|
|
132640
|
+
const senderOpenId = event.sender?.sender_id?.open_id ?? null;
|
|
132641
|
+
if (UNSUPPORTED_MESSAGE_TYPES.has(messageType)) {
|
|
132642
|
+
return {
|
|
132643
|
+
messageId,
|
|
132644
|
+
chatId,
|
|
132645
|
+
chatType,
|
|
132646
|
+
senderOpenId,
|
|
132647
|
+
text: "",
|
|
132648
|
+
resources: [],
|
|
132649
|
+
unsupportedType: messageType
|
|
132650
|
+
};
|
|
132651
|
+
}
|
|
132652
|
+
const mentions = message.mentions ?? [];
|
|
132653
|
+
const post = messageType === "post" ? serializePost(message.content ?? "", botIdsOf(mentions, appId)) : void 0;
|
|
132654
|
+
const single = post === void 0 ? readResource(messageType, message.content) : null;
|
|
132655
|
+
const resources = post !== void 0 ? post.resources : single === null ? [] : [single];
|
|
132656
|
+
const raw = post !== void 0 ? post.text : messageType === "text" ? readText(message.content) : null;
|
|
132657
|
+
const text2 = mentions.reduce((value, mention) => value.replaceAll(mention.key, ""), raw ?? "").trim();
|
|
132658
|
+
if (text2.length === 0 && resources.length === 0) return null;
|
|
132659
|
+
return { messageId, chatId, chatType, senderOpenId, text: text2, resources };
|
|
132382
132660
|
};
|
|
132383
132661
|
var fetchMessageResource = async (input) => {
|
|
132384
|
-
const url =
|
|
132662
|
+
const url = `${LARK_BASE}/im/v1/messages/${encodeURIComponent(
|
|
132385
132663
|
input.messageId
|
|
132386
132664
|
)}/resources/${encodeURIComponent(input.resource.fileKey)}?type=${input.resource.kind}`;
|
|
132387
132665
|
const response = await input.http(url, {
|
|
132388
132666
|
headers: { authorization: `Bearer ${input.token}` }
|
|
132389
132667
|
});
|
|
132390
132668
|
if (!response.ok) {
|
|
132669
|
+
await response.body?.cancel().catch(() => void 0);
|
|
132391
132670
|
throw new Error(`lark resource fetch failed: HTTP ${String(response.status)}`);
|
|
132392
132671
|
}
|
|
132393
|
-
|
|
132672
|
+
const bytes = await readBodyLimited(response, input.maxBytes);
|
|
132673
|
+
if ((response.headers.get("content-type") ?? "").includes("application/json")) {
|
|
132674
|
+
const envelope = readJson(new TextDecoder().decode(bytes.subarray(0, 4096)));
|
|
132675
|
+
const code2 = envelope?.["code"];
|
|
132676
|
+
if (typeof code2 === "number" && code2 !== 0) {
|
|
132677
|
+
const detail = envelope?.["msg"];
|
|
132678
|
+
throw new LarkApiError("resource", code2, typeof detail === "string" ? detail : "no message");
|
|
132679
|
+
}
|
|
132680
|
+
}
|
|
132681
|
+
return bytes;
|
|
132394
132682
|
};
|
|
132395
132683
|
var cardActionValue = (event) => {
|
|
132396
132684
|
const roots = [event, event?.event];
|
|
@@ -132526,12 +132814,13 @@ var makeLarkAdapter = (deps) => {
|
|
|
132526
132814
|
chatId: message.chatId,
|
|
132527
132815
|
senderId: message.senderOpenId,
|
|
132528
132816
|
text: message.text,
|
|
132529
|
-
|
|
132530
|
-
key:
|
|
132531
|
-
name:
|
|
132532
|
-
mimeType:
|
|
132533
|
-
kind:
|
|
132534
|
-
}
|
|
132817
|
+
resources: message.resources.map((resource) => ({
|
|
132818
|
+
key: resource.fileKey,
|
|
132819
|
+
name: resource.name,
|
|
132820
|
+
mimeType: resource.mimeType,
|
|
132821
|
+
kind: resource.kind
|
|
132822
|
+
})),
|
|
132823
|
+
...message.unsupportedType === void 0 ? {} : { unsupportedType: message.unsupportedType }
|
|
132535
132824
|
});
|
|
132536
132825
|
}
|
|
132537
132826
|
});
|
|
@@ -132560,6 +132849,20 @@ var makeLarkAdapter = (deps) => {
|
|
|
132560
132849
|
await sendChatImage({ http: deps.http, token, chatId: binding.chatId, imageKey });
|
|
132561
132850
|
deps.log.info("lark delivered an image", { name: image2.name });
|
|
132562
132851
|
},
|
|
132852
|
+
sendFile: async (binding, file) => {
|
|
132853
|
+
const token = await tenantToken(binding);
|
|
132854
|
+
const fileKey = await uploadFile({
|
|
132855
|
+
http: deps.http,
|
|
132856
|
+
token,
|
|
132857
|
+
bytes: file.bytes,
|
|
132858
|
+
name: file.name,
|
|
132859
|
+
mimeType: file.mimeType
|
|
132860
|
+
});
|
|
132861
|
+
await sendChatFile({ http: deps.http, token, chatId: binding.chatId, fileKey });
|
|
132862
|
+
deps.log.info("lark delivered a file", { name: file.name });
|
|
132863
|
+
},
|
|
132864
|
+
// 图 10 MB、文件 30 MB(飞书开放平台文档:上传图片接口、上传文件接口;空文件会被拒)。
|
|
132865
|
+
outboundLimits: { imageBytes: 10 * 1024 * 1024, fileBytes: 30 * 1024 * 1024 },
|
|
132563
132866
|
askApproval: async (binding, request) => sendCard({
|
|
132564
132867
|
http: deps.http,
|
|
132565
132868
|
token: await tenantToken(binding),
|
|
@@ -132650,6 +132953,7 @@ var makeLarkAdapter = (deps) => {
|
|
|
132650
132953
|
http: deps.http,
|
|
132651
132954
|
token: await tenantToken(binding),
|
|
132652
132955
|
messageId: input.messageId,
|
|
132956
|
+
maxBytes: input.maxBytes,
|
|
132653
132957
|
resource: {
|
|
132654
132958
|
fileKey: resource.key,
|
|
132655
132959
|
kind: resource.kind,
|
|
@@ -132746,7 +133050,20 @@ var en_default = {
|
|
|
132746
133050
|
projectArchived: 'Project "{{title}}" is archived. Restore it, then message again.',
|
|
132747
133051
|
projectMissing: `The project bound to "{{group}}" is no longer on this machine (it may have been deleted), so a thread cannot be started. In Ycodium's chat settings, pick a project for this group again.`,
|
|
132748
133052
|
threadGone: "The earlier session was deleted or archived, so this group now starts a new one.",
|
|
132749
|
-
teamGone: "The team this group was sending to no longer exists, so messages go to a normal session again."
|
|
133053
|
+
teamGone: "The team this group was sending to no longer exists, so messages go to a normal session again.",
|
|
133054
|
+
attachmentReceived: "Got {{names}} ({{count}} waiting). Tell me what to do with them.",
|
|
133055
|
+
attachmentFailed: "Could not take {{name}}: {{reason}}.",
|
|
133056
|
+
attachmentReason: {
|
|
133057
|
+
tooLarge: "it is over the {{limit}} limit",
|
|
133058
|
+
downloadFailed: "the download failed",
|
|
133059
|
+
storeFailed: "it could not be saved",
|
|
133060
|
+
empty: "it is empty"
|
|
133061
|
+
},
|
|
133062
|
+
attachmentLimit: "At most {{max}} attachments go with one message, so {{name}} was left out. Send a message about the current ones first.",
|
|
133063
|
+
attachmentsNoTeam: "Files cannot be sent to a team yet, so they were left out. Send /thread to go back to a normal session.",
|
|
133064
|
+
unsupportedType: "I can't process {{type}} messages yet.",
|
|
133065
|
+
unsupportedFolder: "Folders can't be downloaded. Zip it and send the archive instead.",
|
|
133066
|
+
sendingFile: "Sending {{name}}:"
|
|
132750
133067
|
},
|
|
132751
133068
|
approval: {
|
|
132752
133069
|
allow: "Allow",
|
|
@@ -132757,6 +133074,12 @@ var en_default = {
|
|
|
132757
133074
|
question: {
|
|
132758
133075
|
title: "\u2753 Question",
|
|
132759
133076
|
planTitle: "\u{1F4DD} Plan review",
|
|
133077
|
+
planApprove: "Approve plan",
|
|
133078
|
+
planApproveHint: "Let the agent start implementing this plan.",
|
|
133079
|
+
planRevise: "Revise plan",
|
|
133080
|
+
planReviseHint: "Ask the agent to rework the plan before going ahead.",
|
|
133081
|
+
planStop: "Stop here",
|
|
133082
|
+
planStopHint: "Leave plan mode without implementing this plan.",
|
|
132760
133083
|
freeformHint: "Or reply with a message to answer in your own words.",
|
|
132761
133084
|
replyOne: "Reply with the number.",
|
|
132762
133085
|
replyMany: "Reply with the numbers, separated by commas.",
|
|
@@ -133039,7 +133362,20 @@ var zh_CN_default = {
|
|
|
133039
133362
|
projectArchived: "\u9879\u76EE\u300C{{title}}\u300D\u5DF2\u5F52\u6863\uFF0C\u6062\u590D\u540E\u518D\u53D1\u6D88\u606F\u3002",
|
|
133040
133363
|
projectMissing: "\u300C{{group}}\u300D\u7ED1\u7684\u9879\u76EE\u5DF2\u7ECF\u4E0D\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u4E86\uFF08\u53EF\u80FD\u88AB\u5220\u4E86\uFF09\uFF0C\u5F00\u4E0D\u4E86\u4F1A\u8BDD\u3002\u5230 Ycodium \u7684\u804A\u5929\u8BBE\u7F6E\u91CC\u7ED9\u8FD9\u4E2A\u7FA4\u91CD\u65B0\u9009\u4E00\u4E2A\u9879\u76EE\u3002",
|
|
133041
133364
|
threadGone: "\u4E4B\u524D\u90A3\u6761\u4F1A\u8BDD\u5DF2\u88AB\u5220\u9664\u6216\u5F52\u6863\uFF0C\u8FD9\u4E2A\u7FA4\u6539\u4E3A\u5F00\u4E00\u6761\u65B0\u4F1A\u8BDD\u3002",
|
|
133042
|
-
teamGone: "\u8FD9\u4E2A\u7FA4\u539F\u6765\u6D3E\u7ED9\u7684\u961F\u5DF2\u7ECF\u4E0D\u5728\u4E86\uFF0C\u63A5\u4E0B\u6765\u6539\u56DE\u666E\u901A\u4F1A\u8BDD\u3002"
|
|
133365
|
+
teamGone: "\u8FD9\u4E2A\u7FA4\u539F\u6765\u6D3E\u7ED9\u7684\u961F\u5DF2\u7ECF\u4E0D\u5728\u4E86\uFF0C\u63A5\u4E0B\u6765\u6539\u56DE\u666E\u901A\u4F1A\u8BDD\u3002",
|
|
133366
|
+
attachmentReceived: "\u6536\u5230 {{names}}\uFF08\u73B0\u6709 {{count}} \u4E2A\u5F85\u5904\u7406\uFF09\u3002\u8BF4\u4E00\u53E5\u60F3\u8BA9\u6211\u600E\u4E48\u5904\u7406\u3002",
|
|
133367
|
+
attachmentFailed: "\u6CA1\u80FD\u6536\u4E0B {{name}}\uFF1A{{reason}}\u3002",
|
|
133368
|
+
attachmentReason: {
|
|
133369
|
+
tooLarge: "\u8D85\u8FC7\u4E86 {{limit}} \u7684\u4E0A\u9650",
|
|
133370
|
+
downloadFailed: "\u4E0B\u8F7D\u5931\u8D25\u4E86",
|
|
133371
|
+
storeFailed: "\u4FDD\u5B58\u4E0D\u4E86",
|
|
133372
|
+
empty: "\u6587\u4EF6\u662F\u7A7A\u7684"
|
|
133373
|
+
},
|
|
133374
|
+
attachmentLimit: "\u4E00\u6761\u6D88\u606F\u6700\u591A\u5E26 {{max}} \u4E2A\u9644\u4EF6\uFF0C\u6240\u4EE5 {{name}} \u6CA1\u6536\u3002\u5148\u53D1\u4E00\u53E5\u8BDD\u5904\u7406\u624B\u4E0A\u8FD9\u4E9B\u3002",
|
|
133375
|
+
attachmentsNoTeam: "\u6587\u4EF6\u8FD8\u53D1\u4E0D\u5230\u56E2\u961F\uFF0C\u8FD9\u6B21\u6CA1\u5E26\u4E0A\u3002\u53D1 /thread \u56DE\u5230\u666E\u901A\u4F1A\u8BDD\u3002",
|
|
133376
|
+
unsupportedType: "\u6211\u8FD8\u4E0D\u80FD\u5904\u7406 {{type}} \u7C7B\u578B\u7684\u6D88\u606F\u3002",
|
|
133377
|
+
unsupportedFolder: "\u6587\u4EF6\u5939\u6CA1\u6CD5\u4E0B\u8F7D\uFF0C\u8BF7\u6253\u5305\u6210\u538B\u7F29\u6587\u4EF6\u518D\u53D1\u3002",
|
|
133378
|
+
sendingFile: "\u6B63\u5728\u53D1\u9001 {{name}}\uFF1A"
|
|
133043
133379
|
},
|
|
133044
133380
|
approval: {
|
|
133045
133381
|
allow: "\u5141\u8BB8",
|
|
@@ -133050,6 +133386,12 @@ var zh_CN_default = {
|
|
|
133050
133386
|
question: {
|
|
133051
133387
|
title: "\u2753 \u63D0\u95EE",
|
|
133052
133388
|
planTitle: "\u{1F4DD} \u8BA1\u5212\u786E\u8BA4",
|
|
133389
|
+
planApprove: "\u6279\u51C6\u8BA1\u5212",
|
|
133390
|
+
planApproveHint: "\u8BA9 agent \u6309\u8FD9\u4EFD\u8BA1\u5212\u5F00\u59CB\u5B9E\u65BD\u3002",
|
|
133391
|
+
planRevise: "\u4FEE\u6539\u8BA1\u5212",
|
|
133392
|
+
planReviseHint: "\u8BA9 agent \u5148\u4FEE\u6539\u8BA1\u5212\uFF0C\u518D\u7EE7\u7EED\u5F80\u4E0B\u505A\u3002",
|
|
133393
|
+
planStop: "\u5230\u6B64\u4E3A\u6B62",
|
|
133394
|
+
planStopHint: "\u9000\u51FA\u8BA1\u5212\u6A21\u5F0F\uFF0C\u4E0D\u5B9E\u65BD\u8FD9\u4EFD\u8BA1\u5212\u3002",
|
|
133053
133395
|
freeformHint: "\u4E5F\u53EF\u4EE5\u76F4\u63A5\u56DE\u590D\u4E00\u6761\u6D88\u606F\uFF0C\u7528\u81EA\u5DF1\u7684\u8BDD\u4F5C\u7B54\u3002",
|
|
133054
133396
|
replyOne: "\u56DE\u590D\u7F16\u53F7\u5373\u53EF\u3002",
|
|
133055
133397
|
replyMany: "\u56DE\u590D\u7F16\u53F7\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u9694\u5F00\u3002",
|
|
@@ -133776,6 +134118,20 @@ var NOT_MINE = { handled: false };
|
|
|
133776
134118
|
var handledWith = (notice) => ({ handled: true, notice });
|
|
133777
134119
|
|
|
133778
134120
|
// src/questionCards.ts
|
|
134121
|
+
var PLAN_ACTION_TEXT = {
|
|
134122
|
+
approve: { label: "question.planApprove", hint: "question.planApproveHint" },
|
|
134123
|
+
revise: { label: "question.planRevise", hint: "question.planReviseHint" },
|
|
134124
|
+
stop: { label: "question.planStop", hint: "question.planStopHint" }
|
|
134125
|
+
};
|
|
134126
|
+
var planTextOf = (item, option) => item.kind === "plan" && option.planAction !== void 0 ? PLAN_ACTION_TEXT[option.planAction] : void 0;
|
|
134127
|
+
var optionLabelOf = (item, option, t) => {
|
|
134128
|
+
const plan = planTextOf(item, option);
|
|
134129
|
+
return plan === void 0 ? option.label : t(plan.label);
|
|
134130
|
+
};
|
|
134131
|
+
var optionDescriptionOf = (item, option, t) => {
|
|
134132
|
+
const plan = planTextOf(item, option);
|
|
134133
|
+
return (plan === void 0 ? option.description : t(plan.hint)).trim();
|
|
134134
|
+
};
|
|
133779
134135
|
var QUESTION_TEXT_MAX_CHARS = 1500;
|
|
133780
134136
|
var ANSWER_ECHO_MAX_CHARS = 200;
|
|
133781
134137
|
var clip2 = (text2, limit) => {
|
|
@@ -133786,8 +134142,10 @@ var titleOf = (item, index, total, t) => {
|
|
|
133786
134142
|
const base = item.kind === "plan" ? t("question.planTitle") : t("question.title");
|
|
133787
134143
|
return total > 1 ? `${base} ${String(index + 1)}/${String(total)}` : base;
|
|
133788
134144
|
};
|
|
133789
|
-
var bodyOf = (item) => clip2(
|
|
133790
|
-
item.options.filter((option) => option
|
|
134145
|
+
var bodyOf = (item, t) => clip2(
|
|
134146
|
+
item.options.filter((option) => optionDescriptionOf(item, option, t) !== "").map(
|
|
134147
|
+
(option) => `\xB7 ${optionLabelOf(item, option, t)}: ${optionDescriptionOf(item, option, t)}`
|
|
134148
|
+
).join("\n"),
|
|
133791
134149
|
QUESTION_TEXT_MAX_CHARS
|
|
133792
134150
|
);
|
|
133793
134151
|
var headingOf = (item) => clip2(
|
|
@@ -133798,14 +134156,14 @@ var questionCardOf = (item, index, total, t) => ({
|
|
|
133798
134156
|
tone: "ask",
|
|
133799
134157
|
title: titleOf(item, index, total, t),
|
|
133800
134158
|
heading: headingOf(item),
|
|
133801
|
-
body: bodyOf(item),
|
|
134159
|
+
body: bodyOf(item, t),
|
|
133802
134160
|
note: item.allowFreeform ? t("question.freeformHint") : null,
|
|
133803
134161
|
rows: [
|
|
133804
134162
|
...item.options.map((option) => {
|
|
133805
134163
|
const ticked = item.multiSelect && item.selected.includes(option.value);
|
|
133806
134164
|
return [
|
|
133807
134165
|
{
|
|
133808
|
-
label: ticked ? `\u2705 ${option
|
|
134166
|
+
label: ticked ? `\u2705 ${optionLabelOf(item, option, t)}` : optionLabelOf(item, option, t),
|
|
133809
134167
|
token: option.token,
|
|
133810
134168
|
style: ticked ? "primary" : "default"
|
|
133811
134169
|
}
|
|
@@ -133826,14 +134184,14 @@ var questionPlainText = (item, index, total, t) => [
|
|
|
133826
134184
|
titleOf(item, index, total, t),
|
|
133827
134185
|
headingOf(item),
|
|
133828
134186
|
...item.options.map((option, position) => {
|
|
133829
|
-
const description = option
|
|
133830
|
-
return `${String(position + 1)}. ${option
|
|
134187
|
+
const description = optionDescriptionOf(item, option, t);
|
|
134188
|
+
return `${String(position + 1)}. ${optionLabelOf(item, option, t)}${description === "" ? "" : ` \u2014 ${description}`}`;
|
|
133831
134189
|
}),
|
|
133832
134190
|
"",
|
|
133833
134191
|
item.multiSelect ? t("question.replyMany") : t("question.replyOne"),
|
|
133834
134192
|
...item.allowFreeform ? [t("question.freeformHint")] : []
|
|
133835
134193
|
].join("\n");
|
|
133836
|
-
var parseNumberedReply = (item, text2) => {
|
|
134194
|
+
var parseNumberedReply = (item, text2, t) => {
|
|
133837
134195
|
const trimmed = text2.trim();
|
|
133838
134196
|
const pattern = item.multiSelect ? /^\d+(?:[\s,,、]+\d+)*$/ : /^\d+$/;
|
|
133839
134197
|
if (!pattern.test(trimmed)) return null;
|
|
@@ -133844,11 +134202,11 @@ var parseNumberedReply = (item, text2) => {
|
|
|
133844
134202
|
if (item.multiSelect) {
|
|
133845
134203
|
return {
|
|
133846
134204
|
answer: options.map((option) => option.value),
|
|
133847
|
-
label: options.map((option) => option
|
|
134205
|
+
label: options.map((option) => optionLabelOf(item, option, t)).join(", ")
|
|
133848
134206
|
};
|
|
133849
134207
|
}
|
|
133850
134208
|
const only = options[0];
|
|
133851
|
-
return only === void 0 ? null : { answer: only.value, label: only
|
|
134209
|
+
return only === void 0 ? null : { answer: only.value, label: optionLabelOf(item, only, t) };
|
|
133852
134210
|
};
|
|
133853
134211
|
|
|
133854
134212
|
// src/questionFlow.ts
|
|
@@ -133958,7 +134316,8 @@ var makeQuestionFlow = (host) => {
|
|
|
133958
134316
|
token: host.newToken(),
|
|
133959
134317
|
label: option.label,
|
|
133960
134318
|
value: option.value,
|
|
133961
|
-
description: option.description
|
|
134319
|
+
description: option.description,
|
|
134320
|
+
...option.planAction === void 0 ? {} : { planAction: option.planAction }
|
|
133962
134321
|
})),
|
|
133963
134322
|
...question.multiSelect ? { submitToken: host.newToken() } : {}
|
|
133964
134323
|
}))
|
|
@@ -134008,7 +134367,7 @@ var makeQuestionFlow = (host) => {
|
|
|
134008
134367
|
if (found.click.kind === "option") {
|
|
134009
134368
|
const option = found.click.option;
|
|
134010
134369
|
if (!item.multiSelect) {
|
|
134011
|
-
void answer(requestId, item.id, option.value, option
|
|
134370
|
+
void answer(requestId, item.id, option.value, optionLabelOf(item, option, t));
|
|
134012
134371
|
return handledWith(null);
|
|
134013
134372
|
}
|
|
134014
134373
|
const selected = item.selected.includes(option.value) ? item.selected.filter((value) => value !== option.value) : [...item.selected, option.value];
|
|
@@ -134022,7 +134381,7 @@ var makeQuestionFlow = (host) => {
|
|
|
134022
134381
|
requestId,
|
|
134023
134382
|
item.id,
|
|
134024
134383
|
chosen.map((option) => option.value),
|
|
134025
|
-
chosen.map((option) => option
|
|
134384
|
+
chosen.map((option) => optionLabelOf(item, option, t)).join(", ")
|
|
134026
134385
|
);
|
|
134027
134386
|
return handledWith(null);
|
|
134028
134387
|
};
|
|
@@ -134034,7 +134393,7 @@ var makeQuestionFlow = (host) => {
|
|
|
134034
134393
|
const first = unanswered[0];
|
|
134035
134394
|
if (first === void 0) return false;
|
|
134036
134395
|
if (!hasButtons(chatKey)) {
|
|
134037
|
-
const numbered = parseNumberedReply(first, trimmed);
|
|
134396
|
+
const numbered = parseNumberedReply(first, trimmed, t);
|
|
134038
134397
|
if (numbered !== null) {
|
|
134039
134398
|
return answer(record.requestId, first.id, numbered.answer, numbered.label);
|
|
134040
134399
|
}
|
|
@@ -134055,6 +134414,145 @@ var makeQuestionFlow = (host) => {
|
|
|
134055
134414
|
return { offer, click, answerByText, resolved };
|
|
134056
134415
|
};
|
|
134057
134416
|
|
|
134417
|
+
// src/sendFile.ts
|
|
134418
|
+
var failure = (code2, text2) => ({
|
|
134419
|
+
text: text2,
|
|
134420
|
+
isError: true,
|
|
134421
|
+
errorCode: code2
|
|
134422
|
+
});
|
|
134423
|
+
var describeError = (error) => error instanceof Error ? error.message : String(error);
|
|
134424
|
+
var reasonOf = (error) => {
|
|
134425
|
+
const reason = error?.reason;
|
|
134426
|
+
return typeof reason === "string" ? reason : void 0;
|
|
134427
|
+
};
|
|
134428
|
+
var formatSize = (bytes) => bytes < 1024 * 1024 ? `${String(Math.max(1, Math.round(bytes / 1024)))} KB` : formatMegabytes(bytes);
|
|
134429
|
+
var baseName = (path) => path.split(/[\\/]/).filter(Boolean).at(-1) ?? "";
|
|
134430
|
+
var readFailure = (error, path) => {
|
|
134431
|
+
switch (reasonOf(error)) {
|
|
134432
|
+
case "outside-workspace":
|
|
134433
|
+
return failure(
|
|
134434
|
+
"outside-workspace",
|
|
134435
|
+
`"${path}" is outside this conversation's working folder. Only files inside the project can be sent.`
|
|
134436
|
+
);
|
|
134437
|
+
case "not-a-file":
|
|
134438
|
+
return failure("not-a-file", `"${path}" is not a regular file (a folder cannot be sent).`);
|
|
134439
|
+
case "not-found":
|
|
134440
|
+
return failure("not-found", `"${path}" does not exist. Check the path and try again.`);
|
|
134441
|
+
case "too-large":
|
|
134442
|
+
return failure("too-large", `"${path}" is too large to send.`);
|
|
134443
|
+
case "invocation-ended":
|
|
134444
|
+
return failure(
|
|
134445
|
+
"invocation-ended",
|
|
134446
|
+
"The file could not be read: the tool call had already ended."
|
|
134447
|
+
);
|
|
134448
|
+
default:
|
|
134449
|
+
return failure("unreadable", `"${path}" could not be read: ${describeError(error)}`);
|
|
134450
|
+
}
|
|
134451
|
+
};
|
|
134452
|
+
var makeSendFileTool = (deps) => ({
|
|
134453
|
+
name: "send_file",
|
|
134454
|
+
description: "Send a file from this project to the chat this conversation is connected to (Lark, Telegram or WeCom), as a native image or file message. Use it when the user asked for a concrete deliverable, or would clearly benefit from one: a chart, a screenshot, a generated report, a CSV, an archive. Do not use it for text you can just say. `path` is relative to your working folder, or an absolute path inside the project; folders, and files outside the project, are refused. Images are sent as pictures when the platform allows it, everything else (documents, audio, video) as a file. Size limits are per platform (about 10 MB for a picture and 20 to 50 MB for a file); a too-large file is refused, so compress or split it. The chat first gets one line of caption, then the file. It only works in a conversation that was started from a chat; elsewhere it fails with not-in-chat and sends nothing.",
|
|
134455
|
+
inputSchema: {
|
|
134456
|
+
type: "object",
|
|
134457
|
+
properties: {
|
|
134458
|
+
path: {
|
|
134459
|
+
type: "string",
|
|
134460
|
+
description: "The file to send: a path relative to your working folder, or an absolute path inside the project"
|
|
134461
|
+
},
|
|
134462
|
+
description: {
|
|
134463
|
+
type: "string",
|
|
134464
|
+
description: "Optional caption: one short sentence, in the language the user writes in, saying what is being sent. Shown in the chat before the file. Leave it out to use a default line."
|
|
134465
|
+
}
|
|
134466
|
+
},
|
|
134467
|
+
required: ["path"]
|
|
134468
|
+
},
|
|
134469
|
+
handle: async (input, invocation) => {
|
|
134470
|
+
const path = typeof input["path"] === "string" ? input["path"].trim() : "";
|
|
134471
|
+
if (path === "") return failure("unreadable", "`path` is required.");
|
|
134472
|
+
const binding = invocation.threadId === null ? void 0 : deps.boundChat(invocation.threadId);
|
|
134473
|
+
if (binding === void 0) {
|
|
134474
|
+
return failure(
|
|
134475
|
+
"not-in-chat",
|
|
134476
|
+
"This conversation is not connected to a chat, so there is nowhere to send the file. Nothing was sent."
|
|
134477
|
+
);
|
|
134478
|
+
}
|
|
134479
|
+
const adapter = deps.adapterFor(binding);
|
|
134480
|
+
if (adapter?.sendFile === void 0 || adapter.outboundLimits === void 0) {
|
|
134481
|
+
return failure(
|
|
134482
|
+
"unsupported-platform",
|
|
134483
|
+
`${adapter?.label ?? "This chat platform"} cannot receive files from the agent yet. Nothing was sent.`
|
|
134484
|
+
);
|
|
134485
|
+
}
|
|
134486
|
+
const workspace = invocation.workspace;
|
|
134487
|
+
if (workspace === void 0) {
|
|
134488
|
+
return failure(
|
|
134489
|
+
"no-workspace",
|
|
134490
|
+
"Files cannot be read for this call (it did not come from a conversation)."
|
|
134491
|
+
);
|
|
134492
|
+
}
|
|
134493
|
+
const limits = adapter.outboundLimits;
|
|
134494
|
+
let size;
|
|
134495
|
+
try {
|
|
134496
|
+
const info = await workspace.stat(path);
|
|
134497
|
+
if (!info.isFile) {
|
|
134498
|
+
return failure("not-a-file", `"${path}" is not a regular file (a folder cannot be sent).`);
|
|
134499
|
+
}
|
|
134500
|
+
size = info.size;
|
|
134501
|
+
} catch (error) {
|
|
134502
|
+
return readFailure(error, path);
|
|
134503
|
+
}
|
|
134504
|
+
const minBytes = limits.minBytes ?? 1;
|
|
134505
|
+
if (size < minBytes) {
|
|
134506
|
+
return failure(
|
|
134507
|
+
"empty",
|
|
134508
|
+
size === 0 ? `"${path}" is empty, and an empty file cannot be sent.` : `"${path}" is only ${String(size)} bytes, and ${adapter.label} cannot send a file smaller than ${String(minBytes)} bytes.`
|
|
134509
|
+
);
|
|
134510
|
+
}
|
|
134511
|
+
const tooLarge = (bytes2) => failure(
|
|
134512
|
+
"too-large",
|
|
134513
|
+
`"${path}" is ${formatSize(bytes2)}, over the ${formatMegabytes(limits.fileBytes)} limit for ${adapter.label}. Compress or split it and try again.`
|
|
134514
|
+
);
|
|
134515
|
+
if (size > limits.fileBytes) return tooLarge(size);
|
|
134516
|
+
let bytes;
|
|
134517
|
+
try {
|
|
134518
|
+
bytes = (await workspace.readFile(path, { maxBytes: limits.fileBytes })).bytes;
|
|
134519
|
+
} catch (error) {
|
|
134520
|
+
return reasonOf(error) === "too-large" ? tooLarge(size) : readFailure(error, path);
|
|
134521
|
+
}
|
|
134522
|
+
if (bytes.byteLength === 0)
|
|
134523
|
+
return failure("empty", `"${path}" is empty, and an empty file cannot be sent.`);
|
|
134524
|
+
if (bytes.byteLength > limits.fileBytes) return tooLarge(bytes.byteLength);
|
|
134525
|
+
const name = sanitizeAttachmentName(baseName(path), "file");
|
|
134526
|
+
const mimeType = resolveMime(mimeFromName(name), bytes);
|
|
134527
|
+
const asImage = isInlineImageMime(mimeType) && (adapter.imageMimeTypes?.has(mimeType) ?? true) && bytes.byteLength <= limits.imageBytes && adapter.sendImage !== void 0;
|
|
134528
|
+
const caption = typeof input["description"] === "string" && input["description"].trim() !== "" ? input["description"].trim() : deps.translate("reply.sendingFile", { name });
|
|
134529
|
+
try {
|
|
134530
|
+
await adapter.sendText(binding, caption);
|
|
134531
|
+
} catch (error) {
|
|
134532
|
+
deps.log.warn("chat could not send the caption of a file", { error: describeError(error) });
|
|
134533
|
+
}
|
|
134534
|
+
try {
|
|
134535
|
+
const file = { bytes, name, mimeType };
|
|
134536
|
+
if (asImage && adapter.sendImage !== void 0) await adapter.sendImage(binding, file);
|
|
134537
|
+
else await adapter.sendFile(binding, file);
|
|
134538
|
+
} catch (error) {
|
|
134539
|
+
deps.log.warn("chat could not deliver a file", { name, error: describeError(error) });
|
|
134540
|
+
return failure(
|
|
134541
|
+
"upload-failed",
|
|
134542
|
+
`${adapter.label} did not accept "${name}": ${describeError(error)}. The caption line may already be in the chat; nothing else was sent.`
|
|
134543
|
+
);
|
|
134544
|
+
}
|
|
134545
|
+
deps.log.info("chat delivered a file from the agent", {
|
|
134546
|
+
name,
|
|
134547
|
+
sizeBytes: bytes.byteLength,
|
|
134548
|
+
asImage
|
|
134549
|
+
});
|
|
134550
|
+
return {
|
|
134551
|
+
text: `Sent "${name}" (${formatSize(bytes.byteLength)}) to ${adapter.label}.`
|
|
134552
|
+
};
|
|
134553
|
+
}
|
|
134554
|
+
});
|
|
134555
|
+
|
|
134058
134556
|
// src/queueCards.ts
|
|
134059
134557
|
var QUEUE_PREVIEW_MAX_CHARS = 200;
|
|
134060
134558
|
var WAITING_STATUSES = /* @__PURE__ */ new Set(["queued", "blocked"]);
|
|
@@ -134255,7 +134753,7 @@ var makeQueueFlow = (host, deps) => {
|
|
|
134255
134753
|
host.outbound(record.chatKey, () => redraw(record.intentId, record.intentId === released));
|
|
134256
134754
|
}
|
|
134257
134755
|
};
|
|
134258
|
-
const run = async (record, work,
|
|
134756
|
+
const run = async (record, work, failure2) => {
|
|
134259
134757
|
notes.delete(record.intentId);
|
|
134260
134758
|
let ok = false;
|
|
134261
134759
|
try {
|
|
@@ -134263,7 +134761,7 @@ var makeQueueFlow = (host, deps) => {
|
|
|
134263
134761
|
} catch (error) {
|
|
134264
134762
|
host.warn("chat could not change the queue", error);
|
|
134265
134763
|
}
|
|
134266
|
-
if (!ok) notes.set(record.intentId,
|
|
134764
|
+
if (!ok) notes.set(record.intentId, failure2);
|
|
134267
134765
|
host.outbound(record.chatKey, () => redraw(record.intentId, false));
|
|
134268
134766
|
};
|
|
134269
134767
|
const steer = async (record) => {
|
|
@@ -134351,28 +134849,19 @@ var TelegramApiError = class extends Error {
|
|
|
134351
134849
|
var isNotModified = (error) => error instanceof TelegramApiError && error.code === 400 && error.message.toLowerCase().includes("message is not modified");
|
|
134352
134850
|
var PHOTO_NAME = "photo.jpg";
|
|
134353
134851
|
var PHOTO_MIME = "image/jpeg";
|
|
134354
|
-
var
|
|
134355
|
-
|
|
134356
|
-
const known = {
|
|
134357
|
-
png: "image/png",
|
|
134358
|
-
jpg: "image/jpeg",
|
|
134359
|
-
jpeg: "image/jpeg",
|
|
134360
|
-
gif: "image/gif",
|
|
134361
|
-
webp: "image/webp",
|
|
134362
|
-
pdf: "application/pdf",
|
|
134363
|
-
txt: "text/plain",
|
|
134364
|
-
md: "text/markdown",
|
|
134365
|
-
json: "application/json",
|
|
134366
|
-
csv: "text/csv"
|
|
134367
|
-
};
|
|
134368
|
-
return known[ext] ?? "application/octet-stream";
|
|
134369
|
-
};
|
|
134370
|
-
var resourceOf = (message) => {
|
|
134852
|
+
var withSize = (size) => size === void 0 ? {} : { sizeBytes: size };
|
|
134853
|
+
var resourceOf2 = (message) => {
|
|
134371
134854
|
const photo = message.photo;
|
|
134372
134855
|
if (photo !== void 0 && photo.length > 0) {
|
|
134373
134856
|
const largest = photo[photo.length - 1];
|
|
134374
134857
|
if (largest !== void 0) {
|
|
134375
|
-
return {
|
|
134858
|
+
return {
|
|
134859
|
+
fileId: largest.file_id,
|
|
134860
|
+
name: PHOTO_NAME,
|
|
134861
|
+
mimeType: PHOTO_MIME,
|
|
134862
|
+
kind: "image",
|
|
134863
|
+
...withSize(largest.file_size)
|
|
134864
|
+
};
|
|
134376
134865
|
}
|
|
134377
134866
|
}
|
|
134378
134867
|
const document2 = message.document;
|
|
@@ -134381,12 +134870,38 @@ var resourceOf = (message) => {
|
|
|
134381
134870
|
return {
|
|
134382
134871
|
fileId: document2.file_id,
|
|
134383
134872
|
name,
|
|
134384
|
-
mimeType: document2.mime_type ??
|
|
134385
|
-
kind: "file"
|
|
134873
|
+
mimeType: document2.mime_type ?? mimeFromName(name),
|
|
134874
|
+
kind: "file",
|
|
134875
|
+
...withSize(document2.file_size)
|
|
134386
134876
|
};
|
|
134387
134877
|
}
|
|
134878
|
+
const media = [
|
|
134879
|
+
[message.voice, "voice.ogg", "audio/ogg"],
|
|
134880
|
+
[message.audio, "audio", "application/octet-stream"],
|
|
134881
|
+
[message.video, "video.mp4", "video/mp4"],
|
|
134882
|
+
[message.video_note, "video-note.mp4", "video/mp4"],
|
|
134883
|
+
[message.animation, "animation.mp4", "video/mp4"]
|
|
134884
|
+
];
|
|
134885
|
+
for (const [entry, fallbackName, fallbackMime] of media) {
|
|
134886
|
+
if (entry === void 0) continue;
|
|
134887
|
+
const name = entry.file_name ?? fallbackName;
|
|
134888
|
+
return {
|
|
134889
|
+
fileId: entry.file_id,
|
|
134890
|
+
name,
|
|
134891
|
+
mimeType: entry.mime_type ?? (entry.file_name === void 0 ? fallbackMime : mimeFromName(name)),
|
|
134892
|
+
kind: "file",
|
|
134893
|
+
...withSize(entry.file_size)
|
|
134894
|
+
};
|
|
134895
|
+
}
|
|
134896
|
+
return null;
|
|
134897
|
+
};
|
|
134898
|
+
var unsupportedTypeOf = (message) => {
|
|
134899
|
+
for (const type of ["sticker", "location", "venue", "contact", "poll", "dice"]) {
|
|
134900
|
+
if (message[type] !== void 0) return type;
|
|
134901
|
+
}
|
|
134388
134902
|
return null;
|
|
134389
134903
|
};
|
|
134904
|
+
var TELEGRAM_DOWNLOAD_LIMIT_BYTES = 20 * 1024 * 1024;
|
|
134390
134905
|
var splitForTelegram = (text2, limit = TELEGRAM_TEXT_MAX_CHARS) => splitMessage(text2, limit);
|
|
134391
134906
|
var nextOffset = (updates, current) => updates.reduce((offset, update) => Math.max(offset, update.update_id + 1), current);
|
|
134392
134907
|
var commandForBot = (text2, botUsername) => {
|
|
@@ -134416,6 +134931,24 @@ var makeTelegramApi = (input) => {
|
|
|
134416
134931
|
}
|
|
134417
134932
|
return envelope.result;
|
|
134418
134933
|
};
|
|
134934
|
+
const upload = async (method, field, chatId, file) => {
|
|
134935
|
+
const form = new FormData();
|
|
134936
|
+
form.set("chat_id", chatId);
|
|
134937
|
+
form.set(field, new Blob([new Uint8Array(file.bytes)], { type: file.mimeType }), file.name);
|
|
134938
|
+
const response = await input.http(`${API_BASE}/bot${input.token}/${method}`, {
|
|
134939
|
+
method: "POST",
|
|
134940
|
+
body: form
|
|
134941
|
+
});
|
|
134942
|
+
const envelope = await response.json();
|
|
134943
|
+
if (envelope.ok !== true) {
|
|
134944
|
+
throw new TelegramApiError(
|
|
134945
|
+
method,
|
|
134946
|
+
envelope.error_code,
|
|
134947
|
+
envelope.description ?? "no reason",
|
|
134948
|
+
envelope.parameters?.retry_after
|
|
134949
|
+
);
|
|
134950
|
+
}
|
|
134951
|
+
};
|
|
134419
134952
|
const keyboard = (rows) => ({
|
|
134420
134953
|
inline_keyboard: rows.map(
|
|
134421
134954
|
(row) => row.map((button) => ({ text: button.text, callback_data: button.data }))
|
|
@@ -134484,37 +135017,26 @@ var makeTelegramApi = (input) => {
|
|
|
134484
135017
|
callback_query_id: callbackQueryId,
|
|
134485
135018
|
...text2 === void 0 ? {} : { text: text2 }
|
|
134486
135019
|
}),
|
|
134487
|
-
sendPhoto:
|
|
134488
|
-
|
|
134489
|
-
|
|
134490
|
-
form.set(
|
|
134491
|
-
"photo",
|
|
134492
|
-
new Blob([new Uint8Array(photo.bytes)], { type: photo.mimeType }),
|
|
134493
|
-
photo.name
|
|
134494
|
-
);
|
|
134495
|
-
const response = await input.http(`${API_BASE}/bot${input.token}/sendPhoto`, {
|
|
134496
|
-
method: "POST",
|
|
134497
|
-
body: form
|
|
134498
|
-
});
|
|
134499
|
-
const envelope = await response.json();
|
|
134500
|
-
if (envelope.ok !== true) {
|
|
134501
|
-
throw new TelegramApiError(
|
|
134502
|
-
"sendPhoto",
|
|
134503
|
-
envelope.error_code,
|
|
134504
|
-
envelope.description ?? "no reason"
|
|
134505
|
-
);
|
|
134506
|
-
}
|
|
134507
|
-
},
|
|
135020
|
+
sendPhoto: (chatId, photo) => upload("sendPhoto", "photo", chatId, photo),
|
|
135021
|
+
/** 以文件形式发(不压缩、不限尺寸比例):什么类型都行,音频与视频也走这条。 */
|
|
135022
|
+
sendDocument: (chatId, file) => upload("sendDocument", "document", chatId, file),
|
|
134508
135023
|
/** 取一个文件的字节。要两步:先换路径,再按路径下载。 */
|
|
134509
|
-
fetchFile: async (fileId) => {
|
|
134510
|
-
const file = await call("getFile", {
|
|
135024
|
+
fetchFile: async (fileId, maxBytes) => {
|
|
135025
|
+
const file = await call("getFile", {
|
|
135026
|
+
file_id: fileId
|
|
135027
|
+
}).catch((error) => {
|
|
135028
|
+
if (error instanceof TelegramApiError && error.code === 400 && error.message.toLowerCase().includes("too big")) {
|
|
135029
|
+
throw new ImResourceTooLargeError(TELEGRAM_DOWNLOAD_LIMIT_BYTES);
|
|
135030
|
+
}
|
|
135031
|
+
throw error;
|
|
135032
|
+
});
|
|
134511
135033
|
const path = file.file_path;
|
|
134512
135034
|
if (path === void 0) throw new TelegramApiError("getFile", void 0, "no file_path");
|
|
134513
135035
|
const response = await input.http(`${API_BASE}/file/bot${input.token}/${path}`);
|
|
134514
135036
|
if (!response.ok) {
|
|
134515
135037
|
throw new TelegramApiError("file", response.status, "download failed");
|
|
134516
135038
|
}
|
|
134517
|
-
return
|
|
135039
|
+
return readBodyLimited(response, maxBytes);
|
|
134518
135040
|
}
|
|
134519
135041
|
};
|
|
134520
135042
|
};
|
|
@@ -134643,14 +135165,14 @@ var tokenOf = (binding) => binding.values["telegramToken"] ?? "";
|
|
|
134643
135165
|
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
134644
135166
|
var makeTelegramAdapter = (deps) => {
|
|
134645
135167
|
const apis = /* @__PURE__ */ new Map();
|
|
134646
|
-
const
|
|
135168
|
+
const sleep2 = deps.sleep ?? defaultSleep;
|
|
134647
135169
|
const t = deps.translate;
|
|
134648
135170
|
const withFloodWait = async (run) => {
|
|
134649
135171
|
try {
|
|
134650
135172
|
return await run();
|
|
134651
135173
|
} catch (error) {
|
|
134652
135174
|
if (error instanceof TelegramApiError && error.code === 429 && error.retryAfterSeconds !== void 0 && error.retryAfterSeconds <= FLOOD_WAIT_MAX_SECONDS) {
|
|
134653
|
-
await
|
|
135175
|
+
await sleep2(error.retryAfterSeconds * 1e3);
|
|
134654
135176
|
return run();
|
|
134655
135177
|
}
|
|
134656
135178
|
throw error;
|
|
@@ -134740,23 +135262,39 @@ var makeTelegramAdapter = (deps) => {
|
|
|
134740
135262
|
}
|
|
134741
135263
|
const message = update.message;
|
|
134742
135264
|
if (message === void 0) continue;
|
|
134743
|
-
const resource =
|
|
135265
|
+
const resource = resourceOf2(message);
|
|
134744
135266
|
const text2 = commandForBot(
|
|
134745
135267
|
(message.text ?? message.caption ?? "").trim(),
|
|
134746
135268
|
botUsername
|
|
134747
135269
|
);
|
|
134748
|
-
if (text2 === null
|
|
135270
|
+
if (text2 === null) continue;
|
|
135271
|
+
if (text2 === "" && resource === null) {
|
|
135272
|
+
const unsupported = unsupportedTypeOf(message);
|
|
135273
|
+
if (unsupported === null) continue;
|
|
135274
|
+
handlers.onMessage({
|
|
135275
|
+
messageId: String(message.message_id),
|
|
135276
|
+
chatId: String(message.chat.id),
|
|
135277
|
+
senderId: message.from === void 0 ? null : String(message.from.id),
|
|
135278
|
+
text: "",
|
|
135279
|
+
resources: [],
|
|
135280
|
+
unsupportedType: unsupported
|
|
135281
|
+
});
|
|
135282
|
+
continue;
|
|
135283
|
+
}
|
|
134749
135284
|
handlers.onMessage({
|
|
134750
135285
|
messageId: String(message.message_id),
|
|
134751
135286
|
chatId: String(message.chat.id),
|
|
134752
135287
|
senderId: message.from === void 0 ? null : String(message.from.id),
|
|
134753
135288
|
text: text2,
|
|
134754
|
-
|
|
134755
|
-
|
|
134756
|
-
|
|
134757
|
-
|
|
134758
|
-
|
|
134759
|
-
|
|
135289
|
+
resources: resource === null ? [] : [
|
|
135290
|
+
{
|
|
135291
|
+
key: resource.fileId,
|
|
135292
|
+
name: resource.name,
|
|
135293
|
+
mimeType: resource.mimeType,
|
|
135294
|
+
kind: resource.kind,
|
|
135295
|
+
...resource.sizeBytes === void 0 ? {} : { sizeBytes: resource.sizeBytes }
|
|
135296
|
+
}
|
|
135297
|
+
]
|
|
134760
135298
|
});
|
|
134761
135299
|
}
|
|
134762
135300
|
} catch (error) {
|
|
@@ -134783,8 +135321,23 @@ var makeTelegramAdapter = (deps) => {
|
|
|
134783
135321
|
},
|
|
134784
135322
|
sendImage: async (binding, image2) => {
|
|
134785
135323
|
const api = await apiFor(binding);
|
|
134786
|
-
|
|
135324
|
+
try {
|
|
135325
|
+
await api.sendPhoto(binding.chatId, image2);
|
|
135326
|
+
} catch (error) {
|
|
135327
|
+
if (!(error instanceof TelegramApiError && error.code === 400)) throw error;
|
|
135328
|
+
deps.log.info("telegram could not send the picture as a photo, sending it as a file", {
|
|
135329
|
+
name: image2.name,
|
|
135330
|
+
reason: error.message
|
|
135331
|
+
});
|
|
135332
|
+
await api.sendDocument(binding.chatId, image2);
|
|
135333
|
+
}
|
|
135334
|
+
},
|
|
135335
|
+
sendFile: async (binding, file) => {
|
|
135336
|
+
const api = await apiFor(binding);
|
|
135337
|
+
await api.sendDocument(binding.chatId, file);
|
|
134787
135338
|
},
|
|
135339
|
+
// 单张照片 10 MB、单个文件 50 MB。这两个数来自 Bot API 文档的记忆,本仓库里没有核对过。
|
|
135340
|
+
outboundLimits: { imageBytes: 10 * 1024 * 1024, fileBytes: 50 * 1024 * 1024 },
|
|
134788
135341
|
askApproval: async (binding, request) => {
|
|
134789
135342
|
const api = await apiFor(binding);
|
|
134790
135343
|
return api.sendButtons(binding.chatId, request.body, request.options);
|
|
@@ -134868,9 +135421,10 @@ ${line}`
|
|
|
134868
135421
|
);
|
|
134869
135422
|
}
|
|
134870
135423
|
},
|
|
135424
|
+
downloadLimitBytes: TELEGRAM_DOWNLOAD_LIMIT_BYTES,
|
|
134871
135425
|
fetchResource: async (binding, input) => {
|
|
134872
135426
|
const api = await apiFor(binding);
|
|
134873
|
-
return api.fetchFile(input.resource.key);
|
|
135427
|
+
return api.fetchFile(input.resource.key, input.maxBytes);
|
|
134874
135428
|
}
|
|
134875
135429
|
};
|
|
134876
135430
|
};
|
|
@@ -135099,34 +135653,177 @@ var makeTurnCards = (deps) => {
|
|
|
135099
135653
|
};
|
|
135100
135654
|
};
|
|
135101
135655
|
|
|
135656
|
+
// src/wecomUpload.ts
|
|
135657
|
+
import * as NodeCrypto from "node:crypto";
|
|
135658
|
+
var WECOM_CHUNK_BYTES = 512 * 1024;
|
|
135659
|
+
var WECOM_MAX_CHUNKS = 100;
|
|
135660
|
+
var WECOM_MIN_UPLOAD_BYTES = 5;
|
|
135661
|
+
var FILENAME_MAX_BYTES = 256;
|
|
135662
|
+
var CHUNK_RETRY_BACKOFF_MS = [500, 1e3];
|
|
135663
|
+
var utf82 = new TextEncoder();
|
|
135664
|
+
var concurrencyFor = (chunks) => chunks <= 4 ? chunks : chunks <= 10 ? 3 : 2;
|
|
135665
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
135666
|
+
var clampName = (name) => {
|
|
135667
|
+
if (utf82.encode(name).byteLength <= FILENAME_MAX_BYTES) return name;
|
|
135668
|
+
const chars = [...name];
|
|
135669
|
+
while (chars.length > 1 && utf82.encode(chars.join("")).byteLength > FILENAME_MAX_BYTES) {
|
|
135670
|
+
chars.pop();
|
|
135671
|
+
}
|
|
135672
|
+
return chars.join("");
|
|
135673
|
+
};
|
|
135674
|
+
var uploadWecomMedia = async (input) => {
|
|
135675
|
+
const { bytes } = input;
|
|
135676
|
+
if (bytes.byteLength < WECOM_MIN_UPLOAD_BYTES) {
|
|
135677
|
+
throw new Error(`WeCom needs a file of at least ${String(WECOM_MIN_UPLOAD_BYTES)} bytes`);
|
|
135678
|
+
}
|
|
135679
|
+
const totalChunks = Math.ceil(bytes.byteLength / WECOM_CHUNK_BYTES);
|
|
135680
|
+
if (totalChunks > WECOM_MAX_CHUNKS) {
|
|
135681
|
+
throw new Error(
|
|
135682
|
+
`the file needs ${String(totalChunks)} chunks, WeCom allows ${String(WECOM_MAX_CHUNKS)}`
|
|
135683
|
+
);
|
|
135684
|
+
}
|
|
135685
|
+
const init = await input.request("aibot_upload_media_init", {
|
|
135686
|
+
type: input.type,
|
|
135687
|
+
filename: clampName(input.filename),
|
|
135688
|
+
total_size: bytes.byteLength,
|
|
135689
|
+
total_chunks: totalChunks,
|
|
135690
|
+
md5: NodeCrypto.createHash("md5").update(bytes).digest("hex")
|
|
135691
|
+
});
|
|
135692
|
+
const uploadId = init.upload_id;
|
|
135693
|
+
if (typeof uploadId !== "string" || uploadId === "") {
|
|
135694
|
+
throw new Error("WeCom upload init returned no upload_id");
|
|
135695
|
+
}
|
|
135696
|
+
const sendChunk = async (index) => {
|
|
135697
|
+
const part = bytes.subarray(index * WECOM_CHUNK_BYTES, (index + 1) * WECOM_CHUNK_BYTES);
|
|
135698
|
+
const body = {
|
|
135699
|
+
upload_id: uploadId,
|
|
135700
|
+
chunk_index: index,
|
|
135701
|
+
base64_data: Buffer.from(part).toString("base64")
|
|
135702
|
+
};
|
|
135703
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
135704
|
+
try {
|
|
135705
|
+
await input.request("aibot_upload_media_chunk", body);
|
|
135706
|
+
return;
|
|
135707
|
+
} catch (error) {
|
|
135708
|
+
const backoff = CHUNK_RETRY_BACKOFF_MS[attempt];
|
|
135709
|
+
if (backoff === void 0) throw error;
|
|
135710
|
+
await sleep(backoff);
|
|
135711
|
+
}
|
|
135712
|
+
}
|
|
135713
|
+
};
|
|
135714
|
+
let next = 0;
|
|
135715
|
+
let failed = null;
|
|
135716
|
+
const worker = async () => {
|
|
135717
|
+
while (failed === null) {
|
|
135718
|
+
const index = next;
|
|
135719
|
+
next += 1;
|
|
135720
|
+
if (index >= totalChunks) return;
|
|
135721
|
+
try {
|
|
135722
|
+
await sendChunk(index);
|
|
135723
|
+
} catch (error) {
|
|
135724
|
+
failed ??= error;
|
|
135725
|
+
return;
|
|
135726
|
+
}
|
|
135727
|
+
}
|
|
135728
|
+
};
|
|
135729
|
+
await Promise.all(Array.from({ length: concurrencyFor(totalChunks) }, () => worker()));
|
|
135730
|
+
if (failed !== null) throw failed;
|
|
135731
|
+
const finish = await input.request("aibot_upload_media_finish", { upload_id: uploadId });
|
|
135732
|
+
const mediaId = finish.media_id;
|
|
135733
|
+
if (typeof mediaId !== "string" || mediaId === "") {
|
|
135734
|
+
throw new Error("WeCom upload finish returned no media_id");
|
|
135735
|
+
}
|
|
135736
|
+
return mediaId;
|
|
135737
|
+
};
|
|
135738
|
+
|
|
135102
135739
|
// src/wecomApi.ts
|
|
135103
135740
|
var DEFAULT_WECOM_ENDPOINT = "wss://openws.work.weixin.qq.com/wssvr/";
|
|
135104
135741
|
var PING_INTERVAL_MS = 3e4;
|
|
135105
135742
|
var MAX_MISSED_PONGS = 2;
|
|
135106
135743
|
var SUBSCRIBE_TIMEOUT_MS = 1e4;
|
|
135107
135744
|
var SEND_ACK_TIMEOUT_MS = 5e3;
|
|
135745
|
+
var UPLOAD_ACK_TIMEOUT_MS = 15e3;
|
|
135108
135746
|
var WECOM_TEXT_MAX_CHARS = 4e3;
|
|
135109
135747
|
var normalizeWecomEndpoint = (endpoint) => `${endpoint.replace(/\/+$/, "")}/`;
|
|
135748
|
+
var mediaResource = (payload, defaults) => typeof payload?.url === "string" && payload.url !== "" ? {
|
|
135749
|
+
url: payload.url,
|
|
135750
|
+
aesKey: typeof payload.aeskey === "string" && payload.aeskey !== "" ? payload.aeskey : void 0,
|
|
135751
|
+
...defaults
|
|
135752
|
+
} : null;
|
|
135110
135753
|
var parseWecomMessage = (frame, botId) => {
|
|
135111
135754
|
const body = frame.body;
|
|
135112
135755
|
if (body === void 0) return null;
|
|
135113
135756
|
const messageId = body.msgid;
|
|
135114
135757
|
const chatId = body.chatid ?? body.from?.userid;
|
|
135115
135758
|
if (typeof messageId !== "string" || typeof chatId !== "string") return null;
|
|
135116
|
-
if (body.msgtype !== "text") return null;
|
|
135117
|
-
const raw = body.text?.content;
|
|
135118
|
-
if (typeof raw !== "string") return null;
|
|
135119
135759
|
const ownIds = [...new Set([body.aibotid, botId].filter((id) => Boolean(id)))];
|
|
135760
|
+
const stripOwn = (value) => ownIds.reduce((rest, id) => rest.replaceAll(`@${id}`, ""), value);
|
|
135761
|
+
let mentionSource = "";
|
|
135762
|
+
let text2 = "";
|
|
135763
|
+
const resources = [];
|
|
135764
|
+
switch (body.msgtype) {
|
|
135765
|
+
case "text": {
|
|
135766
|
+
const raw = body.text?.content;
|
|
135767
|
+
if (typeof raw !== "string") return null;
|
|
135768
|
+
mentionSource = raw;
|
|
135769
|
+
text2 = stripOwn(raw).trim();
|
|
135770
|
+
break;
|
|
135771
|
+
}
|
|
135772
|
+
case "voice": {
|
|
135773
|
+
const transcript = body.voice?.content;
|
|
135774
|
+
if (typeof transcript !== "string" || transcript.trim() === "") return null;
|
|
135775
|
+
mentionSource = transcript;
|
|
135776
|
+
text2 = stripOwn(transcript).trim();
|
|
135777
|
+
break;
|
|
135778
|
+
}
|
|
135779
|
+
case "image":
|
|
135780
|
+
case "file":
|
|
135781
|
+
case "video": {
|
|
135782
|
+
const resource = body.msgtype === "image" ? mediaResource(body.image, { name: "image.png", mimeType: "image/png", kind: "image" }) : body.msgtype === "file" ? mediaResource(body.file, {
|
|
135783
|
+
name: "file",
|
|
135784
|
+
mimeType: "application/octet-stream",
|
|
135785
|
+
kind: "file"
|
|
135786
|
+
}) : mediaResource(body.video, { name: "video.mp4", mimeType: "video/mp4", kind: "file" });
|
|
135787
|
+
if (resource === null) return null;
|
|
135788
|
+
resources.push(resource);
|
|
135789
|
+
break;
|
|
135790
|
+
}
|
|
135791
|
+
case "mixed": {
|
|
135792
|
+
const parts = [];
|
|
135793
|
+
for (const item of body.mixed?.msg_item ?? []) {
|
|
135794
|
+
if (item.msgtype === "text" && typeof item.text?.content === "string") {
|
|
135795
|
+
mentionSource += item.text.content;
|
|
135796
|
+
const part = stripOwn(item.text.content).trim();
|
|
135797
|
+
if (part !== "") parts.push(part);
|
|
135798
|
+
} else if (item.msgtype === "image") {
|
|
135799
|
+
const resource = mediaResource(item.image, {
|
|
135800
|
+
name: `image-${String(resources.length + 1)}.png`,
|
|
135801
|
+
mimeType: "image/png",
|
|
135802
|
+
kind: "image"
|
|
135803
|
+
});
|
|
135804
|
+
if (resource === null) continue;
|
|
135805
|
+
resources.push(resource);
|
|
135806
|
+
parts.push(`<image ${String(resources.length)}>`);
|
|
135807
|
+
}
|
|
135808
|
+
}
|
|
135809
|
+
if (parts.length === 0) return null;
|
|
135810
|
+
text2 = parts.join("\n");
|
|
135811
|
+
break;
|
|
135812
|
+
}
|
|
135813
|
+
default:
|
|
135814
|
+
return null;
|
|
135815
|
+
}
|
|
135816
|
+
const mentioned = ownIds.some((id) => mentionSource.includes(`@${id}`));
|
|
135120
135817
|
const chatType = body.chattype ?? "single";
|
|
135121
|
-
const mentioned = ownIds.some((id) => raw.includes(`@${id}`));
|
|
135122
|
-
const text2 = ownIds.reduce((value, id) => value.replaceAll(`@${id}`, ""), raw).trim();
|
|
135123
135818
|
return {
|
|
135124
135819
|
messageId,
|
|
135125
135820
|
chatId,
|
|
135126
135821
|
senderUserId: body.from?.userid ?? null,
|
|
135127
135822
|
text: text2,
|
|
135823
|
+
resources,
|
|
135128
135824
|
requestId: frame.headers?.req_id ?? null,
|
|
135129
|
-
chatType: mentioned ? "single" : chatType
|
|
135825
|
+
chatType: mentioned ? "single" : chatType,
|
|
135826
|
+
conversation: chatType === "group" ? "group" : "single"
|
|
135130
135827
|
};
|
|
135131
135828
|
};
|
|
135132
135829
|
var splitForWecom = (text2, limit = WECOM_TEXT_MAX_CHARS) => splitMessage(text2, limit);
|
|
@@ -135135,6 +135832,7 @@ var runWecomClient = (input) => {
|
|
|
135135
135832
|
let sequence = 0;
|
|
135136
135833
|
const nextRequestId = (kind) => `${kind}_${++sequence}`;
|
|
135137
135834
|
const pendingAcks = /* @__PURE__ */ new Map();
|
|
135835
|
+
const conversations = /* @__PURE__ */ new Map();
|
|
135138
135836
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
135139
135837
|
const openStreams = /* @__PURE__ */ new Map();
|
|
135140
135838
|
const subscribeId = nextRequestId("aibot_subscribe");
|
|
@@ -135143,11 +135841,11 @@ var runWecomClient = (input) => {
|
|
|
135143
135841
|
let finished = false;
|
|
135144
135842
|
const ended = Promise.withResolvers();
|
|
135145
135843
|
const send = (frame) => input.socket.send(JSON.stringify(frame));
|
|
135146
|
-
const settleAck = (requestId, error) => {
|
|
135844
|
+
const settleAck = (requestId, error, frame) => {
|
|
135147
135845
|
const pending = pendingAcks.get(requestId);
|
|
135148
135846
|
if (pending === void 0) return;
|
|
135149
135847
|
pendingAcks.delete(requestId);
|
|
135150
|
-
if (error === null) pending.resolve();
|
|
135848
|
+
if (error === null) pending.resolve(frame);
|
|
135151
135849
|
else pending.reject(error);
|
|
135152
135850
|
};
|
|
135153
135851
|
const handleFrame = (raw) => {
|
|
@@ -135168,7 +135866,7 @@ var runWecomClient = (input) => {
|
|
|
135168
135866
|
const error = typeof frame.errcode === "number" && frame.errcode !== 0 ? new Error(`WeCom ack failed: ${String(frame.errcode)} ${frame.errmsg ?? ""}`) : null;
|
|
135169
135867
|
const stream = openStreams.get(requestId);
|
|
135170
135868
|
if (stream !== void 0 && error !== null) stream.failed = true;
|
|
135171
|
-
settleAck(requestId, error);
|
|
135869
|
+
settleAck(requestId, error, frame);
|
|
135172
135870
|
return;
|
|
135173
135871
|
}
|
|
135174
135872
|
if (frame.cmd !== "aibot_msg_callback") return;
|
|
@@ -135210,7 +135908,7 @@ var runWecomClient = (input) => {
|
|
|
135210
135908
|
}, PING_INTERVAL_MS);
|
|
135211
135909
|
void readLoop();
|
|
135212
135910
|
const subscribed = new Promise((resolve, reject) => {
|
|
135213
|
-
pendingAcks.set(subscribeId, { resolve, reject });
|
|
135911
|
+
pendingAcks.set(subscribeId, { resolve: () => resolve(), reject });
|
|
135214
135912
|
setTimeout(() => {
|
|
135215
135913
|
if (pendingAcks.delete(subscribeId)) reject(new Error("ETIMEDOUT: WeCom subscribe ack"));
|
|
135216
135914
|
}, SUBSCRIBE_TIMEOUT_MS);
|
|
@@ -135247,7 +135945,7 @@ var runWecomClient = (input) => {
|
|
|
135247
135945
|
}
|
|
135248
135946
|
const requestId = nextRequestId("aibot_send_msg");
|
|
135249
135947
|
const ack = new Promise((resolve, reject) => {
|
|
135250
|
-
pendingAcks.set(requestId, { resolve, reject });
|
|
135948
|
+
pendingAcks.set(requestId, { resolve: () => resolve(), reject });
|
|
135251
135949
|
setTimeout(() => {
|
|
135252
135950
|
if (pendingAcks.delete(requestId)) resolve();
|
|
135253
135951
|
}, SEND_ACK_TIMEOUT_MS);
|
|
@@ -135267,6 +135965,50 @@ var runWecomClient = (input) => {
|
|
|
135267
135965
|
}
|
|
135268
135966
|
}
|
|
135269
135967
|
};
|
|
135968
|
+
const request = async (cmd, body) => {
|
|
135969
|
+
const requestId = nextRequestId(cmd);
|
|
135970
|
+
let timer;
|
|
135971
|
+
const ack = new Promise((resolve, reject) => {
|
|
135972
|
+
pendingAcks.set(requestId, { resolve, reject });
|
|
135973
|
+
timer = setTimeout(() => {
|
|
135974
|
+
if (pendingAcks.delete(requestId)) {
|
|
135975
|
+
reject(
|
|
135976
|
+
new Error(
|
|
135977
|
+
`WeCom did not acknowledge ${cmd} within ${String(UPLOAD_ACK_TIMEOUT_MS / 1e3)} s`
|
|
135978
|
+
)
|
|
135979
|
+
);
|
|
135980
|
+
}
|
|
135981
|
+
}, UPLOAD_ACK_TIMEOUT_MS);
|
|
135982
|
+
});
|
|
135983
|
+
try {
|
|
135984
|
+
const [, frame] = await Promise.all([
|
|
135985
|
+
send({ cmd, headers: { req_id: requestId }, body }),
|
|
135986
|
+
ack
|
|
135987
|
+
]);
|
|
135988
|
+
return frame?.body ?? {};
|
|
135989
|
+
} catch (error) {
|
|
135990
|
+
pendingAcks.delete(requestId);
|
|
135991
|
+
throw error;
|
|
135992
|
+
} finally {
|
|
135993
|
+
clearTimeout(timer);
|
|
135994
|
+
}
|
|
135995
|
+
};
|
|
135996
|
+
const sendMedia = async (chatId, kind, media) => {
|
|
135997
|
+
const mediaId = await uploadWecomMedia({
|
|
135998
|
+
request,
|
|
135999
|
+
type: kind,
|
|
136000
|
+
filename: media.name,
|
|
136001
|
+
bytes: media.bytes
|
|
136002
|
+
});
|
|
136003
|
+
const conversation = conversations.get(chatId);
|
|
136004
|
+
await request("aibot_send_msg", {
|
|
136005
|
+
chatid: chatId,
|
|
136006
|
+
// 不知道(重启后还没收到过这个会话的消息)就不填:发文字也是只给 chatid。
|
|
136007
|
+
...conversation === void 0 ? {} : { chat_type: conversation === "group" ? 2 : 1 },
|
|
136008
|
+
msgtype: kind,
|
|
136009
|
+
[kind]: { media_id: mediaId }
|
|
136010
|
+
});
|
|
136011
|
+
};
|
|
135270
136012
|
const openStream = (chatId) => {
|
|
135271
136013
|
const requestId = pendingRequests.get(chatId);
|
|
135272
136014
|
if (requestId === void 0) return null;
|
|
@@ -135298,6 +136040,10 @@ var runWecomClient = (input) => {
|
|
|
135298
136040
|
};
|
|
135299
136041
|
return {
|
|
135300
136042
|
sendText,
|
|
136043
|
+
sendMedia,
|
|
136044
|
+
rememberConversation: (chatId, conversation) => {
|
|
136045
|
+
conversations.set(chatId, conversation);
|
|
136046
|
+
},
|
|
135301
136047
|
openStream,
|
|
135302
136048
|
rememberRequest: (chatId, requestId) => {
|
|
135303
136049
|
if (requestId !== null) pendingRequests.set(chatId, requestId);
|
|
@@ -135312,6 +136058,83 @@ var runWecomClient = (input) => {
|
|
|
135312
136058
|
};
|
|
135313
136059
|
};
|
|
135314
136060
|
|
|
136061
|
+
// src/wecomMedia.ts
|
|
136062
|
+
import * as NodeCrypto2 from "node:crypto";
|
|
136063
|
+
var PADDING_BLOCK = 32;
|
|
136064
|
+
var KEY_BYTES = 32;
|
|
136065
|
+
var IV_BYTES = 16;
|
|
136066
|
+
var AES_BLOCK = 16;
|
|
136067
|
+
var WecomDecryptError = class extends Error {
|
|
136068
|
+
constructor(reason) {
|
|
136069
|
+
super(`wecom media could not be decrypted: ${reason}`);
|
|
136070
|
+
this.name = "WecomDecryptError";
|
|
136071
|
+
}
|
|
136072
|
+
};
|
|
136073
|
+
var decryptWecomMedia = (cipher, aesKey) => {
|
|
136074
|
+
const key = Buffer.from(aesKey, "base64");
|
|
136075
|
+
if (key.byteLength !== KEY_BYTES) throw new WecomDecryptError("the key is not 32 bytes");
|
|
136076
|
+
if (cipher.byteLength === 0 || cipher.byteLength % AES_BLOCK !== 0) {
|
|
136077
|
+
throw new WecomDecryptError("the ciphertext is not a whole number of blocks");
|
|
136078
|
+
}
|
|
136079
|
+
const decipher = NodeCrypto2.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
|
|
136080
|
+
decipher.setAutoPadding(false);
|
|
136081
|
+
const plain = Buffer.concat([decipher.update(cipher), decipher.final()]);
|
|
136082
|
+
const pad = plain[plain.byteLength - 1] ?? 0;
|
|
136083
|
+
if (pad < 1 || pad > PADDING_BLOCK || pad > plain.byteLength) {
|
|
136084
|
+
throw new WecomDecryptError("the padding length is out of range");
|
|
136085
|
+
}
|
|
136086
|
+
for (let index = plain.byteLength - pad; index < plain.byteLength; index += 1) {
|
|
136087
|
+
if (plain[index] !== pad) throw new WecomDecryptError("the padding bytes are not all equal");
|
|
136088
|
+
}
|
|
136089
|
+
return new Uint8Array(plain.subarray(0, plain.byteLength - pad));
|
|
136090
|
+
};
|
|
136091
|
+
var decodeQuietly = (value) => {
|
|
136092
|
+
try {
|
|
136093
|
+
return decodeURIComponent(value);
|
|
136094
|
+
} catch {
|
|
136095
|
+
return value;
|
|
136096
|
+
}
|
|
136097
|
+
};
|
|
136098
|
+
var filenameFromContentDisposition = (header) => {
|
|
136099
|
+
if (header === null) return void 0;
|
|
136100
|
+
const extended = /filename\*\s*=\s*(?:UTF-8|utf-8)''([^;]+)/i.exec(header);
|
|
136101
|
+
if (extended?.[1] !== void 0) {
|
|
136102
|
+
const name = decodeQuietly(extended[1].trim());
|
|
136103
|
+
if (name !== "") return name;
|
|
136104
|
+
}
|
|
136105
|
+
const quoted = /filename\s*=\s*"([^"]*)"/i.exec(header);
|
|
136106
|
+
const plain = quoted === null ? /filename\s*=\s*([^;]+)/i.exec(header) : null;
|
|
136107
|
+
const raw = quoted?.[1] ?? plain?.[1]?.trim();
|
|
136108
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
136109
|
+
return decodeQuietly(raw);
|
|
136110
|
+
};
|
|
136111
|
+
var fetchWecomMedia = async (input) => {
|
|
136112
|
+
const response = await input.http(input.url);
|
|
136113
|
+
if (!response.ok) {
|
|
136114
|
+
await response.body?.cancel().catch(() => void 0);
|
|
136115
|
+
throw new Error(`wecom media fetch failed: HTTP ${String(response.status)}`);
|
|
136116
|
+
}
|
|
136117
|
+
const name = filenameFromContentDisposition(response.headers.get("content-disposition"));
|
|
136118
|
+
const encrypted = input.aesKey !== void 0 && input.aesKey !== "";
|
|
136119
|
+
let body;
|
|
136120
|
+
try {
|
|
136121
|
+
body = await readBodyLimited(
|
|
136122
|
+
response,
|
|
136123
|
+
encrypted ? input.maxBytes + PADDING_BLOCK : input.maxBytes
|
|
136124
|
+
);
|
|
136125
|
+
} catch (error) {
|
|
136126
|
+
if (error instanceof ImResourceTooLargeError) throw new ImResourceTooLargeError(input.maxBytes);
|
|
136127
|
+
throw error;
|
|
136128
|
+
}
|
|
136129
|
+
if (!encrypted) {
|
|
136130
|
+
input.onUnencrypted?.();
|
|
136131
|
+
return { bytes: body, name };
|
|
136132
|
+
}
|
|
136133
|
+
const bytes = decryptWecomMedia(body, input.aesKey ?? "");
|
|
136134
|
+
if (bytes.byteLength > input.maxBytes) throw new ImResourceTooLargeError(input.maxBytes);
|
|
136135
|
+
return { bytes, name };
|
|
136136
|
+
};
|
|
136137
|
+
|
|
135315
136138
|
// src/wecomAdapter.ts
|
|
135316
136139
|
var WECOM_CARD_THROTTLE_MS = 400;
|
|
135317
136140
|
var LIVE_STREAM_LIMIT = 200;
|
|
@@ -135365,6 +136188,12 @@ ${kept}`;
|
|
|
135365
136188
|
await entry.client.sendText(entry.chatId, chunk);
|
|
135366
136189
|
}
|
|
135367
136190
|
};
|
|
136191
|
+
const sendMedia = async (binding, kind, media) => {
|
|
136192
|
+
const client = clients.get(binding.rowId);
|
|
136193
|
+
if (client === void 0) throw new Error("WeCom is not connected right now");
|
|
136194
|
+
await client.sendMedia(binding.chatId, kind, media);
|
|
136195
|
+
deps.log.info("wecom delivered a file", { name: media.name, as: kind });
|
|
136196
|
+
};
|
|
135368
136197
|
return {
|
|
135369
136198
|
platform: "wecom",
|
|
135370
136199
|
icon: "wecom",
|
|
@@ -135398,13 +136227,19 @@ ${kept}`;
|
|
|
135398
136227
|
onMessage: (message) => {
|
|
135399
136228
|
if (message.chatType !== "single") return;
|
|
135400
136229
|
client.rememberRequest(message.chatId, message.requestId);
|
|
136230
|
+
client.rememberConversation(message.chatId, message.conversation);
|
|
135401
136231
|
handlers.onMessage({
|
|
135402
136232
|
messageId: message.messageId,
|
|
135403
136233
|
chatId: message.chatId,
|
|
135404
136234
|
senderId: message.senderUserId,
|
|
135405
136235
|
text: message.text,
|
|
135406
|
-
//
|
|
135407
|
-
|
|
136236
|
+
// 下载地址与密钥打成一个键:核心只管把它原样交回来。
|
|
136237
|
+
resources: message.resources.map((resource) => ({
|
|
136238
|
+
key: JSON.stringify({ url: resource.url, aesKey: resource.aesKey ?? null }),
|
|
136239
|
+
name: resource.name,
|
|
136240
|
+
mimeType: resource.mimeType,
|
|
136241
|
+
kind: resource.kind
|
|
136242
|
+
}))
|
|
135408
136243
|
});
|
|
135409
136244
|
}
|
|
135410
136245
|
}
|
|
@@ -135482,9 +136317,33 @@ ${kept}`;
|
|
|
135482
136317
|
if (view.status !== "working") liveStreams.delete(key);
|
|
135483
136318
|
}
|
|
135484
136319
|
},
|
|
136320
|
+
sendImage: (binding, image2) => sendMedia(binding, "image", image2),
|
|
136321
|
+
sendFile: (binding, file) => sendMedia(binding, "file", file),
|
|
136322
|
+
// 图 10 MB(只认 png / jpg / jpeg / gif)、文件 20 MB,来自企微官方文档「智能机器人长连接」。
|
|
136323
|
+
outboundLimits: { imageBytes: 10 * 1024 * 1024, fileBytes: 20 * 1024 * 1024, minBytes: 5 },
|
|
136324
|
+
imageMimeTypes: /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif"]),
|
|
135485
136325
|
answerByText: (_binding, text2, options) => {
|
|
135486
136326
|
const index = optionIndexFrom(text2, options.length);
|
|
135487
136327
|
return index === null ? null : options[index - 1]?.token ?? null;
|
|
136328
|
+
},
|
|
136329
|
+
fetchResource: async (binding, input) => {
|
|
136330
|
+
const parsed = JSON.parse(input.resource.key);
|
|
136331
|
+
const media = await fetchWecomMedia({
|
|
136332
|
+
http: deps.http,
|
|
136333
|
+
url: parsed.url,
|
|
136334
|
+
aesKey: parsed.aesKey ?? void 0,
|
|
136335
|
+
maxBytes: input.maxBytes,
|
|
136336
|
+
onUnencrypted: () => {
|
|
136337
|
+
deps.log.info("wecom media arrived without a key, used as is", {
|
|
136338
|
+
group: binding.displayName
|
|
136339
|
+
});
|
|
136340
|
+
}
|
|
136341
|
+
});
|
|
136342
|
+
return {
|
|
136343
|
+
bytes: media.bytes,
|
|
136344
|
+
name: media.name,
|
|
136345
|
+
mimeType: media.name === void 0 ? void 0 : mimeFromName(media.name)
|
|
136346
|
+
};
|
|
135488
136347
|
}
|
|
135489
136348
|
};
|
|
135490
136349
|
};
|
|
@@ -135498,6 +136357,7 @@ var TITLE_MAX_CHARS = 72;
|
|
|
135498
136357
|
var DEFAULT_PLATFORM = "lark";
|
|
135499
136358
|
var SESSION_SCAN_LIMIT = 50;
|
|
135500
136359
|
var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
|
|
136360
|
+
var UNSUPPORTED_REPLY_COOLDOWN_MS = 6e4;
|
|
135501
136361
|
var INBOUND_RUNTIME_MODES = /* @__PURE__ */ new Set([
|
|
135502
136362
|
"approval-required",
|
|
135503
136363
|
"auto-accept-edits",
|
|
@@ -135510,7 +136370,7 @@ var readString = (row, key) => {
|
|
|
135510
136370
|
const value = row[key];
|
|
135511
136371
|
return typeof value === "string" ? value.trim() : "";
|
|
135512
136372
|
};
|
|
135513
|
-
var
|
|
136373
|
+
var describeError2 = (error) => error instanceof Error ? error.message : String(error);
|
|
135514
136374
|
var titleFrom = (text2) => text2.replace(/\s+/g, " ").slice(0, TITLE_MAX_CHARS) || "Chat thread";
|
|
135515
136375
|
var imPlugin = definePlugin({
|
|
135516
136376
|
id: "im",
|
|
@@ -135544,6 +136404,7 @@ var imPlugin = definePlugin({
|
|
|
135544
136404
|
makeWecomAdapter({
|
|
135545
136405
|
// 企微不需要厂商 SDK:全程 JSON over WebSocket,走插件面那条出网长连接。
|
|
135546
136406
|
connectSocket: (url) => ctx.socket.connect({ url }),
|
|
136407
|
+
http: ctx.http,
|
|
135547
136408
|
translate: t,
|
|
135548
136409
|
log: {
|
|
135549
136410
|
info: (message, fields) => ctx.log.info(message, fields),
|
|
@@ -135715,7 +136576,7 @@ var imPlugin = definePlugin({
|
|
|
135715
136576
|
} catch (error) {
|
|
135716
136577
|
ctx.log.warn("chat could not deliver an image", {
|
|
135717
136578
|
name: attachment.name,
|
|
135718
|
-
error:
|
|
136579
|
+
error: describeError2(error)
|
|
135719
136580
|
});
|
|
135720
136581
|
}
|
|
135721
136582
|
}
|
|
@@ -135726,7 +136587,7 @@ var imPlugin = definePlugin({
|
|
|
135726
136587
|
try {
|
|
135727
136588
|
await adapterFor(binding.platform)?.sendText(binding, text2);
|
|
135728
136589
|
} catch (error) {
|
|
135729
|
-
ctx.log.warn("chat could not deliver", { error:
|
|
136590
|
+
ctx.log.warn("chat could not deliver", { error: describeError2(error) });
|
|
135730
136591
|
}
|
|
135731
136592
|
};
|
|
135732
136593
|
const projectByThread = /* @__PURE__ */ new Map();
|
|
@@ -135760,7 +136621,7 @@ var imPlugin = definePlugin({
|
|
|
135760
136621
|
acknowledgements.set(threadId, pending);
|
|
135761
136622
|
ctx.log.info("chat acknowledged a message", { threadId, messageId });
|
|
135762
136623
|
} catch (error) {
|
|
135763
|
-
ctx.log.warn("chat could not acknowledge the message", { error:
|
|
136624
|
+
ctx.log.warn("chat could not acknowledge the message", { error: describeError2(error) });
|
|
135764
136625
|
}
|
|
135765
136626
|
};
|
|
135766
136627
|
const clearWorking = async (binding, threadId) => {
|
|
@@ -135773,7 +136634,7 @@ var imPlugin = definePlugin({
|
|
|
135773
136634
|
await adapter.clearAcknowledge(binding, entry.messageId, entry.ackId);
|
|
135774
136635
|
} catch (error) {
|
|
135775
136636
|
ctx.log.warn("chat could not clear the acknowledgement", {
|
|
135776
|
-
error:
|
|
136637
|
+
error: describeError2(error)
|
|
135777
136638
|
});
|
|
135778
136639
|
}
|
|
135779
136640
|
}
|
|
@@ -135860,7 +136721,7 @@ ${final.text}`;
|
|
|
135860
136721
|
state = restored;
|
|
135861
136722
|
},
|
|
135862
136723
|
(error) => {
|
|
135863
|
-
ctx.log.warn("chat could not restore its saved state", { error:
|
|
136724
|
+
ctx.log.warn("chat could not restore its saved state", { error: describeError2(error) });
|
|
135864
136725
|
}
|
|
135865
136726
|
);
|
|
135866
136727
|
let persisting = Promise.resolve();
|
|
@@ -135869,7 +136730,7 @@ ${final.text}`;
|
|
|
135869
136730
|
persisting = persisting.then(() => ctx.events.append({ name, aggregate: { kind: "plugin", id: ctx.id }, data })).then(
|
|
135870
136731
|
() => void 0,
|
|
135871
136732
|
(error) => {
|
|
135872
|
-
ctx.log.warn("chat could not save its state", { name, error:
|
|
136733
|
+
ctx.log.warn("chat could not save its state", { name, error: describeError2(error) });
|
|
135873
136734
|
}
|
|
135874
136735
|
);
|
|
135875
136736
|
};
|
|
@@ -135877,7 +136738,7 @@ ${final.text}`;
|
|
|
135877
136738
|
commit("chat-updated", { chatKey, threadId });
|
|
135878
136739
|
};
|
|
135879
136740
|
const outbound = makeKeyedQueue((error) => {
|
|
135880
|
-
ctx.log.warn("chat could not deliver a card", { error:
|
|
136741
|
+
ctx.log.warn("chat could not deliver a card", { error: describeError2(error) });
|
|
135881
136742
|
});
|
|
135882
136743
|
const settleApprovalCard = async (approval, settlement) => {
|
|
135883
136744
|
if (approval.cardId === void 0) return;
|
|
@@ -135887,7 +136748,7 @@ ${final.text}`;
|
|
|
135887
136748
|
try {
|
|
135888
136749
|
await settle(binding, approval.cardId, settlement);
|
|
135889
136750
|
} catch (error) {
|
|
135890
|
-
ctx.log.warn("chat could not update the approval card", { error:
|
|
136751
|
+
ctx.log.warn("chat could not update the approval card", { error: describeError2(error) });
|
|
135891
136752
|
}
|
|
135892
136753
|
};
|
|
135893
136754
|
const answerApproval = async (approval, decision, label) => {
|
|
@@ -135939,12 +136800,12 @@ ${final.text}`;
|
|
|
135939
136800
|
},
|
|
135940
136801
|
endOf: async (threadId) => {
|
|
135941
136802
|
const stopped = stopRequested.delete(threadId);
|
|
135942
|
-
const
|
|
136803
|
+
const failure2 = failureSummaries.get(threadId);
|
|
135943
136804
|
failureSummaries.delete(threadId);
|
|
135944
136805
|
const thread = await ctx.projections.getThread(threadId).catch(() => null);
|
|
135945
136806
|
const session = thread?.session;
|
|
135946
136807
|
if (session?.status === "error") {
|
|
135947
|
-
return { status: "error", errorText: session.lastError ??
|
|
136808
|
+
return { status: "error", errorText: session.lastError ?? failure2 ?? null };
|
|
135948
136809
|
}
|
|
135949
136810
|
if (stopped || session?.status === "interrupted" || session?.status === "stopped") {
|
|
135950
136811
|
return { status: "interrupted", errorText: null };
|
|
@@ -135964,7 +136825,7 @@ ${final.text}`;
|
|
|
135964
136825
|
return () => clearTimeout(timer);
|
|
135965
136826
|
},
|
|
135966
136827
|
warn: (message, error) => {
|
|
135967
|
-
ctx.log.warn(`chat ${message}`, { error:
|
|
136828
|
+
ctx.log.warn(`chat ${message}`, { error: describeError2(error) });
|
|
135968
136829
|
}
|
|
135969
136830
|
});
|
|
135970
136831
|
ctx.events.on(TURN_PROGRESS_TRIGGER_KIND, (event) => {
|
|
@@ -135990,7 +136851,7 @@ ${final.text}`;
|
|
|
135990
136851
|
);
|
|
135991
136852
|
} catch (error) {
|
|
135992
136853
|
ctx.log.warn("chat could not mark a stale card as interrupted", {
|
|
135993
|
-
error:
|
|
136854
|
+
error: describeError2(error)
|
|
135994
136855
|
});
|
|
135995
136856
|
}
|
|
135996
136857
|
}
|
|
@@ -136011,7 +136872,7 @@ ${final.text}`;
|
|
|
136011
136872
|
const record = state.approvals[offer.requestId];
|
|
136012
136873
|
if (record?.resolved === true) await settleApprovalCard(record, { kind: "elsewhere" });
|
|
136013
136874
|
} catch (error) {
|
|
136014
|
-
ctx.log.warn("chat could not ask for the approval", { error:
|
|
136875
|
+
ctx.log.warn("chat could not ask for the approval", { error: describeError2(error) });
|
|
136015
136876
|
}
|
|
136016
136877
|
};
|
|
136017
136878
|
ctx.events.on("thread.approval-pending", async (event) => {
|
|
@@ -136106,7 +136967,7 @@ ${final.text}`;
|
|
|
136106
136967
|
newId: () => crypto.randomUUID(),
|
|
136107
136968
|
now: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
136108
136969
|
warn: (message, error) => {
|
|
136109
|
-
ctx.log.warn(`chat ${message}`, error === void 0 ? {} : { error:
|
|
136970
|
+
ctx.log.warn(`chat ${message}`, error === void 0 ? {} : { error: describeError2(error) });
|
|
136110
136971
|
}
|
|
136111
136972
|
};
|
|
136112
136973
|
const questionFlow = makeQuestionFlow(flowHost);
|
|
@@ -136143,7 +137004,7 @@ ${final.text}`;
|
|
|
136143
137004
|
await ctx.projections.listPendingUserInputs(threadId)
|
|
136144
137005
|
);
|
|
136145
137006
|
} catch (error) {
|
|
136146
|
-
ctx.log.warn("chat could not read the pending questions", { error:
|
|
137007
|
+
ctx.log.warn("chat could not read the pending questions", { error: describeError2(error) });
|
|
136147
137008
|
}
|
|
136148
137009
|
});
|
|
136149
137010
|
ctx.reactor({
|
|
@@ -136186,12 +137047,13 @@ ${final.text}`;
|
|
|
136186
137047
|
}
|
|
136187
137048
|
void answerApproval(found.approval, found.button.decision, found.button.label).catch(
|
|
136188
137049
|
(error) => {
|
|
136189
|
-
ctx.log.warn("chat could not answer the approval", { error:
|
|
137050
|
+
ctx.log.warn("chat could not answer the approval", { error: describeError2(error) });
|
|
136190
137051
|
}
|
|
136191
137052
|
);
|
|
136192
137053
|
return null;
|
|
136193
137054
|
};
|
|
136194
137055
|
const archivedReplyCooldown = makeCooldown(ARCHIVED_REPLY_COOLDOWN_MS, () => Date.now());
|
|
137056
|
+
const unsupportedReplyCooldown = makeCooldown(UNSUPPORTED_REPLY_COOLDOWN_MS, () => Date.now());
|
|
136195
137057
|
const alreadyHandled = (target, messageId) => {
|
|
136196
137058
|
const key = `${chatKeyOf(target)}:${messageId}`;
|
|
136197
137059
|
if (seen.has(key)) return true;
|
|
@@ -136203,33 +137065,103 @@ ${final.text}`;
|
|
|
136203
137065
|
}
|
|
136204
137066
|
return false;
|
|
136205
137067
|
};
|
|
136206
|
-
const
|
|
136207
|
-
|
|
137068
|
+
const pendingByChat = /* @__PURE__ */ new Map();
|
|
137069
|
+
const pendingFor = (chatKey, threadId) => {
|
|
137070
|
+
const existing = pendingByChat.get(chatKey);
|
|
137071
|
+
if (existing !== void 0 && existing.threadId === threadId) return existing;
|
|
137072
|
+
const fresh = { threadId, attachments: [], notes: [] };
|
|
137073
|
+
pendingByChat.set(chatKey, fresh);
|
|
137074
|
+
return fresh;
|
|
137075
|
+
};
|
|
137076
|
+
const failureForUser = (failure2) => failure2.kind === "tooLarge" ? t("reply.attachmentReason.tooLarge", { limit: formatMegabytes(failure2.limitBytes) }) : t(`reply.attachmentReason.${failure2.kind}`);
|
|
137077
|
+
const failureForAgent = (failure2) => failure2.kind === "tooLarge" ? `it is over the ${formatMegabytes(failure2.limitBytes)} limit` : failure2.kind === "downloadFailed" ? "the download failed" : failure2.kind === "storeFailed" ? "it could not be saved" : "it is empty";
|
|
137078
|
+
const collectResources = async (binding, threadId, messageId, resources, room) => {
|
|
136208
137079
|
const adapter = adapterFor(binding.platform);
|
|
136209
|
-
|
|
136210
|
-
|
|
136211
|
-
|
|
136212
|
-
|
|
136213
|
-
|
|
136214
|
-
|
|
136215
|
-
|
|
136216
|
-
|
|
136217
|
-
name:
|
|
136218
|
-
|
|
136219
|
-
|
|
136220
|
-
|
|
136221
|
-
|
|
136222
|
-
|
|
136223
|
-
|
|
136224
|
-
|
|
136225
|
-
|
|
136226
|
-
|
|
136227
|
-
|
|
136228
|
-
|
|
136229
|
-
|
|
136230
|
-
|
|
136231
|
-
|
|
137080
|
+
const attachments = [];
|
|
137081
|
+
const notes = [];
|
|
137082
|
+
const reject = async (name, failure2) => {
|
|
137083
|
+
notes.push(
|
|
137084
|
+
`[the user also sent "${name}", which could not be taken: ${failureForAgent(failure2)}]`
|
|
137085
|
+
);
|
|
137086
|
+
await deliver(
|
|
137087
|
+
binding,
|
|
137088
|
+
t("reply.attachmentFailed", { name, reason: failureForUser(failure2) })
|
|
137089
|
+
);
|
|
137090
|
+
};
|
|
137091
|
+
for (const resource of resources) {
|
|
137092
|
+
const name = sanitizeAttachmentName(resource.name, "file");
|
|
137093
|
+
if (attachments.length >= room) {
|
|
137094
|
+
notes.push(`[the user also sent "${name}", which was left out: too many attachments]`);
|
|
137095
|
+
await deliver(
|
|
137096
|
+
binding,
|
|
137097
|
+
t("reply.attachmentLimit", { max: MAX_ATTACHMENTS_PER_TURN, name })
|
|
137098
|
+
);
|
|
137099
|
+
continue;
|
|
137100
|
+
}
|
|
137101
|
+
const limitBytes = Math.min(
|
|
137102
|
+
limitForKind(resource.kind),
|
|
137103
|
+
adapter?.downloadLimitBytes ?? Number.POSITIVE_INFINITY
|
|
137104
|
+
);
|
|
137105
|
+
if (resource.sizeBytes !== void 0 && resource.sizeBytes > limitBytes) {
|
|
137106
|
+
await reject(name, { kind: "tooLarge", limitBytes });
|
|
137107
|
+
continue;
|
|
137108
|
+
}
|
|
137109
|
+
if (adapter?.fetchResource === void 0) {
|
|
137110
|
+
await reject(name, { kind: "downloadFailed" });
|
|
137111
|
+
continue;
|
|
137112
|
+
}
|
|
137113
|
+
let fetched;
|
|
137114
|
+
try {
|
|
137115
|
+
fetched = await adapter.fetchResource(binding, {
|
|
137116
|
+
messageId,
|
|
137117
|
+
resource,
|
|
137118
|
+
maxBytes: limitBytes
|
|
137119
|
+
});
|
|
137120
|
+
} catch (error) {
|
|
137121
|
+
ctx.log.warn("chat dropped an inbound attachment", {
|
|
137122
|
+
name,
|
|
137123
|
+
error: describeError2(error)
|
|
137124
|
+
});
|
|
137125
|
+
await reject(
|
|
137126
|
+
name,
|
|
137127
|
+
error instanceof ImResourceTooLargeError ? { kind: "tooLarge", limitBytes: error.limitBytes } : { kind: "downloadFailed" }
|
|
137128
|
+
);
|
|
137129
|
+
continue;
|
|
137130
|
+
}
|
|
137131
|
+
const bytes = fetched instanceof Uint8Array ? fetched : fetched.bytes;
|
|
137132
|
+
const knownName = fetched instanceof Uint8Array ? void 0 : fetched.name;
|
|
137133
|
+
const finalName = knownName === void 0 ? name : sanitizeAttachmentName(knownName, name);
|
|
137134
|
+
const claimedMime = (fetched instanceof Uint8Array ? void 0 : fetched.mimeType) ?? resource.mimeType;
|
|
137135
|
+
if (bytes.byteLength === 0) {
|
|
137136
|
+
await reject(finalName, { kind: "empty" });
|
|
137137
|
+
continue;
|
|
137138
|
+
}
|
|
137139
|
+
const mimeType = resolveMime(claimedMime, bytes);
|
|
137140
|
+
if (bytes.byteLength > limitForMime(mimeType)) {
|
|
137141
|
+
await reject(finalName, { kind: "tooLarge", limitBytes: limitForMime(mimeType) });
|
|
137142
|
+
continue;
|
|
137143
|
+
}
|
|
137144
|
+
try {
|
|
137145
|
+
const stored = await ctx.attachments.store({
|
|
137146
|
+
threadId,
|
|
137147
|
+
name: withImageExtension(finalName, mimeType),
|
|
137148
|
+
mimeType,
|
|
137149
|
+
bytes
|
|
137150
|
+
});
|
|
137151
|
+
ctx.log.info("chat stored an inbound attachment", {
|
|
137152
|
+
name: stored.name,
|
|
137153
|
+
sizeBytes: stored.sizeBytes
|
|
137154
|
+
});
|
|
137155
|
+
attachments.push(stored);
|
|
137156
|
+
} catch (error) {
|
|
137157
|
+
ctx.log.warn("chat dropped an inbound attachment", {
|
|
137158
|
+
name: finalName,
|
|
137159
|
+
error: describeError2(error)
|
|
137160
|
+
});
|
|
137161
|
+
await reject(finalName, { kind: "storeFailed" });
|
|
137162
|
+
}
|
|
136232
137163
|
}
|
|
137164
|
+
return { attachments, notes };
|
|
136233
137165
|
};
|
|
136234
137166
|
const targetOf = (binding) => chatOf(state, chatKeyOf(binding)).target;
|
|
136235
137167
|
const chatByRun = /* @__PURE__ */ new Map();
|
|
@@ -136347,7 +137279,7 @@ ${final.text}`;
|
|
|
136347
137279
|
modelSelection: selection
|
|
136348
137280
|
});
|
|
136349
137281
|
} catch (error) {
|
|
136350
|
-
ctx.log.warn("chat could not switch the thread model", { error:
|
|
137282
|
+
ctx.log.warn("chat could not switch the thread model", { error: describeError2(error) });
|
|
136351
137283
|
await deliver(binding, t("model.setFailed"));
|
|
136352
137284
|
return;
|
|
136353
137285
|
}
|
|
@@ -136427,7 +137359,7 @@ ${final.text}`;
|
|
|
136427
137359
|
return true;
|
|
136428
137360
|
} catch (error) {
|
|
136429
137361
|
ctx.log.warn("chat could not open the command panel", {
|
|
136430
|
-
error:
|
|
137362
|
+
error: describeError2(error)
|
|
136431
137363
|
});
|
|
136432
137364
|
}
|
|
136433
137365
|
}
|
|
@@ -136481,7 +137413,7 @@ ${final.text}`;
|
|
|
136481
137413
|
try {
|
|
136482
137414
|
paused = (await ctx.projections.getThreadExecution(threadId))?.queuePaused === true;
|
|
136483
137415
|
} catch (error) {
|
|
136484
|
-
ctx.log.warn("chat could not read the turn queue", { error:
|
|
137416
|
+
ctx.log.warn("chat could not read the turn queue", { error: describeError2(error) });
|
|
136485
137417
|
await deliver(binding, t("command.continueFailed"));
|
|
136486
137418
|
return true;
|
|
136487
137419
|
}
|
|
@@ -136498,7 +137430,7 @@ ${final.text}`;
|
|
|
136498
137430
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
136499
137431
|
});
|
|
136500
137432
|
} catch (error) {
|
|
136501
|
-
ctx.log.warn("chat could not resume the turn queue", { error:
|
|
137433
|
+
ctx.log.warn("chat could not resume the turn queue", { error: describeError2(error) });
|
|
136502
137434
|
await deliver(binding, t("command.continueFailed"));
|
|
136503
137435
|
return true;
|
|
136504
137436
|
}
|
|
@@ -136561,9 +137493,8 @@ ${final.text}`;
|
|
|
136561
137493
|
}
|
|
136562
137494
|
};
|
|
136563
137495
|
const interactionModeOf = (chatKey) => chatOf(state, chatKey).plan ? "plan" : "default";
|
|
136564
|
-
const
|
|
137496
|
+
const ensureThread = async (binding, titleSeed) => {
|
|
136565
137497
|
const chatKey = chatKeyOf(binding);
|
|
136566
|
-
const text2 = message.text;
|
|
136567
137498
|
let threadId = chatOf(state, chatKey).threadId ?? void 0;
|
|
136568
137499
|
if (threadId !== void 0 && !await threadStillUsable(binding, threadId)) {
|
|
136569
137500
|
ctx.log.info("chat dropped a saved thread that is no longer usable", {
|
|
@@ -136574,64 +137505,85 @@ ${final.text}`;
|
|
|
136574
137505
|
threadId = void 0;
|
|
136575
137506
|
await deliver(binding, t("reply.threadGone"));
|
|
136576
137507
|
}
|
|
136577
|
-
if (threadId
|
|
136578
|
-
|
|
136579
|
-
|
|
136580
|
-
|
|
136581
|
-
|
|
136582
|
-
|
|
136583
|
-
});
|
|
136584
|
-
return null;
|
|
136585
|
-
}
|
|
136586
|
-
const modelSelection = chatOf(state, chatKey).model ?? project.defaultModelSelection;
|
|
136587
|
-
if (modelSelection === null) {
|
|
136588
|
-
ctx.log.warn("chat cannot start a thread: the project has no default model", {
|
|
136589
|
-
group: binding.displayName,
|
|
136590
|
-
projectId: binding.projectId
|
|
136591
|
-
});
|
|
136592
|
-
await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
|
|
136593
|
-
return null;
|
|
136594
|
-
}
|
|
136595
|
-
const createdThreadId = crypto.randomUUID();
|
|
136596
|
-
threadId = createdThreadId;
|
|
136597
|
-
await ctx.commands.dispatch({
|
|
136598
|
-
type: "thread.create",
|
|
136599
|
-
commandId: crypto.randomUUID(),
|
|
136600
|
-
threadId,
|
|
136601
|
-
projectId: binding.projectId,
|
|
136602
|
-
title: titleFrom(text2 || message.resource?.name || "Chat"),
|
|
136603
|
-
modelSelection,
|
|
136604
|
-
runtimeMode: binding.runtimeMode,
|
|
136605
|
-
interactionMode: interactionModeOf(chatKey),
|
|
136606
|
-
branch: null,
|
|
136607
|
-
worktreePath: null,
|
|
136608
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
137508
|
+
if (threadId !== void 0) return threadId;
|
|
137509
|
+
const project = await ctx.projections.getProject(binding.projectId);
|
|
137510
|
+
if (project === null) {
|
|
137511
|
+
ctx.log.warn("chat cannot start a thread: the configured project is gone", {
|
|
137512
|
+
group: binding.displayName,
|
|
137513
|
+
projectId: binding.projectId
|
|
136609
137514
|
});
|
|
136610
|
-
|
|
136611
|
-
|
|
136612
|
-
|
|
136613
|
-
|
|
136614
|
-
|
|
137515
|
+
return null;
|
|
137516
|
+
}
|
|
137517
|
+
const modelSelection = chatOf(state, chatKey).model ?? project.defaultModelSelection;
|
|
137518
|
+
if (modelSelection === null) {
|
|
137519
|
+
ctx.log.warn("chat cannot start a thread: the project has no default model", {
|
|
137520
|
+
group: binding.displayName,
|
|
137521
|
+
projectId: binding.projectId
|
|
136615
137522
|
});
|
|
136616
|
-
|
|
137523
|
+
await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
|
|
137524
|
+
return null;
|
|
136617
137525
|
}
|
|
136618
|
-
const
|
|
136619
|
-
const userMessageId = crypto.randomUUID();
|
|
137526
|
+
const createdThreadId = crypto.randomUUID();
|
|
136620
137527
|
await ctx.commands.dispatch({
|
|
136621
|
-
type: "thread.
|
|
137528
|
+
type: "thread.create",
|
|
136622
137529
|
commandId: crypto.randomUUID(),
|
|
136623
|
-
threadId,
|
|
136624
|
-
|
|
136625
|
-
|
|
136626
|
-
|
|
136627
|
-
text: text2,
|
|
136628
|
-
attachments
|
|
136629
|
-
},
|
|
136630
|
-
titleSeed: titleFrom(text2 || attachments[0]?.name || "Chat"),
|
|
137530
|
+
threadId: createdThreadId,
|
|
137531
|
+
projectId: binding.projectId,
|
|
137532
|
+
title: titleFrom(titleSeed),
|
|
137533
|
+
modelSelection,
|
|
136631
137534
|
runtimeMode: binding.runtimeMode,
|
|
136632
137535
|
interactionMode: interactionModeOf(chatKey),
|
|
137536
|
+
branch: null,
|
|
137537
|
+
worktreePath: null,
|
|
136633
137538
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
136634
137539
|
});
|
|
137540
|
+
commit("thread-opened", {
|
|
137541
|
+
chatKey,
|
|
137542
|
+
threadId: createdThreadId,
|
|
137543
|
+
projectId: binding.projectId,
|
|
137544
|
+
openedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
137545
|
+
});
|
|
137546
|
+
projectByThread.set(createdThreadId, binding.projectId);
|
|
137547
|
+
return createdThreadId;
|
|
137548
|
+
};
|
|
137549
|
+
const startTurn = async (binding, message) => {
|
|
137550
|
+
const chatKey = chatKeyOf(binding);
|
|
137551
|
+
const text2 = message.text;
|
|
137552
|
+
const threadId = await ensureThread(binding, text2 || message.resources[0]?.name || "Chat");
|
|
137553
|
+
if (threadId === null) return null;
|
|
137554
|
+
const waiting = pendingByChat.get(chatKey);
|
|
137555
|
+
pendingByChat.delete(chatKey);
|
|
137556
|
+
const carried = waiting?.threadId === threadId ? waiting : void 0;
|
|
137557
|
+
const own = await collectResources(
|
|
137558
|
+
binding,
|
|
137559
|
+
threadId,
|
|
137560
|
+
message.messageId,
|
|
137561
|
+
message.resources,
|
|
137562
|
+
MAX_ATTACHMENTS_PER_TURN - (carried?.attachments.length ?? 0)
|
|
137563
|
+
);
|
|
137564
|
+
const attachments = [...carried?.attachments ?? [], ...own.attachments];
|
|
137565
|
+
const notes = [...carried?.notes ?? [], ...own.notes];
|
|
137566
|
+
const userMessageId = crypto.randomUUID();
|
|
137567
|
+
try {
|
|
137568
|
+
await ctx.commands.dispatch({
|
|
137569
|
+
type: "thread.turn.start",
|
|
137570
|
+
commandId: crypto.randomUUID(),
|
|
137571
|
+
threadId,
|
|
137572
|
+
message: {
|
|
137573
|
+
messageId: userMessageId,
|
|
137574
|
+
role: "user",
|
|
137575
|
+
text: notes.length === 0 ? text2 : [text2, ...notes].join("\n"),
|
|
137576
|
+
attachments
|
|
137577
|
+
},
|
|
137578
|
+
titleSeed: titleFrom(text2 || attachments[0]?.name || "Chat"),
|
|
137579
|
+
runtimeMode: binding.runtimeMode,
|
|
137580
|
+
interactionMode: interactionModeOf(chatKey),
|
|
137581
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
137582
|
+
});
|
|
137583
|
+
} catch (error) {
|
|
137584
|
+
if (carried !== void 0) pendingByChat.set(chatKey, carried);
|
|
137585
|
+
throw error;
|
|
137586
|
+
}
|
|
136635
137587
|
ctx.log.info("chat started a turn from a message", {
|
|
136636
137588
|
group: binding.displayName,
|
|
136637
137589
|
chatId: binding.chatId,
|
|
@@ -136641,10 +137593,48 @@ ${final.text}`;
|
|
|
136641
137593
|
chatKey,
|
|
136642
137594
|
threadId,
|
|
136643
137595
|
userMessageId,
|
|
136644
|
-
text2.trim() || attachments[0]?.name || message.
|
|
137596
|
+
text2.trim() || attachments[0]?.name || message.resources[0]?.name || ""
|
|
136645
137597
|
);
|
|
136646
137598
|
return threadId;
|
|
136647
137599
|
};
|
|
137600
|
+
const receiveAttachmentsOnly = async (binding, message) => {
|
|
137601
|
+
const threadId = await ensureThread(binding, message.resources[0]?.name || "Chat");
|
|
137602
|
+
if (threadId === null) return;
|
|
137603
|
+
const waiting = pendingFor(chatKeyOf(binding), threadId);
|
|
137604
|
+
const collected = await collectResources(
|
|
137605
|
+
binding,
|
|
137606
|
+
threadId,
|
|
137607
|
+
message.messageId,
|
|
137608
|
+
message.resources,
|
|
137609
|
+
MAX_ATTACHMENTS_PER_TURN - waiting.attachments.length
|
|
137610
|
+
);
|
|
137611
|
+
waiting.attachments.push(...collected.attachments);
|
|
137612
|
+
waiting.notes.push(...collected.notes.slice(0, MAX_ATTACHMENTS_PER_TURN));
|
|
137613
|
+
if (collected.attachments.length === 0) return;
|
|
137614
|
+
await deliver(
|
|
137615
|
+
binding,
|
|
137616
|
+
t("reply.attachmentReceived", {
|
|
137617
|
+
names: collected.attachments.map((attachment) => attachment.name).join(", "),
|
|
137618
|
+
count: waiting.attachments.length
|
|
137619
|
+
})
|
|
137620
|
+
);
|
|
137621
|
+
};
|
|
137622
|
+
ctx.toolkit({
|
|
137623
|
+
name: "im",
|
|
137624
|
+
tools: [
|
|
137625
|
+
makeSendFileTool({
|
|
137626
|
+
boundChat: (threadId) => readBindings().find(
|
|
137627
|
+
(binding) => binding.projectId !== "" && chatOf(state, chatKeyOf(binding)).threadId === threadId
|
|
137628
|
+
),
|
|
137629
|
+
adapterFor: (binding) => adapterFor(binding.platform),
|
|
137630
|
+
translate: t,
|
|
137631
|
+
log: {
|
|
137632
|
+
info: (message, fields) => ctx.log.info(message, fields),
|
|
137633
|
+
warn: (message, fields) => ctx.log.warn(message, fields)
|
|
137634
|
+
}
|
|
137635
|
+
})
|
|
137636
|
+
]
|
|
137637
|
+
});
|
|
136648
137638
|
const receiving = readBindings().filter((binding) => binding.projectId !== "");
|
|
136649
137639
|
const connections = [];
|
|
136650
137640
|
const started = /* @__PURE__ */ new Set();
|
|
@@ -136652,7 +137642,7 @@ ${final.text}`;
|
|
|
136652
137642
|
const chatQueues = /* @__PURE__ */ new Map();
|
|
136653
137643
|
const enqueueForChat = (chatKey, work) => {
|
|
136654
137644
|
const run = (chatQueues.get(chatKey) ?? Promise.resolve()).then(work).catch((error) => {
|
|
136655
|
-
ctx.log.warn("chat could not handle the message", { error:
|
|
137645
|
+
ctx.log.warn("chat could not handle the message", { error: describeError2(error) });
|
|
136656
137646
|
}).then(() => {
|
|
136657
137647
|
if (chatQueues.get(chatKey) === run) chatQueues.delete(chatKey);
|
|
136658
137648
|
});
|
|
@@ -136703,7 +137693,7 @@ ${final.text}`;
|
|
|
136703
137693
|
},
|
|
136704
137694
|
now: () => Date.now(),
|
|
136705
137695
|
warn: (message, error) => {
|
|
136706
|
-
ctx.log.warn(`chat ${message}`, error === void 0 ? {} : { error:
|
|
137696
|
+
ctx.log.warn(`chat ${message}`, error === void 0 ? {} : { error: describeError2(error) });
|
|
136707
137697
|
}
|
|
136708
137698
|
});
|
|
136709
137699
|
const handlePanelClick = (platform, click) => {
|
|
@@ -136738,6 +137728,17 @@ ${final.text}`;
|
|
|
136738
137728
|
});
|
|
136739
137729
|
return;
|
|
136740
137730
|
}
|
|
137731
|
+
if (message.unsupportedType !== void 0) {
|
|
137732
|
+
const unsupported = message.unsupportedType;
|
|
137733
|
+
enqueueForChat(chatKeyOf(target), async () => {
|
|
137734
|
+
if (!unsupportedReplyCooldown.tryAcquire(chatKeyOf(target))) return;
|
|
137735
|
+
await deliver(
|
|
137736
|
+
target,
|
|
137737
|
+
unsupported === "folder" ? t("reply.unsupportedFolder") : t("reply.unsupportedType", { type: unsupported })
|
|
137738
|
+
);
|
|
137739
|
+
});
|
|
137740
|
+
return;
|
|
137741
|
+
}
|
|
136741
137742
|
const open = openApprovalOf(state, chatKeyOf(target));
|
|
136742
137743
|
const answerToken = open === void 0 ? null : adapterFor(target.platform)?.answerByText?.(
|
|
136743
137744
|
target,
|
|
@@ -136746,7 +137747,7 @@ ${final.text}`;
|
|
|
136746
137747
|
) ?? null;
|
|
136747
137748
|
if (answerToken !== null) {
|
|
136748
137749
|
void answerByToken(answerToken).catch((error) => {
|
|
136749
|
-
ctx.log.warn("chat could not answer the approval", { error:
|
|
137750
|
+
ctx.log.warn("chat could not answer the approval", { error: describeError2(error) });
|
|
136750
137751
|
});
|
|
136751
137752
|
return;
|
|
136752
137753
|
}
|
|
@@ -136754,7 +137755,7 @@ ${final.text}`;
|
|
|
136754
137755
|
const teams = await fetchTeams(target);
|
|
136755
137756
|
if (await handleCommand(target, message.text, teams ?? [])) return;
|
|
136756
137757
|
const boundThread = chatOf(state, chatKeyOf(target)).threadId;
|
|
136757
|
-
if (boundThread !== null && await questionFlow.answerByText(chatKeyOf(target), boundThread, message.text)) {
|
|
137758
|
+
if (boundThread !== null && message.text !== "" && await questionFlow.answerByText(chatKeyOf(target), boundThread, message.text)) {
|
|
136758
137759
|
return;
|
|
136759
137760
|
}
|
|
136760
137761
|
const project = await ctx.projections.getProject(target.projectId);
|
|
@@ -136774,6 +137775,7 @@ ${final.text}`;
|
|
|
136774
137775
|
return;
|
|
136775
137776
|
}
|
|
136776
137777
|
const routed = targetOf(target);
|
|
137778
|
+
const attachmentsOnly = message.text.trim() === "" && message.resources.length > 0;
|
|
136777
137779
|
if (routed.kind === "team") {
|
|
136778
137780
|
if (teams !== null && !teams.some((team) => team.teamId === routed.teamId)) {
|
|
136779
137781
|
commit("chat-updated", {
|
|
@@ -136783,11 +137785,17 @@ ${final.text}`;
|
|
|
136783
137785
|
});
|
|
136784
137786
|
await deliver(target, t("reply.teamGone"));
|
|
136785
137787
|
} else {
|
|
137788
|
+
if (message.resources.length > 0) await deliver(target, t("reply.attachmentsNoTeam"));
|
|
137789
|
+
if (attachmentsOnly) return;
|
|
136786
137790
|
await startTeamRun(target, routed.teamId, message.text);
|
|
136787
137791
|
await markWorking(target, chatKeyOf(target), message.messageId);
|
|
136788
137792
|
return;
|
|
136789
137793
|
}
|
|
136790
137794
|
}
|
|
137795
|
+
if (attachmentsOnly) {
|
|
137796
|
+
await receiveAttachmentsOnly(target, message);
|
|
137797
|
+
return;
|
|
137798
|
+
}
|
|
136791
137799
|
const threadId = await startTurn(target, message);
|
|
136792
137800
|
if (threadId !== null) await markWorking(target, threadId, message.messageId);
|
|
136793
137801
|
});
|
|
@@ -136806,7 +137814,7 @@ ${final.text}`;
|
|
|
136806
137814
|
ctx.log.warn("chat could not start an inbound connection", {
|
|
136807
137815
|
group: binding.displayName,
|
|
136808
137816
|
platform: binding.platform,
|
|
136809
|
-
error:
|
|
137817
|
+
error: describeError2(error)
|
|
136810
137818
|
});
|
|
136811
137819
|
});
|
|
136812
137820
|
}
|