opencode-feishu-plugin 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +111 -446
- package/README.md +78 -254
- package/dist/index.js +252 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -144025,6 +144025,10 @@ function resolveConfig2(raw, env = process.env, deps = {}) {
|
|
|
144025
144025
|
const topicStatusThrottleMs = clamp4(asNumber(merged.topicStatusThrottleMs, 1e3), 500, 1e4);
|
|
144026
144026
|
const cardMaxTables = clamp4(asNumber(merged.cardMaxTables, 4), 1, 5);
|
|
144027
144027
|
const keepalive = asBoolean(merged.keepalive, true);
|
|
144028
|
+
const runnerCardMaxTools = clamp4(asNumber(merged.runnerCardMaxTools, 12), 1, 50);
|
|
144029
|
+
const runnerCardTextMax = clamp4(asNumber(merged.runnerCardTextMax, 2048), 512, 8192);
|
|
144030
|
+
const finalAnswerMinChars = clamp4(asNumber(merged.finalAnswerMinChars, 600), 0, 2e4);
|
|
144031
|
+
const finalAnswerFileMinBytes = clamp4(asNumber(merged.finalAnswerFileMinBytes, 20 * 1024), 8192, 102400);
|
|
144028
144032
|
const keepaliveIntervalMs = clamp4(
|
|
144029
144033
|
asNumber(merged.keepaliveIntervalMs, 20 * 60 * 1e3),
|
|
144030
144034
|
5 * 60 * 1e3,
|
|
@@ -144081,7 +144085,11 @@ function resolveConfig2(raw, env = process.env, deps = {}) {
|
|
|
144081
144085
|
topicStatusThrottleMs,
|
|
144082
144086
|
cardMaxTables,
|
|
144083
144087
|
keepalive,
|
|
144084
|
-
keepaliveIntervalMs
|
|
144088
|
+
keepaliveIntervalMs,
|
|
144089
|
+
runnerCardMaxTools,
|
|
144090
|
+
runnerCardTextMax,
|
|
144091
|
+
finalAnswerMinChars,
|
|
144092
|
+
finalAnswerFileMinBytes
|
|
144085
144093
|
};
|
|
144086
144094
|
}
|
|
144087
144095
|
function loadConfigFile(env, deps, warnings) {
|
|
@@ -145173,6 +145181,29 @@ function createFeishuSender(client, log2, options2 = {}) {
|
|
|
145173
145181
|
return void 0;
|
|
145174
145182
|
}
|
|
145175
145183
|
},
|
|
145184
|
+
async sendFile(chatId, fileName, content, replyToMessageId) {
|
|
145185
|
+
try {
|
|
145186
|
+
const uploaded = await client.im.file.create({
|
|
145187
|
+
data: { file_type: "stream", file_name: fileName, file: content }
|
|
145188
|
+
});
|
|
145189
|
+
const fileKey = uploaded?.file_key ?? "";
|
|
145190
|
+
if (!fileKey) return { ok: false, error: "missing file_key" };
|
|
145191
|
+
const payload = { msg_type: "file", content: JSON.stringify({ file_key: fileKey }) };
|
|
145192
|
+
const res = replyToMessageId ? await client.im.message.reply({ path: { message_id: replyToMessageId }, data: payload }) : await client.im.message.create({
|
|
145193
|
+
params: { receive_id_type: "chat_id" },
|
|
145194
|
+
data: { receive_id: chatId, ...payload }
|
|
145195
|
+
});
|
|
145196
|
+
if (res?.code && res.code !== 0) {
|
|
145197
|
+
log2.warn("\u53D1\u9001\u6587\u4EF6\u5931\u8D25", { chatId, fileName, code: res.code, msg: res.msg });
|
|
145198
|
+
return { ok: false, error: `code=${res.code} msg=${res.msg ?? ""}` };
|
|
145199
|
+
}
|
|
145200
|
+
const messageId = res?.data?.message_id ?? "";
|
|
145201
|
+
return messageId ? { ok: true, messageId } : { ok: true };
|
|
145202
|
+
} catch (err2) {
|
|
145203
|
+
log2.warn("\u53D1\u9001\u6587\u4EF6\u5F02\u5E38", { chatId, fileName, error: describeLarkError(err2) });
|
|
145204
|
+
return { ok: false, error: describeLarkError(err2) };
|
|
145205
|
+
}
|
|
145206
|
+
},
|
|
145176
145207
|
async deleteMessage(messageId) {
|
|
145177
145208
|
if (!messageId) return { ok: false, error: "missing messageId" };
|
|
145178
145209
|
try {
|
|
@@ -145567,7 +145598,8 @@ function parseRootCard(value3) {
|
|
|
145567
145598
|
...value3.compactPending === true ? { compactPending: true } : {},
|
|
145568
145599
|
...compactError ? { compactError } : {},
|
|
145569
145600
|
...value3.compactButton === true ? { compactButton: true } : {},
|
|
145570
|
-
...note2 ? { note: note2 } : {}
|
|
145601
|
+
...note2 ? { note: note2 } : {},
|
|
145602
|
+
...value3.openedTopic === true ? { openedTopic: true } : {}
|
|
145571
145603
|
};
|
|
145572
145604
|
}
|
|
145573
145605
|
function parseStringArray(value3) {
|
|
@@ -145885,6 +145917,8 @@ function reduce6(state, event) {
|
|
|
145885
145917
|
status: "error",
|
|
145886
145918
|
output: event.output
|
|
145887
145919
|
}));
|
|
145920
|
+
case "final.separated":
|
|
145921
|
+
return { ...state, finalSeparated: true };
|
|
145888
145922
|
default:
|
|
145889
145923
|
return state;
|
|
145890
145924
|
}
|
|
@@ -146061,6 +146095,14 @@ function closeCodeFences(text) {
|
|
|
146061
146095
|
if (fences && fences.length % 2 !== 0) return `${text}${CODE_FENCE}`;
|
|
146062
146096
|
return text;
|
|
146063
146097
|
}
|
|
146098
|
+
function buildFinalAnswerCard(text) {
|
|
146099
|
+
return {
|
|
146100
|
+
schema: "2.0",
|
|
146101
|
+
config: { update_multi: true },
|
|
146102
|
+
header: { title: { tag: "plain_text", content: "\u2705 \u5B8C\u6574\u56DE\u7B54" }, template: "green" },
|
|
146103
|
+
body: { elements: [{ tag: "markdown", content: truncateCardContent(text, 24 * 1024) }] }
|
|
146104
|
+
};
|
|
146105
|
+
}
|
|
146064
146106
|
|
|
146065
146107
|
// src/feishu/run-renderer.ts
|
|
146066
146108
|
var COLLAPSE_TOOL_THRESHOLD = 3;
|
|
@@ -146068,11 +146110,30 @@ var HEADER_SUMMARY_MAX = 80;
|
|
|
146068
146110
|
var TOOL_INPUT_MAX = 600;
|
|
146069
146111
|
var OUTPUT_FIRST_LINE_MAX = 200;
|
|
146070
146112
|
var TEXT_ELEMENT_MAX = 8 * 1024;
|
|
146113
|
+
var DEFAULT_MAX_TOOLS = 12;
|
|
146114
|
+
var DEFAULT_TEXT_MAX = 2 * 1024;
|
|
146071
146115
|
function renderRunCard(state, stopValue, options2 = {}) {
|
|
146072
146116
|
const elements = [];
|
|
146073
|
-
|
|
146117
|
+
const maxTools = options2.maxTools ?? DEFAULT_MAX_TOOLS;
|
|
146118
|
+
const textMax = options2.textMax ?? DEFAULT_TEXT_MAX;
|
|
146119
|
+
const capped = capToolBlocks(state.blocks, maxTools);
|
|
146120
|
+
if (capped.omitted > 0) {
|
|
146121
|
+
elements.push(note(`\u2026\u5DF2\u7701\u7565\u524D ${capped.omitted} \u6B21\u5DE5\u5177\u8C03\u7528\uFF08\u5B8C\u6574\u8BB0\u5F55\u89C1 opencode \u4F1A\u8BDD\uFF09`));
|
|
146122
|
+
}
|
|
146123
|
+
const groups = groupBlocks(capped.blocks);
|
|
146124
|
+
const lastTextIndex = groups.reduce(
|
|
146125
|
+
(acc, g, i) => g.kind === "text" && g.content.trim() ? i : acc,
|
|
146126
|
+
-1
|
|
146127
|
+
);
|
|
146128
|
+
for (let i = 0; i < groups.length; i += 1) {
|
|
146129
|
+
const group = groups[i];
|
|
146074
146130
|
if (group.kind === "text") {
|
|
146075
|
-
if (group.content.trim())
|
|
146131
|
+
if (!group.content.trim()) continue;
|
|
146132
|
+
if (state.finalSeparated && i === lastTextIndex) {
|
|
146133
|
+
elements.push(note("\u2705 \u5B8C\u6574\u56DE\u7B54\u5DF2\u5355\u72EC\u53D1\u9001\uFF08\u89C1\u4E0B\u65B9\u5361\u7247 / \u9644\u4EF6\uFF09"));
|
|
146134
|
+
continue;
|
|
146135
|
+
}
|
|
146136
|
+
elements.push(markdown(truncateCardContent(group.content, Math.min(textMax, TEXT_ELEMENT_MAX))));
|
|
146076
146137
|
} else {
|
|
146077
146138
|
elements.push(...renderToolGroup(group.tools, state.terminal !== "running"));
|
|
146078
146139
|
}
|
|
@@ -146109,6 +146170,20 @@ function renderRunCard(state, stopValue, options2 = {}) {
|
|
|
146109
146170
|
}
|
|
146110
146171
|
return enforceSize(guarded);
|
|
146111
146172
|
}
|
|
146173
|
+
function capToolBlocks(blocks, maxTools) {
|
|
146174
|
+
const total2 = blocks.reduce((n, b) => n + (b.kind === "tool" ? 1 : 0), 0);
|
|
146175
|
+
if (maxTools <= 0 || total2 <= maxTools) return { blocks: [...blocks], omitted: 0 };
|
|
146176
|
+
let toDrop = total2 - maxTools;
|
|
146177
|
+
const out = [];
|
|
146178
|
+
for (const block of blocks) {
|
|
146179
|
+
if (block.kind === "tool" && toDrop > 0) {
|
|
146180
|
+
toDrop -= 1;
|
|
146181
|
+
continue;
|
|
146182
|
+
}
|
|
146183
|
+
out.push(block);
|
|
146184
|
+
}
|
|
146185
|
+
return { blocks: out, omitted: total2 - maxTools };
|
|
146186
|
+
}
|
|
146112
146187
|
function groupBlocks(blocks) {
|
|
146113
146188
|
const groups = [];
|
|
146114
146189
|
let toolBuf = [];
|
|
@@ -146295,6 +146370,19 @@ function byteLength(value3) {
|
|
|
146295
146370
|
}
|
|
146296
146371
|
|
|
146297
146372
|
// src/feishu/run-controller.ts
|
|
146373
|
+
function trailingText(blocks) {
|
|
146374
|
+
const parts = [];
|
|
146375
|
+
for (let i = blocks.length - 1; i >= 0; i -= 1) {
|
|
146376
|
+
const block = blocks[i];
|
|
146377
|
+
if (block.kind === "tool") {
|
|
146378
|
+
if (parts.length > 0) break;
|
|
146379
|
+
continue;
|
|
146380
|
+
}
|
|
146381
|
+
if (block.content.trim()) parts.unshift(block.content);
|
|
146382
|
+
else if (parts.length > 0) break;
|
|
146383
|
+
}
|
|
146384
|
+
return parts.join("\n\n").trim();
|
|
146385
|
+
}
|
|
146298
146386
|
function createRunController(deps) {
|
|
146299
146387
|
const sessions = /* @__PURE__ */ new Map();
|
|
146300
146388
|
const throttlers = /* @__PURE__ */ new Map();
|
|
@@ -146312,6 +146400,8 @@ function createRunController(deps) {
|
|
|
146312
146400
|
};
|
|
146313
146401
|
const renderCard = (sessionID, state) => renderRunCard(state, deps.buildStopValue?.(sessionID), {
|
|
146314
146402
|
maxTables: deps.cardMaxTables,
|
|
146403
|
+
maxTools: deps.runnerCardMaxTools,
|
|
146404
|
+
textMax: deps.runnerCardTextMax,
|
|
146315
146405
|
onLimit: (report) => {
|
|
146316
146406
|
deps.log.warn("\u8FD0\u884C\u5361\u5185\u5BB9\u8D85\u9650\uFF0C\u5DF2\u964D\u7EA7", {
|
|
146317
146407
|
sessionID,
|
|
@@ -146389,7 +146479,8 @@ function createRunController(deps) {
|
|
|
146389
146479
|
state,
|
|
146390
146480
|
chain: Promise.resolve(),
|
|
146391
146481
|
finalized: false,
|
|
146392
|
-
queuedAt: Date.now()
|
|
146482
|
+
queuedAt: Date.now(),
|
|
146483
|
+
...input.replyToMessageId ? { replyToMessageId: input.replyToMessageId } : {}
|
|
146393
146484
|
};
|
|
146394
146485
|
if (input.delivery === "queue") {
|
|
146395
146486
|
runs.queued.push(card);
|
|
@@ -146419,7 +146510,29 @@ function createRunController(deps) {
|
|
|
146419
146510
|
}
|
|
146420
146511
|
const card = runs.active;
|
|
146421
146512
|
if (!card) return;
|
|
146422
|
-
if (event.type === "execution.succeeded"
|
|
146513
|
+
if (event.type === "execution.succeeded") {
|
|
146514
|
+
const text = trailingText(card.state.blocks);
|
|
146515
|
+
const long = deps.sendFinalAnswer !== void 0 && deps.finalAnswer !== void 0 && text.length >= deps.finalAnswer.minChars;
|
|
146516
|
+
if (long) {
|
|
146517
|
+
card.state = reduce6(reduce6(card.state, { type: "final.separated" }), event);
|
|
146518
|
+
finalize2(card);
|
|
146519
|
+
runs.active = void 0;
|
|
146520
|
+
void deps.sendFinalAnswer({
|
|
146521
|
+
sessionID,
|
|
146522
|
+
chatId: card.chatId,
|
|
146523
|
+
...card.replyToMessageId ? { replyToMessageId: card.replyToMessageId } : {},
|
|
146524
|
+
text
|
|
146525
|
+
}).catch((err2) => {
|
|
146526
|
+
deps.log.warn("\u6700\u7EC8\u7B54\u6848\u53D1\u9001\u5931\u8D25", { sessionID, error: errorMessage(err2) });
|
|
146527
|
+
});
|
|
146528
|
+
return;
|
|
146529
|
+
}
|
|
146530
|
+
update2(card, event, true);
|
|
146531
|
+
finalize2(card);
|
|
146532
|
+
runs.active = void 0;
|
|
146533
|
+
return;
|
|
146534
|
+
}
|
|
146535
|
+
if (event.type === "execution.failed") {
|
|
146423
146536
|
update2(card, event, true);
|
|
146424
146537
|
finalize2(card);
|
|
146425
146538
|
runs.active = void 0;
|
|
@@ -147971,7 +148084,10 @@ function renderResumedBody(title, base2, now3) {
|
|
|
147971
148084
|
setup.push(`- \u6700\u8FD1\u6D3B\u52A8\uFF1A${relativeTime(base2.updatedAt, now3)}`);
|
|
147972
148085
|
}
|
|
147973
148086
|
if (setup.length > 0) lines.push("", ...setup);
|
|
147974
|
-
lines.push(
|
|
148087
|
+
lines.push(
|
|
148088
|
+
"",
|
|
148089
|
+
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"
|
|
148090
|
+
);
|
|
147975
148091
|
if (base2.summary || base2.summaryPending || base2.compactPending || base2.compactError) {
|
|
147976
148092
|
lines.push("", `**${base2.summaryLabel ?? "\u6458\u8981"}**\uFF1A`);
|
|
147977
148093
|
if (base2.summaryPending) lines.push("\u23F3 \u6B63\u5728\u603B\u7ED3\u8BE5\u4F1A\u8BDD\u2026");
|
|
@@ -148010,7 +148126,8 @@ function buildSessionOpenedCard(input) {
|
|
|
148010
148126
|
...input.summaryPending ? { summaryPending: true } : {},
|
|
148011
148127
|
...input.compactPending ? { compactPending: true } : {},
|
|
148012
148128
|
...input.compactError ? { compactError: input.compactError } : {},
|
|
148013
|
-
...input.compactButton ? { compactButton: true } : {}
|
|
148129
|
+
...input.compactButton ? { compactButton: true } : {},
|
|
148130
|
+
...input.openedTopic ? { openedTopic: true } : {}
|
|
148014
148131
|
};
|
|
148015
148132
|
return buildSessionRootCard(base2, void 0, {
|
|
148016
148133
|
now: input.now ?? Date.now(),
|
|
@@ -149934,21 +150051,20 @@ async function enterSessionThread(ctx, input) {
|
|
|
149934
150051
|
const onLimit = (report) => {
|
|
149935
150052
|
ctx.deps.log.warn("\u4F1A\u8BDD\u6062\u590D\u5361\u5185\u5BB9\u8D85\u9650\uFF0C\u5DF2\u964D\u7EA7", { sessionID, ...report });
|
|
149936
150053
|
};
|
|
149937
|
-
const
|
|
150054
|
+
const newNow = ctx.now();
|
|
150055
|
+
const baseInput = {
|
|
149938
150056
|
title: info?.title ?? "",
|
|
149939
150057
|
sessionID,
|
|
149940
150058
|
...dir ? { dir } : {},
|
|
149941
150059
|
...link2?.model ? { model: modelLabel(link2.model) } : {},
|
|
149942
150060
|
...info?.updatedAt ? { updatedAt: info.updatedAt } : {},
|
|
149943
|
-
now:
|
|
150061
|
+
now: newNow,
|
|
149944
150062
|
...ctx.deps.cardMaxTables !== void 0 ? { maxTables: ctx.deps.cardMaxTables } : {},
|
|
149945
150063
|
onLimit,
|
|
149946
|
-
|
|
149947
|
-
// 「🗜 压缩并总结」只在开关开启且运行时装配了签名时渲染。
|
|
149948
|
-
...showSummary && ctx.deps.signCompact ? { compactButton: { sessionID, token: ctx.deps.signCompact(sessionID) } } : {}
|
|
150064
|
+
openedTopic: true
|
|
149949
150065
|
};
|
|
149950
|
-
const
|
|
149951
|
-
const res = await ctx.deps.sender.sendCard(chatId,
|
|
150066
|
+
const rootCard = buildSessionOpenedCard(baseInput);
|
|
150067
|
+
const res = await ctx.deps.sender.sendCard(chatId, rootCard);
|
|
149952
150068
|
ctx.deps.log.info("\u53D1\u9001\u4F1A\u8BDD\u6062\u590D\u5361", {
|
|
149953
150069
|
sessionID,
|
|
149954
150070
|
source: input.source,
|
|
@@ -149970,26 +150086,63 @@ async function enterSessionThread(ctx, input) {
|
|
|
149970
150086
|
}
|
|
149971
150087
|
await ctx.deps.sessionMap.bindRoot(res.messageId, sessionID);
|
|
149972
150088
|
ctx.deps.log.info("resume card bound root to session", { sessionID, rootId: res.messageId });
|
|
149973
|
-
|
|
150089
|
+
const summaryBlock = {
|
|
150090
|
+
...showSummary ? { summaryPending: true } : {},
|
|
150091
|
+
...showSummary && ctx.deps.signCompact ? { compactButton: { sessionID, token: ctx.deps.signCompact(sessionID) } } : {}
|
|
150092
|
+
};
|
|
150093
|
+
const openerInput = { ...baseInput, ...summaryBlock };
|
|
150094
|
+
const openerRes = await ctx.deps.sender.replyCard(res.messageId, buildSessionOpenedCard(openerInput), {
|
|
150095
|
+
replyInThread: true
|
|
150096
|
+
});
|
|
150097
|
+
const openerMessageId = openerRes.ok ? openerRes.messageId : void 0;
|
|
150098
|
+
let threadId = openerRes.threadId;
|
|
150099
|
+
if (!threadId && openerMessageId) {
|
|
150100
|
+
threadId = (await ctx.deps.sender.getMessageMeta(openerMessageId))?.threadId;
|
|
150101
|
+
}
|
|
150102
|
+
const rootCardBase = {
|
|
149974
150103
|
style: "resumed",
|
|
149975
150104
|
sessionID,
|
|
149976
150105
|
title: info?.title ?? "",
|
|
149977
150106
|
...dir ? { dir } : {},
|
|
149978
150107
|
...link2?.model ? { model: modelLabel(link2.model) } : {},
|
|
149979
|
-
...info?.updatedAt ? { updatedAt: info.updatedAt } : {}
|
|
149980
|
-
|
|
150108
|
+
...info?.updatedAt ? { updatedAt: info.updatedAt } : {}
|
|
150109
|
+
};
|
|
150110
|
+
if (threadId) {
|
|
150111
|
+
await ctx.deps.sessionMap.bindThread(threadId, sessionID, chatId, operatorOpenId, res.messageId);
|
|
150112
|
+
ctx.deps.log.info("\u6062\u590D\u4F1A\u8BDD\u5DF2\u81EA\u52A8\u5F00\u8BDD\u9898", {
|
|
150113
|
+
sessionID,
|
|
150114
|
+
threadId,
|
|
150115
|
+
rootMessageId: res.messageId,
|
|
150116
|
+
openerMessageId
|
|
150117
|
+
});
|
|
150118
|
+
await ctx.deps.sessionMap.setRootCard(sessionID, { ...rootCardBase, openedTopic: true });
|
|
150119
|
+
if (showSummary && openerMessageId) {
|
|
150120
|
+
void patchResumeSummary(ctx, sessionID, dir, openerMessageId, openerInput, false);
|
|
150121
|
+
}
|
|
150122
|
+
return { ok: true, threadId, messageId: res.messageId };
|
|
150123
|
+
}
|
|
150124
|
+
ctx.deps.log.warn("\u6062\u590D\u4F1A\u8BDD\u81EA\u52A8\u5F00\u8BDD\u9898\u5931\u8D25\uFF0C\u56DE\u9000\u4E3A\u56DE\u590D\u6839\u5361", {
|
|
150125
|
+
sessionID,
|
|
150126
|
+
openError: openerRes.error ?? "unknown"
|
|
150127
|
+
});
|
|
150128
|
+
const fallbackInput = { ...baseInput, openedTopic: false, ...summaryBlock };
|
|
150129
|
+
await ctx.deps.sender.patchCard(res.messageId, buildSessionOpenedCard(fallbackInput));
|
|
150130
|
+
await ctx.deps.sessionMap.setRootCard(sessionID, {
|
|
150131
|
+
...rootCardBase,
|
|
150132
|
+
...showSummary ? { summaryPending: true, compactButton: Boolean(fallbackInput.compactButton) } : {}
|
|
149981
150133
|
});
|
|
149982
150134
|
if (showSummary) {
|
|
149983
|
-
void patchResumeSummary(ctx, sessionID, dir, res.messageId,
|
|
150135
|
+
void patchResumeSummary(ctx, sessionID, dir, res.messageId, fallbackInput, true);
|
|
149984
150136
|
}
|
|
149985
150137
|
return { ok: true, messageId: res.messageId };
|
|
149986
150138
|
}
|
|
149987
150139
|
var SUMMARY_LABEL = {
|
|
149988
150140
|
reused: "\u4F1A\u8BDD\u6458\u8981",
|
|
149989
150141
|
generated: "\u6458\u8981\uFF08\u5FEB\u6458\u8981\uFF09",
|
|
150142
|
+
excerpt: "\u6700\u8FD1\u4F1A\u8BDD\u8BB0\u5F55\uFF08\u622A\u53D6\u6458\u8981\uFF09",
|
|
149990
150143
|
compacted: "\u5DF2\u538B\u7F29 \xB7 \u4F1A\u8BDD\u6458\u8981"
|
|
149991
150144
|
};
|
|
149992
|
-
async function patchResumeSummary(ctx, sessionID, dir, messageId, cardInput) {
|
|
150145
|
+
async function patchResumeSummary(ctx, sessionID, dir, messageId, cardInput, persistBase) {
|
|
149993
150146
|
try {
|
|
149994
150147
|
const outcome = await ctx.deps.summarizeSession({
|
|
149995
150148
|
sessionID,
|
|
@@ -149998,9 +150151,11 @@ async function patchResumeSummary(ctx, sessionID, dir, messageId, cardInput) {
|
|
|
149998
150151
|
});
|
|
149999
150152
|
const summary = outcome.summary ?? "\uFF08\u6458\u8981\u751F\u6210\u5931\u8D25\uFF0C\u53EF\u76F4\u63A5\u53D1\u6D88\u606F\u7EE7\u7EED\uFF09";
|
|
150000
150153
|
const summaryLabel = SUMMARY_LABEL[outcome.source] ?? "\u6458\u8981";
|
|
150001
|
-
|
|
150002
|
-
|
|
150003
|
-
|
|
150154
|
+
if (persistBase) {
|
|
150155
|
+
const base2 = await ctx.deps.sessionMap.getRootCard(sessionID);
|
|
150156
|
+
if (base2) {
|
|
150157
|
+
await ctx.deps.sessionMap.setRootCard(sessionID, { ...base2, summaryPending: false, summary, summaryLabel });
|
|
150158
|
+
}
|
|
150004
150159
|
}
|
|
150005
150160
|
const res = await ctx.deps.sender.patchCard(
|
|
150006
150161
|
messageId,
|
|
@@ -150640,6 +150795,20 @@ function buildSummaryPrompt(transcript) {
|
|
|
150640
150795
|
\u4F1A\u8BDD\u6700\u8FD1\u8BB0\u5F55\uFF1A
|
|
150641
150796
|
${transcript}` : RESUME_SUMMARY_PROMPT;
|
|
150642
150797
|
}
|
|
150798
|
+
function buildExcerptSummary(transcript, maxLines = 5, maxChars = 900) {
|
|
150799
|
+
if (!transcript) return void 0;
|
|
150800
|
+
const lines = transcript.split("\n").filter(Boolean);
|
|
150801
|
+
if (lines.length === 0) return void 0;
|
|
150802
|
+
const out = [];
|
|
150803
|
+
let bytes = 0;
|
|
150804
|
+
for (const line of lines) {
|
|
150805
|
+
const size6 = Buffer.byteLength(line, "utf8");
|
|
150806
|
+
if (out.length >= maxLines || bytes + size6 > maxChars) break;
|
|
150807
|
+
out.push(line);
|
|
150808
|
+
bytes += size6;
|
|
150809
|
+
}
|
|
150810
|
+
return out.length > 0 ? out.join("\n") : void 0;
|
|
150811
|
+
}
|
|
150643
150812
|
async function summarizeSession(deps, input) {
|
|
150644
150813
|
const timeoutMs = Math.max(1, Math.floor(input.timeoutMs));
|
|
150645
150814
|
let messages;
|
|
@@ -150664,6 +150833,8 @@ async function summarizeSession(deps, input) {
|
|
|
150664
150833
|
);
|
|
150665
150834
|
const generated = extractGeneratedText(raw);
|
|
150666
150835
|
if (generated) return { summary: generated, source: "generated" };
|
|
150836
|
+
const excerpt = buildExcerptSummary(transcript);
|
|
150837
|
+
if (excerpt) return { summary: excerpt, source: "excerpt" };
|
|
150667
150838
|
return { source: "none", error: "empty-summary" };
|
|
150668
150839
|
} catch (err2) {
|
|
150669
150840
|
const error = errorMessage(err2);
|
|
@@ -150672,6 +150843,11 @@ async function summarizeSession(deps, input) {
|
|
|
150672
150843
|
timedOut: err2 instanceof SummaryTimeoutError,
|
|
150673
150844
|
error
|
|
150674
150845
|
});
|
|
150846
|
+
try {
|
|
150847
|
+
const excerpt = buildExcerptSummary(buildTranscript(messages));
|
|
150848
|
+
if (excerpt) return { summary: excerpt, source: "excerpt" };
|
|
150849
|
+
} catch {
|
|
150850
|
+
}
|
|
150675
150851
|
return { source: "none", error };
|
|
150676
150852
|
}
|
|
150677
150853
|
}
|
|
@@ -151194,9 +151370,38 @@ async function start(ctx, config, log2, logSink) {
|
|
|
151194
151370
|
enabled: config.stream,
|
|
151195
151371
|
throttleMs: config.streamThrottleMs,
|
|
151196
151372
|
cardMaxTables: config.cardMaxTables,
|
|
151373
|
+
runnerCardMaxTools: config.runnerCardMaxTools,
|
|
151374
|
+
runnerCardTextMax: config.runnerCardTextMax,
|
|
151375
|
+
finalAnswer: { minChars: config.finalAnswerMinChars },
|
|
151376
|
+
// 长回答单独成卡/成文件:运行卡只留「完整回答已单独发送」提示。
|
|
151377
|
+
sendFinalAnswer: (input) => sendFinalAnswer(input),
|
|
151197
151378
|
// 每次 patch 重签强停 token(`stop` 在下方定义,闭包运行时才求值)。
|
|
151198
151379
|
buildStopValue: (sessionID) => stop.buildStopValue(sessionID)
|
|
151199
151380
|
});
|
|
151381
|
+
async function sendFinalAnswer(input) {
|
|
151382
|
+
const bytes = Buffer.byteLength(input.text, "utf8");
|
|
151383
|
+
if (bytes > config.finalAnswerFileMinBytes) {
|
|
151384
|
+
const fileName = `opencode-${input.sessionID.slice(-6)}.md`;
|
|
151385
|
+
const res2 = await sender.sendFile(
|
|
151386
|
+
input.chatId,
|
|
151387
|
+
fileName,
|
|
151388
|
+
Buffer.from(input.text, "utf8"),
|
|
151389
|
+
input.replyToMessageId
|
|
151390
|
+
);
|
|
151391
|
+
if (res2.ok) {
|
|
151392
|
+
log2.info("\u6700\u7EC8\u7B54\u6848\u5DF2\u8F6C\u4E3A\u6587\u4EF6\u53D1\u9001", { sessionID: input.sessionID, bytes, fileName });
|
|
151393
|
+
return;
|
|
151394
|
+
}
|
|
151395
|
+
log2.warn("\u6700\u7EC8\u7B54\u6848\u8F6C\u6587\u4EF6\u5931\u8D25\uFF0C\u964D\u7EA7\u4E3A\u5361\u7247", { sessionID: input.sessionID, error: res2.error ?? "unknown" });
|
|
151396
|
+
}
|
|
151397
|
+
const card = buildFinalAnswerCard(input.text);
|
|
151398
|
+
const res = input.replyToMessageId ? await sender.replyCard(input.replyToMessageId, card) : await sender.sendCard(input.chatId, card);
|
|
151399
|
+
if (!res.ok) {
|
|
151400
|
+
log2.warn("\u6700\u7EC8\u7B54\u6848\u5361\u7247\u53D1\u9001\u5931\u8D25", { sessionID: input.sessionID, error: res.error ?? "unknown" });
|
|
151401
|
+
return;
|
|
151402
|
+
}
|
|
151403
|
+
log2.info("\u6700\u7EC8\u7B54\u6848\u5DF2\u5355\u72EC\u6210\u5361", { sessionID: input.sessionID, bytes });
|
|
151404
|
+
}
|
|
151200
151405
|
const recovery = createSessionRecovery({
|
|
151201
151406
|
log: log2,
|
|
151202
151407
|
resolveLink: (sessionID) => sessionMap.resolveBySession(sessionID),
|
|
@@ -151647,13 +151852,32 @@ async function start(ctx, config, log2, logSink) {
|
|
|
151647
151852
|
})
|
|
151648
151853
|
});
|
|
151649
151854
|
const abort = new AbortController();
|
|
151855
|
+
const EVENT_RETRY_BASE_MS = 1e3;
|
|
151856
|
+
const EVENT_RETRY_MAX_MS = 3e4;
|
|
151857
|
+
const sleep3 = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
151650
151858
|
const subscription = (async () => {
|
|
151651
|
-
|
|
151652
|
-
|
|
151653
|
-
|
|
151859
|
+
let attempt = 0;
|
|
151860
|
+
while (!abort.signal.aborted) {
|
|
151861
|
+
try {
|
|
151862
|
+
for await (const event of ctx.event.subscribe({ signal: abort.signal })) {
|
|
151863
|
+
if (attempt > 0) {
|
|
151864
|
+
log2.info("\u4E8B\u4EF6\u8BA2\u9605\u5DF2\u91CD\u8FDE", { attempt });
|
|
151865
|
+
}
|
|
151866
|
+
attempt = 0;
|
|
151867
|
+
await handleEvent(event);
|
|
151868
|
+
}
|
|
151869
|
+
if (abort.signal.aborted) break;
|
|
151870
|
+
attempt += 1;
|
|
151871
|
+
const delay3 = Math.min(EVENT_RETRY_MAX_MS, EVENT_RETRY_BASE_MS * 2 ** Math.min(attempt, 5));
|
|
151872
|
+
log2.warn("\u4E8B\u4EF6\u8BA2\u9605\u6D41\u5DF2\u7ED3\u675F\uFF0C\u51C6\u5907\u91CD\u8FDE", { attempt, delayMs: delay3 });
|
|
151873
|
+
await sleep3(delay3);
|
|
151874
|
+
} catch (err2) {
|
|
151875
|
+
if (abort.signal.aborted) break;
|
|
151876
|
+
attempt += 1;
|
|
151877
|
+
const delay3 = Math.min(EVENT_RETRY_MAX_MS, EVENT_RETRY_BASE_MS * 2 ** Math.min(attempt, 5));
|
|
151878
|
+
log2.error("\u4E8B\u4EF6\u8BA2\u9605\u5F02\u5E38\uFF0C\u51C6\u5907\u91CD\u8FDE", { attempt, delayMs: delay3, error: errorMessage(err2) });
|
|
151879
|
+
await sleep3(delay3);
|
|
151654
151880
|
}
|
|
151655
|
-
} catch (err2) {
|
|
151656
|
-
if (!abort.signal.aborted) log2.error("\u4E8B\u4EF6\u8BA2\u9605\u5F02\u5E38\u9000\u51FA", { error: errorMessage(err2) });
|
|
151657
151881
|
}
|
|
151658
151882
|
})();
|
|
151659
151883
|
const eventDeps = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-feishu-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
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",
|