opencode-feishu 1.0.1 → 1.1.0

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/dist/index.js CHANGED
@@ -112509,7 +112509,9 @@ var AutoPromptSchema = external_exports.object({
112509
112509
  enabled: external_exports.boolean().default(false),
112510
112510
  intervalSeconds: external_exports.number().int().positive().default(30),
112511
112511
  maxIterations: external_exports.number().int().positive().default(10),
112512
- message: external_exports.string().min(1).default("\u8BF7\u540C\u6B65\u5F53\u524D\u8FDB\u5EA6\uFF0C\u5982\u9700\u5E2E\u52A9\u8BF7\u8BF4\u660E")
112512
+ message: external_exports.string().min(1).default("\u8BF7\u540C\u6B65\u5F53\u524D\u8FDB\u5EA6\uFF0C\u5982\u9700\u5E2E\u52A9\u8BF7\u8BF4\u660E"),
112513
+ idleThreshold: external_exports.number().int().min(1).default(2),
112514
+ idleMaxLength: external_exports.number().int().min(10).default(50)
112513
112515
  });
112514
112516
  var FeishuConfigSchema = external_exports.object({
112515
112517
  appId: external_exports.string().min(1, "appId \u4E0D\u80FD\u4E3A\u7A7A"),
@@ -112535,15 +112537,35 @@ var CardKitClient = class {
112535
112537
  * 创建 CardKit 2.0 卡片 → cardId
112536
112538
  */
112537
112539
  async createCard(schema) {
112538
- const res = await this.larkClient.cardkit.v1.card.create({
112539
- data: {
112540
- type: "card_json",
112541
- data: JSON.stringify(schema.data)
112540
+ let res;
112541
+ try {
112542
+ res = await this.larkClient.cardkit.v1.card.create({
112543
+ data: {
112544
+ type: "card_json",
112545
+ data: JSON.stringify(schema.data)
112546
+ }
112547
+ });
112548
+ } catch (err) {
112549
+ let detail = "no response body";
112550
+ if (err && typeof err === "object" && "response" in err) {
112551
+ const axiosData = err.response?.data;
112552
+ if (axiosData) {
112553
+ try {
112554
+ detail = JSON.stringify(axiosData);
112555
+ } catch {
112556
+ detail = String(axiosData);
112557
+ }
112558
+ }
112542
112559
  }
112543
- });
112560
+ throw new Error(
112561
+ `CardKit createCard HTTP \u9519\u8BEF: ${err instanceof Error ? err.message : String(err)} | detail: ${detail}`
112562
+ );
112563
+ }
112544
112564
  const cardId = res?.data?.card_id;
112545
112565
  if (!cardId) {
112546
- throw new Error(`CardKit createCard \u5931\u8D25: ${res?.msg ?? "unknown"} (code: ${res?.code})`);
112566
+ throw new Error(
112567
+ `CardKit createCard \u5931\u8D25: ${res?.msg ?? "unknown"} (code: ${res?.code})`
112568
+ );
112547
112569
  }
112548
112570
  return cardId;
112549
112571
  }
@@ -113742,7 +113764,6 @@ var StreamingCard = class {
113742
113764
  };
113743
113765
 
113744
113766
  // src/handler/chat.ts
113745
- var activeAutoPrompts = /* @__PURE__ */ new Map();
113746
113767
  async function finalizeReply(streamingCard, feishuClient, chatId, placeholderId, text) {
113747
113768
  if (streamingCard) {
113748
113769
  await streamingCard.close(text);
@@ -113760,19 +113781,13 @@ async function abortCleanup(streamingCard, feishuClient, placeholderId) {
113760
113781
  }
113761
113782
  async function handleChat(ctx, deps, signal) {
113762
113783
  const { content, chatId, chatType, senderId, shouldReply, messageType, rawContent, messageId } = ctx;
113763
- if (!content.trim() && messageType === "text") return;
113784
+ if (!content.trim() && messageType === "text") return void 0;
113764
113785
  const { config: config2, client, feishuClient, log, directory } = deps;
113765
113786
  const query = directory ? { directory } : void 0;
113766
113787
  const sessionKey = buildSessionKey(chatType, chatType === "p2p" ? senderId : chatId);
113767
- const existing = activeAutoPrompts.get(sessionKey);
113768
- if (existing) {
113769
- existing.abort();
113770
- activeAutoPrompts.delete(sessionKey);
113771
- log("info", "\u7528\u6237\u4ECB\u5165\uFF0C\u81EA\u52A8\u63D0\u793A\u5DF2\u4E2D\u65AD", { sessionKey });
113772
- }
113773
113788
  const session = await getOrCreateSession(client, sessionKey, directory);
113774
113789
  const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log);
113775
- if (!parts.length) return;
113790
+ if (!parts.length) return void 0;
113776
113791
  log("info", "\u6536\u5230\u7528\u6237\u6D88\u606F", {
113777
113792
  sessionKey,
113778
113793
  sessionId: session.id,
@@ -113796,7 +113811,7 @@ async function handleChat(ctx, deps, signal) {
113796
113811
  error: err instanceof Error ? err.message : String(err)
113797
113812
  });
113798
113813
  }
113799
- return;
113814
+ return void 0;
113800
113815
  }
113801
113816
  const timeout = config2.timeout;
113802
113817
  const thinkingDelay = config2.thinkingDelay;
@@ -113837,46 +113852,6 @@ async function handleChat(ctx, deps, signal) {
113837
113852
  });
113838
113853
  }
113839
113854
  }, thinkingDelay) : null;
113840
- async function runAutoPromptLoop(activeId) {
113841
- const { autoPrompt } = config2;
113842
- if (!autoPrompt.enabled || !shouldReply) return;
113843
- const ac = new AbortController();
113844
- activeAutoPrompts.set(sessionKey, ac);
113845
- log("info", "\u542F\u52A8\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF", { sessionKey, maxIterations: autoPrompt.maxIterations });
113846
- try {
113847
- for (let i = 0; i < autoPrompt.maxIterations; i++) {
113848
- await abortableSleep(autoPrompt.intervalSeconds * 1e3, ac.signal);
113849
- log("info", "\u53D1\u9001\u81EA\u52A8\u63D0\u793A", { sessionKey, iteration: i + 1 });
113850
- clearSessionError(activeId);
113851
- await client.session.prompt({
113852
- path: { id: activeId },
113853
- query,
113854
- body: { parts: [{ type: "text", text: autoPrompt.message }] }
113855
- });
113856
- const text = await pollForResponse(client, activeId, { timeout, pollInterval, stablePolls, query, signal: ac.signal });
113857
- if (text) {
113858
- log("info", "\u81EA\u52A8\u63D0\u793A\u54CD\u5E94", {
113859
- sessionKey,
113860
- iteration: i + 1,
113861
- output: text
113862
- });
113863
- await sendTextMessage(feishuClient, chatId, text);
113864
- }
113865
- }
113866
- log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", { sessionKey });
113867
- } catch (loopErr) {
113868
- if (loopErr.name === "AbortError") {
113869
- log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u88AB\u4E2D\u65AD", { sessionKey });
113870
- } else {
113871
- log("error", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u5F02\u5E38", {
113872
- sessionKey,
113873
- error: loopErr instanceof Error ? loopErr.message : String(loopErr)
113874
- });
113875
- }
113876
- } finally {
113877
- activeAutoPrompts.delete(sessionKey);
113878
- }
113879
- }
113880
113855
  let cardUnsub;
