opencode-feishu 1.0.2 → 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"),
@@ -113762,7 +113764,6 @@ var StreamingCard = class {
113762
113764
  };
113763
113765
 
113764
113766
  // src/handler/chat.ts
113765
- var activeAutoPrompts = /* @__PURE__ */ new Map();
113766
113767
  async function finalizeReply(streamingCard, feishuClient, chatId, placeholderId, text) {
113767
113768
  if (streamingCard) {
113768
113769
  await streamingCard.close(text);
@@ -113780,19 +113781,13 @@ async function abortCleanup(streamingCard, feishuClient, placeholderId) {
113780
113781
  }
113781
113782
  async function handleChat(ctx, deps, signal) {
113782
113783
  const { content, chatId, chatType, senderId, shouldReply, messageType, rawContent, messageId } = ctx;
113783
- if (!content.trim() && messageType === "text") return;
113784
+ if (!content.trim() && messageType === "text") return void 0;
113784
113785
  const { config: config2, client, feishuClient, log, directory } = deps;
113785
113786
  const query = directory ? { directory } : void 0;
113786
113787
  const sessionKey = buildSessionKey(chatType, chatType === "p2p" ? senderId : chatId);
113787
- const existing = activeAutoPrompts.get(sessionKey);
113788
- if (existing) {
113789
- existing.abort();
113790
- activeAutoPrompts.delete(sessionKey);
113791
- log("info", "\u7528\u6237\u4ECB\u5165\uFF0C\u81EA\u52A8\u63D0\u793A\u5DF2\u4E2D\u65AD", { sessionKey });
113792
- }
113793
113788
  const session = await getOrCreateSession(client, sessionKey, directory);
113794
113789
  const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log);
113795
- if (!parts.length) return;
113790
+ if (!parts.length) return void 0;
113796
113791
  log("info", "\u6536\u5230\u7528\u6237\u6D88\u606F", {
113797
113792
  sessionKey,
113798
113793
  sessionId: session.id,
@@ -113816,7 +113811,7 @@ async function handleChat(ctx, deps, signal) {
113816
113811
  error: err instanceof Error ? err.message : String(err)
113817
113812
  });
113818
113813
  }
113819
- return;
113814
+ return void 0;
113820
113815
  }
113821
113816
  const timeout = config2.timeout;
113822
113817
  const thinkingDelay = config2.thinkingDelay;
@@ -113857,46 +113852,6 @@ async function handleChat(ctx, deps, signal) {
113857
113852
  });
113858
113853
  }
113859
113854
  }, thinkingDelay) : null;
113860
- async function runAutoPromptLoop(activeId) {
113861
- const { autoPrompt } = config2;
113862
- if (!autoPrompt.enabled || !shouldReply) return;
113863
- const ac = new AbortController();
113864
- activeAutoPrompts.set(sessionKey, ac);
113865
- log("info", "\u542F\u52A8\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF", { sessionKey, maxIterations: autoPrompt.maxIterations });
113866
- try {
113867
- for (let i = 0; i < autoPrompt.maxIterations; i++) {
113868
- await abortableSleep(autoPrompt.intervalSeconds * 1e3, ac.signal);
113869
- log("info", "\u53D1\u9001\u81EA\u52A8\u63D0\u793A", { sessionKey, iteration: i + 1 });
113870
- clearSessionError(activeId);
113871
- await client.session.prompt({
113872
- path: { id: activeId },
113873
- query,
113874
- body: { parts: [{ type: "text", text: autoPrompt.message }] }
113875
- });
113876
- const text = await pollForResponse(client, activeId, { timeout, pollInterval, stablePolls, query, signal: ac.signal });
113877
- if (text) {
113878
- log("info", "\u81EA\u52A8\u63D0\u793A\u54CD\u5E94", {
113879
- sessionKey,
113880
- iteration: i + 1,
113881
- output: text
113882
- });
113883
- await sendTextMessage(feishuClient, chatId, text);
113884
- }
113885
- }
113886
- log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", { sessionKey });
113887
- } catch (loopErr) {
113888
- if (loopErr.name === "AbortError") {
113889
- log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u88AB\u4E2D\u65AD", { sessionKey });
113890
- } else {
113891
- log("error", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u5F02\u5E38", {
113892
- sessionKey,
113893
- error: loopErr instanceof Error ? loopErr.message : String(loopErr)
113894
- });
113895
- }
113896
- } finally {
113897
- activeAutoPrompts.delete(sessionKey);
113898
- }
113899
- }
113900
113855
  let cardUnsub;
113901
113856
  {
113902
113857
  const card = streamingCard;
@@ -113942,12 +113897,15 @@ async function handleChat(ctx, deps, signal) {
113942
113897
  });
113943
113898
  clearRetryAttempts(sessionKey);
