@ycodium-ai/plugin-im 0.2.2596 → 0.2.2658
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 +80 -7
- package/dist/index.js +2385 -270
- package/locales/en.json +64 -2
- package/locales/zh-CN.json +64 -2
- package/package.json +10 -3
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;
|
|
@@ -126163,9 +126163,155 @@ import {
|
|
|
126163
126163
|
readRequestResolved,
|
|
126164
126164
|
readTurnProgress,
|
|
126165
126165
|
REQUEST_RESOLVED_TRIGGER_KIND,
|
|
126166
|
-
TURN_PROGRESS_TRIGGER_KIND
|
|
126166
|
+
TURN_PROGRESS_TRIGGER_KIND,
|
|
126167
|
+
USER_INPUT_PENDING_TRIGGER_KIND
|
|
126167
126168
|
} from "@ycodium-ai/plugin-api";
|
|
126168
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
|
+
|
|
126169
126315
|
// src/chatCommand.ts
|
|
126170
126316
|
var teamArgument = (text2, chinese) => {
|
|
126171
126317
|
const match = text2.match(/^\/team(?:\s+([\s\S]*))?$/i);
|
|
@@ -126205,6 +126351,8 @@ var isThreadCommand = (text2, chinese) => /^\/thread$/i.test(text2.trim()) || ch
|
|
|
126205
126351
|
var ZH_ALIASES = {
|
|
126206
126352
|
\u505C: "stop",
|
|
126207
126353
|
\u505C\u6B62: "stop",
|
|
126354
|
+
// 不用单独的「继续」:那是人对 agent 说的话,不该被当成命令吞掉。
|
|
126355
|
+
\u7EE7\u7EED\u53D1\u9001: "continue",
|
|
126208
126356
|
\u72B6\u6001: "status",
|
|
126209
126357
|
\u65B0\u5BF9\u8BDD: "new",
|
|
126210
126358
|
\u5E2E\u52A9: "help",
|
|
@@ -126223,6 +126371,7 @@ var resolveTeam = (query, teams) => {
|
|
|
126223
126371
|
var parseChatCommand = (input) => {
|
|
126224
126372
|
const trimmed = input.text.trim();
|
|
126225
126373
|
if (/^\/stop$/i.test(trimmed)) return { kind: "stop" };
|
|
126374
|
+
if (/^\/continue$/i.test(trimmed)) return { kind: "continue" };
|
|
126226
126375
|
if (/^\/status$/i.test(trimmed)) return { kind: "status" };
|
|
126227
126376
|
if (/^\/new$/i.test(trimmed)) return { kind: "new" };
|
|
126228
126377
|
if (/^\/help$/i.test(trimmed)) return { kind: "help" };
|
|
@@ -126254,6 +126403,7 @@ var parseChatCommand = (input) => {
|
|
|
126254
126403
|
var helpText = (t, options = {}) => [
|
|
126255
126404
|
t("help.title"),
|
|
126256
126405
|
t("help.stop"),
|
|
126406
|
+
t("help.continue"),
|
|
126257
126407
|
t("help.status"),
|
|
126258
126408
|
t("help.new"),
|
|
126259
126409
|
t("help.sessions"),
|
|
@@ -126278,9 +126428,20 @@ var IM_STATE_KEY = "chats";
|
|
|
126278
126428
|
var IM_STATE_VERSION = 1;
|
|
126279
126429
|
var OPENED_THREAD_LIMIT = 200;
|
|
126280
126430
|
var APPROVAL_RECORD_LIMIT = 100;
|
|
126281
|
-
var emptyState = () => ({
|
|
126431
|
+
var emptyState = () => ({
|
|
126432
|
+
chats: {},
|
|
126433
|
+
opened: {},
|
|
126434
|
+
approvals: {},
|
|
126435
|
+
turnCards: {},
|
|
126436
|
+
questions: {},
|
|
126437
|
+
queue: {}
|
|
126438
|
+
});
|
|
126439
|
+
var questionsOf = (state) => state.questions ?? {};
|
|
126440
|
+
var queueOf = (state) => state.queue ?? {};
|
|
126282
126441
|
var openTurnCardsOf = (state) => state.turnCards ?? {};
|
|
126283
126442
|
var TURN_CARD_RECORD_LIMIT = 50;
|
|
126443
|
+
var QUESTION_RECORD_LIMIT = 50;
|
|
126444
|
+
var QUEUE_RECORD_LIMIT = 50;
|
|
126284
126445
|
var DEFAULT_CHAT_RECORD = {
|
|
126285
126446
|
threadId: null,
|
|
126286
126447
|
target: { kind: "thread" },
|
|
@@ -126301,6 +126462,31 @@ var findApprovalByToken = (state, token) => {
|
|
|
126301
126462
|
var openApprovalOf = (state, chatKey) => Object.values(state.approvals).findLast(
|
|
126302
126463
|
(approval) => approval.chatKey === chatKey && !approval.resolved
|
|
126303
126464
|
);
|
|
126465
|
+
var findQuestionByToken = (state, token) => {
|
|
126466
|
+
for (const record of Object.values(questionsOf(state))) {
|
|
126467
|
+
for (const item of record.items) {
|
|
126468
|
+
const option = item.options.find((candidate) => candidate.token === token);
|
|
126469
|
+
if (option !== void 0) return { record, item, click: { kind: "option", option } };
|
|
126470
|
+
if (item.submitToken === token) return { record, item, click: { kind: "submit" } };
|
|
126471
|
+
}
|
|
126472
|
+
}
|
|
126473
|
+
return void 0;
|
|
126474
|
+
};
|
|
126475
|
+
var openQuestionOf = (state, chatKey, threadId) => Object.values(questionsOf(state)).findLast(
|
|
126476
|
+
(record) => record.chatKey === chatKey && record.threadId === threadId && !record.resolved
|
|
126477
|
+
);
|
|
126478
|
+
var isUnanswered = (item) => item.answer === void 0;
|
|
126479
|
+
var findQueueByToken = (state, token) => {
|
|
126480
|
+
for (const record of Object.values(queueOf(state))) {
|
|
126481
|
+
if (record.steerToken === token) return { record, op: "steer" };
|
|
126482
|
+
if (record.withdrawToken === token) return { record, op: "withdraw" };
|
|
126483
|
+
if (record.resumeToken === token) return { record, op: "resume" };
|
|
126484
|
+
}
|
|
126485
|
+
return void 0;
|
|
126486
|
+
};
|
|
126487
|
+
var openQueueOfThread = (state, threadId) => Object.values(queueOf(state)).filter(
|
|
126488
|
+
(record) => record.threadId === threadId && record.settled === void 0
|
|
126489
|
+
);
|
|
126304
126490
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
126305
126491
|
var nonEmpty = (value) => typeof value === "string" && value !== "" ? value : null;
|
|
126306
126492
|
var readTarget = (value) => {
|
|
@@ -126322,6 +126508,76 @@ var readButtons = (value) => {
|
|
|
126322
126508
|
}
|
|
126323
126509
|
return buttons.length === 0 ? null : buttons;
|
|
126324
126510
|
};
|
|
126511
|
+
var readQuestionOptions = (value) => {
|
|
126512
|
+
if (!Array.isArray(value)) return null;
|
|
126513
|
+
const options = [];
|
|
126514
|
+
for (const entry of value) {
|
|
126515
|
+
if (!isRecord(entry)) return null;
|
|
126516
|
+
const token = nonEmpty(entry["token"]);
|
|
126517
|
+
const optionValue = nonEmpty(entry["value"]);
|
|
126518
|
+
if (token === null || optionValue === null || typeof entry["label"] !== "string") return null;
|
|
126519
|
+
const planAction = entry["planAction"];
|
|
126520
|
+
options.push({
|
|
126521
|
+
token,
|
|
126522
|
+
value: optionValue,
|
|
126523
|
+
label: entry["label"],
|
|
126524
|
+
description: typeof entry["description"] === "string" ? entry["description"] : "",
|
|
126525
|
+
...planAction === "approve" || planAction === "revise" || planAction === "stop" ? { planAction } : {}
|
|
126526
|
+
});
|
|
126527
|
+
}
|
|
126528
|
+
return options.length === 0 ? null : options;
|
|
126529
|
+
};
|
|
126530
|
+
var readQuestionItems = (value) => {
|
|
126531
|
+
if (!Array.isArray(value)) return null;
|
|
126532
|
+
const items = [];
|
|
126533
|
+
for (const entry of value) {
|
|
126534
|
+
if (!isRecord(entry)) return null;
|
|
126535
|
+
const id = nonEmpty(entry["id"]);
|
|
126536
|
+
const options = readQuestionOptions(entry["options"]);
|
|
126537
|
+
if (id === null || options === null || typeof entry["question"] !== "string") return null;
|
|
126538
|
+
const submitToken = nonEmpty(entry["submitToken"]);
|
|
126539
|
+
items.push({
|
|
126540
|
+
id,
|
|
126541
|
+
header: typeof entry["header"] === "string" ? entry["header"] : "",
|
|
126542
|
+
question: entry["question"],
|
|
126543
|
+
kind: entry["kind"] === "plan" ? "plan" : "question",
|
|
126544
|
+
multiSelect: entry["multiSelect"] === true,
|
|
126545
|
+
// 老事实没写这个字段时按「允许」处理:与宿主「没明说算 true」一致。
|
|
126546
|
+
allowFreeform: entry["allowFreeform"] !== false,
|
|
126547
|
+
options,
|
|
126548
|
+
...submitToken === null ? {} : { submitToken },
|
|
126549
|
+
selected: []
|
|
126550
|
+
});
|
|
126551
|
+
}
|
|
126552
|
+
return items.length === 0 ? null : items;
|
|
126553
|
+
};
|
|
126554
|
+
var readAnswer = (value) => {
|
|
126555
|
+
if (typeof value === "string") return value;
|
|
126556
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
|
|
126557
|
+
return value;
|
|
126558
|
+
}
|
|
126559
|
+
return null;
|
|
126560
|
+
};
|
|
126561
|
+
var withQuestionItem = (state, requestId, questionId, patch) => {
|
|
126562
|
+
const record = requestId === null ? void 0 : questionsOf(state)[requestId];
|
|
126563
|
+
if (requestId === null || questionId === null || record === void 0) return state;
|
|
126564
|
+
if (!record.items.some((item) => item.id === questionId)) return state;
|
|
126565
|
+
return {
|
|
126566
|
+
...state,
|
|
126567
|
+
questions: {
|
|
126568
|
+
...questionsOf(state),
|
|
126569
|
+
[requestId]: {
|
|
126570
|
+
...record,
|
|
126571
|
+
items: record.items.map((item) => item.id === questionId ? patch(item) : item)
|
|
126572
|
+
}
|
|
126573
|
+
}
|
|
126574
|
+
};
|
|
126575
|
+
};
|
|
126576
|
+
var withQueueRecord = (state, intentId, patch) => {
|
|
126577
|
+
const record = intentId === null ? void 0 : queueOf(state)[intentId];
|
|
126578
|
+
if (intentId === null || record === void 0) return state;
|
|
126579
|
+
return { ...state, queue: { ...queueOf(state), [intentId]: patch(record) } };
|
|
126580
|
+
};
|
|
126325
126581
|
var keepNewest = (record, limit) => {
|
|
126326
126582
|
const entries = Object.entries(record);
|
|
126327
126583
|
return entries.length <= limit ? record : Object.fromEntries(entries.slice(entries.length - limit));
|
|
@@ -126405,6 +126661,124 @@ var foldImEvent = (state, name, data) => {
|
|
|
126405
126661
|
approvals: { ...state.approvals, [requestId]: { ...existing, cardId } }
|
|
126406
126662
|
};
|
|
126407
126663
|
}
|
|
126664
|
+
case "question-offered": {
|
|
126665
|
+
const chatKey = nonEmpty(data["chatKey"]);
|
|
126666
|
+
const threadId = nonEmpty(data["threadId"]);
|
|
126667
|
+
const requestId = nonEmpty(data["requestId"]);
|
|
126668
|
+
const items = readQuestionItems(data["items"]);
|
|
126669
|
+
if (chatKey === null || threadId === null || requestId === null || items === null) {
|
|
126670
|
+
return state;
|
|
126671
|
+
}
|
|
126672
|
+
return {
|
|
126673
|
+
...state,
|
|
126674
|
+
questions: keepNewest(
|
|
126675
|
+
{
|
|
126676
|
+
...questionsOf(state),
|
|
126677
|
+
[requestId]: { chatKey, threadId, requestId, items, resolved: false }
|
|
126678
|
+
},
|
|
126679
|
+
QUESTION_RECORD_LIMIT
|
|
126680
|
+
)
|
|
126681
|
+
};
|
|
126682
|
+
}
|
|
126683
|
+
case "question-carded": {
|
|
126684
|
+
const cardId = nonEmpty(data["cardId"]);
|
|
126685
|
+
if (cardId === null) return state;
|
|
126686
|
+
return withQuestionItem(
|
|
126687
|
+
state,
|
|
126688
|
+
nonEmpty(data["requestId"]),
|
|
126689
|
+
nonEmpty(data["questionId"]),
|
|
126690
|
+
(item) => ({ ...item, cardId })
|
|
126691
|
+
);
|
|
126692
|
+
}
|
|
126693
|
+
case "question-selected": {
|
|
126694
|
+
const selected = readAnswer(data["selected"]);
|
|
126695
|
+
if (!Array.isArray(selected)) return state;
|
|
126696
|
+
return withQuestionItem(
|
|
126697
|
+
state,
|
|
126698
|
+
nonEmpty(data["requestId"]),
|
|
126699
|
+
nonEmpty(data["questionId"]),
|
|
126700
|
+
(item) => ({ ...item, selected })
|
|
126701
|
+
);
|
|
126702
|
+
}
|
|
126703
|
+
case "question-answered": {
|
|
126704
|
+
const answer = readAnswer(data["answer"]);
|
|
126705
|
+
if (answer === null) return state;
|
|
126706
|
+
const label = typeof data["label"] === "string" ? data["label"] : "";
|
|
126707
|
+
return withQuestionItem(
|
|
126708
|
+
state,
|
|
126709
|
+
nonEmpty(data["requestId"]),
|
|
126710
|
+
nonEmpty(data["questionId"]),
|
|
126711
|
+
(item) => ({ ...item, answer, answerLabel: label })
|
|
126712
|
+
);
|
|
126713
|
+
}
|
|
126714
|
+
case "question-resolved": {
|
|
126715
|
+
const requestId = nonEmpty(data["requestId"]);
|
|
126716
|
+
const existing = requestId === null ? void 0 : questionsOf(state)[requestId];
|
|
126717
|
+
if (requestId === null || existing === void 0) return state;
|
|
126718
|
+
const ending = data["ending"];
|
|
126719
|
+
return {
|
|
126720
|
+
...state,
|
|
126721
|
+
questions: {
|
|
126722
|
+
...questionsOf(state),
|
|
126723
|
+
[requestId]: {
|
|
126724
|
+
...existing,
|
|
126725
|
+
resolved: true,
|
|
126726
|
+
ending: ending === "elsewhere" || ending === "cancelled" ? ending : "here"
|
|
126727
|
+
}
|
|
126728
|
+
}
|
|
126729
|
+
};
|
|
126730
|
+
}
|
|
126731
|
+
case "queue-offered": {
|
|
126732
|
+
const chatKey = nonEmpty(data["chatKey"]);
|
|
126733
|
+
const threadId = nonEmpty(data["threadId"]);
|
|
126734
|
+
const intentId = nonEmpty(data["intentId"]);
|
|
126735
|
+
const steerToken = nonEmpty(data["steerToken"]);
|
|
126736
|
+
const withdrawToken = nonEmpty(data["withdrawToken"]);
|
|
126737
|
+
const resumeToken = nonEmpty(data["resumeToken"]);
|
|
126738
|
+
if (chatKey === null || threadId === null || intentId === null || steerToken === null || withdrawToken === null || resumeToken === null) {
|
|
126739
|
+
return state;
|
|
126740
|
+
}
|
|
126741
|
+
return {
|
|
126742
|
+
...state,
|
|
126743
|
+
queue: keepNewest(
|
|
126744
|
+
{
|
|
126745
|
+
...queueOf(state),
|
|
126746
|
+
[intentId]: {
|
|
126747
|
+
chatKey,
|
|
126748
|
+
threadId,
|
|
126749
|
+
intentId,
|
|
126750
|
+
text: typeof data["text"] === "string" ? data["text"] : "",
|
|
126751
|
+
steerToken,
|
|
126752
|
+
withdrawToken,
|
|
126753
|
+
resumeToken,
|
|
126754
|
+
steering: false
|
|
126755
|
+
}
|
|
126756
|
+
},
|
|
126757
|
+
QUEUE_RECORD_LIMIT
|
|
126758
|
+
)
|
|
126759
|
+
};
|
|
126760
|
+
}
|
|
126761
|
+
case "queue-carded": {
|
|
126762
|
+
const cardId = nonEmpty(data["cardId"]);
|
|
126763
|
+
if (cardId === null) return state;
|
|
126764
|
+
return withQueueRecord(state, nonEmpty(data["intentId"]), (record) => ({
|
|
126765
|
+
...record,
|
|
126766
|
+
cardId
|
|
126767
|
+
}));
|
|
126768
|
+
}
|
|
126769
|
+
case "queue-steering":
|
|
126770
|
+
return withQueueRecord(state, nonEmpty(data["intentId"]), (record) => ({
|
|
126771
|
+
...record,
|
|
126772
|
+
steering: data["steering"] === true
|
|
126773
|
+
}));
|
|
126774
|
+
case "queue-settled": {
|
|
126775
|
+
const outcome = data["outcome"];
|
|
126776
|
+
if (outcome !== "steered" && outcome !== "sent" && outcome !== "removed") return state;
|
|
126777
|
+
return withQueueRecord(state, nonEmpty(data["intentId"]), (record) => ({
|
|
126778
|
+
...record,
|
|
126779
|
+
settled: outcome
|
|
126780
|
+
}));
|
|
126781
|
+
}
|
|
126408
126782
|
case "turn-card-opened": {
|
|
126409
126783
|
const cardId = nonEmpty(data["cardId"]);
|
|
126410
126784
|
const chatKey = nonEmpty(data["chatKey"]);
|
|
@@ -126580,6 +126954,39 @@ var uploadImage = async (input) => {
|
|
|
126580
126954
|
}
|
|
126581
126955
|
return body.data.image_key;
|
|
126582
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
|
+
};
|
|
126583
126990
|
var sendChatImage = async (input) => {
|
|
126584
126991
|
const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=chat_id`, {
|
|
126585
126992
|
method: "POST",
|
|
@@ -132009,85 +132416,215 @@ var buildPanelCard = (card) => {
|
|
|
132009
132416
|
elements
|
|
132010
132417
|
};
|
|
132011
132418
|
};
|
|
132419
|
+
var CHOICE_TEMPLATE = {
|
|
132420
|
+
ask: "wathet",
|
|
132421
|
+
queue: "wathet",
|
|
132422
|
+
done: "grey"
|
|
132423
|
+
};
|
|
132424
|
+
var buildChoiceCard = (card) => {
|
|
132425
|
+
const elements = [];
|
|
132426
|
+
const lead = [
|
|
132427
|
+
card.heading === null || card.heading === "" ? "" : `**${stripAngleBrackets(card.heading).replaceAll("*", "")}**`,
|
|
132428
|
+
stripAngleBrackets(card.body)
|
|
132429
|
+
].filter((part) => part !== "");
|
|
132430
|
+
if (lead.length > 0) elements.push({ tag: "markdown", content: lead.join("\n\n") });
|
|
132431
|
+
for (const row of card.rows) {
|
|
132432
|
+
if (row.length === 0) continue;
|
|
132433
|
+
elements.push({
|
|
132434
|
+
tag: "action",
|
|
132435
|
+
actions: row.map((button) => ({
|
|
132436
|
+
tag: "button",
|
|
132437
|
+
text: { tag: "plain_text", content: button.label },
|
|
132438
|
+
type: button.style,
|
|
132439
|
+
value: { tk: button.token }
|
|
132440
|
+
}))
|
|
132441
|
+
});
|
|
132442
|
+
}
|
|
132443
|
+
if (card.note !== null && card.note !== "") {
|
|
132444
|
+
if (elements.length > 0) elements.push({ tag: "hr" });
|
|
132445
|
+
elements.push({
|
|
132446
|
+
tag: "note",
|
|
132447
|
+
elements: [{ tag: "plain_text", content: card.note }]
|
|
132448
|
+
});
|
|
132449
|
+
}
|
|
132450
|
+
if (elements.length === 0) elements.push({ tag: "markdown", content: " " });
|
|
132451
|
+
return {
|
|
132452
|
+
config: { wide_screen_mode: true },
|
|
132453
|
+
header: {
|
|
132454
|
+
title: { tag: "plain_text", content: card.title },
|
|
132455
|
+
template: CHOICE_TEMPLATE[card.tone]
|
|
132456
|
+
},
|
|
132457
|
+
elements
|
|
132458
|
+
};
|
|
132459
|
+
};
|
|
132012
132460
|
|
|
132013
132461
|
// src/larkInbound.ts
|
|
132014
132462
|
var import_node_sdk = __toESM(require_lib2(), 1);
|
|
132015
|
-
|
|
132016
|
-
|
|
132017
|
-
)
|
|
132018
|
-
|
|
132019
|
-
|
|
132020
|
-
|
|
132021
|
-
|
|
132022
|
-
|
|
132023
|
-
|
|
132024
|
-
gif: "image/gif",
|
|
132025
|
-
webp: "image/webp",
|
|
132026
|
-
pdf: "application/pdf",
|
|
132027
|
-
txt: "text/plain",
|
|
132028
|
-
md: "text/markdown",
|
|
132029
|
-
json: "application/json",
|
|
132030
|
-
csv: "text/csv"
|
|
132031
|
-
};
|
|
132032
|
-
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;
|
|
132033
132472
|
};
|
|
132034
|
-
var
|
|
132035
|
-
|
|
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()) => {
|
|
132036
132525
|
let parsed;
|
|
132037
132526
|
try {
|
|
132038
132527
|
parsed = JSON.parse(content);
|
|
132039
132528
|
} catch {
|
|
132040
|
-
return
|
|
132529
|
+
return void 0;
|
|
132041
132530
|
}
|
|
132042
|
-
const
|
|
132043
|
-
const
|
|
132044
|
-
|
|
132045
|
-
for (const
|
|
132046
|
-
const
|
|
132047
|
-
|
|
132048
|
-
|
|
132049
|
-
|
|
132050
|
-
imageKey = segment.image_key;
|
|
132051
|
-
}
|
|
132052
|
-
}
|
|
132053
|
-
const line = parts.join("").trim();
|
|
132054
|
-
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);
|
|
132055
132539
|
}
|
|
132056
|
-
|
|
132057
|
-
|
|
132058
|
-
|
|
132059
|
-
|
|
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 };
|
|
132060
132562
|
};
|
|
132061
|
-
|
|
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) => {
|
|
132062
132588
|
if (content === void 0) return null;
|
|
132063
|
-
let parsed;
|
|
132064
132589
|
try {
|
|
132065
|
-
parsed = JSON.parse(content);
|
|
132590
|
+
const parsed = JSON.parse(content);
|
|
132591
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
132066
132592
|
} catch {
|
|
132067
132593
|
return null;
|
|
132068
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
|
+
};
|
|
132069
132603
|
if (messageType === "image") {
|
|
132070
|
-
const
|
|
132071
|
-
return
|
|
132604
|
+
const imageKey = key("image_key");
|
|
132605
|
+
return imageKey === null ? null : { fileKey: imageKey, kind: "image", name: "image.png", mimeType: "image/png" };
|
|
132072
132606
|
}
|
|
132073
132607
|
if (messageType === "file") {
|
|
132074
|
-
const
|
|
132075
|
-
if (
|
|
132076
|
-
const
|
|
132077
|
-
|
|
132078
|
-
|
|
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) };
|
|
132079
132622
|
}
|
|
132080
132623
|
return null;
|
|
132081
132624
|
};
|
|
132082
132625
|
var readText = (content) => {
|
|
132083
|
-
|
|
132084
|
-
|
|
132085
|
-
const parsed = JSON.parse(content);
|
|
132086
|
-
const text2 = parsed.text;
|
|
132087
|
-
return typeof text2 === "string" ? text2 : null;
|
|
132088
|
-
} catch {
|
|
132089
|
-
return null;
|
|
132090
|
-
}
|
|
132626
|
+
const text2 = readJson(content)?.["text"];
|
|
132627
|
+
return typeof text2 === "string" ? text2 : null;
|
|
132091
132628
|
};
|
|
132092
132629
|
var parseInboundMessage = (event, appId) => {
|
|
132093
132630
|
if (event.sender?.sender_type === "app") return null;
|
|
@@ -132096,33 +132633,52 @@ var parseInboundMessage = (event, appId) => {
|
|
|
132096
132633
|
const { message_id: messageId, chat_id: chatId } = message;
|
|
132097
132634
|
if (typeof messageId !== "string" || typeof chatId !== "string") return null;
|
|
132098
132635
|
const chatType = message.chat_type ?? "group";
|
|
132099
|
-
if (chatType !== "p2p" && !mentionsApp(event, appId)) return null;
|
|
132100
132636
|
const messageType = message.message_type ?? "";
|
|
132101
|
-
|
|
132102
|
-
|
|
132103
|
-
|
|
132104
|
-
const
|
|
132105
|
-
if (
|
|
132106
|
-
|
|
132107
|
-
|
|
132108
|
-
|
|
132109
|
-
|
|
132110
|
-
|
|
132111
|
-
|
|
132112
|
-
|
|
132113
|
-
|
|
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 };
|
|
132114
132660
|
};
|
|
132115
132661
|
var fetchMessageResource = async (input) => {
|
|
132116
|
-
const url =
|
|
132662
|
+
const url = `${LARK_BASE}/im/v1/messages/${encodeURIComponent(
|
|
132117
132663
|
input.messageId
|
|
132118
132664
|
)}/resources/${encodeURIComponent(input.resource.fileKey)}?type=${input.resource.kind}`;
|
|
132119
132665
|
const response = await input.http(url, {
|
|
132120
132666
|
headers: { authorization: `Bearer ${input.token}` }
|
|
132121
132667
|
});
|
|
132122
132668
|
if (!response.ok) {
|
|
132669
|
+
await response.body?.cancel().catch(() => void 0);
|
|
132123
132670
|
throw new Error(`lark resource fetch failed: HTTP ${String(response.status)}`);
|
|
132124
132671
|
}
|
|
132125
|
-
|
|
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;
|
|
132126
132682
|
};
|
|
132127
132683
|
var cardActionValue = (event) => {
|
|
132128
132684
|
const roots = [event, event?.event];
|
|
@@ -132244,7 +132800,7 @@ var makeLarkAdapter = (deps) => {
|
|
|
132244
132800
|
appId,
|
|
132245
132801
|
appSecret,
|
|
132246
132802
|
onError: handlers.onError,
|
|
132247
|
-
onCardAction: handlers.
|
|
132803
|
+
onCardAction: handlers.onButton,
|
|
132248
132804
|
onPanelClick: (click, operatorOpenId) => handlers.onPanelClick?.({
|
|
132249
132805
|
chatId: click.chatId,
|
|
132250
132806
|
cardId: click.cardId,
|
|
@@ -132258,12 +132814,13 @@ var makeLarkAdapter = (deps) => {
|
|
|
132258
132814
|
chatId: message.chatId,
|
|
132259
132815
|
senderId: message.senderOpenId,
|
|
132260
132816
|
text: message.text,
|
|
132261
|
-
|
|
132262
|
-
key:
|
|
132263
|
-
name:
|
|
132264
|
-
mimeType:
|
|
132265
|
-
kind:
|
|
132266
|
-
}
|
|
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 }
|
|
132267
132824
|
});
|
|
132268
132825
|
}
|
|
132269
132826
|
});
|
|
@@ -132292,6 +132849,20 @@ var makeLarkAdapter = (deps) => {
|
|
|
132292
132849
|
await sendChatImage({ http: deps.http, token, chatId: binding.chatId, imageKey });
|
|
132293
132850
|
deps.log.info("lark delivered an image", { name: image2.name });
|
|
132294
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 },
|
|
132295
132866
|
askApproval: async (binding, request) => sendCard({
|
|
132296
132867
|
http: deps.http,
|
|
132297
132868
|
token: await tenantToken(binding),
|
|
@@ -132346,6 +132917,22 @@ var makeLarkAdapter = (deps) => {
|
|
|
132346
132917
|
});
|
|
132347
132918
|
}
|
|
132348
132919
|
},
|
|
132920
|
+
choiceCards: {
|
|
132921
|
+
open: async (binding, card) => sendCard({
|
|
132922
|
+
http: deps.http,
|
|
132923
|
+
token: await tenantToken(binding),
|
|
132924
|
+
chatId: binding.chatId,
|
|
132925
|
+
card: buildChoiceCard(card)
|
|
132926
|
+
}),
|
|
132927
|
+
update: async (binding, cardId, card) => {
|
|
132928
|
+
await patchCard({
|
|
132929
|
+
http: deps.http,
|
|
132930
|
+
token: await tenantToken(binding),
|
|
132931
|
+
messageId: cardId,
|
|
132932
|
+
card: buildChoiceCard(card)
|
|
132933
|
+
});
|
|
132934
|
+
}
|
|
132935
|
+
},
|
|
132349
132936
|
acknowledge: async (binding, messageId) => addReaction({
|
|
132350
132937
|
http: deps.http,
|
|
132351
132938
|
token: await tenantToken(binding),
|
|
@@ -132366,6 +132953,7 @@ var makeLarkAdapter = (deps) => {
|
|
|
132366
132953
|
http: deps.http,
|
|
132367
132954
|
token: await tenantToken(binding),
|
|
132368
132955
|
messageId: input.messageId,
|
|
132956
|
+
maxBytes: input.maxBytes,
|
|
132369
132957
|
resource: {
|
|
132370
132958
|
fileKey: resource.key,
|
|
132371
132959
|
kind: resource.kind,
|
|
@@ -132462,7 +133050,20 @@ var en_default = {
|
|
|
132462
133050
|
projectArchived: 'Project "{{title}}" is archived. Restore it, then message again.',
|
|
132463
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.`,
|
|
132464
133052
|
threadGone: "The earlier session was deleted or archived, so this group now starts a new one.",
|
|
132465
|
-
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}}:"
|
|
132466
133067
|
},
|
|
132467
133068
|
approval: {
|
|
132468
133069
|
allow: "Allow",
|
|
@@ -132470,6 +133071,50 @@ var en_default = {
|
|
|
132470
133071
|
alreadyHandled: "Already handled",
|
|
132471
133072
|
replyHint: "Reply with the number."
|
|
132472
133073
|
},
|
|
133074
|
+
question: {
|
|
133075
|
+
title: "\u2753 Question",
|
|
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.",
|
|
133083
|
+
freeformHint: "Or reply with a message to answer in your own words.",
|
|
133084
|
+
replyOne: "Reply with the number.",
|
|
133085
|
+
replyMany: "Reply with the numbers, separated by commas.",
|
|
133086
|
+
submit: "\u2705 Submit",
|
|
133087
|
+
pickFirst: "Pick at least one option first.",
|
|
133088
|
+
answered: "Answer: {{answer}}",
|
|
133089
|
+
elsewhere: "Already answered elsewhere (the web app or another client).",
|
|
133090
|
+
cancelled: "This question was closed without an answer.",
|
|
133091
|
+
alreadyHandled: "Already handled",
|
|
133092
|
+
sendFailed: "Could not hand your answer to the agent. The question may have been answered elsewhere or already closed."
|
|
133093
|
+
},
|
|
133094
|
+
queue: {
|
|
133095
|
+
title: "\u23F3 Queued",
|
|
133096
|
+
waitRunning: "It goes out when the current reply finishes. To add it to the reply in progress now, tap Steer.",
|
|
133097
|
+
waitPaused: "Sending is paused (the last reply was stopped or failed). Tap Resume sending to carry on.",
|
|
133098
|
+
waitIdle: "It goes out shortly.",
|
|
133099
|
+
steer: "\u27A1\uFE0F Steer",
|
|
133100
|
+
withdraw: "\u21A9\uFE0F Withdraw",
|
|
133101
|
+
resume: "\u25B6\uFE0F Resume sending",
|
|
133102
|
+
steeringTitle: "\u23F3 Steering\u2026",
|
|
133103
|
+
steering: "\u{1F4AC} Adding it to the current reply\u2026",
|
|
133104
|
+
sendingTitle: "\u{1F4E4} Sending\u2026",
|
|
133105
|
+
sending: "Sending it now.",
|
|
133106
|
+
steeredTitle: "\u2705 Steered",
|
|
133107
|
+
steered: "Added to the current reply.",
|
|
133108
|
+
sentTitle: "\u{1F4E4} Sent",
|
|
133109
|
+
sent: "It went out as its own turn.",
|
|
133110
|
+
removedTitle: "\u{1F5D1}\uFE0F Withdrawn",
|
|
133111
|
+
removed: "Withdrawn. It will not be sent.",
|
|
133112
|
+
steerFailed: "Could not add it to the current reply (the reply may have just ended or changed). It stays queued and goes out when the reply finishes.",
|
|
133113
|
+
withdrawFailed: "Could not withdraw it: it has already started sending.",
|
|
133114
|
+
resumeFailed: "Could not resume sending. Try again in a moment.",
|
|
133115
|
+
alreadyHandled: "Already handled",
|
|
133116
|
+
plainNotice: "Queued: this message goes out when the current reply finishes."
|
|
133117
|
+
},
|
|
132473
133118
|
command: {
|
|
132474
133119
|
teamSelected: 'OK. From now on messages go to "{{title}}".',
|
|
132475
133120
|
threadSelected: "OK. From now on messages start a normal session.",
|
|
@@ -132478,6 +133123,9 @@ var en_default = {
|
|
|
132478
133123
|
newSession: "OK. The next message starts a new session.",
|
|
132479
133124
|
stopNoSession: "Nothing is running in this group.",
|
|
132480
133125
|
stopNoTurn: "No turn is running right now.",
|
|
133126
|
+
continued: "Resumed. The waiting messages are being sent.",
|
|
133127
|
+
continueNothing: "Nothing to resume: sending is not paused in this group.",
|
|
133128
|
+
continueFailed: "Could not resume sending. Try again in a moment.",
|
|
132481
133129
|
stopped: "Stopped. The session is still there \u2014 just keep talking."
|
|
132482
133130
|
},
|
|
132483
133131
|
status: {
|
|
@@ -132499,12 +133147,13 @@ var en_default = {
|
|
|
132499
133147
|
},
|
|
132500
133148
|
help: {
|
|
132501
133149
|
title: "Commands:",
|
|
133150
|
+
continue: "\xB7 /continue \u2014 resume sending after /stop or a failure paused it; the waiting messages go out",
|
|
132502
133151
|
stop: "\xB7 /stop \u2014 stop the turn that is running; the session stays",
|
|
132503
133152
|
status: "\xB7 /status \u2014 what is going on right now",
|
|
132504
133153
|
new: "\xB7 /new \u2014 forget the current session; the next message starts a new one",
|
|
132505
133154
|
team: "\xB7 /team name \u2014 send the next messages to a team; /team alone lists the teams",
|
|
132506
133155
|
thread: "\xB7 /thread \u2014 go back to a normal session",
|
|
132507
|
-
aliases: "Chinese shortcuts (\u505C / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD) work when the reply language is Chinese.",
|
|
133156
|
+
aliases: "Chinese shortcuts (\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD) work when the reply language is Chinese.",
|
|
132508
133157
|
sessions: "\xB7 /sessions \u2014 list the sessions this bot opened for this group's project",
|
|
132509
133158
|
resume: "\xB7 /resume number \u2014 switch to one of them (a number from /sessions, or the start of its id)",
|
|
132510
133159
|
model: "\xB7 /model \u2014 see the model this group uses; /model number or /model instance/model switches it",
|
|
@@ -132593,6 +133242,7 @@ var en_default = {
|
|
|
132593
133242
|
},
|
|
132594
133243
|
cmd: {
|
|
132595
133244
|
new: "New session",
|
|
133245
|
+
continue: "Resume sending",
|
|
132596
133246
|
stop: "Stop this turn",
|
|
132597
133247
|
sessions: "Sessions",
|
|
132598
133248
|
team: "Send to a team",
|
|
@@ -132712,7 +133362,20 @@ var zh_CN_default = {
|
|
|
132712
133362
|
projectArchived: "\u9879\u76EE\u300C{{title}}\u300D\u5DF2\u5F52\u6863\uFF0C\u6062\u590D\u540E\u518D\u53D1\u6D88\u606F\u3002",
|
|
132713
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",
|
|
132714
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",
|
|
132715
|
-
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"
|
|
132716
133379
|
},
|
|
132717
133380
|
approval: {
|
|
132718
133381
|
allow: "\u5141\u8BB8",
|
|
@@ -132720,6 +133383,50 @@ var zh_CN_default = {
|
|
|
132720
133383
|
alreadyHandled: "\u5DF2\u5904\u7406",
|
|
132721
133384
|
replyHint: "\u56DE\u590D\u7F16\u53F7\u5373\u53EF\u3002"
|
|
132722
133385
|
},
|
|
133386
|
+
question: {
|
|
133387
|
+
title: "\u2753 \u63D0\u95EE",
|
|
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",
|
|
133395
|
+
freeformHint: "\u4E5F\u53EF\u4EE5\u76F4\u63A5\u56DE\u590D\u4E00\u6761\u6D88\u606F\uFF0C\u7528\u81EA\u5DF1\u7684\u8BDD\u4F5C\u7B54\u3002",
|
|
133396
|
+
replyOne: "\u56DE\u590D\u7F16\u53F7\u5373\u53EF\u3002",
|
|
133397
|
+
replyMany: "\u56DE\u590D\u7F16\u53F7\uFF0C\u591A\u4E2A\u7528\u9017\u53F7\u9694\u5F00\u3002",
|
|
133398
|
+
submit: "\u2705 \u63D0\u4EA4",
|
|
133399
|
+
pickFirst: "\u5148\u9009\u4E00\u9879\u518D\u63D0\u4EA4\u3002",
|
|
133400
|
+
answered: "\u56DE\u7B54\uFF1A{{answer}}",
|
|
133401
|
+
elsewhere: "\u5DF2\u5728\u522B\u5904\u56DE\u7B54\uFF08\u7F51\u9875\u6216\u5176\u4ED6\u5BA2\u6237\u7AEF\uFF09\u3002",
|
|
133402
|
+
cancelled: "\u8FD9\u9053\u63D0\u95EE\u5DF2\u7ED3\u675F\uFF0C\u6CA1\u6709\u6536\u5230\u56DE\u7B54\u3002",
|
|
133403
|
+
alreadyHandled: "\u5DF2\u5904\u7406",
|
|
133404
|
+
sendFailed: "\u6CA1\u80FD\u628A\u4F60\u7684\u56DE\u7B54\u4EA4\u7ED9 agent\u3002\u8FD9\u9053\u63D0\u95EE\u53EF\u80FD\u5DF2\u5728\u522B\u5904\u56DE\u7B54\uFF0C\u6216\u5DF2\u7ECF\u7ED3\u675F\u3002"
|
|
133405
|
+
},
|
|
133406
|
+
queue: {
|
|
133407
|
+
title: "\u23F3 \u5DF2\u6392\u961F",
|
|
133408
|
+
waitRunning: "\u5F53\u524D\u8FD9\u4E00\u8F6E\u56DE\u590D\u7ED3\u675F\u540E\u81EA\u52A8\u53D1\u51FA\u3002\u60F3\u73B0\u5728\u5C31\u9001\u8FDB\u6B63\u5728\u8FDB\u884C\u7684\u8FD9\u4E00\u8F6E\uFF0C\u70B9\u300C\u63D2\u8BDD\u300D\u3002",
|
|
133409
|
+
waitPaused: "\u53D1\u9001\u5DF2\u6682\u505C\uFF08\u4E0A\u4E00\u8F6E\u88AB\u505C\u6B62\u6216\u51FA\u9519\u4E86\uFF09\u3002\u70B9\u300C\u6062\u590D\u53D1\u9001\u300D\u7EE7\u7EED\u3002",
|
|
133410
|
+
waitIdle: "\u9A6C\u4E0A\u53D1\u51FA\u3002",
|
|
133411
|
+
steer: "\u27A1\uFE0F \u63D2\u8BDD",
|
|
133412
|
+
withdraw: "\u21A9\uFE0F \u64A4\u56DE",
|
|
133413
|
+
resume: "\u25B6\uFE0F \u6062\u590D\u53D1\u9001",
|
|
133414
|
+
steeringTitle: "\u23F3 \u63D2\u8BDD\u4E2D\u2026",
|
|
133415
|
+
steering: "\u{1F4AC} \u6B63\u5728\u9001\u8FDB\u5F53\u524D\u8FD9\u4E00\u8F6E\u2026",
|
|
133416
|
+
sendingTitle: "\u{1F4E4} \u53D1\u9001\u4E2D\u2026",
|
|
133417
|
+
sending: "\u6B63\u5728\u53D1\u51FA\u3002",
|
|
133418
|
+
steeredTitle: "\u2705 \u5DF2\u63D2\u8BDD",
|
|
133419
|
+
steered: "\u5DF2\u9001\u8FDB\u5F53\u524D\u8FD9\u4E00\u8F6E\u3002",
|
|
133420
|
+
sentTitle: "\u{1F4E4} \u5DF2\u53D1\u9001",
|
|
133421
|
+
sent: "\u5DF2\u4F5C\u4E3A\u5355\u72EC\u7684\u4E00\u8F6E\u53D1\u51FA\u3002",
|
|
133422
|
+
removedTitle: "\u{1F5D1}\uFE0F \u5DF2\u64A4\u56DE",
|
|
133423
|
+
removed: "\u5DF2\u64A4\u56DE\uFF0C\u4E0D\u4F1A\u53D1\u51FA\u3002",
|
|
133424
|
+
steerFailed: "\u6CA1\u80FD\u9001\u8FDB\u5F53\u524D\u8FD9\u4E00\u8F6E\uFF08\u8FD9\u4E00\u8F6E\u53EF\u80FD\u521A\u7ED3\u675F\u6216\u5DF2\u7ECF\u6362\u4E86\uFF09\u3002\u6D88\u606F\u4ECD\u5728\u6392\u961F\uFF0C\u8FD9\u4E00\u8F6E\u7ED3\u675F\u540E\u53D1\u51FA\u3002",
|
|
133425
|
+
withdrawFailed: "\u6CA1\u80FD\u64A4\u56DE\uFF1A\u8FD9\u6761\u6D88\u606F\u5DF2\u7ECF\u5F00\u59CB\u53D1\u9001\u4E86\u3002",
|
|
133426
|
+
resumeFailed: "\u6CA1\u80FD\u6062\u590D\u53D1\u9001\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002",
|
|
133427
|
+
alreadyHandled: "\u5DF2\u5904\u7406",
|
|
133428
|
+
plainNotice: "\u5DF2\u6392\u961F\uFF1A\u5F53\u524D\u8FD9\u4E00\u8F6E\u56DE\u590D\u7ED3\u675F\u540E\u53D1\u51FA\u3002"
|
|
133429
|
+
},
|
|
132723
133430
|
command: {
|
|
132724
133431
|
teamSelected: "\u597D\uFF0C\u63A5\u4E0B\u6765\u6D3E\u7ED9\u300C{{title}}\u300D\u3002",
|
|
132725
133432
|
threadSelected: "\u597D\uFF0C\u63A5\u4E0B\u6765\u5F00\u666E\u901A\u4F1A\u8BDD\u3002",
|
|
@@ -132728,6 +133435,9 @@ var zh_CN_default = {
|
|
|
132728
133435
|
newSession: "\u597D\uFF0C\u4E0B\u4E00\u53E5\u8BDD\u5F00\u4E00\u6761\u65B0\u4F1A\u8BDD\u3002",
|
|
132729
133436
|
stopNoSession: "\u8FD9\u4E2A\u7FA4\u73B0\u5728\u6CA1\u6709\u5728\u8DD1\u7684\u6D3B\u513F\u3002",
|
|
132730
133437
|
stopNoTurn: "\u73B0\u5728\u6CA1\u6709\u5728\u8DD1\u7684\u4E00\u8F6E\u3002",
|
|
133438
|
+
continued: "\u5DF2\u6062\u590D\uFF0C\u6392\u7740\u7684\u6D88\u606F\u5F00\u59CB\u53D1\u9001\u3002",
|
|
133439
|
+
continueNothing: "\u6CA1\u6709\u53EF\u6062\u590D\u7684\uFF1A\u8FD9\u4E2A\u7FA4\u7684\u53D1\u9001\u6CA1\u6709\u88AB\u6682\u505C\u3002",
|
|
133440
|
+
continueFailed: "\u6CA1\u80FD\u6062\u590D\u53D1\u9001\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002",
|
|
132731
133441
|
stopped: "\u505C\u4E86\u3002\u4F1A\u8BDD\u8FD8\u5728\uFF0C\u63A5\u7740\u8BF4\u5C31\u884C\u3002"
|
|
132732
133442
|
},
|
|
132733
133443
|
status: {
|
|
@@ -132749,12 +133459,13 @@ var zh_CN_default = {
|
|
|
132749
133459
|
},
|
|
132750
133460
|
help: {
|
|
132751
133461
|
title: "\u80FD\u7528\u7684\u547D\u4EE4\uFF1A",
|
|
133462
|
+
continue: "\xB7 /continue \u2014\u2014 /stop \u6216\u51FA\u9519\u6682\u505C\u4E86\u53D1\u9001\u4E4B\u540E\uFF0C\u7528\u5B83\u6062\u590D\uFF0C\u6392\u7740\u7684\u6D88\u606F\u7EE7\u7EED\u53D1\u51FA",
|
|
132752
133463
|
stop: "\xB7 /stop \u2014\u2014 \u505C\u6389\u6B63\u5728\u8DD1\u7684\u8FD9\u4E00\u8F6E\uFF0C\u4F1A\u8BDD\u7559\u7740",
|
|
132753
133464
|
status: "\xB7 /status \u2014\u2014 \u73B0\u5728\u5728\u5E72\u4EC0\u4E48",
|
|
132754
133465
|
new: "\xB7 /new \u2014\u2014 \u5FD8\u6389\u5F53\u524D\u4F1A\u8BDD\uFF0C\u4E0B\u4E00\u53E5\u5F00\u65B0\u7684",
|
|
132755
133466
|
team: "\xB7 /team \u961F\u540D \u2014\u2014 \u628A\u63A5\u4E0B\u6765\u7684\u6D3B\u6D3E\u7ED9\u4E00\u652F\u961F\uFF1B/team \u4E0D\u5E26\u540D\u5B57\u770B\u6709\u54EA\u4E9B\u961F",
|
|
132756
133467
|
thread: "\xB7 /thread \u2014\u2014 \u5207\u56DE\u666E\u901A\u4F1A\u8BDD",
|
|
132757
|
-
aliases: "\u4E2D\u6587\u4E5F\u884C\uFF1A\u505C / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002",
|
|
133468
|
+
aliases: "\u4E2D\u6587\u4E5F\u884C\uFF1A\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002",
|
|
132758
133469
|
sessions: "\xB7 /sessions \u2014\u2014 \u5217\u51FA\u672C\u673A\u5668\u4EBA\u4E3A\u8FD9\u4E2A\u7FA4\u7684\u9879\u76EE\u5F00\u8FC7\u7684\u4F1A\u8BDD",
|
|
132759
133470
|
resume: "\xB7 /resume \u7F16\u53F7 \u2014\u2014 \u5207\u5230\u5176\u4E2D\u4E00\u6761\uFF08/sessions \u91CC\u7684\u7F16\u53F7\uFF0C\u6216\u4F1A\u8BDD id \u7684\u5F00\u5934\uFF09",
|
|
132760
133471
|
model: "\xB7 /model \u2014\u2014 \u770B\u8FD9\u4E2A\u7FA4\u7528\u7684\u6A21\u578B\uFF1B/model \u7F16\u53F7 \u6216 /model \u5B9E\u4F8B/\u6A21\u578B \u5207\u6362",
|
|
@@ -132843,6 +133554,7 @@ var zh_CN_default = {
|
|
|
132843
133554
|
},
|
|
132844
133555
|
cmd: {
|
|
132845
133556
|
new: "\u65B0\u4F1A\u8BDD",
|
|
133557
|
+
continue: "\u6062\u590D\u53D1\u9001",
|
|
132846
133558
|
stop: "\u505C\u6B62\u672C\u8F6E",
|
|
132847
133559
|
sessions: "\u4F1A\u8BDD\u5217\u8868",
|
|
132848
133560
|
team: "\u6D3E\u7ED9\u56E2\u961F",
|
|
@@ -133033,6 +133745,14 @@ var PANEL_COMMANDS = [
|
|
|
133033
133745
|
labelKey: "panel.cmd.stop",
|
|
133034
133746
|
action: { op: "run", arg: "stop" }
|
|
133035
133747
|
},
|
|
133748
|
+
{
|
|
133749
|
+
id: "continue",
|
|
133750
|
+
category: "session",
|
|
133751
|
+
line: "/continue",
|
|
133752
|
+
kind: "continue",
|
|
133753
|
+
labelKey: "panel.cmd.continue",
|
|
133754
|
+
action: { op: "run", arg: "continue" }
|
|
133755
|
+
},
|
|
133036
133756
|
{
|
|
133037
133757
|
id: "sessions",
|
|
133038
133758
|
category: "session",
|
|
@@ -133393,6 +134113,724 @@ var makePanelController = (deps) => {
|
|
|
133393
134113
|
};
|
|
133394
134114
|
};
|
|
133395
134115
|
|
|
134116
|
+
// src/flowHost.ts
|
|
134117
|
+
var NOT_MINE = { handled: false };
|
|
134118
|
+
var handledWith = (notice) => ({ handled: true, notice });
|
|
134119
|
+
|
|
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
|
+
};
|
|
134135
|
+
var QUESTION_TEXT_MAX_CHARS = 1500;
|
|
134136
|
+
var ANSWER_ECHO_MAX_CHARS = 200;
|
|
134137
|
+
var clip2 = (text2, limit) => {
|
|
134138
|
+
const chars = Array.from(text2.trim());
|
|
134139
|
+
return chars.length <= limit ? chars.join("") : `${chars.slice(0, limit).join("")}\u2026`;
|
|
134140
|
+
};
|
|
134141
|
+
var titleOf = (item, index, total, t) => {
|
|
134142
|
+
const base = item.kind === "plan" ? t("question.planTitle") : t("question.title");
|
|
134143
|
+
return total > 1 ? `${base} ${String(index + 1)}/${String(total)}` : base;
|
|
134144
|
+
};
|
|
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"),
|
|
134149
|
+
QUESTION_TEXT_MAX_CHARS
|
|
134150
|
+
);
|
|
134151
|
+
var headingOf = (item) => clip2(
|
|
134152
|
+
[item.header, item.question].filter((part) => part.trim() !== "").join("\n"),
|
|
134153
|
+
QUESTION_TEXT_MAX_CHARS
|
|
134154
|
+
);
|
|
134155
|
+
var questionCardOf = (item, index, total, t) => ({
|
|
134156
|
+
tone: "ask",
|
|
134157
|
+
title: titleOf(item, index, total, t),
|
|
134158
|
+
heading: headingOf(item),
|
|
134159
|
+
body: bodyOf(item, t),
|
|
134160
|
+
note: item.allowFreeform ? t("question.freeformHint") : null,
|
|
134161
|
+
rows: [
|
|
134162
|
+
...item.options.map((option) => {
|
|
134163
|
+
const ticked = item.multiSelect && item.selected.includes(option.value);
|
|
134164
|
+
return [
|
|
134165
|
+
{
|
|
134166
|
+
label: ticked ? `\u2705 ${optionLabelOf(item, option, t)}` : optionLabelOf(item, option, t),
|
|
134167
|
+
token: option.token,
|
|
134168
|
+
style: ticked ? "primary" : "default"
|
|
134169
|
+
}
|
|
134170
|
+
];
|
|
134171
|
+
}),
|
|
134172
|
+
...item.multiSelect && item.submitToken !== void 0 ? [[{ label: t("question.submit"), token: item.submitToken, style: "primary" }]] : []
|
|
134173
|
+
]
|
|
134174
|
+
});
|
|
134175
|
+
var questionSettledCardOf = (item, index, total, ending, t) => ({
|
|
134176
|
+
tone: "done",
|
|
134177
|
+
title: titleOf(item, index, total, t),
|
|
134178
|
+
heading: headingOf(item),
|
|
134179
|
+
body: "",
|
|
134180
|
+
note: item.answer !== void 0 ? t("question.answered", { answer: item.answerLabel ?? "" }) : ending === "cancelled" ? t("question.cancelled") : t("question.elsewhere"),
|
|
134181
|
+
rows: []
|
|
134182
|
+
});
|
|
134183
|
+
var questionPlainText = (item, index, total, t) => [
|
|
134184
|
+
titleOf(item, index, total, t),
|
|
134185
|
+
headingOf(item),
|
|
134186
|
+
...item.options.map((option, position) => {
|
|
134187
|
+
const description = optionDescriptionOf(item, option, t);
|
|
134188
|
+
return `${String(position + 1)}. ${optionLabelOf(item, option, t)}${description === "" ? "" : ` \u2014 ${description}`}`;
|
|
134189
|
+
}),
|
|
134190
|
+
"",
|
|
134191
|
+
item.multiSelect ? t("question.replyMany") : t("question.replyOne"),
|
|
134192
|
+
...item.allowFreeform ? [t("question.freeformHint")] : []
|
|
134193
|
+
].join("\n");
|
|
134194
|
+
var parseNumberedReply = (item, text2, t) => {
|
|
134195
|
+
const trimmed = text2.trim();
|
|
134196
|
+
const pattern = item.multiSelect ? /^\d+(?:[\s,,、]+\d+)*$/ : /^\d+$/;
|
|
134197
|
+
if (!pattern.test(trimmed)) return null;
|
|
134198
|
+
const numbers = [...new Set(trimmed.split(/[\s,,、]+/).map(Number))];
|
|
134199
|
+
const picked = numbers.map((number) => item.options[number - 1]);
|
|
134200
|
+
if (picked.some((option) => option === void 0)) return null;
|
|
134201
|
+
const options = item.options.filter((option) => picked.includes(option));
|
|
134202
|
+
if (item.multiSelect) {
|
|
134203
|
+
return {
|
|
134204
|
+
answer: options.map((option) => option.value),
|
|
134205
|
+
label: options.map((option) => optionLabelOf(item, option, t)).join(", ")
|
|
134206
|
+
};
|
|
134207
|
+
}
|
|
134208
|
+
const only = options[0];
|
|
134209
|
+
return only === void 0 ? null : { answer: only.value, label: optionLabelOf(item, only, t) };
|
|
134210
|
+
};
|
|
134211
|
+
|
|
134212
|
+
// src/questionFlow.ts
|
|
134213
|
+
var makeQuestionFlow = (host) => {
|
|
134214
|
+
const { t } = host;
|
|
134215
|
+
const recordOf = (requestId) => questionsOf(host.state())[requestId];
|
|
134216
|
+
const itemOf = (requestId, questionId) => recordOf(requestId)?.items.find((item) => item.id === questionId);
|
|
134217
|
+
const hasButtons = (chatKey) => host.target(chatKey)?.adapter.choiceCards !== void 0;
|
|
134218
|
+
const redraw = async (requestId, questionId) => {
|
|
134219
|
+
const record = recordOf(requestId);
|
|
134220
|
+
const item = itemOf(requestId, questionId);
|
|
134221
|
+
const target = record === void 0 ? void 0 : host.target(record.chatKey);
|
|
134222
|
+
if (record === void 0 || item?.cardId === void 0 || target === void 0) return;
|
|
134223
|
+
if (record.resolved || item.answer !== void 0) return;
|
|
134224
|
+
try {
|
|
134225
|
+
await target.adapter.choiceCards?.update(
|
|
134226
|
+
target.binding,
|
|
134227
|
+
item.cardId,
|
|
134228
|
+
questionCardOf(item, record.items.indexOf(item), record.items.length, t)
|
|
134229
|
+
);
|
|
134230
|
+
} catch (error) {
|
|
134231
|
+
host.warn("chat could not update the question card", error);
|
|
134232
|
+
}
|
|
134233
|
+
};
|
|
134234
|
+
const freeze = async (requestId, questionId) => {
|
|
134235
|
+
const record = recordOf(requestId);
|
|
134236
|
+
const item = itemOf(requestId, questionId);
|
|
134237
|
+
const target = record === void 0 ? void 0 : host.target(record.chatKey);
|
|
134238
|
+
if (record === void 0 || item?.cardId === void 0 || target === void 0) return;
|
|
134239
|
+
try {
|
|
134240
|
+
await target.adapter.choiceCards?.update(
|
|
134241
|
+
target.binding,
|
|
134242
|
+
item.cardId,
|
|
134243
|
+
questionSettledCardOf(
|
|
134244
|
+
item,
|
|
134245
|
+
record.items.indexOf(item),
|
|
134246
|
+
record.items.length,
|
|
134247
|
+
record.ending ?? "here",
|
|
134248
|
+
t
|
|
134249
|
+
)
|
|
134250
|
+
);
|
|
134251
|
+
} catch (error) {
|
|
134252
|
+
host.warn("chat could not update the question card", error);
|
|
134253
|
+
}
|
|
134254
|
+
};
|
|
134255
|
+
const sayNext = async (requestId) => {
|
|
134256
|
+
const record = recordOf(requestId);
|
|
134257
|
+
if (record === void 0 || record.resolved) return;
|
|
134258
|
+
const item = record.items.find(isUnanswered);
|
|
134259
|
+
if (item === void 0) return;
|
|
134260
|
+
await host.say(
|
|
134261
|
+
record.chatKey,
|
|
134262
|
+
questionPlainText(item, record.items.indexOf(item), record.items.length, t)
|
|
134263
|
+
);
|
|
134264
|
+
};
|
|
134265
|
+
const post = async (requestId) => {
|
|
134266
|
+
const first = recordOf(requestId);
|
|
134267
|
+
const target = first === void 0 ? void 0 : host.target(first.chatKey);
|
|
134268
|
+
if (first === void 0 || target === void 0) return;
|
|
134269
|
+
const cards = target.adapter.choiceCards;
|
|
134270
|
+
if (cards === void 0) {
|
|
134271
|
+
await sayNext(requestId);
|
|
134272
|
+
return;
|
|
134273
|
+
}
|
|
134274
|
+
for (const questionId of first.items.map((item) => item.id)) {
|
|
134275
|
+
const record = recordOf(requestId);
|
|
134276
|
+
const item = itemOf(requestId, questionId);
|
|
134277
|
+
if (record === void 0 || record.resolved || item === void 0) return;
|
|
134278
|
+
try {
|
|
134279
|
+
const cardId = await cards.open(
|
|
134280
|
+
target.binding,
|
|
134281
|
+
questionCardOf(item, record.items.indexOf(item), record.items.length, t)
|
|
134282
|
+
);
|
|
134283
|
+
if (cardId === null) continue;
|
|
134284
|
+
host.commit("question-carded", { requestId, questionId, cardId });
|
|
134285
|
+
const after = recordOf(requestId);
|
|
134286
|
+
if (after?.resolved === true || itemOf(requestId, questionId)?.answer !== void 0) {
|
|
134287
|
+
await freeze(requestId, questionId);
|
|
134288
|
+
}
|
|
134289
|
+
} catch (error) {
|
|
134290
|
+
host.warn("chat could not ask the question", error);
|
|
134291
|
+
}
|
|
134292
|
+
}
|
|
134293
|
+
};
|
|
134294
|
+
const offer = (chatKey, threadId, pending) => {
|
|
134295
|
+
const target = host.target(chatKey);
|
|
134296
|
+
if (target === void 0) return;
|
|
134297
|
+
const buttons = target.adapter.choiceCards !== void 0;
|
|
134298
|
+
if (!buttons && target.adapter.answerByText === void 0) {
|
|
134299
|
+
host.warn("chat cannot show questions on this platform");
|
|
134300
|
+
return;
|
|
134301
|
+
}
|
|
134302
|
+
const fresh = pending.filter((request) => recordOf(request.requestId) === void 0);
|
|
134303
|
+
for (const request of buttons ? fresh : fresh.slice(0, 1)) {
|
|
134304
|
+
host.commit("question-offered", {
|
|
134305
|
+
chatKey,
|
|
134306
|
+
threadId,
|
|
134307
|
+
requestId: request.requestId,
|
|
134308
|
+
items: request.questions.map((question) => ({
|
|
134309
|
+
id: question.id,
|
|
134310
|
+
header: question.header,
|
|
134311
|
+
question: question.question,
|
|
134312
|
+
kind: question.kind,
|
|
134313
|
+
multiSelect: question.multiSelect,
|
|
134314
|
+
allowFreeform: question.allowFreeform,
|
|
134315
|
+
options: question.options.map((option) => ({
|
|
134316
|
+
token: host.newToken(),
|
|
134317
|
+
label: option.label,
|
|
134318
|
+
value: option.value,
|
|
134319
|
+
description: option.description,
|
|
134320
|
+
...option.planAction === void 0 ? {} : { planAction: option.planAction }
|
|
134321
|
+
})),
|
|
134322
|
+
...question.multiSelect ? { submitToken: host.newToken() } : {}
|
|
134323
|
+
}))
|
|
134324
|
+
});
|
|
134325
|
+
if (recordOf(request.requestId) === void 0) continue;
|
|
134326
|
+
host.outbound(chatKey, () => post(request.requestId));
|
|
134327
|
+
}
|
|
134328
|
+
};
|
|
134329
|
+
const answer = async (requestId, questionId, value, label) => {
|
|
134330
|
+
const before = recordOf(requestId);
|
|
134331
|
+
const item = itemOf(requestId, questionId);
|
|
134332
|
+
if (before === void 0 || before.resolved || item === void 0 || !isUnanswered(item)) {
|
|
134333
|
+
return false;
|
|
134334
|
+
}
|
|
134335
|
+
host.commit("question-answered", { requestId, questionId, answer: value, label });
|
|
134336
|
+
const after = recordOf(requestId);
|
|
134337
|
+
if (after === void 0) return false;
|
|
134338
|
+
const complete = after.items.every((entry) => !isUnanswered(entry));
|
|
134339
|
+
if (complete) host.commit("question-resolved", { requestId, ending: "here" });
|
|
134340
|
+
host.outbound(after.chatKey, () => freeze(requestId, questionId));
|
|
134341
|
+
if (!complete) {
|
|
134342
|
+
if (!hasButtons(after.chatKey)) host.outbound(after.chatKey, () => sayNext(requestId));
|
|
134343
|
+
return true;
|
|
134344
|
+
}
|
|
134345
|
+
try {
|
|
134346
|
+
await host.dispatch({
|
|
134347
|
+
type: "thread.user-input.respond",
|
|
134348
|
+
commandId: host.newId(),
|
|
134349
|
+
threadId: after.threadId,
|
|
134350
|
+
requestId,
|
|
134351
|
+
answers: Object.fromEntries(after.items.map((entry) => [entry.id, entry.answer])),
|
|
134352
|
+
createdAt: host.now()
|
|
134353
|
+
});
|
|
134354
|
+
} catch (error) {
|
|
134355
|
+
host.warn("chat could not hand the answer to the agent", error);
|
|
134356
|
+
await host.say(after.chatKey, t("question.sendFailed"));
|
|
134357
|
+
}
|
|
134358
|
+
return true;
|
|
134359
|
+
};
|
|
134360
|
+
const click = (token, operatorId) => {
|
|
134361
|
+
const found = findQuestionByToken(host.state(), token);
|
|
134362
|
+
if (found === void 0) return NOT_MINE;
|
|
134363
|
+
const { record, item } = found;
|
|
134364
|
+
if (record.resolved || !isUnanswered(item)) return handledWith(t("question.alreadyHandled"));
|
|
134365
|
+
if (!host.allowed(record.chatKey, operatorId)) return handledWith(t("reply.notAllowed"));
|
|
134366
|
+
const requestId = record.requestId;
|
|
134367
|
+
if (found.click.kind === "option") {
|
|
134368
|
+
const option = found.click.option;
|
|
134369
|
+
if (!item.multiSelect) {
|
|
134370
|
+
void answer(requestId, item.id, option.value, optionLabelOf(item, option, t));
|
|
134371
|
+
return handledWith(null);
|
|
134372
|
+
}
|
|
134373
|
+
const selected = item.selected.includes(option.value) ? item.selected.filter((value) => value !== option.value) : [...item.selected, option.value];
|
|
134374
|
+
host.commit("question-selected", { requestId, questionId: item.id, selected });
|
|
134375
|
+
host.outbound(record.chatKey, () => redraw(requestId, item.id));
|
|
134376
|
+
return handledWith(null);
|
|
134377
|
+
}
|
|
134378
|
+
const chosen = item.options.filter((option) => item.selected.includes(option.value));
|
|
134379
|
+
if (chosen.length === 0) return handledWith(t("question.pickFirst"));
|
|
134380
|
+
void answer(
|
|
134381
|
+
requestId,
|
|
134382
|
+
item.id,
|
|
134383
|
+
chosen.map((option) => option.value),
|
|
134384
|
+
chosen.map((option) => optionLabelOf(item, option, t)).join(", ")
|
|
134385
|
+
);
|
|
134386
|
+
return handledWith(null);
|
|
134387
|
+
};
|
|
134388
|
+
const answerByText = async (chatKey, threadId, text2) => {
|
|
134389
|
+
const record = openQuestionOf(host.state(), chatKey, threadId);
|
|
134390
|
+
const trimmed = text2.trim();
|
|
134391
|
+
if (record === void 0 || trimmed === "") return false;
|
|
134392
|
+
const unanswered = record.items.filter(isUnanswered);
|
|
134393
|
+
const first = unanswered[0];
|
|
134394
|
+
if (first === void 0) return false;
|
|
134395
|
+
if (!hasButtons(chatKey)) {
|
|
134396
|
+
const numbered = parseNumberedReply(first, trimmed, t);
|
|
134397
|
+
if (numbered !== null) {
|
|
134398
|
+
return answer(record.requestId, first.id, numbered.answer, numbered.label);
|
|
134399
|
+
}
|
|
134400
|
+
}
|
|
134401
|
+
const freeform = unanswered.find((item) => item.allowFreeform);
|
|
134402
|
+
if (freeform === void 0) return false;
|
|
134403
|
+
return answer(record.requestId, freeform.id, trimmed, clip2(trimmed, ANSWER_ECHO_MAX_CHARS));
|
|
134404
|
+
};
|
|
134405
|
+
const resolved = (requestId, outcome) => {
|
|
134406
|
+
const record = recordOf(requestId);
|
|
134407
|
+
if (record === void 0 || record.resolved) return;
|
|
134408
|
+
const ending = outcome === "answered" ? "elsewhere" : "cancelled";
|
|
134409
|
+
host.commit("question-resolved", { requestId, ending });
|
|
134410
|
+
for (const item of record.items) {
|
|
134411
|
+
if (isUnanswered(item)) host.outbound(record.chatKey, () => freeze(requestId, item.id));
|
|
134412
|
+
}
|
|
134413
|
+
};
|
|
134414
|
+
return { offer, click, answerByText, resolved };
|
|
134415
|
+
};
|
|
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
|
+
|
|
134556
|
+
// src/queueCards.ts
|
|
134557
|
+
var QUEUE_PREVIEW_MAX_CHARS = 200;
|
|
134558
|
+
var WAITING_STATUSES = /* @__PURE__ */ new Set(["queued", "blocked"]);
|
|
134559
|
+
var IN_FLIGHT_STATUSES = /* @__PURE__ */ new Set([
|
|
134560
|
+
"preparing",
|
|
134561
|
+
"dispatching",
|
|
134562
|
+
"reconciling"
|
|
134563
|
+
]);
|
|
134564
|
+
var WITHDRAWN_STATUSES = /* @__PURE__ */ new Set(["cancelled", "abandoned"]);
|
|
134565
|
+
var queueViewOf = (record, facts, released) => {
|
|
134566
|
+
const status = facts.intent?.status;
|
|
134567
|
+
if (status === void 0) return null;
|
|
134568
|
+
if (WITHDRAWN_STATUSES.has(status)) return { kind: "settled", outcome: "removed" };
|
|
134569
|
+
if (IN_FLIGHT_STATUSES.has(status))
|
|
134570
|
+
return record.steering ? { kind: "steering" } : { kind: "sending" };
|
|
134571
|
+
if (!WAITING_STATUSES.has(status)) {
|
|
134572
|
+
return { kind: "settled", outcome: record.steering ? "steered" : "sent" };
|
|
134573
|
+
}
|
|
134574
|
+
if (record.steering && !released) return { kind: "steering" };
|
|
134575
|
+
return { kind: "queued", running: facts.activeTurnId !== null, paused: facts.queuePaused };
|
|
134576
|
+
};
|
|
134577
|
+
var SETTLED_TITLE = {
|
|
134578
|
+
steered: "queue.steeredTitle",
|
|
134579
|
+
sent: "queue.sentTitle",
|
|
134580
|
+
removed: "queue.removedTitle"
|
|
134581
|
+
};
|
|
134582
|
+
var SETTLED_NOTE = {
|
|
134583
|
+
steered: "queue.steered",
|
|
134584
|
+
sent: "queue.sent",
|
|
134585
|
+
removed: "queue.removed"
|
|
134586
|
+
};
|
|
134587
|
+
var queueCardOf = (record, view, note, t) => {
|
|
134588
|
+
const body = clip2(record.text, QUEUE_PREVIEW_MAX_CHARS);
|
|
134589
|
+
if (view.kind === "steering") {
|
|
134590
|
+
return {
|
|
134591
|
+
tone: "queue",
|
|
134592
|
+
title: t("queue.steeringTitle"),
|
|
134593
|
+
heading: null,
|
|
134594
|
+
body,
|
|
134595
|
+
note: t("queue.steering"),
|
|
134596
|
+
rows: []
|
|
134597
|
+
};
|
|
134598
|
+
}
|
|
134599
|
+
if (view.kind === "sending") {
|
|
134600
|
+
return {
|
|
134601
|
+
tone: "queue",
|
|
134602
|
+
title: t("queue.sendingTitle"),
|
|
134603
|
+
heading: null,
|
|
134604
|
+
body,
|
|
134605
|
+
note: t("queue.sending"),
|
|
134606
|
+
rows: []
|
|
134607
|
+
};
|
|
134608
|
+
}
|
|
134609
|
+
if (view.kind === "settled") {
|
|
134610
|
+
return {
|
|
134611
|
+
tone: "done",
|
|
134612
|
+
title: t(SETTLED_TITLE[view.outcome]),
|
|
134613
|
+
heading: null,
|
|
134614
|
+
body,
|
|
134615
|
+
note: t(SETTLED_NOTE[view.outcome]),
|
|
134616
|
+
rows: []
|
|
134617
|
+
};
|
|
134618
|
+
}
|
|
134619
|
+
const steerable = view.running && !view.paused;
|
|
134620
|
+
const state = view.paused ? t("queue.waitPaused") : view.running ? t("queue.waitRunning") : t("queue.waitIdle");
|
|
134621
|
+
return {
|
|
134622
|
+
tone: "queue",
|
|
134623
|
+
title: t("queue.title"),
|
|
134624
|
+
heading: null,
|
|
134625
|
+
body,
|
|
134626
|
+
note: note ?? state,
|
|
134627
|
+
rows: [
|
|
134628
|
+
[
|
|
134629
|
+
...steerable ? [{ label: t("queue.steer"), token: record.steerToken, style: "primary" }] : [],
|
|
134630
|
+
{ label: t("queue.withdraw"), token: record.withdrawToken, style: "default" }
|
|
134631
|
+
],
|
|
134632
|
+
...view.paused ? [[{ label: t("queue.resume"), token: record.resumeToken, style: "primary" }]] : []
|
|
134633
|
+
]
|
|
134634
|
+
};
|
|
134635
|
+
};
|
|
134636
|
+
var queueViewKey = (view, note) => JSON.stringify([view, note]);
|
|
134637
|
+
|
|
134638
|
+
// src/queueFlow.ts
|
|
134639
|
+
var LIVE_STATUSES = /* @__PURE__ */ new Set([
|
|
134640
|
+
"queued",
|
|
134641
|
+
"preparing",
|
|
134642
|
+
"dispatching",
|
|
134643
|
+
"running",
|
|
134644
|
+
"reconciling",
|
|
134645
|
+
"blocked"
|
|
134646
|
+
]);
|
|
134647
|
+
var makeQueueFlow = (host, deps) => {
|
|
134648
|
+
const { t } = host;
|
|
134649
|
+
const shown = /* @__PURE__ */ new Map();
|
|
134650
|
+
const notes = /* @__PURE__ */ new Map();
|
|
134651
|
+
const recordOf = (intentId) => queueOf(host.state())[intentId];
|
|
134652
|
+
const factsOf = (execution, intentId) => ({
|
|
134653
|
+
activeTurnId: execution.activeTurnId,
|
|
134654
|
+
queuePaused: execution.queuePaused,
|
|
134655
|
+
intent: execution.intents.find((intent) => intent.intentId === intentId)
|
|
134656
|
+
});
|
|
134657
|
+
const noteQueued = async (chatKey, threadId, messageId, text2) => {
|
|
134658
|
+
const target = host.target(chatKey);
|
|
134659
|
+
if (target === void 0) return;
|
|
134660
|
+
let execution;
|
|
134661
|
+
try {
|
|
134662
|
+
execution = await deps.readExecution(threadId);
|
|
134663
|
+
} catch (error) {
|
|
134664
|
+
host.warn("chat could not read the turn queue", error);
|
|
134665
|
+
return;
|
|
134666
|
+
}
|
|
134667
|
+
const intent = execution?.intents.find((entry) => entry.messageId === messageId);
|
|
134668
|
+
if (execution === null || intent === void 0 || intent.status !== "queued") return;
|
|
134669
|
+
const waiting = execution.activeTurnId !== null || execution.queuePaused || execution.intents.some(
|
|
134670
|
+
(other) => other.intentId !== intent.intentId && LIVE_STATUSES.has(other.status) && other.sequence < intent.sequence
|
|
134671
|
+
);
|
|
134672
|
+
if (!waiting) return;
|
|
134673
|
+
const cards = target.adapter.choiceCards;
|
|
134674
|
+
if (cards === void 0) {
|
|
134675
|
+
await host.say(chatKey, t("queue.plainNotice"));
|
|
134676
|
+
return;
|
|
134677
|
+
}
|
|
134678
|
+
const intentId = intent.intentId;
|
|
134679
|
+
host.commit("queue-offered", {
|
|
134680
|
+
chatKey,
|
|
134681
|
+
threadId,
|
|
134682
|
+
intentId,
|
|
134683
|
+
text: text2,
|
|
134684
|
+
steerToken: host.newToken(),
|
|
134685
|
+
withdrawToken: host.newToken(),
|
|
134686
|
+
resumeToken: host.newToken()
|
|
134687
|
+
});
|
|
134688
|
+
host.outbound(chatKey, async () => {
|
|
134689
|
+
const record = recordOf(intentId);
|
|
134690
|
+
if (record === void 0) return;
|
|
134691
|
+
try {
|
|
134692
|
+
const latest = await deps.readExecution(threadId) ?? execution;
|
|
134693
|
+
const view = queueViewOf(record, factsOf(latest, intentId), false) ?? {
|
|
134694
|
+
kind: "queued",
|
|
134695
|
+
running: latest.activeTurnId !== null,
|
|
134696
|
+
paused: latest.queuePaused
|
|
134697
|
+
};
|
|
134698
|
+
if (view.kind === "settled") {
|
|
134699
|
+
host.commit("queue-settled", { intentId, outcome: view.outcome });
|
|
134700
|
+
return;
|
|
134701
|
+
}
|
|
134702
|
+
const cardId = await cards.open(target.binding, queueCardOf(record, view, null, t));
|
|
134703
|
+
if (cardId === null) return;
|
|
134704
|
+
host.commit("queue-carded", { intentId, cardId });
|
|
134705
|
+
shown.set(intentId, queueViewKey(view, null));
|
|
134706
|
+
} catch (error) {
|
|
134707
|
+
host.warn("chat could not show the queued message", error);
|
|
134708
|
+
}
|
|
134709
|
+
});
|
|
134710
|
+
};
|
|
134711
|
+
const redraw = async (intentId, released) => {
|
|
134712
|
+
const record = recordOf(intentId);
|
|
134713
|
+
const target = record === void 0 ? void 0 : host.target(record.chatKey);
|
|
134714
|
+
if (record === void 0 || record.settled !== void 0 || target === void 0) return;
|
|
134715
|
+
if (record.cardId === void 0) return;
|
|
134716
|
+
let execution;
|
|
134717
|
+
try {
|
|
134718
|
+
execution = await deps.readExecution(record.threadId);
|
|
134719
|
+
} catch (error) {
|
|
134720
|
+
host.warn("chat could not read the turn queue", error);
|
|
134721
|
+
return;
|
|
134722
|
+
}
|
|
134723
|
+
if (execution === null) return;
|
|
134724
|
+
const view = queueViewOf(record, factsOf(execution, intentId), released);
|
|
134725
|
+
if (view === null) return;
|
|
134726
|
+
if (view.kind === "queued" && record.steering) {
|
|
134727
|
+
host.commit("queue-steering", { intentId, steering: false });
|
|
134728
|
+
notes.set(intentId, t("queue.steerFailed"));
|
|
134729
|
+
}
|
|
134730
|
+
if (view.kind === "settled") notes.delete(intentId);
|
|
134731
|
+
await paint(record, view);
|
|
134732
|
+
if (view.kind === "settled") {
|
|
134733
|
+
host.commit("queue-settled", { intentId, outcome: view.outcome });
|
|
134734
|
+
shown.delete(intentId);
|
|
134735
|
+
}
|
|
134736
|
+
};
|
|
134737
|
+
const paint = async (record, view) => {
|
|
134738
|
+
const target = host.target(record.chatKey);
|
|
134739
|
+
const cards = target?.adapter.choiceCards;
|
|
134740
|
+
if (target === void 0 || cards === void 0 || record.cardId === void 0) return;
|
|
134741
|
+
const note = notes.get(record.intentId) ?? null;
|
|
134742
|
+
const key = queueViewKey(view, note);
|
|
134743
|
+
if (shown.get(record.intentId) === key) return;
|
|
134744
|
+
try {
|
|
134745
|
+
await cards.update(target.binding, record.cardId, queueCardOf(record, view, note, t));
|
|
134746
|
+
shown.set(record.intentId, key);
|
|
134747
|
+
} catch (error) {
|
|
134748
|
+
host.warn("chat could not update the queued message card", error);
|
|
134749
|
+
}
|
|
134750
|
+
};
|
|
134751
|
+
const refresh = (threadId, released = null) => {
|
|
134752
|
+
for (const record of openQueueOfThread(host.state(), threadId)) {
|
|
134753
|
+
host.outbound(record.chatKey, () => redraw(record.intentId, record.intentId === released));
|
|
134754
|
+
}
|
|
134755
|
+
};
|
|
134756
|
+
const run = async (record, work, failure2) => {
|
|
134757
|
+
notes.delete(record.intentId);
|
|
134758
|
+
let ok = false;
|
|
134759
|
+
try {
|
|
134760
|
+
ok = await work();
|
|
134761
|
+
} catch (error) {
|
|
134762
|
+
host.warn("chat could not change the queue", error);
|
|
134763
|
+
}
|
|
134764
|
+
if (!ok) notes.set(record.intentId, failure2);
|
|
134765
|
+
host.outbound(record.chatKey, () => redraw(record.intentId, false));
|
|
134766
|
+
};
|
|
134767
|
+
const steer = async (record) => {
|
|
134768
|
+
const execution = await deps.readExecution(record.threadId);
|
|
134769
|
+
if (execution?.activeTurnId == null) return false;
|
|
134770
|
+
host.commit("queue-steering", { intentId: record.intentId, steering: true });
|
|
134771
|
+
try {
|
|
134772
|
+
await host.dispatch({
|
|
134773
|
+
type: "thread.turn-intent.run",
|
|
134774
|
+
commandId: host.newId(),
|
|
134775
|
+
threadId: record.threadId,
|
|
134776
|
+
intentId: record.intentId,
|
|
134777
|
+
mode: "steer",
|
|
134778
|
+
expectedTurnId: execution.activeTurnId,
|
|
134779
|
+
createdAt: host.now()
|
|
134780
|
+
});
|
|
134781
|
+
return true;
|
|
134782
|
+
} catch (error) {
|
|
134783
|
+
host.commit("queue-steering", { intentId: record.intentId, steering: false });
|
|
134784
|
+
throw error;
|
|
134785
|
+
}
|
|
134786
|
+
};
|
|
134787
|
+
const click = (token, operatorId) => {
|
|
134788
|
+
const found = findQueueByToken(host.state(), token);
|
|
134789
|
+
if (found === void 0) return NOT_MINE;
|
|
134790
|
+
const { record, op } = found;
|
|
134791
|
+
if (record.settled !== void 0) return handledWith(t("queue.alreadyHandled"));
|
|
134792
|
+
if (!host.allowed(record.chatKey, operatorId)) return handledWith(t("reply.notAllowed"));
|
|
134793
|
+
if (op === "steer") {
|
|
134794
|
+
if (record.steering) return handledWith(t("queue.alreadyHandled"));
|
|
134795
|
+
void run(record, () => steer(record), t("queue.steerFailed"));
|
|
134796
|
+
} else if (op === "withdraw") {
|
|
134797
|
+
void run(
|
|
134798
|
+
record,
|
|
134799
|
+
async () => {
|
|
134800
|
+
await host.dispatch({
|
|
134801
|
+
type: "thread.turn-intent.cancel",
|
|
134802
|
+
commandId: host.newId(),
|
|
134803
|
+
threadId: record.threadId,
|
|
134804
|
+
intentId: record.intentId,
|
|
134805
|
+
// 只撤还没开始发送的:已经在路上的由宿主拒绝,而不是被当成「已放弃」。
|
|
134806
|
+
expectedStatus: "queued",
|
|
134807
|
+
expectedDeliveryCertainty: "not-dispatched",
|
|
134808
|
+
createdAt: host.now()
|
|
134809
|
+
});
|
|
134810
|
+
return true;
|
|
134811
|
+
},
|
|
134812
|
+
t("queue.withdrawFailed")
|
|
134813
|
+
);
|
|
134814
|
+
} else {
|
|
134815
|
+
void run(
|
|
134816
|
+
record,
|
|
134817
|
+
async () => {
|
|
134818
|
+
await host.dispatch({
|
|
134819
|
+
type: "thread.turn-queue.resume",
|
|
134820
|
+
commandId: host.newId(),
|
|
134821
|
+
threadId: record.threadId,
|
|
134822
|
+
createdAt: host.now()
|
|
134823
|
+
});
|
|
134824
|
+
return true;
|
|
134825
|
+
},
|
|
134826
|
+
t("queue.resumeFailed")
|
|
134827
|
+
);
|
|
134828
|
+
}
|
|
134829
|
+
return handledWith(null);
|
|
134830
|
+
};
|
|
134831
|
+
return { noteQueued, refresh, click };
|
|
134832
|
+
};
|
|
134833
|
+
|
|
133396
134834
|
// src/telegramApi.ts
|
|
133397
134835
|
var API_BASE = "https://api.telegram.org";
|
|
133398
134836
|
var POLL_TIMEOUT_SECONDS = 45;
|
|
@@ -133411,28 +134849,19 @@ var TelegramApiError = class extends Error {
|
|
|
133411
134849
|
var isNotModified = (error) => error instanceof TelegramApiError && error.code === 400 && error.message.toLowerCase().includes("message is not modified");
|
|
133412
134850
|
var PHOTO_NAME = "photo.jpg";
|
|
133413
134851
|
var PHOTO_MIME = "image/jpeg";
|
|
133414
|
-
var
|
|
133415
|
-
|
|
133416
|
-
const known = {
|
|
133417
|
-
png: "image/png",
|
|
133418
|
-
jpg: "image/jpeg",
|
|
133419
|
-
jpeg: "image/jpeg",
|
|
133420
|
-
gif: "image/gif",
|
|
133421
|
-
webp: "image/webp",
|
|
133422
|
-
pdf: "application/pdf",
|
|
133423
|
-
txt: "text/plain",
|
|
133424
|
-
md: "text/markdown",
|
|
133425
|
-
json: "application/json",
|
|
133426
|
-
csv: "text/csv"
|
|
133427
|
-
};
|
|
133428
|
-
return known[ext] ?? "application/octet-stream";
|
|
133429
|
-
};
|
|
133430
|
-
var resourceOf = (message) => {
|
|
134852
|
+
var withSize = (size) => size === void 0 ? {} : { sizeBytes: size };
|
|
134853
|
+
var resourceOf2 = (message) => {
|
|
133431
134854
|
const photo = message.photo;
|
|
133432
134855
|
if (photo !== void 0 && photo.length > 0) {
|
|
133433
134856
|
const largest = photo[photo.length - 1];
|
|
133434
134857
|
if (largest !== void 0) {
|
|
133435
|
-
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
|
+
};
|
|
133436
134865
|
}
|
|
133437
134866
|
}
|
|
133438
134867
|
const document2 = message.document;
|
|
@@ -133441,12 +134870,38 @@ var resourceOf = (message) => {
|
|
|
133441
134870
|
return {
|
|
133442
134871
|
fileId: document2.file_id,
|
|
133443
134872
|
name,
|
|
133444
|
-
mimeType: document2.mime_type ??
|
|
133445
|
-
kind: "file"
|
|
134873
|
+
mimeType: document2.mime_type ?? mimeFromName(name),
|
|
134874
|
+
kind: "file",
|
|
134875
|
+
...withSize(document2.file_size)
|
|
134876
|
+
};
|
|
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)
|
|
133446
134894
|
};
|
|
133447
134895
|
}
|
|
133448
134896
|
return null;
|
|
133449
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
|
+
}
|
|
134902
|
+
return null;
|
|
134903
|
+
};
|
|
134904
|
+
var TELEGRAM_DOWNLOAD_LIMIT_BYTES = 20 * 1024 * 1024;
|
|
133450
134905
|
var splitForTelegram = (text2, limit = TELEGRAM_TEXT_MAX_CHARS) => splitMessage(text2, limit);
|
|
133451
134906
|
var nextOffset = (updates, current) => updates.reduce((offset, update) => Math.max(offset, update.update_id + 1), current);
|
|
133452
134907
|
var commandForBot = (text2, botUsername) => {
|
|
@@ -133476,6 +134931,24 @@ var makeTelegramApi = (input) => {
|
|
|
133476
134931
|
}
|
|
133477
134932
|
return envelope.result;
|
|
133478
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
|
+
};
|
|
133479
134952
|
const keyboard = (rows) => ({
|
|
133480
134953
|
inline_keyboard: rows.map(
|
|
133481
134954
|
(row) => row.map((button) => ({ text: button.text, callback_data: button.data }))
|
|
@@ -133544,41 +135017,38 @@ var makeTelegramApi = (input) => {
|
|
|
133544
135017
|
callback_query_id: callbackQueryId,
|
|
133545
135018
|
...text2 === void 0 ? {} : { text: text2 }
|
|
133546
135019
|
}),
|
|
133547
|
-
sendPhoto:
|
|
133548
|
-
|
|
133549
|
-
|
|
133550
|
-
form.set(
|
|
133551
|
-
"photo",
|
|
133552
|
-
new Blob([new Uint8Array(photo.bytes)], { type: photo.mimeType }),
|
|
133553
|
-
photo.name
|
|
133554
|
-
);
|
|
133555
|
-
const response = await input.http(`${API_BASE}/bot${input.token}/sendPhoto`, {
|
|
133556
|
-
method: "POST",
|
|
133557
|
-
body: form
|
|
133558
|
-
});
|
|
133559
|
-
const envelope = await response.json();
|
|
133560
|
-
if (envelope.ok !== true) {
|
|
133561
|
-
throw new TelegramApiError(
|
|
133562
|
-
"sendPhoto",
|
|
133563
|
-
envelope.error_code,
|
|
133564
|
-
envelope.description ?? "no reason"
|
|
133565
|
-
);
|
|
133566
|
-
}
|
|
133567
|
-
},
|
|
135020
|
+
sendPhoto: (chatId, photo) => upload("sendPhoto", "photo", chatId, photo),
|
|
135021
|
+
/** 以文件形式发(不压缩、不限尺寸比例):什么类型都行,音频与视频也走这条。 */
|
|
135022
|
+
sendDocument: (chatId, file) => upload("sendDocument", "document", chatId, file),
|
|
133568
135023
|
/** 取一个文件的字节。要两步:先换路径,再按路径下载。 */
|
|
133569
|
-
fetchFile: async (fileId) => {
|
|
133570
|
-
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
|
+
});
|
|
133571
135033
|
const path = file.file_path;
|
|
133572
135034
|
if (path === void 0) throw new TelegramApiError("getFile", void 0, "no file_path");
|
|
133573
135035
|
const response = await input.http(`${API_BASE}/file/bot${input.token}/${path}`);
|
|
133574
135036
|
if (!response.ok) {
|
|
133575
135037
|
throw new TelegramApiError("file", response.status, "download failed");
|
|
133576
135038
|
}
|
|
133577
|
-
return
|
|
135039
|
+
return readBodyLimited(response, maxBytes);
|
|
133578
135040
|
}
|
|
133579
135041
|
};
|
|
133580
135042
|
};
|
|
133581
135043
|
|
|
135044
|
+
// src/telegramChoice.ts
|
|
135045
|
+
var renderChoiceCard = (card) => ({
|
|
135046
|
+
text: [card.title, card.heading ?? "", card.body, card.note ?? ""].filter((part) => part !== "").join("\n\n"),
|
|
135047
|
+
rows: card.rows.filter((row) => row.length > 0).map(
|
|
135048
|
+
(row) => row.map((button) => ({ text: button.label, data: button.token }))
|
|
135049
|
+
)
|
|
135050
|
+
});
|
|
135051
|
+
|
|
133582
135052
|
// src/telegramPanel.ts
|
|
133583
135053
|
var PANEL_DATA_PREFIX = "pn:";
|
|
133584
135054
|
var renderPanel = (card, version) => {
|
|
@@ -133609,7 +135079,7 @@ var parsePanelData = (data) => {
|
|
|
133609
135079
|
|
|
133610
135080
|
// src/plainTurnText.ts
|
|
133611
135081
|
var RUNNING_LABEL_MAX_CHARS = 200;
|
|
133612
|
-
var
|
|
135082
|
+
var clip3 = (text2, max) => {
|
|
133613
135083
|
const chars = Array.from(text2);
|
|
133614
135084
|
return chars.length <= max ? text2 : `${chars.slice(0, max - 1).join("")}\u2026`;
|
|
133615
135085
|
};
|
|
@@ -133618,7 +135088,7 @@ var turnTextParts = (view, t) => {
|
|
|
133618
135088
|
if (view.omitted > 0) names.unshift(t("card.step.omitted", { count: view.omitted }));
|
|
133619
135089
|
const headLines = [view.title === "" ? t("card.untitled") : view.title];
|
|
133620
135090
|
if (names.length > 0) headLines.push(names.join(" \u2192 "));
|
|
133621
|
-
if (view.running !== null) headLines.push(`\u{1F527} ${
|
|
135091
|
+
if (view.running !== null) headLines.push(`\u{1F527} ${clip3(view.running, RUNNING_LABEL_MAX_CHARS)}`);
|
|
133622
135092
|
const body = view.body.trim();
|
|
133623
135093
|
const footLines = [];
|
|
133624
135094
|
if (view.status === "working") {
|
|
@@ -133695,14 +135165,14 @@ var tokenOf = (binding) => binding.values["telegramToken"] ?? "";
|
|
|
133695
135165
|
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
133696
135166
|
var makeTelegramAdapter = (deps) => {
|
|
133697
135167
|
const apis = /* @__PURE__ */ new Map();
|
|
133698
|
-
const
|
|
135168
|
+
const sleep2 = deps.sleep ?? defaultSleep;
|
|
133699
135169
|
const t = deps.translate;
|
|
133700
135170
|
const withFloodWait = async (run) => {
|
|
133701
135171
|
try {
|
|
133702
135172
|
return await run();
|
|
133703
135173
|
} catch (error) {
|
|
133704
135174
|
if (error instanceof TelegramApiError && error.code === 429 && error.retryAfterSeconds !== void 0 && error.retryAfterSeconds <= FLOOD_WAIT_MAX_SECONDS) {
|
|
133705
|
-
await
|
|
135175
|
+
await sleep2(error.retryAfterSeconds * 1e3);
|
|
133706
135176
|
return run();
|
|
133707
135177
|
}
|
|
133708
135178
|
throw error;
|
|
@@ -133786,29 +135256,45 @@ var makeTelegramAdapter = (deps) => {
|
|
|
133786
135256
|
const callback = update.callback_query;
|
|
133787
135257
|
if (callback?.data !== void 0) {
|
|
133788
135258
|
const operatorId = callback.from === void 0 ? null : String(callback.from.id);
|
|
133789
|
-
const notice = callback.data.startsWith(PANEL_DATA_PREFIX) ? panelClick(callback, operatorId, handlers) : handlers.
|
|
135259
|
+
const notice = callback.data.startsWith(PANEL_DATA_PREFIX) ? panelClick(callback, operatorId, handlers) : handlers.onButton(callback.data, operatorId);
|
|
133790
135260
|
await api.answerCallback(callback.id, notice ?? void 0).catch(() => void 0);
|
|
133791
135261
|
continue;
|
|
133792
135262
|
}
|
|
133793
135263
|
const message = update.message;
|
|
133794
135264
|
if (message === void 0) continue;
|
|
133795
|
-
const resource =
|
|
135265
|
+
const resource = resourceOf2(message);
|
|
133796
135266
|
const text2 = commandForBot(
|
|
133797
135267
|
(message.text ?? message.caption ?? "").trim(),
|
|
133798
135268
|
botUsername
|
|
133799
135269
|
);
|
|
133800
|
-
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
|
+
}
|
|
133801
135284
|
handlers.onMessage({
|
|
133802
135285
|
messageId: String(message.message_id),
|
|
133803
135286
|
chatId: String(message.chat.id),
|
|
133804
135287
|
senderId: message.from === void 0 ? null : String(message.from.id),
|
|
133805
135288
|
text: text2,
|
|
133806
|
-
|
|
133807
|
-
|
|
133808
|
-
|
|
133809
|
-
|
|
133810
|
-
|
|
133811
|
-
|
|
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
|
+
]
|
|
133812
135298
|
});
|
|
133813
135299
|
}
|
|
133814
135300
|
} catch (error) {
|
|
@@ -133835,8 +135321,23 @@ var makeTelegramAdapter = (deps) => {
|
|
|
133835
135321
|
},
|
|
133836
135322
|
sendImage: async (binding, image2) => {
|
|
133837
135323
|
const api = await apiFor(binding);
|
|
133838
|
-
|
|
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
|
+
}
|
|
133839
135334
|
},
|
|
135335
|
+
sendFile: async (binding, file) => {
|
|
135336
|
+
const api = await apiFor(binding);
|
|
135337
|
+
await api.sendDocument(binding.chatId, file);
|
|
135338
|
+
},
|
|
135339
|
+
// 单张照片 10 MB、单个文件 50 MB。这两个数来自 Bot API 文档的记忆,本仓库里没有核对过。
|
|
135340
|
+
outboundLimits: { imageBytes: 10 * 1024 * 1024, fileBytes: 50 * 1024 * 1024 },
|
|
133840
135341
|
askApproval: async (binding, request) => {
|
|
133841
135342
|
const api = await apiFor(binding);
|
|
133842
135343
|
return api.sendButtons(binding.chatId, request.body, request.options);
|
|
@@ -133906,9 +135407,24 @@ ${line}`
|
|
|
133906
135407
|
panelKeyboards.set(key, { version, actions: rendered.actions });
|
|
133907
135408
|
}
|
|
133908
135409
|
},
|
|
135410
|
+
choiceCards: {
|
|
135411
|
+
open: async (binding, card) => {
|
|
135412
|
+
const api = await apiFor(binding);
|
|
135413
|
+
const rendered = renderChoiceCard(card);
|
|
135414
|
+
return withFloodWait(() => api.sendOne(binding.chatId, rendered.text, rendered.rows));
|
|
135415
|
+
},
|
|
135416
|
+
update: async (binding, cardId, card) => {
|
|
135417
|
+
const api = await apiFor(binding);
|
|
135418
|
+
const rendered = renderChoiceCard(card);
|
|
135419
|
+
await withFloodWait(
|
|
135420
|
+
() => api.editMessage(binding.chatId, cardId, rendered.text, rendered.rows)
|
|
135421
|
+
);
|
|
135422
|
+
}
|
|
135423
|
+
},
|
|
135424
|
+
downloadLimitBytes: TELEGRAM_DOWNLOAD_LIMIT_BYTES,
|
|
133909
135425
|
fetchResource: async (binding, input) => {
|
|
133910
135426
|
const api = await apiFor(binding);
|
|
133911
|
-
return api.fetchFile(input.resource.key);
|
|
135427
|
+
return api.fetchFile(input.resource.key, input.maxBytes);
|
|
133912
135428
|
}
|
|
133913
135429
|
};
|
|
133914
135430
|
};
|
|
@@ -134137,34 +135653,177 @@ var makeTurnCards = (deps) => {
|
|
|
134137
135653
|
};
|
|
134138
135654
|
};
|
|
134139
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
|
+
|
|
134140
135739
|
// src/wecomApi.ts
|
|
134141
135740
|
var DEFAULT_WECOM_ENDPOINT = "wss://openws.work.weixin.qq.com/wssvr/";
|
|
134142
135741
|
var PING_INTERVAL_MS = 3e4;
|
|
134143
135742
|
var MAX_MISSED_PONGS = 2;
|
|
134144
135743
|
var SUBSCRIBE_TIMEOUT_MS = 1e4;
|
|
134145
135744
|
var SEND_ACK_TIMEOUT_MS = 5e3;
|
|
135745
|
+
var UPLOAD_ACK_TIMEOUT_MS = 15e3;
|
|
134146
135746
|
var WECOM_TEXT_MAX_CHARS = 4e3;
|
|
134147
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;
|
|
134148
135753
|
var parseWecomMessage = (frame, botId) => {
|
|
134149
135754
|
const body = frame.body;
|
|
134150
135755
|
if (body === void 0) return null;
|
|
134151
135756
|
const messageId = body.msgid;
|
|
134152
135757
|
const chatId = body.chatid ?? body.from?.userid;
|
|
134153
135758
|
if (typeof messageId !== "string" || typeof chatId !== "string") return null;
|
|
134154
|
-
if (body.msgtype !== "text") return null;
|
|
134155
|
-
const raw = body.text?.content;
|
|
134156
|
-
if (typeof raw !== "string") return null;
|
|
134157
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}`));
|
|
134158
135817
|
const chatType = body.chattype ?? "single";
|
|
134159
|
-
const mentioned = ownIds.some((id) => raw.includes(`@${id}`));
|
|
134160
|
-
const text2 = ownIds.reduce((value, id) => value.replaceAll(`@${id}`, ""), raw).trim();
|
|
134161
135818
|
return {
|
|
134162
135819
|
messageId,
|
|
134163
135820
|
chatId,
|
|
134164
135821
|
senderUserId: body.from?.userid ?? null,
|
|
134165
135822
|
text: text2,
|
|
135823
|
+
resources,
|
|
134166
135824
|
requestId: frame.headers?.req_id ?? null,
|
|
134167
|
-
chatType: mentioned ? "single" : chatType
|
|
135825
|
+
chatType: mentioned ? "single" : chatType,
|
|
135826
|
+
conversation: chatType === "group" ? "group" : "single"
|
|
134168
135827
|
};
|
|
134169
135828
|
};
|
|
134170
135829
|
var splitForWecom = (text2, limit = WECOM_TEXT_MAX_CHARS) => splitMessage(text2, limit);
|
|
@@ -134173,6 +135832,7 @@ var runWecomClient = (input) => {
|
|
|
134173
135832
|
let sequence = 0;
|
|
134174
135833
|
const nextRequestId = (kind) => `${kind}_${++sequence}`;
|
|
134175
135834
|
const pendingAcks = /* @__PURE__ */ new Map();
|
|
135835
|
+
const conversations = /* @__PURE__ */ new Map();
|
|
134176
135836
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
134177
135837
|
const openStreams = /* @__PURE__ */ new Map();
|
|
134178
135838
|
const subscribeId = nextRequestId("aibot_subscribe");
|
|
@@ -134181,11 +135841,11 @@ var runWecomClient = (input) => {
|
|
|
134181
135841
|
let finished = false;
|
|
134182
135842
|
const ended = Promise.withResolvers();
|
|
134183
135843
|
const send = (frame) => input.socket.send(JSON.stringify(frame));
|
|
134184
|
-
const settleAck = (requestId, error) => {
|
|
135844
|
+
const settleAck = (requestId, error, frame) => {
|
|
134185
135845
|
const pending = pendingAcks.get(requestId);
|
|
134186
135846
|
if (pending === void 0) return;
|
|
134187
135847
|
pendingAcks.delete(requestId);
|
|
134188
|
-
if (error === null) pending.resolve();
|
|
135848
|
+
if (error === null) pending.resolve(frame);
|
|
134189
135849
|
else pending.reject(error);
|
|
134190
135850
|
};
|
|
134191
135851
|
const handleFrame = (raw) => {
|
|
@@ -134206,7 +135866,7 @@ var runWecomClient = (input) => {
|
|
|
134206
135866
|
const error = typeof frame.errcode === "number" && frame.errcode !== 0 ? new Error(`WeCom ack failed: ${String(frame.errcode)} ${frame.errmsg ?? ""}`) : null;
|
|
134207
135867
|
const stream = openStreams.get(requestId);
|
|
134208
135868
|
if (stream !== void 0 && error !== null) stream.failed = true;
|
|
134209
|
-
settleAck(requestId, error);
|
|
135869
|
+
settleAck(requestId, error, frame);
|
|
134210
135870
|
return;
|
|
134211
135871
|
}
|
|
134212
135872
|
if (frame.cmd !== "aibot_msg_callback") return;
|
|
@@ -134248,7 +135908,7 @@ var runWecomClient = (input) => {
|
|
|
134248
135908
|
}, PING_INTERVAL_MS);
|
|
134249
135909
|
void readLoop();
|
|
134250
135910
|
const subscribed = new Promise((resolve, reject) => {
|
|
134251
|
-
pendingAcks.set(subscribeId, { resolve, reject });
|
|
135911
|
+
pendingAcks.set(subscribeId, { resolve: () => resolve(), reject });
|
|
134252
135912
|
setTimeout(() => {
|
|
134253
135913
|
if (pendingAcks.delete(subscribeId)) reject(new Error("ETIMEDOUT: WeCom subscribe ack"));
|
|
134254
135914
|
}, SUBSCRIBE_TIMEOUT_MS);
|
|
@@ -134285,7 +135945,7 @@ var runWecomClient = (input) => {
|
|
|
134285
135945
|
}
|
|
134286
135946
|
const requestId = nextRequestId("aibot_send_msg");
|
|
134287
135947
|
const ack = new Promise((resolve, reject) => {
|
|
134288
|
-
pendingAcks.set(requestId, { resolve, reject });
|
|
135948
|
+
pendingAcks.set(requestId, { resolve: () => resolve(), reject });
|
|
134289
135949
|
setTimeout(() => {
|
|
134290
135950
|
if (pendingAcks.delete(requestId)) resolve();
|
|
134291
135951
|
}, SEND_ACK_TIMEOUT_MS);
|
|
@@ -134305,6 +135965,50 @@ var runWecomClient = (input) => {
|
|
|
134305
135965
|
}
|
|
134306
135966
|
}
|
|
134307
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
|
+
};
|
|
134308
136012
|
const openStream = (chatId) => {
|
|
134309
136013
|
const requestId = pendingRequests.get(chatId);
|
|
134310
136014
|
if (requestId === void 0) return null;
|
|
@@ -134336,6 +136040,10 @@ var runWecomClient = (input) => {
|
|
|
134336
136040
|
};
|
|
134337
136041
|
return {
|
|
134338
136042
|
sendText,
|
|
136043
|
+
sendMedia,
|
|
136044
|
+
rememberConversation: (chatId, conversation) => {
|
|
136045
|
+
conversations.set(chatId, conversation);
|
|
136046
|
+
},
|
|
134339
136047
|
openStream,
|
|
134340
136048
|
rememberRequest: (chatId, requestId) => {
|
|
134341
136049
|
if (requestId !== null) pendingRequests.set(chatId, requestId);
|
|
@@ -134350,6 +136058,83 @@ var runWecomClient = (input) => {
|
|
|
134350
136058
|
};
|
|
134351
136059
|
};
|
|
134352
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
|
+
|
|
134353
136138
|
// src/wecomAdapter.ts
|
|
134354
136139
|
var WECOM_CARD_THROTTLE_MS = 400;
|
|
134355
136140
|
var LIVE_STREAM_LIMIT = 200;
|
|
@@ -134403,6 +136188,12 @@ ${kept}`;
|
|
|
134403
136188
|
await entry.client.sendText(entry.chatId, chunk);
|
|
134404
136189
|
}
|
|
134405
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
|
+
};
|
|
134406
136197
|
return {
|
|
134407
136198
|
platform: "wecom",
|
|
134408
136199
|
icon: "wecom",
|
|
@@ -134436,13 +136227,19 @@ ${kept}`;
|
|
|
134436
136227
|
onMessage: (message) => {
|
|
134437
136228
|
if (message.chatType !== "single") return;
|
|
134438
136229
|
client.rememberRequest(message.chatId, message.requestId);
|
|
136230
|
+
client.rememberConversation(message.chatId, message.conversation);
|
|
134439
136231
|
handlers.onMessage({
|
|
134440
136232
|
messageId: message.messageId,
|
|
134441
136233
|
chatId: message.chatId,
|
|
134442
136234
|
senderId: message.senderUserId,
|
|
134443
136235
|
text: message.text,
|
|
134444
|
-
//
|
|
134445
|
-
|
|
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
|
+
}))
|
|
134446
136243
|
});
|
|
134447
136244
|
}
|
|
134448
136245
|
}
|
|
@@ -134520,9 +136317,33 @@ ${kept}`;
|
|
|
134520
136317
|
if (view.status !== "working") liveStreams.delete(key);
|
|
134521
136318
|
}
|
|
134522
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"]),
|
|
134523
136325
|
answerByText: (_binding, text2, options) => {
|
|
134524
136326
|
const index = optionIndexFrom(text2, options.length);
|
|
134525
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
|
+
};
|
|
134526
136347
|
}
|
|
134527
136348
|
};
|
|
134528
136349
|
};
|
|
@@ -134536,6 +136357,7 @@ var TITLE_MAX_CHARS = 72;
|
|
|
134536
136357
|
var DEFAULT_PLATFORM = "lark";
|
|
134537
136358
|
var SESSION_SCAN_LIMIT = 50;
|
|
134538
136359
|
var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
|
|
136360
|
+
var UNSUPPORTED_REPLY_COOLDOWN_MS = 6e4;
|
|
134539
136361
|
var INBOUND_RUNTIME_MODES = /* @__PURE__ */ new Set([
|
|
134540
136362
|
"approval-required",
|
|
134541
136363
|
"auto-accept-edits",
|
|
@@ -134548,7 +136370,7 @@ var readString = (row, key) => {
|
|
|
134548
136370
|
const value = row[key];
|
|
134549
136371
|
return typeof value === "string" ? value.trim() : "";
|
|
134550
136372
|
};
|
|
134551
|
-
var
|
|
136373
|
+
var describeError2 = (error) => error instanceof Error ? error.message : String(error);
|
|
134552
136374
|
var titleFrom = (text2) => text2.replace(/\s+/g, " ").slice(0, TITLE_MAX_CHARS) || "Chat thread";
|
|
134553
136375
|
var imPlugin = definePlugin({
|
|
134554
136376
|
id: "im",
|
|
@@ -134582,6 +136404,7 @@ var imPlugin = definePlugin({
|
|
|
134582
136404
|
makeWecomAdapter({
|
|
134583
136405
|
// 企微不需要厂商 SDK:全程 JSON over WebSocket,走插件面那条出网长连接。
|
|
134584
136406
|
connectSocket: (url) => ctx.socket.connect({ url }),
|
|
136407
|
+
http: ctx.http,
|
|
134585
136408
|
translate: t,
|
|
134586
136409
|
log: {
|
|
134587
136410
|
info: (message, fields) => ctx.log.info(message, fields),
|
|
@@ -134753,7 +136576,7 @@ var imPlugin = definePlugin({
|
|
|
134753
136576
|
} catch (error) {
|
|
134754
136577
|
ctx.log.warn("chat could not deliver an image", {
|
|
134755
136578
|
name: attachment.name,
|
|
134756
|
-
error:
|
|
136579
|
+
error: describeError2(error)
|
|
134757
136580
|
});
|
|
134758
136581
|
}
|
|
134759
136582
|
}
|
|
@@ -134764,7 +136587,7 @@ var imPlugin = definePlugin({
|
|
|
134764
136587
|
try {
|
|
134765
136588
|
await adapterFor(binding.platform)?.sendText(binding, text2);
|
|
134766
136589
|
} catch (error) {
|
|
134767
|
-
ctx.log.warn("chat could not deliver", { error:
|
|
136590
|
+
ctx.log.warn("chat could not deliver", { error: describeError2(error) });
|
|
134768
136591
|
}
|
|
134769
136592
|
};
|
|
134770
136593
|
const projectByThread = /* @__PURE__ */ new Map();
|
|
@@ -134798,7 +136621,7 @@ var imPlugin = definePlugin({
|
|
|
134798
136621
|
acknowledgements.set(threadId, pending);
|
|
134799
136622
|
ctx.log.info("chat acknowledged a message", { threadId, messageId });
|
|
134800
136623
|
} catch (error) {
|
|
134801
|
-
ctx.log.warn("chat could not acknowledge the message", { error:
|
|
136624
|
+
ctx.log.warn("chat could not acknowledge the message", { error: describeError2(error) });
|
|
134802
136625
|
}
|
|
134803
136626
|
};
|
|
134804
136627
|
const clearWorking = async (binding, threadId) => {
|
|
@@ -134811,7 +136634,7 @@ var imPlugin = definePlugin({
|
|
|
134811
136634
|
await adapter.clearAcknowledge(binding, entry.messageId, entry.ackId);
|
|
134812
136635
|
} catch (error) {
|
|
134813
136636
|
ctx.log.warn("chat could not clear the acknowledgement", {
|
|
134814
|
-
error:
|
|
136637
|
+
error: describeError2(error)
|
|
134815
136638
|
});
|
|
134816
136639
|
}
|
|
134817
136640
|
}
|
|
@@ -134898,7 +136721,7 @@ ${final.text}`;
|
|
|
134898
136721
|
state = restored;
|
|
134899
136722
|
},
|
|
134900
136723
|
(error) => {
|
|
134901
|
-
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) });
|
|
134902
136725
|
}
|
|
134903
136726
|
);
|
|
134904
136727
|
let persisting = Promise.resolve();
|
|
@@ -134907,7 +136730,7 @@ ${final.text}`;
|
|
|
134907
136730
|
persisting = persisting.then(() => ctx.events.append({ name, aggregate: { kind: "plugin", id: ctx.id }, data })).then(
|
|
134908
136731
|
() => void 0,
|
|
134909
136732
|
(error) => {
|
|
134910
|
-
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) });
|
|
134911
136734
|
}
|
|
134912
136735
|
);
|
|
134913
136736
|
};
|
|
@@ -134915,7 +136738,7 @@ ${final.text}`;
|
|
|
134915
136738
|
commit("chat-updated", { chatKey, threadId });
|
|
134916
136739
|
};
|
|
134917
136740
|
const outbound = makeKeyedQueue((error) => {
|
|
134918
|
-
ctx.log.warn("chat could not deliver a card", { error:
|
|
136741
|
+
ctx.log.warn("chat could not deliver a card", { error: describeError2(error) });
|
|
134919
136742
|
});
|
|
134920
136743
|
const settleApprovalCard = async (approval, settlement) => {
|
|
134921
136744
|
if (approval.cardId === void 0) return;
|
|
@@ -134925,7 +136748,7 @@ ${final.text}`;
|
|
|
134925
136748
|
try {
|
|
134926
136749
|
await settle(binding, approval.cardId, settlement);
|
|
134927
136750
|
} catch (error) {
|
|
134928
|
-
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) });
|
|
134929
136752
|
}
|
|
134930
136753
|
};
|
|
134931
136754
|
const answerApproval = async (approval, decision, label) => {
|
|
@@ -134977,12 +136800,12 @@ ${final.text}`;
|
|
|
134977
136800
|
},
|
|
134978
136801
|
endOf: async (threadId) => {
|
|
134979
136802
|
const stopped = stopRequested.delete(threadId);
|
|
134980
|
-
const
|
|
136803
|
+
const failure2 = failureSummaries.get(threadId);
|
|
134981
136804
|
failureSummaries.delete(threadId);
|
|
134982
136805
|
const thread = await ctx.projections.getThread(threadId).catch(() => null);
|
|
134983
136806
|
const session = thread?.session;
|
|
134984
136807
|
if (session?.status === "error") {
|
|
134985
|
-
return { status: "error", errorText: session.lastError ??
|
|
136808
|
+
return { status: "error", errorText: session.lastError ?? failure2 ?? null };
|
|
134986
136809
|
}
|
|
134987
136810
|
if (stopped || session?.status === "interrupted" || session?.status === "stopped") {
|
|
134988
136811
|
return { status: "interrupted", errorText: null };
|
|
@@ -135002,7 +136825,7 @@ ${final.text}`;
|
|
|
135002
136825
|
return () => clearTimeout(timer);
|
|
135003
136826
|
},
|
|
135004
136827
|
warn: (message, error) => {
|
|
135005
|
-
ctx.log.warn(`chat ${message}`, { error:
|
|
136828
|
+
ctx.log.warn(`chat ${message}`, { error: describeError2(error) });
|
|
135006
136829
|
}
|
|
135007
136830
|
});
|
|
135008
136831
|
ctx.events.on(TURN_PROGRESS_TRIGGER_KIND, (event) => {
|
|
@@ -135028,7 +136851,7 @@ ${final.text}`;
|
|
|
135028
136851
|
);
|
|
135029
136852
|
} catch (error) {
|
|
135030
136853
|
ctx.log.warn("chat could not mark a stale card as interrupted", {
|
|
135031
|
-
error:
|
|
136854
|
+
error: describeError2(error)
|
|
135032
136855
|
});
|
|
135033
136856
|
}
|
|
135034
136857
|
}
|
|
@@ -135049,7 +136872,7 @@ ${final.text}`;
|
|
|
135049
136872
|
const record = state.approvals[offer.requestId];
|
|
135050
136873
|
if (record?.resolved === true) await settleApprovalCard(record, { kind: "elsewhere" });
|
|
135051
136874
|
} catch (error) {
|
|
135052
|
-
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) });
|
|
135053
136876
|
}
|
|
135054
136877
|
};
|
|
135055
136878
|
ctx.events.on("thread.approval-pending", async (event) => {
|
|
@@ -135097,13 +136920,116 @@ ${final.text}`;
|
|
|
135097
136920
|
});
|
|
135098
136921
|
ctx.events.on(REQUEST_RESOLVED_TRIGGER_KIND, async (event) => {
|
|
135099
136922
|
const resolved = readRequestResolved(event);
|
|
135100
|
-
if (resolved === null
|
|
136923
|
+
if (resolved === null) return;
|
|
135101
136924
|
await ready;
|
|
136925
|
+
if (resolved.requestKind === "user-input") {
|
|
136926
|
+
questionFlow.resolved(
|
|
136927
|
+
resolved.requestId,
|
|
136928
|
+
resolved.outcome === "cancelled" ? "cancelled" : "answered"
|
|
136929
|
+
);
|
|
136930
|
+
return;
|
|
136931
|
+
}
|
|
135102
136932
|
const record = state.approvals[resolved.requestId];
|
|
135103
136933
|
if (record === void 0 || record.resolved) return;
|
|
135104
136934
|
commit("approval-resolved", { requestId: resolved.requestId });
|
|
135105
136935
|
outbound(record.chatKey, () => settleApprovalCard(record, { kind: "elsewhere" }));
|
|
135106
136936
|
});
|
|
136937
|
+
const bindingOfThread = (threadId) => readBindings().find((candidate) => chatOf(state, chatKeyOf(candidate)).threadId === threadId);
|
|
136938
|
+
const flowHost = {
|
|
136939
|
+
t,
|
|
136940
|
+
state: () => state,
|
|
136941
|
+
commit,
|
|
136942
|
+
// 改卡必须排在回调应答之后:按钮点击的回调要先同步应答,飞书在应答不带改卡时会把卡
|
|
136943
|
+
// 恢复成点击前的样子;所以这两条流的发卡与改卡都推迟一个宏任务再入群队列。
|
|
136944
|
+
outbound: (chatKey, job) => {
|
|
136945
|
+
setTimeout(() => outbound(chatKey, job), 0);
|
|
136946
|
+
},
|
|
136947
|
+
target: (chatKey) => {
|
|
136948
|
+
const binding = bindingOfChat(chatKey);
|
|
136949
|
+
const adapter = binding === void 0 ? void 0 : adapterFor(binding.platform);
|
|
136950
|
+
return binding === void 0 || adapter === void 0 ? void 0 : { binding, adapter };
|
|
136951
|
+
},
|
|
136952
|
+
allowed: (chatKey, operatorId) => {
|
|
136953
|
+
const binding = bindingOfChat(chatKey);
|
|
136954
|
+
if (binding !== void 0 && isAllowedSender(binding, operatorId)) return true;
|
|
136955
|
+
ctx.log.warn("chat ignored a button click from someone not on the allow list", {
|
|
136956
|
+
group: binding?.displayName ?? chatKey,
|
|
136957
|
+
operator: operatorId
|
|
136958
|
+
});
|
|
136959
|
+
return false;
|
|
136960
|
+
},
|
|
136961
|
+
dispatch: (command) => ctx.commands.dispatch(command),
|
|
136962
|
+
say: async (chatKey, text2) => {
|
|
136963
|
+
const binding = bindingOfChat(chatKey);
|
|
136964
|
+
if (binding !== void 0) await deliver(binding, text2);
|
|
136965
|
+
},
|
|
136966
|
+
newToken: () => crypto.randomUUID().slice(0, 8),
|
|
136967
|
+
newId: () => crypto.randomUUID(),
|
|
136968
|
+
now: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
136969
|
+
warn: (message, error) => {
|
|
136970
|
+
ctx.log.warn(`chat ${message}`, error === void 0 ? {} : { error: describeError2(error) });
|
|
136971
|
+
}
|
|
136972
|
+
};
|
|
136973
|
+
const questionFlow = makeQuestionFlow(flowHost);
|
|
136974
|
+
const queueFlow = makeQueueFlow(flowHost, {
|
|
136975
|
+
readExecution: async (threadId) => {
|
|
136976
|
+
const execution = await ctx.projections.getThreadExecution(threadId);
|
|
136977
|
+
if (execution === null) return null;
|
|
136978
|
+
return {
|
|
136979
|
+
activeTurnId: execution.session?.activeTurnId ?? null,
|
|
136980
|
+
queuePaused: execution.queuePaused,
|
|
136981
|
+
intents: execution.turnIntents.map((intent) => ({
|
|
136982
|
+
intentId: intent.intentId,
|
|
136983
|
+
messageId: intent.messageId,
|
|
136984
|
+
status: intent.status,
|
|
136985
|
+
sequence: intent.sequence
|
|
136986
|
+
}))
|
|
136987
|
+
};
|
|
136988
|
+
}
|
|
136989
|
+
});
|
|
136990
|
+
ctx.events.on(USER_INPUT_PENDING_TRIGGER_KIND, async (event) => {
|
|
136991
|
+
const threadId = event.threadId;
|
|
136992
|
+
if (threadId === null) return;
|
|
136993
|
+
await ready;
|
|
136994
|
+
const binding = bindingOfThread(threadId);
|
|
136995
|
+
if (binding === void 0) return;
|
|
136996
|
+
if (ctx.projections.listPendingUserInputs === void 0) {
|
|
136997
|
+
ctx.log.warn("chat cannot show questions: the host cannot list them");
|
|
136998
|
+
return;
|
|
136999
|
+
}
|
|
137000
|
+
try {
|
|
137001
|
+
questionFlow.offer(
|
|
137002
|
+
chatKeyOf(binding),
|
|
137003
|
+
threadId,
|
|
137004
|
+
await ctx.projections.listPendingUserInputs(threadId)
|
|
137005
|
+
);
|
|
137006
|
+
} catch (error) {
|
|
137007
|
+
ctx.log.warn("chat could not read the pending questions", { error: describeError2(error) });
|
|
137008
|
+
}
|
|
137009
|
+
});
|
|
137010
|
+
ctx.reactor({
|
|
137011
|
+
name: "im.queue-cards",
|
|
137012
|
+
events: [
|
|
137013
|
+
"thread.session-set",
|
|
137014
|
+
"thread.turn-intent-transitioned",
|
|
137015
|
+
"thread.turn-queue-updated",
|
|
137016
|
+
"thread.turn-queue-paused"
|
|
137017
|
+
],
|
|
137018
|
+
handle: async (event) => {
|
|
137019
|
+
const payload = event.payload;
|
|
137020
|
+
if (typeof payload.threadId !== "string") return;
|
|
137021
|
+
await ready;
|
|
137022
|
+
const released = event.type === "thread.turn-intent-transitioned" && payload.status === "queued" && (payload.fromStatus !== "queued" || typeof payload.blockedReason === "string") && typeof payload.intentId === "string" ? payload.intentId : null;
|
|
137023
|
+
queueFlow.refresh(payload.threadId, released);
|
|
137024
|
+
}
|
|
137025
|
+
});
|
|
137026
|
+
const handleButtonClick = (token, operatorId) => {
|
|
137027
|
+
const question = questionFlow.click(token, operatorId);
|
|
137028
|
+
if (question.handled) return question.notice;
|
|
137029
|
+
const queued = queueFlow.click(token, operatorId);
|
|
137030
|
+
if (queued.handled) return queued.notice;
|
|
137031
|
+
return handleApprovalClick(token, operatorId);
|
|
137032
|
+
};
|
|
135107
137033
|
const isAllowedSender = (binding, sender) => binding.allowedSenders.size === 0 || sender !== null && binding.allowedSenders.has(sender);
|
|
135108
137034
|
const handleApprovalClick = (token, operatorId) => {
|
|
135109
137035
|
const found = findApprovalByToken(state, token);
|
|
@@ -135121,12 +137047,13 @@ ${final.text}`;
|
|
|
135121
137047
|
}
|
|
135122
137048
|
void answerApproval(found.approval, found.button.decision, found.button.label).catch(
|
|
135123
137049
|
(error) => {
|
|
135124
|
-
ctx.log.warn("chat could not answer the approval", { error:
|
|
137050
|
+
ctx.log.warn("chat could not answer the approval", { error: describeError2(error) });
|
|
135125
137051
|
}
|
|
135126
137052
|
);
|
|
135127
137053
|
return null;
|
|
135128
137054
|
};
|
|
135129
137055
|
const archivedReplyCooldown = makeCooldown(ARCHIVED_REPLY_COOLDOWN_MS, () => Date.now());
|
|
137056
|
+
const unsupportedReplyCooldown = makeCooldown(UNSUPPORTED_REPLY_COOLDOWN_MS, () => Date.now());
|
|
135130
137057
|
const alreadyHandled = (target, messageId) => {
|
|
135131
137058
|
const key = `${chatKeyOf(target)}:${messageId}`;
|
|
135132
137059
|
if (seen.has(key)) return true;
|
|
@@ -135138,33 +137065,103 @@ ${final.text}`;
|
|
|
135138
137065
|
}
|
|
135139
137066
|
return false;
|
|
135140
137067
|
};
|
|
135141
|
-
const
|
|
135142
|
-
|
|
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) => {
|
|
135143
137079
|
const adapter = adapterFor(binding.platform);
|
|
135144
|
-
|
|
135145
|
-
|
|
135146
|
-
|
|
135147
|
-
|
|
135148
|
-
|
|
135149
|
-
|
|
135150
|
-
|
|
135151
|
-
|
|
135152
|
-
name:
|
|
135153
|
-
|
|
135154
|
-
|
|
135155
|
-
|
|
135156
|
-
|
|
135157
|
-
|
|
135158
|
-
|
|
135159
|
-
|
|
135160
|
-
|
|
135161
|
-
|
|
135162
|
-
|
|
135163
|
-
|
|
135164
|
-
|
|
135165
|
-
|
|
135166
|
-
|
|
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
|
+
}
|
|
135167
137163
|
}
|
|
137164
|
+
return { attachments, notes };
|
|
135168
137165
|
};
|
|
135169
137166
|
const targetOf = (binding) => chatOf(state, chatKeyOf(binding)).target;
|
|
135170
137167
|
const chatByRun = /* @__PURE__ */ new Map();
|
|
@@ -135282,7 +137279,7 @@ ${final.text}`;
|
|
|
135282
137279
|
modelSelection: selection
|
|
135283
137280
|
});
|
|
135284
137281
|
} catch (error) {
|
|
135285
|
-
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) });
|
|
135286
137283
|
await deliver(binding, t("model.setFailed"));
|
|
135287
137284
|
return;
|
|
135288
137285
|
}
|
|
@@ -135362,7 +137359,7 @@ ${final.text}`;
|
|
|
135362
137359
|
return true;
|
|
135363
137360
|
} catch (error) {
|
|
135364
137361
|
ctx.log.warn("chat could not open the command panel", {
|
|
135365
|
-
error:
|
|
137362
|
+
error: describeError2(error)
|
|
135366
137363
|
});
|
|
135367
137364
|
}
|
|
135368
137365
|
}
|
|
@@ -135409,6 +137406,37 @@ ${final.text}`;
|
|
|
135409
137406
|
);
|
|
135410
137407
|
return true;
|
|
135411
137408
|
}
|
|
137409
|
+
case "continue": {
|
|
137410
|
+
const threadId = chatOf(state, chatKey).threadId ?? void 0;
|
|
137411
|
+
let paused = false;
|
|
137412
|
+
if (threadId !== void 0) {
|
|
137413
|
+
try {
|
|
137414
|
+
paused = (await ctx.projections.getThreadExecution(threadId))?.queuePaused === true;
|
|
137415
|
+
} catch (error) {
|
|
137416
|
+
ctx.log.warn("chat could not read the turn queue", { error: describeError2(error) });
|
|
137417
|
+
await deliver(binding, t("command.continueFailed"));
|
|
137418
|
+
return true;
|
|
137419
|
+
}
|
|
137420
|
+
}
|
|
137421
|
+
if (threadId === void 0 || !paused) {
|
|
137422
|
+
await deliver(binding, t("command.continueNothing"));
|
|
137423
|
+
return true;
|
|
137424
|
+
}
|
|
137425
|
+
try {
|
|
137426
|
+
await ctx.commands.dispatch({
|
|
137427
|
+
type: "thread.turn-queue.resume",
|
|
137428
|
+
commandId: crypto.randomUUID(),
|
|
137429
|
+
threadId,
|
|
137430
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
137431
|
+
});
|
|
137432
|
+
} catch (error) {
|
|
137433
|
+
ctx.log.warn("chat could not resume the turn queue", { error: describeError2(error) });
|
|
137434
|
+
await deliver(binding, t("command.continueFailed"));
|
|
137435
|
+
return true;
|
|
137436
|
+
}
|
|
137437
|
+
await deliver(binding, t("command.continued"));
|
|
137438
|
+
return true;
|
|
137439
|
+
}
|
|
135412
137440
|
case "stop": {
|
|
135413
137441
|
const threadId = chatOf(state, chatKey).threadId ?? void 0;
|
|
135414
137442
|
if (threadId === void 0) {
|
|
@@ -135465,9 +137493,8 @@ ${final.text}`;
|
|
|
135465
137493
|
}
|
|
135466
137494
|
};
|
|
135467
137495
|
const interactionModeOf = (chatKey) => chatOf(state, chatKey).plan ? "plan" : "default";
|
|
135468
|
-
const
|
|
137496
|
+
const ensureThread = async (binding, titleSeed) => {
|
|
135469
137497
|
const chatKey = chatKeyOf(binding);
|
|
135470
|
-
const text2 = message.text;
|
|
135471
137498
|
let threadId = chatOf(state, chatKey).threadId ?? void 0;
|
|
135472
137499
|
if (threadId !== void 0 && !await threadStillUsable(binding, threadId)) {
|
|
135473
137500
|
ctx.log.info("chat dropped a saved thread that is no longer usable", {
|
|
@@ -135478,70 +137505,136 @@ ${final.text}`;
|
|
|
135478
137505
|
threadId = void 0;
|
|
135479
137506
|
await deliver(binding, t("reply.threadGone"));
|
|
135480
137507
|
}
|
|
135481
|
-
if (threadId
|
|
135482
|
-
|
|
135483
|
-
|
|
135484
|
-
|
|
135485
|
-
|
|
135486
|
-
|
|
135487
|
-
});
|
|
135488
|
-
return null;
|
|
135489
|
-
}
|
|
135490
|
-
const modelSelection = chatOf(state, chatKey).model ?? project.defaultModelSelection;
|
|
135491
|
-
if (modelSelection === null) {
|
|
135492
|
-
ctx.log.warn("chat cannot start a thread: the project has no default model", {
|
|
135493
|
-
group: binding.displayName,
|
|
135494
|
-
projectId: binding.projectId
|
|
135495
|
-
});
|
|
135496
|
-
await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
|
|
135497
|
-
return null;
|
|
135498
|
-
}
|
|
135499
|
-
const createdThreadId = crypto.randomUUID();
|
|
135500
|
-
threadId = createdThreadId;
|
|
135501
|
-
await ctx.commands.dispatch({
|
|
135502
|
-
type: "thread.create",
|
|
135503
|
-
commandId: crypto.randomUUID(),
|
|
135504
|
-
threadId,
|
|
135505
|
-
projectId: binding.projectId,
|
|
135506
|
-
title: titleFrom(text2 || message.resource?.name || "Chat"),
|
|
135507
|
-
modelSelection,
|
|
135508
|
-
runtimeMode: binding.runtimeMode,
|
|
135509
|
-
interactionMode: interactionModeOf(chatKey),
|
|
135510
|
-
branch: null,
|
|
135511
|
-
worktreePath: null,
|
|
135512
|
-
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
|
|
135513
137514
|
});
|
|
135514
|
-
|
|
135515
|
-
|
|
135516
|
-
|
|
135517
|
-
|
|
135518
|
-
|
|
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
|
|
135519
137522
|
});
|
|
135520
|
-
|
|
137523
|
+
await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
|
|
137524
|
+
return null;
|
|
135521
137525
|
}
|
|
135522
|
-
const
|
|
137526
|
+
const createdThreadId = crypto.randomUUID();
|
|
135523
137527
|
await ctx.commands.dispatch({
|
|
135524
|
-
type: "thread.
|
|
137528
|
+
type: "thread.create",
|
|
135525
137529
|
commandId: crypto.randomUUID(),
|
|
135526
|
-
threadId,
|
|
135527
|
-
|
|
135528
|
-
|
|
135529
|
-
|
|
135530
|
-
text: text2,
|
|
135531
|
-
attachments
|
|
135532
|
-
},
|
|
135533
|
-
titleSeed: titleFrom(text2 || attachments[0]?.name || "Chat"),
|
|
137530
|
+
threadId: createdThreadId,
|
|
137531
|
+
projectId: binding.projectId,
|
|
137532
|
+
title: titleFrom(titleSeed),
|
|
137533
|
+
modelSelection,
|
|
135534
137534
|
runtimeMode: binding.runtimeMode,
|
|
135535
137535
|
interactionMode: interactionModeOf(chatKey),
|
|
137536
|
+
branch: null,
|
|
137537
|
+
worktreePath: null,
|
|
135536
137538
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
135537
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
|
+
}
|
|
135538
137587
|
ctx.log.info("chat started a turn from a message", {
|
|
135539
137588
|
group: binding.displayName,
|
|
135540
137589
|
chatId: binding.chatId,
|
|
135541
137590
|
threadId
|
|
135542
137591
|
});
|
|
137592
|
+
await queueFlow.noteQueued(
|
|
137593
|
+
chatKey,
|
|
137594
|
+
threadId,
|
|
137595
|
+
userMessageId,
|
|
137596
|
+
text2.trim() || attachments[0]?.name || message.resources[0]?.name || ""
|
|
137597
|
+
);
|
|
135543
137598
|
return threadId;
|
|
135544
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
|
+
});
|
|
135545
137638
|
const receiving = readBindings().filter((binding) => binding.projectId !== "");
|
|
135546
137639
|
const connections = [];
|
|
135547
137640
|
const started = /* @__PURE__ */ new Set();
|
|
@@ -135549,7 +137642,7 @@ ${final.text}`;
|
|
|
135549
137642
|
const chatQueues = /* @__PURE__ */ new Map();
|
|
135550
137643
|
const enqueueForChat = (chatKey, work) => {
|
|
135551
137644
|
const run = (chatQueues.get(chatKey) ?? Promise.resolve()).then(work).catch((error) => {
|
|
135552
|
-
ctx.log.warn("chat could not handle the message", { error:
|
|
137645
|
+
ctx.log.warn("chat could not handle the message", { error: describeError2(error) });
|
|
135553
137646
|
}).then(() => {
|
|
135554
137647
|
if (chatQueues.get(chatKey) === run) chatQueues.delete(chatKey);
|
|
135555
137648
|
});
|
|
@@ -135600,7 +137693,7 @@ ${final.text}`;
|
|
|
135600
137693
|
},
|
|
135601
137694
|
now: () => Date.now(),
|
|
135602
137695
|
warn: (message, error) => {
|
|
135603
|
-
ctx.log.warn(`chat ${message}`, error === void 0 ? {} : { error:
|
|
137696
|
+
ctx.log.warn(`chat ${message}`, error === void 0 ? {} : { error: describeError2(error) });
|
|
135604
137697
|
}
|
|
135605
137698
|
});
|
|
135606
137699
|
const handlePanelClick = (platform, click) => {
|
|
@@ -135622,7 +137715,7 @@ ${final.text}`;
|
|
|
135622
137715
|
};
|
|
135623
137716
|
const inboundHandlersFor = (platform) => ({
|
|
135624
137717
|
onError: (message) => ctx.log.warn("chat inbound connection failed", { platform, message }),
|
|
135625
|
-
|
|
137718
|
+
onButton: handleButtonClick,
|
|
135626
137719
|
onPanelClick: (click) => handlePanelClick(platform, click),
|
|
135627
137720
|
onMessage: (message) => {
|
|
135628
137721
|
const target = bindingForChat(platform, message.chatId);
|
|
@@ -135635,6 +137728,17 @@ ${final.text}`;
|
|
|
135635
137728
|
});
|
|
135636
137729
|
return;
|
|
135637
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
|
+
}
|
|
135638
137742
|
const open = openApprovalOf(state, chatKeyOf(target));
|
|
135639
137743
|
const answerToken = open === void 0 ? null : adapterFor(target.platform)?.answerByText?.(
|
|
135640
137744
|
target,
|
|
@@ -135643,13 +137747,17 @@ ${final.text}`;
|
|
|
135643
137747
|
) ?? null;
|
|
135644
137748
|
if (answerToken !== null) {
|
|
135645
137749
|
void answerByToken(answerToken).catch((error) => {
|
|
135646
|
-
ctx.log.warn("chat could not answer the approval", { error:
|
|
137750
|
+
ctx.log.warn("chat could not answer the approval", { error: describeError2(error) });
|
|
135647
137751
|
});
|
|
135648
137752
|
return;
|
|
135649
137753
|
}
|
|
135650
137754
|
enqueueForChat(chatKeyOf(target), async () => {
|
|
135651
137755
|
const teams = await fetchTeams(target);
|
|
135652
137756
|
if (await handleCommand(target, message.text, teams ?? [])) return;
|
|
137757
|
+
const boundThread = chatOf(state, chatKeyOf(target)).threadId;
|
|
137758
|
+
if (boundThread !== null && message.text !== "" && await questionFlow.answerByText(chatKeyOf(target), boundThread, message.text)) {
|
|
137759
|
+
return;
|
|
137760
|
+
}
|
|
135653
137761
|
const project = await ctx.projections.getProject(target.projectId);
|
|
135654
137762
|
if (project === null || project.archivedAt != null) {
|
|
135655
137763
|
if (project === null) {
|
|
@@ -135667,6 +137775,7 @@ ${final.text}`;
|
|
|
135667
137775
|
return;
|
|
135668
137776
|
}
|
|
135669
137777
|
const routed = targetOf(target);
|
|
137778
|
+
const attachmentsOnly = message.text.trim() === "" && message.resources.length > 0;
|
|
135670
137779
|
if (routed.kind === "team") {
|
|
135671
137780
|
if (teams !== null && !teams.some((team) => team.teamId === routed.teamId)) {
|
|
135672
137781
|
commit("chat-updated", {
|
|
@@ -135676,11 +137785,17 @@ ${final.text}`;
|
|
|
135676
137785
|
});
|
|
135677
137786
|
await deliver(target, t("reply.teamGone"));
|
|
135678
137787
|
} else {
|
|
137788
|
+
if (message.resources.length > 0) await deliver(target, t("reply.attachmentsNoTeam"));
|
|
137789
|
+
if (attachmentsOnly) return;
|
|
135679
137790
|
await startTeamRun(target, routed.teamId, message.text);
|
|
135680
137791
|
await markWorking(target, chatKeyOf(target), message.messageId);
|
|
135681
137792
|
return;
|
|
135682
137793
|
}
|
|
135683
137794
|
}
|
|
137795
|
+
if (attachmentsOnly) {
|
|
137796
|
+
await receiveAttachmentsOnly(target, message);
|
|
137797
|
+
return;
|
|
137798
|
+
}
|
|
135684
137799
|
const threadId = await startTurn(target, message);
|
|
135685
137800
|
if (threadId !== null) await markWorking(target, threadId, message.messageId);
|
|
135686
137801
|
});
|
|
@@ -135699,7 +137814,7 @@ ${final.text}`;
|
|
|
135699
137814
|
ctx.log.warn("chat could not start an inbound connection", {
|
|
135700
137815
|
group: binding.displayName,
|
|
135701
137816
|
platform: binding.platform,
|
|
135702
|
-
error:
|
|
137817
|
+
error: describeError2(error)
|
|
135703
137818
|
});
|
|
135704
137819
|
});
|
|
135705
137820
|
}
|