113881
113856
  {
113882
113857
  const card = streamingCard;
@@ -113922,12 +113897,15 @@ async function handleChat(ctx, deps, signal) {
113922
113897
  });
113923
113898
  clearRetryAttempts(sessionKey);
113924
113899
  await finalizeReply(streamingCard, feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
113925
- await runAutoPromptLoop(session.id);
113900
+ if (config2.autoPrompt.enabled && shouldReply) {
113901
+ return { sessionId: session.id, sessionKey, chatId, deps };
113902
+ }
113903
+ return void 0;
113926
113904
  } catch (err) {
113927
113905
  if (err instanceof Error && err.name === "AbortError") {
113928
113906
  log("info", "\u5904\u7406\u88AB\u4E2D\u65AD", { sessionKey, sessionId: session.id });
113929
113907
  await abortCleanup(streamingCard, feishuClient, placeholderId);
113930
- return;
113908
+ return void 0;
113931
113909
  }
113932
113910
  let sessionError;
113933
113911
  if (err instanceof SessionErrorDetected) {
@@ -113986,14 +113964,16 @@ async function handleChat(ctx, deps, signal) {
113986
113964
  model: `${modelOverride.providerID}/${modelOverride.modelID}`,
113987
113965
  attempt: attempts + 1
113988
113966
  });
113989
- await runAutoPromptLoop(session.id);
113990
- return;
113967
+ if (config2.autoPrompt.enabled && shouldReply) {
113968
+ return { sessionId: session.id, sessionKey, chatId, deps };
113969
+ }
113970
+ return void 0;
113991
113971
  }
