opencode-feishu-plugin 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +114 -457
- package/README.md +82 -268
- package/dist/index.js +296 -41
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import crypto3, { randomBytes, createHash, createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
-
import url from 'url';
|
|
2
|
+
import url, { pathToFileURL } from 'url';
|
|
3
3
|
import http from 'http';
|
|
4
4
|
import https from 'https';
|
|
5
5
|
import http2 from 'http2';
|
|
6
6
|
import util from 'util';
|
|
7
|
-
import path, { dirname, isAbsolute, sep, resolve as resolve$1,
|
|
7
|
+
import path, { dirname, isAbsolute, join, sep, resolve as resolve$1, basename } from 'path';
|
|
8
8
|
import zlib from 'zlib';
|
|
9
9
|
import stream3, { Readable } from 'stream';
|
|
10
10
|
import { EventEmitter } from 'events';
|
|
11
11
|
import fs, { mkdirSync, createWriteStream, readFileSync, realpathSync, statSync } from 'fs';
|
|
12
12
|
import qs from 'querystring';
|
|
13
13
|
import { homedir } from 'os';
|
|
14
|
-
import { readFile, readdir } from 'fs/promises';
|
|
14
|
+
import { mkdir, stat, rm, readFile, readdir } from 'fs/promises';
|
|
15
15
|
|
|
16
16
|
var __create = Object.create;
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
@@ -8845,7 +8845,7 @@ var require_mime_db = __commonJS({
|
|
|
8845
8845
|
var require_mime_types = __commonJS({
|
|
8846
8846
|
"node_modules/form-data/node_modules/mime-types/index.js"(exports) {
|
|
8847
8847
|
var db = require_mime_db();
|
|
8848
|
-
var
|
|
8848
|
+
var extname2 = __require("path").extname;
|
|
8849
8849
|
var EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/;
|
|
8850
8850
|
var TEXT_TYPE_REGEXP = /^text\//i;
|
|
8851
8851
|
exports.charset = charset;
|
|
@@ -8899,7 +8899,7 @@ var require_mime_types = __commonJS({
|
|
|
8899
8899
|
if (!path2 || typeof path2 !== "string") {
|
|
8900
8900
|
return false;
|
|
8901
8901
|
}
|
|
8902
|
-
var extension3 =
|
|
8902
|
+
var extension3 = extname2("x." + path2).toLowerCase().substr(1);
|
|
8903
8903
|
if (!extension3) {
|
|
8904
8904
|
return false;
|
|
8905
8905
|
}
|
|
@@ -10040,12 +10040,12 @@ var require_form_data = __commonJS({
|
|
|
10040
10040
|
if (value3.end != void 0 && value3.end != Infinity && value3.start != void 0) {
|
|
10041
10041
|
callback3(null, value3.end + 1 - (value3.start ? value3.start : 0));
|
|
10042
10042
|
} else {
|
|
10043
|
-
fs2.stat(value3.path, function(err2,
|
|
10043
|
+
fs2.stat(value3.path, function(err2, stat2) {
|
|
10044
10044
|
if (err2) {
|
|
10045
10045
|
callback3(err2);
|
|
10046
10046
|
return;
|
|
10047
10047
|
}
|
|
10048
|
-
var fileSize =
|
|
10048
|
+
var fileSize = stat2.size - (value3.start ? value3.start : 0);
|
|
10049
10049
|
callback3(null, fileSize);
|
|
10050
10050
|
});
|
|
10051
10051
|
}
|
|
@@ -144029,6 +144029,15 @@ function resolveConfig2(raw, env = process.env, deps = {}) {
|
|
|
144029
144029
|
const runnerCardTextMax = clamp4(asNumber(merged.runnerCardTextMax, 2048), 512, 8192);
|
|
144030
144030
|
const finalAnswerMinChars = clamp4(asNumber(merged.finalAnswerMinChars, 600), 0, 2e4);
|
|
144031
144031
|
const finalAnswerFileMinBytes = clamp4(asNumber(merged.finalAnswerFileMinBytes, 20 * 1024), 8192, 102400);
|
|
144032
|
+
const acceptAttachments = asBoolean(merged.acceptAttachments, true);
|
|
144033
|
+
const attachmentMaxBytes = clamp4(
|
|
144034
|
+
asNumber(merged.attachmentMaxBytes, 20 * 1024 * 1024),
|
|
144035
|
+
1024 * 1024,
|
|
144036
|
+
100 * 1024 * 1024
|
|
144037
|
+
);
|
|
144038
|
+
const attachmentTimeoutMs = clamp4(asNumber(merged.attachmentTimeoutMs, 3e4), 5e3, 12e4);
|
|
144039
|
+
const attachmentsDirRaw = asString(merged.attachmentsDir).trim();
|
|
144040
|
+
const attachmentsDir = attachmentsDirRaw ? attachmentsDirRaw : join(resolveConfigDir(env, deps.configDir), "plugins", "feishu-files");
|
|
144032
144041
|
const keepaliveIntervalMs = clamp4(
|
|
144033
144042
|
asNumber(merged.keepaliveIntervalMs, 20 * 60 * 1e3),
|
|
144034
144043
|
5 * 60 * 1e3,
|
|
@@ -144089,7 +144098,11 @@ function resolveConfig2(raw, env = process.env, deps = {}) {
|
|
|
144089
144098
|
runnerCardMaxTools,
|
|
144090
144099
|
runnerCardTextMax,
|
|
144091
144100
|
finalAnswerMinChars,
|
|
144092
|
-
finalAnswerFileMinBytes
|
|
144101
|
+
finalAnswerFileMinBytes,
|
|
144102
|
+
acceptAttachments,
|
|
144103
|
+
attachmentMaxBytes,
|
|
144104
|
+
attachmentTimeoutMs,
|
|
144105
|
+
attachmentsDir
|
|
144093
144106
|
};
|
|
144094
144107
|
}
|
|
144095
144108
|
function loadConfigFile(env, deps, warnings) {
|
|
@@ -144706,6 +144719,27 @@ function extractPostText(rawContent) {
|
|
|
144706
144719
|
return "";
|
|
144707
144720
|
}
|
|
144708
144721
|
}
|
|
144722
|
+
function parseIncomingAttachment(messageType, rawContent) {
|
|
144723
|
+
if (messageType !== "image" && messageType !== "file") return void 0;
|
|
144724
|
+
let parsed;
|
|
144725
|
+
try {
|
|
144726
|
+
parsed = JSON.parse(rawContent);
|
|
144727
|
+
} catch {
|
|
144728
|
+
return void 0;
|
|
144729
|
+
}
|
|
144730
|
+
if (!isRecordLike(parsed)) return void 0;
|
|
144731
|
+
if (messageType === "image") {
|
|
144732
|
+
const imageKey = str(parsed.image_key);
|
|
144733
|
+
return imageKey ? { kind: "image", fileKey: imageKey } : void 0;
|
|
144734
|
+
}
|
|
144735
|
+
const fileKey = str(parsed.file_key);
|
|
144736
|
+
if (!fileKey) return void 0;
|
|
144737
|
+
const fileName = str(parsed.file_name);
|
|
144738
|
+
return { kind: "file", fileKey, ...fileName ? { fileName } : {} };
|
|
144739
|
+
}
|
|
144740
|
+
function isRecordLike(value3) {
|
|
144741
|
+
return typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
|
|
144742
|
+
}
|
|
144709
144743
|
function parseIncomingMessage(data) {
|
|
144710
144744
|
if (!isRecord2(data)) return void 0;
|
|
144711
144745
|
const message = data.message;
|
|
@@ -144718,6 +144752,7 @@ function parseIncomingMessage(data) {
|
|
|
144718
144752
|
if (!rawContent) return void 0;
|
|
144719
144753
|
const sender = data.sender;
|
|
144720
144754
|
const senderOpenId = isRecord2(sender) && isRecord2(sender.sender_id) ? str(sender.sender_id.open_id) : "";
|
|
144755
|
+
const attachment = parseIncomingAttachment(messageType, rawContent);
|
|
144721
144756
|
return {
|
|
144722
144757
|
eventId: str(data.event_id),
|
|
144723
144758
|
messageId,
|
|
@@ -144725,6 +144760,7 @@ function parseIncomingMessage(data) {
|
|
|
144725
144760
|
chatType: str(message.chat_type) || "p2p",
|
|
144726
144761
|
messageType,
|
|
144727
144762
|
text: extractMessageText(messageType, rawContent),
|
|
144763
|
+
...attachment ? { attachment } : {},
|
|
144728
144764
|
senderOpenId,
|
|
144729
144765
|
...str(message.create_time) ? { createTime: str(message.create_time) } : {},
|
|
144730
144766
|
...str(message.thread_id) ? { threadId: str(message.thread_id) } : {},
|
|
@@ -145221,6 +145257,114 @@ function createFeishuSender(client, log2, options2 = {}) {
|
|
|
145221
145257
|
}
|
|
145222
145258
|
};
|
|
145223
145259
|
}
|
|
145260
|
+
var AttachmentTimeoutError = class extends Error {
|
|
145261
|
+
constructor() {
|
|
145262
|
+
super("attachment-timeout");
|
|
145263
|
+
this.name = "AttachmentTimeoutError";
|
|
145264
|
+
}
|
|
145265
|
+
};
|
|
145266
|
+
function withTimeout(promise3, ms) {
|
|
145267
|
+
return new Promise((resolve4, reject) => {
|
|
145268
|
+
const timer = setTimeout(() => reject(new AttachmentTimeoutError()), Math.max(1, ms));
|
|
145269
|
+
timer.unref?.();
|
|
145270
|
+
promise3.then(
|
|
145271
|
+
(value3) => {
|
|
145272
|
+
clearTimeout(timer);
|
|
145273
|
+
resolve4(value3);
|
|
145274
|
+
},
|
|
145275
|
+
(err2) => {
|
|
145276
|
+
clearTimeout(timer);
|
|
145277
|
+
reject(err2);
|
|
145278
|
+
}
|
|
145279
|
+
);
|
|
145280
|
+
});
|
|
145281
|
+
}
|
|
145282
|
+
function sanitizeAttachmentName(raw, fallback = "attachment") {
|
|
145283
|
+
const base2 = (raw ?? "").trim();
|
|
145284
|
+
const cleaned = base2.replace(/[\\/]/g, "_").replace(/[\u0000-\u001f\u007f]/g, "").replace(/^\.+/, "").slice(0, 120);
|
|
145285
|
+
return cleaned || fallback;
|
|
145286
|
+
}
|
|
145287
|
+
var IMAGE_EXT_BY_MIME = {
|
|
145288
|
+
"image/png": ".png",
|
|
145289
|
+
"image/jpeg": ".jpg",
|
|
145290
|
+
"image/jpg": ".jpg",
|
|
145291
|
+
"image/gif": ".gif",
|
|
145292
|
+
"image/webp": ".webp",
|
|
145293
|
+
"image/bmp": ".bmp",
|
|
145294
|
+
"image/tiff": ".tiff"
|
|
145295
|
+
};
|
|
145296
|
+
function imageExtFromHeaders(headers) {
|
|
145297
|
+
const raw = headers?.["content-type"] ?? headers?.["Content-Type"];
|
|
145298
|
+
const mime = typeof raw === "string" ? raw.split(";")[0].trim().toLowerCase() : "";
|
|
145299
|
+
return IMAGE_EXT_BY_MIME[mime] ?? ".png";
|
|
145300
|
+
}
|
|
145301
|
+
function buildAttachmentFileName(attachment, headers) {
|
|
145302
|
+
if (attachment.kind === "image") {
|
|
145303
|
+
return sanitizeAttachmentName(`image${imageExtFromHeaders(headers)}`, "image.png");
|
|
145304
|
+
}
|
|
145305
|
+
const raw = sanitizeAttachmentName(attachment.fileName, "file");
|
|
145306
|
+
return raw;
|
|
145307
|
+
}
|
|
145308
|
+
function formatBytes(bytes) {
|
|
145309
|
+
if (!Number.isFinite(bytes) || bytes < 0) return "0 B";
|
|
145310
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
145311
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
145312
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
145313
|
+
}
|
|
145314
|
+
function attachmentSavedPrompt(input) {
|
|
145315
|
+
const size6 = formatBytes(input.size);
|
|
145316
|
+
return input.kind === "image" ? `[\u9644\u4EF6] \u56FE\u7247\u300C${input.name}\u300D\u5DF2\u4FDD\u5B58\u5230 ${input.path}\uFF08${size6}\uFF09\uFF0C\u53EF\u76F4\u63A5\u67E5\u770B\u3002` : `[\u9644\u4EF6] \u6587\u4EF6\u300C${input.name}\u300D\u5DF2\u4FDD\u5B58\u5230 ${input.path}\uFF08${size6}\uFF09\u3002`;
|
|
145317
|
+
}
|
|
145318
|
+
async function downloadAttachment(input) {
|
|
145319
|
+
const { client, messageId, attachment, dir, maxBytes, timeoutMs, log: log2 } = input;
|
|
145320
|
+
const startedAt = Date.now();
|
|
145321
|
+
let target;
|
|
145322
|
+
try {
|
|
145323
|
+
await mkdir(dir, { recursive: true });
|
|
145324
|
+
const res = await withTimeout(
|
|
145325
|
+
client.im.messageResource.get({
|
|
145326
|
+
params: { type: attachment.kind },
|
|
145327
|
+
path: { message_id: messageId, file_key: attachment.fileKey }
|
|
145328
|
+
}),
|
|
145329
|
+
timeoutMs
|
|
145330
|
+
);
|
|
145331
|
+
if (!res || typeof res.writeFile !== "function") {
|
|
145332
|
+
return { ok: false, reason: "\u8D44\u6E90\u63A5\u53E3\u672A\u8FD4\u56DE\u6587\u4EF6\u6D41" };
|
|
145333
|
+
}
|
|
145334
|
+
const name = buildAttachmentFileName(attachment, res.headers);
|
|
145335
|
+
target = join(dir, `${messageId}-${name}`);
|
|
145336
|
+
await res.writeFile(target);
|
|
145337
|
+
const info = await stat(target);
|
|
145338
|
+
if (info.size > maxBytes) {
|
|
145339
|
+
await rm(target, { force: true });
|
|
145340
|
+
return {
|
|
145341
|
+
ok: false,
|
|
145342
|
+
reason: `\u9644\u4EF6\u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650\uFF08${formatBytes(info.size)} > ${formatBytes(maxBytes)}\uFF09`
|
|
145343
|
+
};
|
|
145344
|
+
}
|
|
145345
|
+
log2.info("\u9644\u4EF6\u5DF2\u4E0B\u8F7D", {
|
|
145346
|
+
messageId,
|
|
145347
|
+
kind: attachment.kind,
|
|
145348
|
+
name,
|
|
145349
|
+
size: info.size,
|
|
145350
|
+
ms: Date.now() - startedAt
|
|
145351
|
+
});
|
|
145352
|
+
return { ok: true, path: target, name, size: info.size };
|
|
145353
|
+
} catch (err2) {
|
|
145354
|
+
if (target) await rm(target, { force: true }).catch(() => void 0);
|
|
145355
|
+
const reason = err2 instanceof AttachmentTimeoutError ? `\u4E0B\u8F7D\u8D85\u65F6\uFF08>${Math.round(timeoutMs / 1e3)}s\uFF09` : errorMessage(err2);
|
|
145356
|
+
log2.warn("\u9644\u4EF6\u4E0B\u8F7D\u5931\u8D25", { messageId, kind: attachment.kind, error: reason });
|
|
145357
|
+
return { ok: false, reason };
|
|
145358
|
+
}
|
|
145359
|
+
}
|
|
145360
|
+
function downloadedAttachmentPrompt(attachment, outcome) {
|
|
145361
|
+
return attachmentSavedPrompt({
|
|
145362
|
+
kind: attachment.kind,
|
|
145363
|
+
name: outcome.name,
|
|
145364
|
+
path: outcome.path,
|
|
145365
|
+
size: outcome.size
|
|
145366
|
+
});
|
|
145367
|
+
}
|
|
145224
145368
|
|
|
145225
145369
|
// src/feishu/session-map.ts
|
|
145226
145370
|
var CHAT_KEY_PREFIX = "feishu:v2:chat:";
|
|
@@ -145598,7 +145742,8 @@ function parseRootCard(value3) {
|
|
|
145598
145742
|
...value3.compactPending === true ? { compactPending: true } : {},
|
|
145599
145743
|
...compactError ? { compactError } : {},
|
|
145600
145744
|
...value3.compactButton === true ? { compactButton: true } : {},
|
|
145601
|
-
...note2 ? { note: note2 } : {}
|
|
145745
|
+
...note2 ? { note: note2 } : {},
|
|
145746
|
+
...value3.openedTopic === true ? { openedTopic: true } : {}
|
|
145602
145747
|
};
|
|
145603
145748
|
}
|
|
145604
145749
|
function parseStringArray(value3) {
|
|
@@ -148083,7 +148228,10 @@ function renderResumedBody(title, base2, now3) {
|
|
|
148083
148228
|
setup.push(`- \u6700\u8FD1\u6D3B\u52A8\uFF1A${relativeTime(base2.updatedAt, now3)}`);
|
|
148084
148229
|
}
|
|
148085
148230
|
if (setup.length > 0) lines.push("", ...setup);
|
|
148086
|
-
lines.push(
|
|
148231
|
+
lines.push(
|
|
148232
|
+
"",
|
|
148233
|
+
base2.openedTopic ? "\u2705 **\u8BDD\u9898\u5DF2\u6253\u5F00**\uFF1A\u76F4\u63A5\u5728\u672C\u8BDD\u9898\u5185\u56DE\u590D\u5373\u53EF\u7EE7\u7EED\u8FD9\u4E2A\u5386\u53F2\u4F1A\u8BDD\u3002" : "**\u56DE\u590D\u672C\u5361\u7247**\u5373\u53EF\u7EE7\u7EED\u8FD9\u4E2A\u5386\u53F2\u4F1A\u8BDD\uFF08\u98DE\u4E66\u56DE\u590D\u4F1A\u5728\u672C\u5361\u4E0B\u5F62\u6210\u8BDD\u9898\uFF09\u3002"
|
|
148234
|
+
);
|
|
148087
148235
|
if (base2.summary || base2.summaryPending || base2.compactPending || base2.compactError) {
|
|
148088
148236
|
lines.push("", `**${base2.summaryLabel ?? "\u6458\u8981"}**\uFF1A`);
|
|
148089
148237
|
if (base2.summaryPending) lines.push("\u23F3 \u6B63\u5728\u603B\u7ED3\u8BE5\u4F1A\u8BDD\u2026");
|
|
@@ -148122,7 +148270,8 @@ function buildSessionOpenedCard(input) {
|
|
|
148122
148270
|
...input.summaryPending ? { summaryPending: true } : {},
|
|
148123
148271
|
...input.compactPending ? { compactPending: true } : {},
|
|
148124
148272
|
...input.compactError ? { compactError: input.compactError } : {},
|
|
148125
|
-
...input.compactButton ? { compactButton: true } : {}
|
|
148273
|
+
...input.compactButton ? { compactButton: true } : {},
|
|
148274
|
+
...input.openedTopic ? { openedTopic: true } : {}
|
|
148126
148275
|
};
|
|
148127
148276
|
return buildSessionRootCard(base2, void 0, {
|
|
148128
148277
|
now: input.now ?? Date.now(),
|
|
@@ -148232,9 +148381,9 @@ function validateDirectory(rawPath, allowedRoots, deps = {}) {
|
|
|
148232
148381
|
if (!isAbsolute(raw)) return err("not_absolute", "\u76EE\u5F55\u5FC5\u987B\u662F**\u7EDD\u5BF9\u8DEF\u5F84**\uFF08\u4EE5 `/` \u5F00\u5934\uFF09\u3002");
|
|
148233
148382
|
target = resolve$1(raw);
|
|
148234
148383
|
}
|
|
148235
|
-
const
|
|
148384
|
+
const stat2 = deps.statSync ?? statSync;
|
|
148236
148385
|
const realpath = deps.realpathSync ?? realpathSync;
|
|
148237
|
-
const
|
|
148386
|
+
const mkdir2 = deps.mkdirSync ?? mkdirSync;
|
|
148238
148387
|
const blocked = forbiddenReason(target);
|
|
148239
148388
|
if (blocked) return blocked;
|
|
148240
148389
|
if (!roots.some((root) => isUnder(target, root))) {
|
|
@@ -148242,13 +148391,13 @@ function validateDirectory(rawPath, allowedRoots, deps = {}) {
|
|
|
148242
148391
|
}
|
|
148243
148392
|
let exists2 = true;
|
|
148244
148393
|
try {
|
|
148245
|
-
if (!
|
|
148394
|
+
if (!stat2(target).isDirectory()) return err("not_dir", `\u4E0D\u662F\u76EE\u5F55\uFF1A\`${target}\``);
|
|
148246
148395
|
} catch {
|
|
148247
148396
|
exists2 = false;
|
|
148248
148397
|
}
|
|
148249
148398
|
if (!exists2) {
|
|
148250
148399
|
try {
|
|
148251
|
-
|
|
148400
|
+
mkdir2(target, { recursive: true });
|
|
148252
148401
|
} catch {
|
|
148253
148402
|
return err("create_failed", `\u76EE\u5F55\u4E0D\u5B58\u5728\u4E14\u65E0\u6CD5\u521B\u5EFA\uFF1A\`${target}\`\u3002\u8BF7\u68C0\u67E5\u8DEF\u5F84\u4E0E\u6743\u9650\u3002`);
|
|
148254
148403
|
}
|
|
@@ -150046,21 +150195,20 @@ async function enterSessionThread(ctx, input) {
|
|
|
150046
150195
|
const onLimit = (report) => {
|
|
150047
150196
|
ctx.deps.log.warn("\u4F1A\u8BDD\u6062\u590D\u5361\u5185\u5BB9\u8D85\u9650\uFF0C\u5DF2\u964D\u7EA7", { sessionID, ...report });
|
|
150048
150197
|
};
|
|
150049
|
-
const
|
|
150198
|
+
const newNow = ctx.now();
|
|
150199
|
+
const baseInput = {
|
|
150050
150200
|
title: info?.title ?? "",
|
|
150051
150201
|
sessionID,
|
|
150052
150202
|
...dir ? { dir } : {},
|
|
150053
150203
|
...link2?.model ? { model: modelLabel(link2.model) } : {},
|
|
150054
150204
|
...info?.updatedAt ? { updatedAt: info.updatedAt } : {},
|
|
150055
|
-
now:
|
|
150205
|
+
now: newNow,
|
|
150056
150206
|
...ctx.deps.cardMaxTables !== void 0 ? { maxTables: ctx.deps.cardMaxTables } : {},
|
|
150057
150207
|
onLimit,
|
|
150058
|
-
|
|
150059
|
-
// 「🗜 压缩并总结」只在开关开启且运行时装配了签名时渲染。
|
|
150060
|
-
...showSummary && ctx.deps.signCompact ? { compactButton: { sessionID, token: ctx.deps.signCompact(sessionID) } } : {}
|
|
150208
|
+
openedTopic: true
|
|
150061
150209
|
};
|
|
150062
|
-
const
|
|
150063
|
-
const res = await ctx.deps.sender.sendCard(chatId,
|
|
150210
|
+
const rootCard = buildSessionOpenedCard(baseInput);
|
|
150211
|
+
const res = await ctx.deps.sender.sendCard(chatId, rootCard);
|
|
150064
150212
|
ctx.deps.log.info("\u53D1\u9001\u4F1A\u8BDD\u6062\u590D\u5361", {
|
|
150065
150213
|
sessionID,
|
|
150066
150214
|
source: input.source,
|
|
@@ -150082,26 +150230,63 @@ async function enterSessionThread(ctx, input) {
|
|
|
150082
150230
|
}
|
|
150083
150231
|
await ctx.deps.sessionMap.bindRoot(res.messageId, sessionID);
|
|
150084
150232
|
ctx.deps.log.info("resume card bound root to session", { sessionID, rootId: res.messageId });
|
|
150085
|
-
|
|
150233
|
+
const summaryBlock = {
|
|
150234
|
+
...showSummary ? { summaryPending: true } : {},
|
|
150235
|
+
...showSummary && ctx.deps.signCompact ? { compactButton: { sessionID, token: ctx.deps.signCompact(sessionID) } } : {}
|
|
150236
|
+
};
|
|
150237
|
+
const openerInput = { ...baseInput, ...summaryBlock };
|
|
150238
|
+
const openerRes = await ctx.deps.sender.replyCard(res.messageId, buildSessionOpenedCard(openerInput), {
|
|
150239
|
+
replyInThread: true
|
|
150240
|
+
});
|
|
150241
|
+
const openerMessageId = openerRes.ok ? openerRes.messageId : void 0;
|
|
150242
|
+
let threadId = openerRes.threadId;
|
|
150243
|
+
if (!threadId && openerMessageId) {
|
|
150244
|
+
threadId = (await ctx.deps.sender.getMessageMeta(openerMessageId))?.threadId;
|
|
150245
|
+
}
|
|
150246
|
+
const rootCardBase = {
|
|
150086
150247
|
style: "resumed",
|
|
150087
150248
|
sessionID,
|
|
150088
150249
|
title: info?.title ?? "",
|
|
150089
150250
|
...dir ? { dir } : {},
|
|
150090
150251
|
...link2?.model ? { model: modelLabel(link2.model) } : {},
|
|
150091
|
-
...info?.updatedAt ? { updatedAt: info.updatedAt } : {}
|
|
150092
|
-
|
|
150252
|
+
...info?.updatedAt ? { updatedAt: info.updatedAt } : {}
|
|
150253
|
+
};
|
|
150254
|
+
if (threadId) {
|
|
150255
|
+
await ctx.deps.sessionMap.bindThread(threadId, sessionID, chatId, operatorOpenId, res.messageId);
|
|
150256
|
+
ctx.deps.log.info("\u6062\u590D\u4F1A\u8BDD\u5DF2\u81EA\u52A8\u5F00\u8BDD\u9898", {
|
|
150257
|
+
sessionID,
|
|
150258
|
+
threadId,
|
|
150259
|
+
rootMessageId: res.messageId,
|
|
150260
|
+
openerMessageId
|
|
150261
|
+
});
|
|
150262
|
+
await ctx.deps.sessionMap.setRootCard(sessionID, { ...rootCardBase, openedTopic: true });
|
|
150263
|
+
if (showSummary && openerMessageId) {
|
|
150264
|
+
void patchResumeSummary(ctx, sessionID, dir, openerMessageId, openerInput, false);
|
|
150265
|
+
}
|
|
150266
|
+
return { ok: true, threadId, messageId: res.messageId };
|
|
150267
|
+
}
|
|
150268
|
+
ctx.deps.log.warn("\u6062\u590D\u4F1A\u8BDD\u81EA\u52A8\u5F00\u8BDD\u9898\u5931\u8D25\uFF0C\u56DE\u9000\u4E3A\u56DE\u590D\u6839\u5361", {
|
|
150269
|
+
sessionID,
|
|
150270
|
+
openError: openerRes.error ?? "unknown"
|
|
150271
|
+
});
|
|
150272
|
+
const fallbackInput = { ...baseInput, openedTopic: false, ...summaryBlock };
|
|
150273
|
+
await ctx.deps.sender.patchCard(res.messageId, buildSessionOpenedCard(fallbackInput));
|
|
150274
|
+
await ctx.deps.sessionMap.setRootCard(sessionID, {
|
|
150275
|
+
...rootCardBase,
|
|
150276
|
+
...showSummary ? { summaryPending: true, compactButton: Boolean(fallbackInput.compactButton) } : {}
|
|
150093
150277
|
});
|
|
150094
150278
|
if (showSummary) {
|
|
150095
|
-
void patchResumeSummary(ctx, sessionID, dir, res.messageId,
|
|
150279
|
+
void patchResumeSummary(ctx, sessionID, dir, res.messageId, fallbackInput, true);
|
|
150096
150280
|
}
|
|
150097
150281
|
return { ok: true, messageId: res.messageId };
|
|
150098
150282
|
}
|
|
150099
150283
|
var SUMMARY_LABEL = {
|
|
150100
150284
|
reused: "\u4F1A\u8BDD\u6458\u8981",
|
|
150101
150285
|
generated: "\u6458\u8981\uFF08\u5FEB\u6458\u8981\uFF09",
|
|
150286
|
+
excerpt: "\u6700\u8FD1\u4F1A\u8BDD\u8BB0\u5F55\uFF08\u622A\u53D6\u6458\u8981\uFF09",
|
|
150102
150287
|
compacted: "\u5DF2\u538B\u7F29 \xB7 \u4F1A\u8BDD\u6458\u8981"
|
|
150103
150288
|
};
|
|
150104
|
-
async function patchResumeSummary(ctx, sessionID, dir, messageId, cardInput) {
|
|
150289
|
+
async function patchResumeSummary(ctx, sessionID, dir, messageId, cardInput, persistBase) {
|
|
150105
150290
|
try {
|
|
150106
150291
|
const outcome = await ctx.deps.summarizeSession({
|
|
150107
150292
|
sessionID,
|
|
@@ -150110,9 +150295,11 @@ async function patchResumeSummary(ctx, sessionID, dir, messageId, cardInput) {
|
|
|
150110
150295
|
});
|
|
150111
150296
|
const summary = outcome.summary ?? "\uFF08\u6458\u8981\u751F\u6210\u5931\u8D25\uFF0C\u53EF\u76F4\u63A5\u53D1\u6D88\u606F\u7EE7\u7EED\uFF09";
|
|
150112
150297
|
const summaryLabel = SUMMARY_LABEL[outcome.source] ?? "\u6458\u8981";
|
|
150113
|
-
|
|
150114
|
-
|
|
150115
|
-
|
|
150298
|
+
if (persistBase) {
|
|
150299
|
+
const base2 = await ctx.deps.sessionMap.getRootCard(sessionID);
|
|
150300
|
+
if (base2) {
|
|
150301
|
+
await ctx.deps.sessionMap.setRootCard(sessionID, { ...base2, summaryPending: false, summary, summaryLabel });
|
|
150302
|
+
}
|
|
150116
150303
|
}
|
|
150117
150304
|
const res = await ctx.deps.sender.patchCard(
|
|
150118
150305
|
messageId,
|
|
@@ -150688,7 +150875,7 @@ var SummaryTimeoutError = class extends Error {
|
|
|
150688
150875
|
this.name = "SummaryTimeoutError";
|
|
150689
150876
|
}
|
|
150690
150877
|
};
|
|
150691
|
-
function
|
|
150878
|
+
function withTimeout2(promise3, ms) {
|
|
150692
150879
|
return new Promise((resolve4, reject) => {
|
|
150693
150880
|
const timer = setTimeout(() => reject(new SummaryTimeoutError()), Math.max(1, ms));
|
|
150694
150881
|
timer.unref?.();
|
|
@@ -150752,12 +150939,26 @@ function buildSummaryPrompt(transcript) {
|
|
|
150752
150939
|
\u4F1A\u8BDD\u6700\u8FD1\u8BB0\u5F55\uFF1A
|
|
150753
150940
|
${transcript}` : RESUME_SUMMARY_PROMPT;
|
|
150754
150941
|
}
|
|
150942
|
+
function buildExcerptSummary(transcript, maxLines = 5, maxChars = 900) {
|
|
150943
|
+
if (!transcript) return void 0;
|
|
150944
|
+
const lines = transcript.split("\n").filter(Boolean);
|
|
150945
|
+
if (lines.length === 0) return void 0;
|
|
150946
|
+
const out = [];
|
|
150947
|
+
let bytes = 0;
|
|
150948
|
+
for (const line of lines) {
|
|
150949
|
+
const size6 = Buffer.byteLength(line, "utf8");
|
|
150950
|
+
if (out.length >= maxLines || bytes + size6 > maxChars) break;
|
|
150951
|
+
out.push(line);
|
|
150952
|
+
bytes += size6;
|
|
150953
|
+
}
|
|
150954
|
+
return out.length > 0 ? out.join("\n") : void 0;
|
|
150955
|
+
}
|
|
150755
150956
|
async function summarizeSession(deps, input) {
|
|
150756
150957
|
const timeoutMs = Math.max(1, Math.floor(input.timeoutMs));
|
|
150757
150958
|
let messages;
|
|
150758
150959
|
if (deps.readMessages) {
|
|
150759
150960
|
try {
|
|
150760
|
-
messages = await
|
|
150961
|
+
messages = await withTimeout2(deps.readMessages(input.sessionID, input.directory), timeoutMs);
|
|
150761
150962
|
const reused = extractLatestSummary(messages);
|
|
150762
150963
|
if (reused) return { summary: reused, source: "reused" };
|
|
150763
150964
|
} catch (err2) {
|
|
@@ -150770,12 +150971,14 @@ async function summarizeSession(deps, input) {
|
|
|
150770
150971
|
if (!deps.generateText) return { source: "none" };
|
|
150771
150972
|
try {
|
|
150772
150973
|
const transcript = buildTranscript(messages);
|
|
150773
|
-
const raw = await
|
|
150974
|
+
const raw = await withTimeout2(
|
|
150774
150975
|
deps.generateText(buildSummaryPrompt(transcript), input.directory),
|
|
150775
150976
|
timeoutMs
|
|
150776
150977
|
);
|
|
150777
150978
|
const generated = extractGeneratedText(raw);
|
|
150778
150979
|
if (generated) return { summary: generated, source: "generated" };
|
|
150980
|
+
const excerpt = buildExcerptSummary(transcript);
|
|
150981
|
+
if (excerpt) return { summary: excerpt, source: "excerpt" };
|
|
150779
150982
|
return { source: "none", error: "empty-summary" };
|
|
150780
150983
|
} catch (err2) {
|
|
150781
150984
|
const error = errorMessage(err2);
|
|
@@ -150784,6 +150987,11 @@ async function summarizeSession(deps, input) {
|
|
|
150784
150987
|
timedOut: err2 instanceof SummaryTimeoutError,
|
|
150785
150988
|
error
|
|
150786
150989
|
});
|
|
150990
|
+
try {
|
|
150991
|
+
const excerpt = buildExcerptSummary(buildTranscript(messages));
|
|
150992
|
+
if (excerpt) return { summary: excerpt, source: "excerpt" };
|
|
150993
|
+
} catch {
|
|
150994
|
+
}
|
|
150787
150995
|
return { source: "none", error };
|
|
150788
150996
|
}
|
|
150789
150997
|
}
|
|
@@ -151763,8 +151971,31 @@ async function start(ctx, config, log2, logSink) {
|
|
|
151763
151971
|
...model ? { model } : {}
|
|
151764
151972
|
});
|
|
151765
151973
|
if (!receipt.ok) log2.warn("\u56DE\u6267\u5361\u672A\u53D1\u9001\uFF0C\u4ECD\u7EE7\u7EED prompt", { sessionID, delivery });
|
|
151974
|
+
let promptText = message.text;
|
|
151975
|
+
const files = [];
|
|
151976
|
+
if (config.acceptAttachments && message.attachment) {
|
|
151977
|
+
const outcome = await downloadAttachment({
|
|
151978
|
+
client,
|
|
151979
|
+
messageId: message.messageId,
|
|
151980
|
+
attachment: message.attachment,
|
|
151981
|
+
dir: config.attachmentsDir,
|
|
151982
|
+
maxBytes: config.attachmentMaxBytes,
|
|
151983
|
+
timeoutMs: config.attachmentTimeoutMs,
|
|
151984
|
+
log: log2
|
|
151985
|
+
});
|
|
151986
|
+
if (outcome.ok) {
|
|
151987
|
+
files.push({ uri: pathToFileURL(outcome.path).href });
|
|
151988
|
+
promptText = `${promptText}
|
|
151989
|
+
|
|
151990
|
+
${downloadedAttachmentPrompt(message.attachment, outcome)}`;
|
|
151991
|
+
} else {
|
|
151992
|
+
promptText = `${promptText}
|
|
151993
|
+
|
|
151994
|
+
[\u9644\u4EF6] \u4E0B\u8F7D\u5931\u8D25\uFF1A${outcome.reason}`;
|
|
151995
|
+
}
|
|
151996
|
+
}
|
|
151766
151997
|
try {
|
|
151767
|
-
await promptSession(ctx, sessionID,
|
|
151998
|
+
await promptSession(ctx, sessionID, promptText, delivery, files);
|
|
151768
151999
|
} catch (err2) {
|
|
151769
152000
|
log2.warn("prompt \u53D1\u9001\u5931\u8D25", { sessionID, error: errorMessage(err2) });
|
|
151770
152001
|
runs.apply(sessionID, { type: "execution.failed", error: errorMessage(err2) });
|
|
@@ -151788,13 +152019,32 @@ async function start(ctx, config, log2, logSink) {
|
|
|
151788
152019
|
})
|
|
151789
152020
|
});
|
|
151790
152021
|
const abort = new AbortController();
|
|
152022
|
+
const EVENT_RETRY_BASE_MS = 1e3;
|
|
152023
|
+
const EVENT_RETRY_MAX_MS = 3e4;
|
|
152024
|
+
const sleep3 = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
151791
152025
|
const subscription = (async () => {
|
|
151792
|
-
|
|
151793
|
-
|
|
151794
|
-
|
|
152026
|
+
let attempt = 0;
|
|
152027
|
+
while (!abort.signal.aborted) {
|
|
152028
|
+
try {
|
|
152029
|
+
for await (const event of ctx.event.subscribe({ signal: abort.signal })) {
|
|
152030
|
+
if (attempt > 0) {
|
|
152031
|
+
log2.info("\u4E8B\u4EF6\u8BA2\u9605\u5DF2\u91CD\u8FDE", { attempt });
|
|
152032
|
+
}
|
|
152033
|
+
attempt = 0;
|
|
152034
|
+
await handleEvent(event);
|
|
152035
|
+
}
|
|
152036
|
+
if (abort.signal.aborted) break;
|
|
152037
|
+
attempt += 1;
|
|
152038
|
+
const delay3 = Math.min(EVENT_RETRY_MAX_MS, EVENT_RETRY_BASE_MS * 2 ** Math.min(attempt, 5));
|
|
152039
|
+
log2.warn("\u4E8B\u4EF6\u8BA2\u9605\u6D41\u5DF2\u7ED3\u675F\uFF0C\u51C6\u5907\u91CD\u8FDE", { attempt, delayMs: delay3 });
|
|
152040
|
+
await sleep3(delay3);
|
|
152041
|
+
} catch (err2) {
|
|
152042
|
+
if (abort.signal.aborted) break;
|
|
152043
|
+
attempt += 1;
|
|
152044
|
+
const delay3 = Math.min(EVENT_RETRY_MAX_MS, EVENT_RETRY_BASE_MS * 2 ** Math.min(attempt, 5));
|
|
152045
|
+
log2.error("\u4E8B\u4EF6\u8BA2\u9605\u5F02\u5E38\uFF0C\u51C6\u5907\u91CD\u8FDE", { attempt, delayMs: delay3, error: errorMessage(err2) });
|
|
152046
|
+
await sleep3(delay3);
|
|
151795
152047
|
}
|
|
151796
|
-
} catch (err2) {
|
|
151797
|
-
if (!abort.signal.aborted) log2.error("\u4E8B\u4EF6\u8BA2\u9605\u5F02\u5E38\u9000\u51FA", { error: errorMessage(err2) });
|
|
151798
152048
|
}
|
|
151799
152049
|
})();
|
|
151800
152050
|
const eventDeps = {
|
|
@@ -151983,9 +152233,14 @@ async function replyPermission(ctx, input) {
|
|
|
151983
152233
|
throw err2;
|
|
151984
152234
|
}
|
|
151985
152235
|
}
|
|
151986
|
-
async function promptSession(ctx, sessionID, text, delivery) {
|
|
152236
|
+
async function promptSession(ctx, sessionID, text, delivery, files) {
|
|
151987
152237
|
const api = ctx.session.prompt;
|
|
151988
|
-
await api({
|
|
152238
|
+
await api({
|
|
152239
|
+
sessionID,
|
|
152240
|
+
text,
|
|
152241
|
+
delivery,
|
|
152242
|
+
...files && files.length > 0 ? { files } : {}
|
|
152243
|
+
});
|
|
151989
152244
|
}
|
|
151990
152245
|
async function readSessionMessages(ctx, sessionID, directory, log2) {
|
|
151991
152246
|
const options2 = directory ? { headers: { "x-opencode-directory": directory } } : void 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-feishu-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Feishu/Lark plugin for OpenCode V2 — single-user topics as sessions, in-card permission approvals, streaming cards. Minimal permissions, no public endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|