opencode-feishu-plugin 0.2.7 → 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.
Files changed (4) hide show
  1. package/README.en.md +111 -458
  2. package/README.md +78 -268
  3. package/dist/index.js +106 -23
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -145598,7 +145598,8 @@ function parseRootCard(value3) {
145598
145598
  ...value3.compactPending === true ? { compactPending: true } : {},
145599
145599
  ...compactError ? { compactError } : {},
145600
145600
  ...value3.compactButton === true ? { compactButton: true } : {},
145601
- ...note2 ? { note: note2 } : {}
145601
+ ...note2 ? { note: note2 } : {},
145602
+ ...value3.openedTopic === true ? { openedTopic: true } : {}
145602
145603
  };
145603
145604
  }
145604
145605
  function parseStringArray(value3) {
@@ -148083,7 +148084,10 @@ function renderResumedBody(title, base2, now3) {
148083
148084
  setup.push(`- \u6700\u8FD1\u6D3B\u52A8\uFF1A${relativeTime(base2.updatedAt, now3)}`);
148084
148085
  }
148085
148086
  if (setup.length > 0) lines.push("", ...setup);
148086
- lines.push("", "**\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");
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
+ );
148087
148091
  if (base2.summary || base2.summaryPending || base2.compactPending || base2.compactError) {
148088
148092
  lines.push("", `**${base2.summaryLabel ?? "\u6458\u8981"}**\uFF1A`);
148089
148093
  if (base2.summaryPending) lines.push("\u23F3 \u6B63\u5728\u603B\u7ED3\u8BE5\u4F1A\u8BDD\u2026");
@@ -148122,7 +148126,8 @@ function buildSessionOpenedCard(input) {
148122
148126
  ...input.summaryPending ? { summaryPending: true } : {},
148123
148127
  ...input.compactPending ? { compactPending: true } : {},
148124
148128
  ...input.compactError ? { compactError: input.compactError } : {},
148125
- ...input.compactButton ? { compactButton: true } : {}
148129
+ ...input.compactButton ? { compactButton: true } : {},
148130
+ ...input.openedTopic ? { openedTopic: true } : {}
148126
148131
  };
148127
148132
  return buildSessionRootCard(base2, void 0, {
148128
148133
  now: input.now ?? Date.now(),
@@ -150046,21 +150051,20 @@ async function enterSessionThread(ctx, input) {
150046
150051
  const onLimit = (report) => {
150047
150052
  ctx.deps.log.warn("\u4F1A\u8BDD\u6062\u590D\u5361\u5185\u5BB9\u8D85\u9650\uFF0C\u5DF2\u964D\u7EA7", { sessionID, ...report });
150048
150053
  };
150049
- const cardInput = {
150054
+ const newNow = ctx.now();
150055
+ const baseInput = {
150050
150056
  title: info?.title ?? "",
150051
150057
  sessionID,
150052
150058
  ...dir ? { dir } : {},
150053
150059
  ...link2?.model ? { model: modelLabel(link2.model) } : {},
150054
150060
  ...info?.updatedAt ? { updatedAt: info.updatedAt } : {},
150055
- now: ctx.now(),
150061
+ now: newNow,
150056
150062
  ...ctx.deps.cardMaxTables !== void 0 ? { maxTables: ctx.deps.cardMaxTables } : {},
150057
150063
  onLimit,
150058
- ...showSummary ? { summaryPending: true } : {},
150059
- // 「🗜 压缩并总结」只在开关开启且运行时装配了签名时渲染。
150060
- ...showSummary && ctx.deps.signCompact ? { compactButton: { sessionID, token: ctx.deps.signCompact(sessionID) } } : {}
150064
+ openedTopic: true
150061
150065
  };
150062
- const card = buildSessionOpenedCard(cardInput);
150063
- const res = await ctx.deps.sender.sendCard(chatId, card);
150066
+ const rootCard = buildSessionOpenedCard(baseInput);
150067
+ const res = await ctx.deps.sender.sendCard(chatId, rootCard);
150064
150068
  ctx.deps.log.info("\u53D1\u9001\u4F1A\u8BDD\u6062\u590D\u5361", {
150065
150069
  sessionID,
150066
150070
  source: input.source,
@@ -150082,26 +150086,63 @@ async function enterSessionThread(ctx, input) {
150082
150086
  }
150083
150087
  await ctx.deps.sessionMap.bindRoot(res.messageId, sessionID);
150084
150088
  ctx.deps.log.info("resume card bound root to session", { sessionID, rootId: res.messageId });
150085
- await ctx.deps.sessionMap.setRootCard(sessionID, {
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 = {
150086
150103
  style: "resumed",
150087
150104
  sessionID,
150088
150105
  title: info?.title ?? "",
150089
150106
  ...dir ? { dir } : {},
150090
150107
  ...link2?.model ? { model: modelLabel(link2.model) } : {},
150091
- ...info?.updatedAt ? { updatedAt: info.updatedAt } : {},
150092
- ...showSummary ? { summaryPending: true, compactButton: Boolean(cardInput.compactButton) } : {}
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) } : {}
150093
150133
  });
150094
150134
  if (showSummary) {
150095
- void patchResumeSummary(ctx, sessionID, dir, res.messageId, cardInput);
150135
+ void patchResumeSummary(ctx, sessionID, dir, res.messageId, fallbackInput, true);
150096
150136
  }
150097
150137
  return { ok: true, messageId: res.messageId };
150098
150138
  }
150099
150139
  var SUMMARY_LABEL = {
150100
150140
  reused: "\u4F1A\u8BDD\u6458\u8981",
150101
150141
  generated: "\u6458\u8981\uFF08\u5FEB\u6458\u8981\uFF09",
150142
+ excerpt: "\u6700\u8FD1\u4F1A\u8BDD\u8BB0\u5F55\uFF08\u622A\u53D6\u6458\u8981\uFF09",
150102
150143
  compacted: "\u5DF2\u538B\u7F29 \xB7 \u4F1A\u8BDD\u6458\u8981"
150103
150144
  };
150104
- async function patchResumeSummary(ctx, sessionID, dir, messageId, cardInput) {
150145
+ async function patchResumeSummary(ctx, sessionID, dir, messageId, cardInput, persistBase) {
150105
150146
  try {
150106
150147
  const outcome = await ctx.deps.summarizeSession({
150107
150148
  sessionID,
@@ -150110,9 +150151,11 @@ async function patchResumeSummary(ctx, sessionID, dir, messageId, cardInput) {
150110
150151
  });
150111
150152
  const summary = outcome.summary ?? "\uFF08\u6458\u8981\u751F\u6210\u5931\u8D25\uFF0C\u53EF\u76F4\u63A5\u53D1\u6D88\u606F\u7EE7\u7EED\uFF09";
150112
150153
  const summaryLabel = SUMMARY_LABEL[outcome.source] ?? "\u6458\u8981";
150113
- const base2 = await ctx.deps.sessionMap.getRootCard(sessionID);
150114
- if (base2) {
150115
- await ctx.deps.sessionMap.setRootCard(sessionID, { ...base2, summaryPending: false, summary, summaryLabel });
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
+ }
150116
150159
  }
150117
150160
  const res = await ctx.deps.sender.patchCard(
150118
150161
  messageId,
@@ -150752,6 +150795,20 @@ function buildSummaryPrompt(transcript) {
150752
150795
  \u4F1A\u8BDD\u6700\u8FD1\u8BB0\u5F55\uFF1A
150753
150796
  ${transcript}` : RESUME_SUMMARY_PROMPT;
150754
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
+ }
150755
150812
  async function summarizeSession(deps, input) {
150756
150813
  const timeoutMs = Math.max(1, Math.floor(input.timeoutMs));
150757
150814
  let messages;
@@ -150776,6 +150833,8 @@ async function summarizeSession(deps, input) {
150776
150833
  );
150777
150834
  const generated = extractGeneratedText(raw);
150778
150835
  if (generated) return { summary: generated, source: "generated" };
150836
+ const excerpt = buildExcerptSummary(transcript);
150837
+ if (excerpt) return { summary: excerpt, source: "excerpt" };
150779
150838
  return { source: "none", error: "empty-summary" };
150780
150839
  } catch (err2) {
150781
150840
  const error = errorMessage(err2);
@@ -150784,6 +150843,11 @@ async function summarizeSession(deps, input) {
150784
150843
  timedOut: err2 instanceof SummaryTimeoutError,
150785
150844
  error
150786
150845
  });
150846
+ try {
150847
+ const excerpt = buildExcerptSummary(buildTranscript(messages));
150848
+ if (excerpt) return { summary: excerpt, source: "excerpt" };
150849
+ } catch {
150850
+ }
150787
150851
  return { source: "none", error };
150788
150852
  }
150789
150853
  }
@@ -151788,13 +151852,32 @@ async function start(ctx, config, log2, logSink) {
151788
151852
  })
151789
151853
  });
151790
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));
151791
151858
  const subscription = (async () => {
151792
- try {
151793
- for await (const event of ctx.event.subscribe({ signal: abort.signal })) {
151794
- await handleEvent(event);
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);
151795
151880
  }
151796
- } catch (err2) {
151797
- if (!abort.signal.aborted) log2.error("\u4E8B\u4EF6\u8BA2\u9605\u5F02\u5E38\u9000\u51FA", { error: errorMessage(err2) });
151798
151881
  }
151799
151882
  })();
151800
151883
  const eventDeps = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-feishu-plugin",
3
- "version": "0.2.7",
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",