113992
113972
  } catch (recoveryErr) {
113993
113973
  if (recoveryErr instanceof Error && recoveryErr.name === "AbortError") {
113994
113974
  log("info", "\u6A21\u578B\u6062\u590D\u88AB\u4E2D\u65AD", { sessionKey });
113995
113975
  await abortCleanup(streamingCard, feishuClient, placeholderId);
113996
- return;
113976
+ return void 0;
113997
113977
  }
113998
113978
  const errMsg = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
113999
113979
  if (recoveryErr instanceof SessionErrorDetected) {
@@ -114125,6 +114105,43 @@ async function replyOrUpdate(feishuClient, chatId, placeholderId, text) {
114125
114105
  await sendTextMessage(feishuClient, chatId, text);
114126
114106
  }
114127
114107
  }
114108
+ var idlePatterns = [
114109
+ /^(无|没有)(任务|变化|进行中)/,
114110
+ /空闲|闲置|等待(指令|中|新|你)/,
114111
+ /随时可(开始|开始新)/,
114112
+ /等你指令/
114113
+ ];
114114
+ function isIdleResponse(text, maxLength = 50) {
114115
+ if (text.length >= maxLength) return false;
114116
+ return idlePatterns.some((p) => p.test(text));
114117
+ }
114118
+ async function runOneAutoPromptIteration(apCtx, iteration, signal) {
114119
+ const { sessionId, chatId, deps } = apCtx;
114120
+ const { config: config2, client, feishuClient, log, directory } = deps;
114121
+ const query = directory ? { directory } : void 0;
114122
+ const { autoPrompt, timeout, pollInterval, stablePolls } = config2;
114123
+ log("info", "\u53D1\u9001\u81EA\u52A8\u63D0\u793A", { sessionKey: apCtx.sessionKey, iteration });
114124
+ clearSessionError(sessionId);
114125
+ await client.session.promptAsync({
114126
+ path: { id: sessionId },
114127
+ query,
114128
+ body: { parts: [{ type: "text", text: autoPrompt.message }] }
114129
+ });
114130
+ const text = await pollForResponse(client, sessionId, {
114131
+ timeout,
114132
+ pollInterval,
114133
+ stablePolls,
114134
+ query,
114135
+ signal
114136
+ });
114137
+ if (!text) return { text: null, isIdle: false };
114138
+ const idle = isIdleResponse(text, autoPrompt.idleMaxLength);
114139
+ if (!idle) {
114140
+ log("info", "\u81EA\u52A8\u63D0\u793A\u54CD\u5E94", { sessionKey: apCtx.sessionKey, iteration, output: text });
114141
+ await sendTextMessage(feishuClient, chatId, text);
114142
+ }
114143
+ return { text, isIdle: idle };
114144
+ }
114128
114145
  function abortableSleep(ms, signal) {
114129
114146
  return new Promise((resolve, reject) => {
114130
114147
  if (signal.aborted) {
@@ -114171,6 +114188,9 @@ function cleanupStateIfIdle(sessionKey, state) {
114171
114188
  states.delete(sessionKey);
114172
114189
  }
114173
114190
  }
114191
+ function sleep(ms) {
114192
+ return new Promise((resolve) => setTimeout(resolve, ms));
114193
+ }
114174
114194
  async function enqueueMessage(ctx, deps) {
114175
114195
  if (!ctx.shouldReply) {
114176
114196
  await handleChat(ctx, deps);
@@ -114201,7 +114221,18 @@ async function handleP2PMessage(sessionKey, ctx, deps) {
114201
114221
  const controller = new AbortController();
114202
114222
  state.controller = controller;
114203
114223
  state.processing = true;
114204
- const task = processMessage(ctx, deps, controller.signal).finally(() => {
114224
+ const task = (async () => {
114225
+ const apCtx = await processMessage(ctx, deps, controller.signal);
114226
+ if (apCtx) {
114227
+ await runP2PAutoPrompt(apCtx, controller.signal);
114228
+ }
114229
+ })().catch((err) => {
114230
+ if (err instanceof Error && err.name === "AbortError") return;
114231
+ deps.log("error", "P2P \u6D88\u606F\u6216\u81EA\u52A8\u63D0\u793A\u5904\u7406\u5931\u8D25", {
114232
+ sessionKey,
114233
+ error: err instanceof Error ? err.message : String(err)
114234
+ });
114235
+ }).finally(() => {
114205
114236
  state.processing = false;
114206
114237
  state.controller = null;
114207
114238
  state.currentTask = null;
@@ -114210,6 +114241,31 @@ async function handleP2PMessage(sessionKey, ctx, deps) {
114210
114241
  state.currentTask = task;
114211
114242
  await task;
114212
114243
  }
114244
+ async function runP2PAutoPrompt(apCtx, signal) {
114245
+ const { autoPrompt } = apCtx.deps.config;
114246
+ if (!autoPrompt.enabled) return;
114247
+ let idleCount = 0;
114248
+ for (let i = 0; i < autoPrompt.maxIterations; i++) {
114249
+ await abortableSleep(autoPrompt.intervalSeconds * 1e3, signal);
114250
+ const result = await runOneAutoPromptIteration(apCtx, i + 1, signal);
114251
+ if (result.isIdle) {
114252
+ idleCount++;
114253
+ if (idleCount >= autoPrompt.idleThreshold) {
114254
+ apCtx.deps.log("info", "P2P \u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u68C0\u6D4B\u5230\u7A7A\u95F2\uFF09", {
114255
+ sessionKey: apCtx.sessionKey,
114256
+ iteration: i + 1,
114257
+ idleCount
114258
+ });
114259
+ return;
114260
+ }
114261
+ } else {
114262
+ idleCount = 0;
114263
+ }
114264
+ }
114265
+ apCtx.deps.log("info", "P2P \u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", {
114266
+ sessionKey: apCtx.sessionKey
114267
+ });
114268
+ }
114213
114269
  async function handleGroupMessage(sessionKey, ctx, deps) {
114214
114270
  const state = getOrCreateState(sessionKey);
114215
114271
  state.queue.push({ ctx, deps });
@@ -114218,21 +114274,81 @@ async function handleGroupMessage(sessionKey, ctx, deps) {
114218
114274
  }
114219
114275
  async function drainLoop(sessionKey, state) {
114220
114276
  state.processing = true;
114277
+ let autoPromptCtx;
114278
+ let idleCount = 0;
114279
+ let autoPromptIteration = 0;
114221
114280
  try {
114222
- while (state.queue.length > 0) {
114223
- const item = state.queue.shift();
114224
- if (!item) break;
114225
- const controller = new AbortController();
114226
- state.controller = controller;
114281
+ while (true) {
114282
+ if (state.queue.length > 0) {
114283
+ const item = state.queue.shift();
114284
+ const controller = new AbortController();
114285
+ state.controller = controller;
114286
+ try {
114287
+ autoPromptCtx = await processMessage(item.ctx, item.deps, controller.signal);
114288
+ idleCount = 0;
114289
+ autoPromptIteration = 0;
114290
+ } catch (err) {
114291
+ item.deps.log("error", "\u7FA4\u804A\u961F\u5217\u6D88\u606F\u5904\u7406\u5931\u8D25", {
114292
+ sessionKey,
114293
+ error: err instanceof Error ? err.message : String(err)
114294
+ });
114295
+ } finally {
114296
+ state.controller = null;
114297
+ }
114298
+ continue;
114299
+ }
114300
+ if (!autoPromptCtx) break;
114301
+ const { autoPrompt } = autoPromptCtx.deps.config;
114302
+ if (!autoPrompt.enabled) break;
114303
+ if (autoPromptIteration >= autoPrompt.maxIterations) {
114304
+ autoPromptCtx.deps.log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", { sessionKey });
114305
+ break;
114306
+ }
114307
+ const intervalSeconds = autoPrompt.intervalSeconds;
114308
+ let interrupted = false;
114309
+ for (let s = 0; s < intervalSeconds; s++) {
114310
+ await sleep(1e3);
114311
+ if (state.queue.length > 0) {
114312
+ interrupted = true;
114313
+ break;
114314
+ }
114315
+ }
114316
+ if (interrupted) continue;
114317
+ const autoPromptController = new AbortController();
114318
+ const monitor = setInterval(() => {
114319
+ if (state.queue.length > 0) autoPromptController.abort();
114320
+ }, 200);
114227
114321
  try {
114228
- await processMessage(item.ctx, item.deps, controller.signal);
114322
+ const result = await runOneAutoPromptIteration(
114323
+ autoPromptCtx,
114324
+ autoPromptIteration + 1,
114325
+ autoPromptController.signal
114326
+ );
114327
+ autoPromptIteration++;
114328
+ if (result.isIdle) {
114329
+ idleCount++;
114330
+ if (idleCount >= autoPrompt.idleThreshold) {
114331
+ autoPromptCtx.deps.log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u68C0\u6D4B\u5230\u7A7A\u95F2\uFF09", {
114332
+ sessionKey,
114333
+ iteration: autoPromptIteration,
114334
+ idleCount
114335
+ });
114336
+ break;
114337
+ }
114338
+ } else {
114339
+ idleCount = 0;
114340
+ }
114229
114341
  } catch (err) {
114230
- item.deps.log("error", "\u7FA4\u804A\u961F\u5217\u6D88\u606F\u5904\u7406\u5931\u8D25", {
114342
+ if (err instanceof Error && err.name === "AbortError") {
114343
+ continue;
114344
+ }
114345
+ autoPromptCtx.deps.log("error", "\u81EA\u52A8\u63D0\u793A\u8FED\u4EE3\u5F02\u5E38", {
114231
114346
  sessionKey,
114232
114347
  error: err instanceof Error ? err.message : String(err)
114233
114348
  });
114349
+ break;
114234
114350
  } finally {
114235
- state.controller = null;
114351
+ clearInterval(monitor);
114236
114352
  }
114237
114353
  }
114238
114354
  } finally {
@@ -114241,7 +114357,7 @@ async function drainLoop(sessionKey, state) {
114241
114357
  }
114242
114358
  }
114243
114359
  async function processMessage(ctx, deps, signal) {
114244
- await handleChat(ctx, deps, signal);
114360
+ return handleChat(ctx, deps, signal);
114245
114361
  }
114246
114362
  async function abortServerSession(sessionKey, deps) {
114247
114363
  const { client, log, directory } = deps;