113944
113899
  await finalizeReply(streamingCard, feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
113945
- await runAutoPromptLoop(session.id);
113900
+ if (config2.autoPrompt.enabled && shouldReply) {
113901
+ return { sessionId: session.id, sessionKey, chatId, deps };
113902
+ }
113903
+ return void 0;
113946
113904
  } catch (err) {
113947
113905
  if (err instanceof Error && err.name === "AbortError") {
113948
113906
  log("info", "\u5904\u7406\u88AB\u4E2D\u65AD", { sessionKey, sessionId: session.id });
113949
113907
  await abortCleanup(streamingCard, feishuClient, placeholderId);
113950
- return;
113908
+ return void 0;
113951
113909
  }
113952
113910
  let sessionError;
113953
113911
  if (err instanceof SessionErrorDetected) {
@@ -114006,14 +113964,16 @@ async function handleChat(ctx, deps, signal) {
114006
113964
  model: `${modelOverride.providerID}/${modelOverride.modelID}`,
114007
113965
  attempt: attempts + 1
114008
113966
  });
114009
- await runAutoPromptLoop(session.id);
114010
- return;
113967
+ if (config2.autoPrompt.enabled && shouldReply) {
113968
+ return { sessionId: session.id, sessionKey, chatId, deps };
113969
+ }
113970
+ return void 0;
114011
113971
  }
114012
113972
  } catch (recoveryErr) {
114013
113973
  if (recoveryErr instanceof Error && recoveryErr.name === "AbortError") {
114014
113974
  log("info", "\u6A21\u578B\u6062\u590D\u88AB\u4E2D\u65AD", { sessionKey });
114015
113975
  await abortCleanup(streamingCard, feishuClient, placeholderId);
114016
- return;
113976
+ return void 0;
114017
113977
  }
114018
113978
  const errMsg = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
114019
113979
  if (recoveryErr instanceof SessionErrorDetected) {
@@ -114145,6 +114105,43 @@ async function replyOrUpdate(feishuClient, chatId, placeholderId, text) {
114145
114105
  await sendTextMessage(feishuClient, chatId, text);
114146
114106
  }
114147
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
+ }
114148
114145
  function abortableSleep(ms, signal) {
114149
114146
  return new Promise((resolve, reject) => {
114150
114147
  if (signal.aborted) {
@@ -114191,6 +114188,9 @@ function cleanupStateIfIdle(sessionKey, state) {
114191
114188
  states.delete(sessionKey);
114192
114189
  }
114193
114190
  }
114191
+ function sleep(ms) {
114192
+ return new Promise((resolve) => setTimeout(resolve, ms));
114193
+ }
114194
114194
  async function enqueueMessage(ctx, deps) {
114195
114195
  if (!ctx.shouldReply) {
114196
114196
  await handleChat(ctx, deps);
@@ -114221,7 +114221,18 @@ async function handleP2PMessage(sessionKey, ctx, deps) {
114221
114221
  const controller = new AbortController();
114222
114222
  state.controller = controller;
114223
114223
  state.processing = true;
114224
- 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(() => {
114225
114236
  state.processing = false;
114226
114237
  state.controller = null;
114227
114238
  state.currentTask = null;
@@ -114230,6 +114241,31 @@ async function handleP2PMessage(sessionKey, ctx, deps) {
114230
114241
  state.currentTask = task;
114231
114242
  await task;
114232
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
+ }
114233
114269
  async function handleGroupMessage(sessionKey, ctx, deps) {
114234
114270
  const state = getOrCreateState(sessionKey);
114235
114271
  state.queue.push({ ctx, deps });
@@ -114238,21 +114274,81 @@ async function handleGroupMessage(sessionKey, ctx, deps) {
114238
114274
  }
114239
114275
  async function drainLoop(sessionKey, state) {
114240
114276
  state.processing = true;
114277
+ let autoPromptCtx;
114278
+ let idleCount = 0;
114279
+ let autoPromptIteration = 0;
114241
114280
  try {
114242
- while (state.queue.length > 0) {
114243
- const item = state.queue.shift();
114244
- if (!item) break;
114245
- const controller = new AbortController();
114246
- 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);
114247
114321
  try {
114248
- 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
+ }
114249
114341
  } catch (err) {
114250
- 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", {
114251
114346
  sessionKey,
114252
114347
  error: err instanceof Error ? err.message : String(err)
114253
114348
  });
114349
+ break;
114254
114350
  } finally {
114255
- state.controller = null;
114351
+ clearInterval(monitor);
114256
114352
  }
114257
114353
  }
114258
114354
  } finally {
@@ -114261,7 +114357,7 @@ async function drainLoop(sessionKey, state) {
114261
114357
  }
114262
114358
  }
114263
114359
  async function processMessage(ctx, deps, signal) {
114264
- await handleChat(ctx, deps, signal);
114360
+ return handleChat(ctx, deps, signal);
114265
114361
  }
114266
114362
  async function abortServerSession(sessionKey, deps) {
114267
114363
  const { client, log, directory } = deps;