opencode-feishu 1.0.2 → 1.2.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
@@ -11587,7 +11587,7 @@ var require_object_inspect = __commonJS({
11587
11587
  "double": /(["\\])/g,
11588
11588
  single: /(['\\])/g
11589
11589
  };
11590
- module2.exports = function inspect_(obj, options, depth, seen2) {
11590
+ module2.exports = function inspect_(obj, options, depth, seen) {
11591
11591
  var opts = options || {};
11592
11592
  if (has(opts, "quoteStyle") && !has(quotes, opts.quoteStyle)) {
11593
11593
  throw new TypeError('option "quoteStyle" must be "single" or "double"');
@@ -11637,15 +11637,15 @@ var require_object_inspect = __commonJS({
11637
11637
  return isArray2(obj) ? "[Array]" : "[Object]";
11638
11638
  }
11639
11639
  var indent = getIndent(opts, depth);
11640
- if (typeof seen2 === "undefined") {
11641
- seen2 = [];
11642
- } else if (indexOf(seen2, obj) >= 0) {
11640
+ if (typeof seen === "undefined") {
11641
+ seen = [];
11642
+ } else if (indexOf(seen, obj) >= 0) {
11643
11643
  return "[Circular]";
11644
11644
  }
11645
11645
  function inspect(value, from, noIndent) {
11646
11646
  if (from) {
11647
- seen2 = $arrSlice.call(seen2);
11648
- seen2.push(from);
11647
+ seen = $arrSlice.call(seen);
11648
+ seen.push(from);
11649
11649
  }
11650
11650
  if (noIndent) {
11651
11651
  var newOpts = {
@@ -11654,9 +11654,9 @@ var require_object_inspect = __commonJS({
11654
11654
  if (has(opts, "quoteStyle")) {
11655
11655
  newOpts.quoteStyle = opts.quoteStyle;
11656
11656
  }
11657
- return inspect_(value, newOpts, depth + 1, seen2);
11657
+ return inspect_(value, newOpts, depth + 1, seen);
11658
11658
  }
11659
- return inspect_(value, opts, depth + 1, seen2);
11659
+ return inspect_(value, opts, depth + 1, seen);
11660
11660
  }
11661
11661
  if (typeof obj === "function" && !isRegExp2(obj)) {
11662
11662
  var name = nameOf(obj);
@@ -13718,7 +13718,7 @@ var require_lodash2 = __commonJS({
13718
13718
  if (stacked && stack.get(other)) {
13719
13719
  return stacked == other;
13720
13720
  }
13721
- var index = -1, result = true, seen2 = bitmask & UNORDERED_COMPARE_FLAG ? new SetCache() : void 0;
13721
+ var index = -1, result = true, seen = bitmask & UNORDERED_COMPARE_FLAG ? new SetCache() : void 0;
13722
13722
  stack.set(array2, other);
13723
13723
  stack.set(other, array2);
13724
13724
  while (++index < arrLength) {
@@ -13733,10 +13733,10 @@ var require_lodash2 = __commonJS({
13733
13733
  result = false;
13734
13734
  break;
13735
13735
  }
13736
- if (seen2) {
13736
+ if (seen) {
13737
13737
  if (!arraySome(other, function(othValue2, othIndex) {
13738
- if (!seen2.has(othIndex) && (arrValue === othValue2 || equalFunc(arrValue, othValue2, customizer, bitmask, stack))) {
13739
- return seen2.add(othIndex);
13738
+ if (!seen.has(othIndex) && (arrValue === othValue2 || equalFunc(arrValue, othValue2, customizer, bitmask, stack))) {
13739
+ return seen.add(othIndex);
13740
13740
  }
13741
13741
  })) {
13742
13742
  result = false;
@@ -109648,14 +109648,14 @@ function initializeContext(params) {
109648
109648
  function process2(schema, ctx, _params = { path: [], schemaPath: [] }) {
109649
109649
  var _a2;
109650
109650
  const def = schema._zod.def;
109651
- const seen2 = ctx.seen.get(schema);
109652
- if (seen2) {
109653
- seen2.count++;
109651
+ const seen = ctx.seen.get(schema);
109652
+ if (seen) {
109653
+ seen.count++;
109654
109654
  const isCycle = _params.schemaPath.includes(schema);
109655
109655
  if (isCycle) {
109656
- seen2.cycle = _params.path;
109656
+ seen.cycle = _params.path;
109657
109657
  }
109658
- return seen2.schema;
109658
+ return seen.schema;
109659
109659
  }
109660
109660
  const result = { schema: {}, count: 1, cycle: void 0, path: _params.path };
109661
109661
  ctx.seen.set(schema, result);
@@ -109738,12 +109738,12 @@ function extractDefs(ctx, schema) {
109738
109738
  if (entry[1].schema.$ref) {
109739
109739
  return;
109740
109740
  }
109741
- const seen2 = entry[1];
109741
+ const seen = entry[1];
109742
109742
  const { ref, defId } = makeURI(entry);
109743
- seen2.def = { ...seen2.schema };
109743
+ seen.def = { ...seen.schema };
109744
109744
  if (defId)
109745
- seen2.defId = defId;
109746
- const schema2 = seen2.schema;
109745
+ seen.defId = defId;
109746
+ const schema2 = seen.schema;
109747
109747
  for (const key in schema2) {
109748
109748
  delete schema2[key];
109749
109749
  }
@@ -109751,16 +109751,16 @@ function extractDefs(ctx, schema) {
109751
109751
  };
109752
109752
  if (ctx.cycles === "throw") {
109753
109753
  for (const entry of ctx.seen.entries()) {
109754
- const seen2 = entry[1];
109755
- if (seen2.cycle) {
109756
- throw new Error(`Cycle detected: #/${seen2.cycle?.join("/")}/<root>
109754
+ const seen = entry[1];
109755
+ if (seen.cycle) {
109756
+ throw new Error(`Cycle detected: #/${seen.cycle?.join("/")}/<root>
109757
109757
 
109758
109758
  Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`);
109759
109759
  }
109760
109760
  }
109761
109761
  }
109762
109762
  for (const entry of ctx.seen.entries()) {
109763
- const seen2 = entry[1];
109763
+ const seen = entry[1];
109764
109764
  if (schema === entry[0]) {
109765
109765
  extractToDef(entry);
109766
109766
  continue;
@@ -109777,11 +109777,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
109777
109777
  extractToDef(entry);
109778
109778
  continue;
109779
109779
  }
109780
- if (seen2.cycle) {
109780
+ if (seen.cycle) {
109781
109781
  extractToDef(entry);
109782
109782
  continue;
109783
109783
  }
109784
- if (seen2.count > 1) {
109784
+ if (seen.count > 1) {
109785
109785
  if (ctx.reused === "ref") {
109786
109786
  extractToDef(entry);
109787
109787
  continue;
@@ -109794,13 +109794,13 @@ function finalize(ctx, schema) {
109794
109794
  if (!root)
109795
109795
  throw new Error("Unprocessed schema. This is a bug in Zod.");
109796
109796
  const flattenRef = (zodSchema) => {
109797
- const seen2 = ctx.seen.get(zodSchema);
109798
- if (seen2.ref === null)
109797
+ const seen = ctx.seen.get(zodSchema);
109798
+ if (seen.ref === null)
109799
109799
  return;
109800
- const schema2 = seen2.def ?? seen2.schema;
109800
+ const schema2 = seen.def ?? seen.schema;
109801
109801
  const _cached = { ...schema2 };
109802
- const ref = seen2.ref;
109803
- seen2.ref = null;
109802
+ const ref = seen.ref;
109803
+ seen.ref = null;
109804
109804
  if (ref) {
109805
109805
  flattenRef(ref);
109806
109806
  const refSeen = ctx.seen.get(ref);
@@ -109852,7 +109852,7 @@ function finalize(ctx, schema) {
109852
109852
  ctx.override({
109853
109853
  zodSchema,
109854
109854
  jsonSchema: schema2,
109855
- path: seen2.path ?? []
109855
+ path: seen.path ?? []
109856
109856
  });
109857
109857
  };
109858
109858
  for (const entry of [...ctx.seen.entries()].reverse()) {
@@ -109875,9 +109875,9 @@ function finalize(ctx, schema) {
109875
109875
  Object.assign(result, root.def ?? root.schema);
109876
109876
  const defs = ctx.external?.defs ?? {};
109877
109877
  for (const entry of ctx.seen.entries()) {
109878
- const seen2 = entry[1];
109879
- if (seen2.def && seen2.defId) {
109880
- defs[seen2.defId] = seen2.def;
109878
+ const seen = entry[1];
109879
+ if (seen.def && seen.defId) {
109880
+ defs[seen.defId] = seen.def;
109881
109881
  }
109882
109882
  }
109883
109883
  if (ctx.external) ; else {
@@ -110371,9 +110371,9 @@ var recordProcessor = (schema, ctx, _json, params) => {
110371
110371
  var nullableProcessor = (schema, ctx, json2, params) => {
110372
110372
  const def = schema._zod.def;
110373
110373
  const inner = process2(def.innerType, ctx, params);
110374
- const seen2 = ctx.seen.get(schema);
110374
+ const seen = ctx.seen.get(schema);
110375
110375
  if (ctx.target === "openapi-3.0") {
110376
- seen2.ref = def.innerType;
110376
+ seen.ref = def.innerType;
110377
110377
  json2.nullable = true;
110378
110378
  } else {
110379
110379
  json2.anyOf = [inner, { type: "null" }];
@@ -110382,29 +110382,29 @@ var nullableProcessor = (schema, ctx, json2, params) => {
110382
110382
  var nonoptionalProcessor = (schema, ctx, _json, params) => {
110383
110383
  const def = schema._zod.def;
110384
110384
  process2(def.innerType, ctx, params);
110385
- const seen2 = ctx.seen.get(schema);
110386
- seen2.ref = def.innerType;
110385
+ const seen = ctx.seen.get(schema);
110386
+ seen.ref = def.innerType;
110387
110387
  };
110388
110388
  var defaultProcessor = (schema, ctx, json2, params) => {
110389
110389
  const def = schema._zod.def;
110390
110390
  process2(def.innerType, ctx, params);
110391
- const seen2 = ctx.seen.get(schema);
110392
- seen2.ref = def.innerType;
110391
+ const seen = ctx.seen.get(schema);
110392
+ seen.ref = def.innerType;
110393
110393
  json2.default = JSON.parse(JSON.stringify(def.defaultValue));
110394
110394
  };
110395
110395
  var prefaultProcessor = (schema, ctx, json2, params) => {
110396
110396
  const def = schema._zod.def;
110397
110397
  process2(def.innerType, ctx, params);
110398
- const seen2 = ctx.seen.get(schema);
110399
- seen2.ref = def.innerType;
110398
+ const seen = ctx.seen.get(schema);
110399
+ seen.ref = def.innerType;
110400
110400
  if (ctx.io === "input")
110401
110401
  json2._prefault = JSON.parse(JSON.stringify(def.defaultValue));
110402
110402
  };
110403
110403
  var catchProcessor = (schema, ctx, json2, params) => {
110404
110404
  const def = schema._zod.def;
110405
110405
  process2(def.innerType, ctx, params);
110406
- const seen2 = ctx.seen.get(schema);
110407
- seen2.ref = def.innerType;
110406
+ const seen = ctx.seen.get(schema);
110407
+ seen.ref = def.innerType;
110408
110408
  let catchValue;
110409
110409
  try {
110410
110410
  catchValue = def.catchValue(void 0);
@@ -110417,33 +110417,33 @@ var pipeProcessor = (schema, ctx, _json, params) => {
110417
110417
  const def = schema._zod.def;
110418
110418
  const innerType = ctx.io === "input" ? def.in._zod.def.type === "transform" ? def.out : def.in : def.out;
110419
110419
  process2(innerType, ctx, params);
110420
- const seen2 = ctx.seen.get(schema);
110421
- seen2.ref = innerType;
110420
+ const seen = ctx.seen.get(schema);
110421
+ seen.ref = innerType;
110422
110422
  };
110423
110423
  var readonlyProcessor = (schema, ctx, json2, params) => {
110424
110424
  const def = schema._zod.def;
110425
110425
  process2(def.innerType, ctx, params);
110426
- const seen2 = ctx.seen.get(schema);
110427
- seen2.ref = def.innerType;
110426
+ const seen = ctx.seen.get(schema);
110427
+ seen.ref = def.innerType;
110428
110428
  json2.readOnly = true;
110429
110429
  };
110430
110430
  var promiseProcessor = (schema, ctx, _json, params) => {
110431
110431
  const def = schema._zod.def;
110432
110432
  process2(def.innerType, ctx, params);
110433
- const seen2 = ctx.seen.get(schema);
110434
- seen2.ref = def.innerType;
110433
+ const seen = ctx.seen.get(schema);
110434
+ seen.ref = def.innerType;
110435
110435
  };
110436
110436
  var optionalProcessor = (schema, ctx, _json, params) => {
110437
110437
  const def = schema._zod.def;
110438
110438
  process2(def.innerType, ctx, params);
110439
- const seen2 = ctx.seen.get(schema);
110440
- seen2.ref = def.innerType;
110439
+ const seen = ctx.seen.get(schema);
110440
+ seen.ref = def.innerType;
110441
110441
  };
110442
110442
  var lazyProcessor = (schema, ctx, _json, params) => {
110443
110443
  const innerType = schema._zod.innerType;
110444
110444
  process2(innerType, ctx, params);
110445
- const seen2 = ctx.seen.get(schema);
110446
- seen2.ref = innerType;
110445
+ const seen = ctx.seen.get(schema);
110446
+ seen.ref = innerType;
110447
110447
  };
110448
110448
  var allProcessors = {
110449
110449
  string: stringProcessor,
@@ -112507,17 +112507,19 @@ config(en_default());
112507
112507
  // src/types.ts
112508
112508
  var AutoPromptSchema = external_exports.object({
112509
112509
  enabled: external_exports.boolean().default(false),
112510
- intervalSeconds: external_exports.number().int().positive().default(30),
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")
112510
+ intervalSeconds: external_exports.number().int().positive().max(300).default(30),
112511
+ maxIterations: external_exports.number().int().positive().max(100).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"),
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"),
112516
112518
  appSecret: external_exports.string().min(1, "appSecret \u4E0D\u80FD\u4E3A\u7A7A"),
112517
- timeout: external_exports.number().int().positive().default(12e4),
112519
+ timeout: external_exports.number().int().positive().max(6e5).default(12e4),
112518
112520
  thinkingDelay: external_exports.number().int().nonnegative().default(2500),
112519
112521
  logLevel: external_exports.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info"),
112520
- maxHistoryMessages: external_exports.number().int().positive().default(200),
112522
+ maxHistoryMessages: external_exports.number().int().positive().max(500).default(200),
112521
112523
  pollInterval: external_exports.number().int().positive().default(1e3),
112522
112524
  stablePolls: external_exports.number().int().positive().default(3),
112523
112525
  dedupTtl: external_exports.number().int().positive().default(10 * 60 * 1e3),
@@ -112712,37 +112714,40 @@ function buildQuestionCard(request) {
112712
112714
  }
112713
112715
 
112714
112716
  // src/feishu/sender.ts
112717
+ async function wrapSendCall(fn, idExtractor = (res) => res?.data?.message_id ?? "") {
112718
+ try {
112719
+ const res = await fn();
112720
+ return { ok: true, messageId: idExtractor(res) };
112721
+ } catch (err) {
112722
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
112723
+ }
112724
+ }
112715
112725
  async function sendTextMessage(client, chatId, text) {
112716
112726
  if (!chatId?.trim()) {
112717
112727
  return { ok: false, error: "No chat_id provided" };
112718
112728
  }
112719
- try {
112720
- const res = await client.im.message.create({
112729
+ return wrapSendCall(
112730
+ () => client.im.message.create({
112721
112731
  params: { receive_id_type: "chat_id" },
112722
112732
  data: {
112723
112733
  receive_id: chatId.trim(),
112724
112734
  msg_type: "text",
112725
112735
  content: JSON.stringify({ text })
112726
112736
  }
112727
- });
112728
- return { ok: true, messageId: res?.data?.message_id ?? "" };
112729
- } catch (err) {
112730
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
112731
- }
112737
+ })
112738
+ );
112732
112739
  }
112733
112740
  async function updateMessage(client, messageId, text) {
112734
- try {
112735
- await client.im.message.update({
112741
+ return wrapSendCall(
112742
+ () => client.im.message.update({
112736
112743
  path: { message_id: messageId },
112737
112744
  data: {
112738
112745
  msg_type: "text",
112739
112746
  content: JSON.stringify({ text })
112740
112747
  }
112741
- });
112742
- return { ok: true, messageId };
112743
- } catch (err) {
112744
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
112745
- }
112748
+ }),
112749
+ () => messageId
112750
+ );
112746
112751
  }
112747
112752
  async function deleteMessage(client, messageId) {
112748
112753
  try {
@@ -112754,49 +112759,71 @@ async function sendInteractiveCard(client, chatId, card) {
112754
112759
  if (!chatId?.trim()) {
112755
112760
  return { ok: false, error: "No chat_id provided" };
112756
112761
  }
112757
- try {
112758
- const res = await client.im.message.create({
112762
+ return wrapSendCall(
112763
+ () => client.im.message.create({
112759
112764
  params: { receive_id_type: "chat_id" },
112760
112765
  data: {
112761
112766
  receive_id: chatId.trim(),
112762
112767
  msg_type: "interactive",
112763
112768
  content: JSON.stringify(card)
112764
112769
  }
112765
- });
112766
- return { ok: true, messageId: res?.data?.message_id ?? "" };
112767
- } catch (err) {
112768
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
112769
- }
112770
+ })
112771
+ );
112770
112772
  }
112771
112773
  async function sendCardMessage(client, chatId, cardId) {
112772
112774
  if (!chatId?.trim()) {
112773
112775
  return { ok: false, error: "No chat_id provided" };
112774
112776
  }
112775
- try {
112776
- const res = await client.im.message.create({
112777
+ return wrapSendCall(
112778
+ () => client.im.message.create({
112777
112779
  params: { receive_id_type: "chat_id" },
112778
112780
  data: {
112779
112781
  receive_id: chatId.trim(),
112780
112782
  msg_type: "interactive",
112781
112783
  content: JSON.stringify({ type: "card_kit", data: { card_id: cardId } })
112782
112784
  }
112783
- });
112784
- return { ok: true, messageId: res?.data?.message_id ?? "" };
112785
- } catch (err) {
112786
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
112787
- }
112785
+ })
112786
+ );
112788
112787
  }
112789
112788
 
112789
+ // src/utils/ttl-map.ts
112790
+ var TtlMap = class {
112791
+ constructor(defaultTtlMs) {
112792
+ this.defaultTtlMs = defaultTtlMs;
112793
+ }
112794
+ data = /* @__PURE__ */ new Map();
112795
+ timers = /* @__PURE__ */ new Map();
112796
+ get(key) {
112797
+ return this.data.get(key);
112798
+ }
112799
+ has(key) {
112800
+ return this.data.has(key);
112801
+ }
112802
+ set(key, value, ttlMs) {
112803
+ this.delete(key);
112804
+ this.data.set(key, value);
112805
+ const timer = setTimeout(() => {
112806
+ this.data.delete(key);
112807
+ this.timers.delete(key);
112808
+ }, ttlMs ?? this.defaultTtlMs);
112809
+ timer.unref();
112810
+ this.timers.set(key, timer);
112811
+ }
112812
+ delete(key) {
112813
+ const timer = this.timers.get(key);
112814
+ if (timer) {
112815
+ clearTimeout(timer);
112816
+ this.timers.delete(key);
112817
+ }
112818
+ this.data.delete(key);
112819
+ }
112820
+ };
112821
+
112790
112822
  // src/handler/interactive.ts
112791
- var SEEN_TTL_MS = 10 * 60 * 1e3;
112792
- var seenRequestIds = /* @__PURE__ */ new Map();
112823
+ var seenIds = new TtlMap(10 * 60 * 1e3);
112793
112824
  function markSeen(requestId) {
112794
- const now = Date.now();
112795
- for (const [id, ts] of seenRequestIds) {
112796
- if (now - ts > SEEN_TTL_MS) seenRequestIds.delete(id);
112797
- }
112798
- if (seenRequestIds.has(requestId)) return false;
112799
- seenRequestIds.set(requestId, now);
112825
+ if (seenIds.has(requestId)) return false;
112826
+ seenIds.set(requestId, true);
112800
112827
  return true;
112801
112828
  }
112802
112829
  function handlePermissionRequested(request, chatId, deps) {
@@ -112864,19 +112891,14 @@ function buildCallbackResponse(action) {
112864
112891
  }
112865
112892
 
112866
112893
  // src/feishu/dedup.ts
112867
- var seenTtlMs = 10 * 60 * 1e3;
112868
- var seen = /* @__PURE__ */ new Map();
112894
+ var dedup = new TtlMap(10 * 60 * 1e3);
112869
112895
  function initDedup(ttl) {
112870
- seenTtlMs = ttl;
112896
+ dedup = new TtlMap(ttl);
112871
112897
  }
112872
112898
  function isDuplicate(messageId) {
112873
- const now = Date.now();
112874
- for (const [k, ts] of seen) {
112875
- if (now - ts > seenTtlMs) seen.delete(k);
112876
- }
112877
112899
  if (!messageId) return false;
112878
- if (seen.has(messageId)) return true;
112879
- seen.set(messageId, now);
112900
+ if (dedup.has(messageId)) return true;
112901
+ dedup.set(messageId, true);
112880
112902
  return false;
112881
112903
  }
112882
112904
 
@@ -113314,57 +113336,24 @@ function emit(sessionId, action) {
113314
113336
 
113315
113337
  // src/handler/event.ts
113316
113338
  var pendingBySession = /* @__PURE__ */ new Map();
113317
- var sessionErrors = /* @__PURE__ */ new Map();
113318
- var sessionErrorTimeouts = /* @__PURE__ */ new Map();
113319
- var SESSION_ERROR_TTL_MS = 3e4;
113320
- var retryAttempts = /* @__PURE__ */ new Map();
113321
- var retryAttemptTimeouts = /* @__PURE__ */ new Map();
113339
+ var sessionErrors = new TtlMap(3e4);
113340
+ var retryAttempts = new TtlMap(36e5);
113322
113341
  var MAX_RETRY_ATTEMPTS = 2;
113323
- var RETRY_ATTEMPTS_TTL_MS = 36e5;
113324
113342
  function clearRetryAttempts(sessionKey) {
113325
113343
  retryAttempts.delete(sessionKey);
113326
- const timer = retryAttemptTimeouts.get(sessionKey);
113327
- if (timer) {
113328
- clearTimeout(timer);
113329
- retryAttemptTimeouts.delete(sessionKey);
113330
- }
113331
113344
  }
113332
113345
  function getRetryAttempts(sessionKey) {
113333
113346
  return retryAttempts.get(sessionKey) ?? 0;
113334
113347
  }
113335
113348
  function setRetryAttempts(sessionKey, count) {
113336
113349
  retryAttempts.set(sessionKey, count);
113337
- const existing = retryAttemptTimeouts.get(sessionKey);
113338
- if (existing) clearTimeout(existing);
113339
- const timeoutId = setTimeout(() => {
113340
- retryAttempts.delete(sessionKey);
113341
- retryAttemptTimeouts.delete(sessionKey);
113342
- }, RETRY_ATTEMPTS_TTL_MS);
113343
- retryAttemptTimeouts.set(sessionKey, timeoutId);
113344
113350
  }
113345
113351
  function getSessionError(sessionId) {
113346
113352
  return sessionErrors.get(sessionId);
113347
113353
  }
113348
113354
  function clearSessionError(sessionId) {
113349
- const timer = sessionErrorTimeouts.get(sessionId);
113350
- if (timer) {
113351
- clearTimeout(timer);
113352
- sessionErrorTimeouts.delete(sessionId);
113353
- }
113354
113355
  sessionErrors.delete(sessionId);
113355
113356
  }
113356
- function setSessionError(sessionId, message, fields) {
113357
- const existing = sessionErrorTimeouts.get(sessionId);
113358
- if (existing) {
113359
- clearTimeout(existing);
113360
- }
113361
- sessionErrors.set(sessionId, { message, fields });
113362
- const timeoutId = setTimeout(() => {
113363
- sessionErrors.delete(sessionId);
113364
- sessionErrorTimeouts.delete(sessionId);
113365
- }, SESSION_ERROR_TTL_MS);
113366
- sessionErrorTimeouts.set(sessionId, timeoutId);
113367
- }
113368
113357
  function registerPending(sessionId, payload) {
113369
113358
  pendingBySession.set(sessionId, { ...payload, textBuffer: "", expectedMessageId: void 0 });
113370
113359
  }
@@ -113415,111 +113404,115 @@ async function handleEvent(event, deps) {
113415
113404
  switch (event.type) {
113416
113405
  case "message.part.updated": {
113417
113406
  const part = event.properties.part;
113418
- if (!part) break;
113419
- const sessionId = part.sessionID;
113420
- if (!sessionId) break;
113421
- const payload = pendingBySession.get(sessionId);
113407
+ if (!part?.sessionID) break;
113408
+ const payload = pendingBySession.get(part.sessionID);
113422
113409
  if (!payload) break;
113423
- const messageId = part.messageID;
113424
- if (messageId) {
113425
- if (!payload.expectedMessageId) {
113426
- payload.expectedMessageId = messageId;
113427
- } else if (payload.expectedMessageId !== messageId) {
113428
- break;
113429
- }
113430
- } else if (payload.expectedMessageId) {
113431
- break;
113432
- }
113433
- const partSessionId = part.sessionID;
113434
- if (part.type === "tool") {
113435
- const p = part;
113436
- const toolName = String(p.toolName ?? p.name ?? "unknown");
113437
- const callID = String(p.toolCallID ?? p.id ?? "");
113438
- const hasError = p.error !== void 0 && p.error !== null;
113439
- const rawState = p.state != null ? String(p.state) : hasError ? "error" : "running";
113440
- const toolState = rawState === "completed" || rawState === "error" ? rawState : "running";
113441
- if (partSessionId) {
113442
- emit(partSessionId, {
113443
- type: "tool-state-changed",
113444
- sessionId: partSessionId,
113445
- callID,
113446
- tool: toolName,
113447
- state: toolState
113448
- });
113449
- }
113450
- break;
113451
- }
113452
- const delta = event.properties.delta;
113453
- if (delta) {
113454
- payload.textBuffer += delta;
113455
- } else {
113456
- const fullText = extractPartText(part);
113457
- if (fullText) {
113458
- payload.textBuffer = fullText;
113459
- }
113460
- }
113461
- if (payload.textBuffer) {
113462
- const res = await updateMessage(payload.feishuClient, payload.placeholderId, payload.textBuffer.trim());
113463
- if (!res.ok) ;
113464
- }
113465
- if (partSessionId) {
113466
- emit(partSessionId, {
113467
- type: "text-updated",
113468
- sessionId: partSessionId,
113469
- messageId: part.messageID,
113470
- delta: delta ?? void 0,
113471
- fullText: payload.textBuffer
113472
- });
113473
- }
113410
+ await handleMessagePartUpdated(event, part, payload);
113474
113411
  break;
113475
113412
  }
113476
- case "session.error": {
113477
- const props = event.properties;
113478
- const sessionId = props.sessionID;
113479
- if (!sessionId) break;
113480
- const error48 = props.error;
113481
- let errMsg;
113482
- if (typeof error48 === "string") {
113483
- errMsg = error48;
113484
- } else if (error48 && typeof error48 === "object") {
113485
- const e = error48;
113486
- const rawDataMsg = e.data && typeof e.data === "object" && "message" in e.data ? e.data.message : void 0;
113487
- const dataMsg = rawDataMsg != null ? String(rawDataMsg) : void 0;
113488
- errMsg = String(e.message ?? dataMsg ?? e.type ?? e.name ?? "An unexpected error occurred");
113489
- } else {
113490
- errMsg = String(error48);
113491
- }
113492
- const fields = extractErrorFields(error48);
113493
- deps.log("warn", "\u6536\u5230 session.error \u4E8B\u4EF6", { sessionId, errMsg });
113494
- setSessionError(sessionId, errMsg, fields);
113413
+ case "session.error":
113414
+ handleSessionErrorEvent(event, deps);
113495
113415
  break;
113496
- }
113497
- default: {
113498
- const evtType = event.type;
113499
- const evtProps = event.properties ?? {};
113500
- const evtSessionId = evtProps.sessionID;
113501
- if (evtType === "permission.asked" && evtSessionId) {
113502
- emit(evtSessionId, {
113503
- type: "permission-requested",
113504
- sessionId: evtSessionId,
113505
- request: evtProps
113506
- });
113507
- deps.log("info", "permission.asked \u4E8B\u4EF6\u5DF2\u5206\u53D1", { sessionId: evtSessionId });
113508
- } else if (evtType === "question.asked" && evtSessionId) {
113509
- emit(evtSessionId, {
113510
- type: "question-requested",
113511
- sessionId: evtSessionId,
113512
- request: evtProps
113513
- });
113514
- deps.log("info", "question.asked \u4E8B\u4EF6\u5DF2\u5206\u53D1", { sessionId: evtSessionId });
113515
- } else if (evtType === "session.idle" && evtSessionId) {
113516
- emit(evtSessionId, {
113517
- type: "session-idle",
113518
- sessionId: evtSessionId
113519
- });
113520
- }
113416
+ default:
113417
+ handleV2Event(event, deps);
113521
113418
  break;
113419
+ }
113420
+ }
113421
+ async function handleMessagePartUpdated(event, part, payload) {
113422
+ const messageId = part.messageID;
113423
+ if (messageId) {
113424
+ if (!payload.expectedMessageId) {
113425
+ payload.expectedMessageId = messageId;
113426
+ } else if (payload.expectedMessageId !== messageId) {
113427
+ return;
113428
+ }
113429
+ } else if (payload.expectedMessageId) {
113430
+ return;
113431
+ }
113432
+ const partSessionId = part.sessionID;
113433
+ if (part.type === "tool") {
113434
+ const p = part;
113435
+ const toolName = String(p.toolName ?? p.name ?? "unknown");
113436
+ const callID = String(p.toolCallID ?? p.id ?? "");
113437
+ const hasError = p.error !== void 0 && p.error !== null;
113438
+ const rawState = p.state != null ? String(p.state) : hasError ? "error" : "running";
113439
+ const toolState = rawState === "completed" || rawState === "error" ? rawState : "running";
113440
+ if (partSessionId) {
113441
+ emit(partSessionId, {
113442
+ type: "tool-state-changed",
113443
+ sessionId: partSessionId,
113444
+ callID,
113445
+ tool: toolName,
113446
+ state: toolState
113447
+ });
113522
113448
  }
113449
+ return;
113450
+ }
113451
+ const delta = event.properties.delta;
113452
+ if (delta) {
113453
+ payload.textBuffer += delta;
113454
+ } else {
113455
+ const fullText = extractPartText(part);
113456
+ if (fullText) {
113457
+ payload.textBuffer = fullText;
113458
+ }
113459
+ }
113460
+ if (payload.textBuffer) {
113461
+ await updateMessage(payload.feishuClient, payload.placeholderId, payload.textBuffer.trim());
113462
+ }
113463
+ if (partSessionId) {
113464
+ emit(partSessionId, {
113465
+ type: "text-updated",
113466
+ sessionId: partSessionId,
113467
+ messageId: part.messageID,
113468
+ delta: delta ?? void 0,
113469
+ fullText: payload.textBuffer
113470
+ });
113471
+ }
113472
+ }
113473
+ function handleSessionErrorEvent(event, deps) {
113474
+ const props = event.properties;
113475
+ const sessionId = props.sessionID;
113476
+ if (!sessionId) return;
113477
+ const error48 = props.error;
113478
+ let errMsg;
113479
+ if (typeof error48 === "string") {
113480
+ errMsg = error48;
113481
+ } else if (error48 && typeof error48 === "object") {
113482
+ const e = error48;
113483
+ const asStr = (v) => typeof v === "string" && v.trim().length > 0 ? v : void 0;
113484
+ const rawDataMsg = e.data && typeof e.data === "object" && "message" in e.data ? e.data.message : void 0;
113485
+ errMsg = asStr(e.message) ?? asStr(rawDataMsg) ?? asStr(e.type) ?? asStr(e.name) ?? "An unexpected error occurred";
113486
+ } else {
113487
+ errMsg = String(error48);
113488
+ }
113489
+ const fields = extractErrorFields(error48);
113490
+ deps.log("warn", "\u6536\u5230 session.error \u4E8B\u4EF6", { sessionId, errMsg });
113491
+ sessionErrors.set(sessionId, { message: errMsg, fields });
113492
+ }
113493
+ function handleV2Event(event, deps) {
113494
+ const evtType = event.type;
113495
+ const evtProps = event.properties ?? {};
113496
+ const evtSessionId = evtProps.sessionID;
113497
+ if (evtType === "permission.asked" && evtSessionId) {
113498
+ emit(evtSessionId, {
113499
+ type: "permission-requested",
113500
+ sessionId: evtSessionId,
113501
+ request: evtProps
113502
+ });
113503
+ deps.log("info", "permission.asked \u4E8B\u4EF6\u5DF2\u5206\u53D1", { sessionId: evtSessionId });
113504
+ } else if (evtType === "question.asked" && evtSessionId) {
113505
+ emit(evtSessionId, {
113506
+ type: "question-requested",
113507
+ sessionId: evtSessionId,
113508
+ request: evtProps
113509
+ });
113510
+ deps.log("info", "question.asked \u4E8B\u4EF6\u5DF2\u5206\u53D1", { sessionId: evtSessionId });
113511
+ } else if (evtType === "session.idle" && evtSessionId) {
113512
+ emit(evtSessionId, {
113513
+ type: "session-idle",
113514
+ sessionId: evtSessionId
113515
+ });
113523
113516
  }
113524
113517
  }
113525
113518
  function extractPartText(part) {
@@ -113530,6 +113523,128 @@ function extractPartText(part) {
113530
113523
  return "";
113531
113524
  }
113532
113525
 
113526
+ // src/handler/error-recovery.ts
113527
+ var SessionErrorDetected = class extends Error {
113528
+ constructor(sessionError) {
113529
+ super(sessionError.message);
113530
+ this.sessionError = sessionError;
113531
+ this.name = "SessionErrorDetected";
113532
+ }
113533
+ };
113534
+ async function getGlobalDefaultModel(client, directory) {
113535
+ const query = directory ? { directory } : void 0;
113536
+ const { data: config2 } = await client.config.get({ query });
113537
+ const model = config2?.model;
113538
+ if (!model || !model.includes("/")) return void 0;
113539
+ const slash = model.indexOf("/");
113540
+ const providerID = model.slice(0, slash).trim();
113541
+ const modelID = model.slice(slash + 1).trim();
113542
+ if (!providerID || !modelID) return void 0;
113543
+ return { providerID, modelID };
113544
+ }
113545
+ function extractSessionError(err, sessionId) {
113546
+ const result = err instanceof SessionErrorDetected ? err.sessionError : getSessionError(sessionId);
113547
+ clearSessionError(sessionId);
113548
+ return result;
113549
+ }
113550
+ async function tryModelRecovery(params) {
113551
+ const {
113552
+ sessionError,
113553
+ sessionId,
113554
+ sessionKey,
113555
+ client,
113556
+ directory,
113557
+ parts,
113558
+ timeout,
113559
+ pollInterval,
113560
+ stablePolls,
113561
+ query,
113562
+ signal,
113563
+ log,
113564
+ poll
113565
+ } = params;
113566
+ log("info", "\u9519\u8BEF\u5B57\u6BB5\u68C0\u67E5", {
113567
+ sessionKey,
113568
+ fields: sessionError.fields,
113569
+ isModel: isModelError(sessionError.fields)
113570
+ });
113571
+ if (!isModelError(sessionError.fields)) {
113572
+ return { recovered: false, sessionError };
113573
+ }
113574
+ const attempts = getRetryAttempts(sessionKey);
113575
+ if (attempts >= MAX_RETRY_ATTEMPTS) {
113576
+ log("warn", "\u5DF2\u8FBE\u91CD\u8BD5\u4E0A\u9650\uFF0C\u653E\u5F03\u6062\u590D", { sessionKey, attempts });
113577
+ return { recovered: false, sessionError };
113578
+ }
113579
+ try {
113580
+ let modelOverride;
113581
+ try {
113582
+ modelOverride = await getGlobalDefaultModel(client, directory);
113583
+ } catch (configErr) {
113584
+ log("warn", "\u8BFB\u53D6\u5168\u5C40\u6A21\u578B\u914D\u7F6E\u5931\u8D25", {
113585
+ sessionKey,
113586
+ error: configErr instanceof Error ? configErr.message : String(configErr)
113587
+ });
113588
+ }
113589
+ if (!modelOverride) {
113590
+ log("warn", "\u5168\u5C40\u9ED8\u8BA4\u6A21\u578B\u672A\u914D\u7F6E\uFF0C\u653E\u5F03\u6062\u590D", { sessionKey });
113591
+ return { recovered: false, sessionError };
113592
+ }
113593
+ setRetryAttempts(sessionKey, attempts + 1);
113594
+ log("info", "\u4F7F\u7528\u5168\u5C40\u9ED8\u8BA4\u6A21\u578B\u6062\u590D", {
113595
+ sessionKey,
113596
+ providerID: modelOverride.providerID,
113597
+ modelID: modelOverride.modelID
113598
+ });
113599
+ clearSessionError(sessionId);
113600
+ await client.session.promptAsync({
113601
+ path: { id: sessionId },
113602
+ query,
113603
+ body: { parts: [...parts], model: modelOverride }
113604
+ });
113605
+ const finalText = await poll(client, sessionId, {
113606
+ timeout,
113607
+ pollInterval,
113608
+ stablePolls,
113609
+ query,
113610
+ signal
113611
+ });
113612
+ log("info", "\u6A21\u578B\u6062\u590D\u540E\u54CD\u5E94\u5B8C\u6210", {
113613
+ sessionKey,
113614
+ sessionId,
113615
+ output: finalText || "(empty)"
113616
+ });
113617
+ clearRetryAttempts(sessionKey);
113618
+ log("info", "\u6A21\u578B\u4E0D\u517C\u5BB9\u6062\u590D\u6210\u529F", {
113619
+ sessionId,
113620
+ sessionKey,
113621
+ model: `${modelOverride.providerID}/${modelOverride.modelID}`,
113622
+ attempt: attempts + 1
113623
+ });
113624
+ return { recovered: true, text: finalText };
113625
+ } catch (recoveryErr) {
113626
+ if (recoveryErr instanceof Error && recoveryErr.name === "AbortError") {
113627
+ throw recoveryErr;
113628
+ }
113629
+ const errMsg = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
113630
+ let updatedError;
113631
+ if (recoveryErr instanceof SessionErrorDetected) {
113632
+ updatedError = recoveryErr.sessionError;
113633
+ clearSessionError(sessionId);
113634
+ } else {
113635
+ const sseError = getSessionError(sessionId);
113636
+ if (sseError) {
113637
+ updatedError = sseError;
113638
+ clearSessionError(sessionId);
113639
+ } else {
113640
+ updatedError = { message: errMsg, fields: [] };
113641
+ }
113642
+ }
113643
+ log("error", "\u6A21\u578B\u6062\u590D\u5931\u8D25", { sessionId, sessionKey, error: errMsg });
113644
+ return { recovered: false, sessionError: updatedError };
113645
+ }
113646
+ }
113647
+
113533
113648
  // src/session.ts
113534
113649
  var SESSION_KEY_PREFIX = "feishu";
113535
113650
  var TITLE_PREFIX = "Feishu";
@@ -113762,7 +113877,6 @@ var StreamingCard = class {
113762
113877
  };
113763
113878
 
113764
113879
  // src/handler/chat.ts
113765
- var activeAutoPrompts = /* @__PURE__ */ new Map();
113766
113880
  async function finalizeReply(streamingCard, feishuClient, chatId, placeholderId, text) {
113767
113881
  if (streamingCard) {
113768
113882
  await streamingCard.close(text);
@@ -113780,19 +113894,13 @@ async function abortCleanup(streamingCard, feishuClient, placeholderId) {
113780
113894
  }
113781
113895
  async function handleChat(ctx, deps, signal) {
113782
113896
  const { content, chatId, chatType, senderId, shouldReply, messageType, rawContent, messageId } = ctx;
113783
- if (!content.trim() && messageType === "text") return;
113897
+ if (!content.trim() && messageType === "text") return void 0;
113784
113898
  const { config: config2, client, feishuClient, log, directory } = deps;
113785
113899
  const query = directory ? { directory } : void 0;
113786
113900
  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
113901
  const session = await getOrCreateSession(client, sessionKey, directory);
113794
113902
  const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log);
113795
- if (!parts.length) return;
113903
+ if (!parts.length) return void 0;
113796
113904
  log("info", "\u6536\u5230\u7528\u6237\u6D88\u606F", {
113797
113905
  sessionKey,
113798
113906
  sessionId: session.id,
@@ -113816,7 +113924,7 @@ async function handleChat(ctx, deps, signal) {
113816
113924
  error: err instanceof Error ? err.message : String(err)
113817
113925
  });
113818
113926
  }
113819
- return;
113927
+ return void 0;
113820
113928
  }
113821
113929
  const timeout = config2.timeout;
113822
113930
  const thinkingDelay = config2.thinkingDelay;
@@ -113857,46 +113965,6 @@ async function handleChat(ctx, deps, signal) {
113857
113965
  });
113858
113966
  }
113859
113967
  }, 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
113968
  let cardUnsub;
113901
113969
  {
113902
113970
  const card = streamingCard;
@@ -113942,113 +114010,60 @@ async function handleChat(ctx, deps, signal) {
113942
114010
  });
113943
114011
  clearRetryAttempts(sessionKey);
113944
114012
  await finalizeReply(streamingCard, feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
113945
- await runAutoPromptLoop(session.id);
114013
+ if (config2.autoPrompt.enabled && shouldReply) {
114014
+ return { sessionId: session.id, sessionKey, chatId, deps };
114015
+ }
114016
+ return void 0;
113946
114017
  } catch (err) {
113947
114018
  if (err instanceof Error && err.name === "AbortError") {
113948
114019
  log("info", "\u5904\u7406\u88AB\u4E2D\u65AD", { sessionKey, sessionId: session.id });
113949
114020
  await abortCleanup(streamingCard, feishuClient, placeholderId);
113950
- return;
113951
- }
113952
- let sessionError;
113953
- if (err instanceof SessionErrorDetected) {
113954
- sessionError = err.sessionError;
113955
- clearSessionError(session.id);
113956
- } else {
113957
- sessionError = getSessionError(session.id);
113958
- clearSessionError(session.id);
114021
+ return void 0;
113959
114022
  }
114023
+ const sessionError = extractSessionError(err, session.id);
114024
+ let displayError = sessionError;
113960
114025
  if (sessionError) {
113961
- log("info", "\u9519\u8BEF\u5B57\u6BB5\u68C0\u67E5", {
113962
- sessionKey,
113963
- fields: sessionError.fields,
113964
- isModel: isModelError(sessionError.fields)
113965
- });
113966
- }
113967
- if (sessionError && isModelError(sessionError.fields)) {
113968
- const attempts = getRetryAttempts(sessionKey);
113969
- if (attempts < MAX_RETRY_ATTEMPTS) {
113970
- try {
113971
- let modelOverride;
113972
- try {
113973
- modelOverride = await getGlobalDefaultModel(client, directory);
113974
- } catch (configErr) {
113975
- log("warn", "\u8BFB\u53D6\u5168\u5C40\u6A21\u578B\u914D\u7F6E\u5931\u8D25", {
113976
- sessionKey,
113977
- error: configErr instanceof Error ? configErr.message : String(configErr)
113978
- });
113979
- }
113980
- if (!modelOverride) {
113981
- log("warn", "\u5168\u5C40\u9ED8\u8BA4\u6A21\u578B\u672A\u914D\u7F6E\uFF0C\u653E\u5F03\u6062\u590D", { sessionKey });
113982
- } else {
113983
- setRetryAttempts(sessionKey, attempts + 1);
113984
- log("info", "\u4F7F\u7528\u5168\u5C40\u9ED8\u8BA4\u6A21\u578B\u6062\u590D", {
113985
- sessionKey,
113986
- providerID: modelOverride.providerID,
113987
- modelID: modelOverride.modelID
113988
- });
113989
- clearSessionError(session.id);
113990
- await client.session.promptAsync({
113991
- path: { id: session.id },
113992
- query,
113993
- body: { ...baseBody, model: modelOverride }
113994
- });
113995
- const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query, signal });
113996
- log("info", "\u6A21\u578B\u6062\u590D\u540E\u54CD\u5E94\u5B8C\u6210", {
113997
- sessionKey,
113998
- sessionId: session.id,
113999
- output: finalText || "(empty)"
114000
- });
114001
- clearRetryAttempts(sessionKey);
114002
- await finalizeReply(streamingCard, feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
114003
- log("info", "\u6A21\u578B\u4E0D\u517C\u5BB9\u6062\u590D\u6210\u529F", {
114004
- sessionId: session.id,
114005
- sessionKey,
114006
- model: `${modelOverride.providerID}/${modelOverride.modelID}`,
114007
- attempt: attempts + 1
114008
- });
114009
- await runAutoPromptLoop(session.id);
114010
- return;
114011
- }
114012
- } catch (recoveryErr) {
114013
- if (recoveryErr instanceof Error && recoveryErr.name === "AbortError") {
114014
- log("info", "\u6A21\u578B\u6062\u590D\u88AB\u4E2D\u65AD", { sessionKey });
114015
- await abortCleanup(streamingCard, feishuClient, placeholderId);
114016
- return;
114017
- }
114018
- const errMsg = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
114019
- if (recoveryErr instanceof SessionErrorDetected) {
114020
- sessionError = recoveryErr.sessionError;
114021
- clearSessionError(session.id);
114022
- } else {
114023
- const sseError = getSessionError(session.id);
114024
- if (sseError) {
114025
- sessionError = sseError;
114026
- clearSessionError(session.id);
114027
- } else {
114028
- sessionError = { message: errMsg, fields: [] };
114029
- }
114030
- }
114031
- log("error", "\u6A21\u578B\u6062\u590D\u5931\u8D25", {
114032
- sessionId: session.id,
114033
- sessionKey,
114034
- error: errMsg
114035
- });
114036
- }
114037
- } else {
114038
- log("warn", "\u5DF2\u8FBE\u91CD\u8BD5\u4E0A\u9650\uFF0C\u653E\u5F03\u6062\u590D", {
114026
+ try {
114027
+ const recovery = await tryModelRecovery({
114028
+ sessionError,
114029
+ sessionId: session.id,
114039
114030
  sessionKey,
114040
- attempts
114031
+ client,
114032
+ directory,
114033
+ parts,
114034
+ timeout,
114035
+ pollInterval,
114036
+ stablePolls,
114037
+ query,
114038
+ signal,
114039
+ log,
114040
+ poll: pollForResponse
114041
114041
  });
114042
+ if (recovery.recovered) {
114043
+ await finalizeReply(streamingCard, feishuClient, chatId, placeholderId, recovery.text || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
114044
+ if (config2.autoPrompt.enabled && shouldReply) {
114045
+ return { sessionId: session.id, sessionKey, chatId, deps };
114046
+ }
114047
+ return void 0;
114048
+ }
114049
+ displayError = recovery.sessionError;
114050
+ } catch (abortErr) {
114051
+ if (abortErr instanceof Error && abortErr.name === "AbortError") {
114052
+ log("info", "\u6A21\u578B\u6062\u590D\u88AB\u4E2D\u65AD", { sessionKey });
114053
+ await abortCleanup(streamingCard, feishuClient, placeholderId);
114054
+ return void 0;
114055
+ }
114056
+ throw abortErr;
114042
114057
  }
114043
114058
  }
114044
114059
  const thrownError = err instanceof Error ? err.message : String(err);
114045
- const errorMessage = sessionError?.message || thrownError;
114060
+ const errorMessage = displayError?.message || thrownError;
114046
114061
  log("error", "\u5BF9\u8BDD\u5904\u7406\u5931\u8D25", {
114047
114062
  sessionId: session.id,
114048
114063
  sessionKey,
114049
114064
  chatType,
114050
114065
  error: thrownError,
114051
- ...sessionError ? { sessionError: sessionError.message } : {}
114066
+ ...displayError ? { sessionError: displayError.message } : {}
114052
114067
  });
114053
114068
  await finalizeReply(streamingCard, feishuClient, chatId, placeholderId, "\u274C " + errorMessage);
114054
114069
  } finally {
@@ -114058,17 +114073,6 @@ async function handleChat(ctx, deps, signal) {
114058
114073
  unregisterPending(activeSessionId);
114059
114074
  }
114060
114075
  }
114061
- async function getGlobalDefaultModel(client, directory) {
114062
- const query = directory ? { directory } : void 0;
114063
- const { data: config2 } = await client.config.get({ query });
114064
- const model = config2?.model;
114065
- if (!model || !model.includes("/")) return void 0;
114066
- const slash = model.indexOf("/");
114067
- const providerID = model.slice(0, slash).trim();
114068
- const modelID = model.slice(slash + 1).trim();
114069
- if (!providerID || !modelID) return void 0;
114070
- return { providerID, modelID };
114071
- }
114072
114076
  async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log) {
114073
114077
  if (messageType === "text") {
114074
114078
  let promptText = textContent;
@@ -114083,13 +114087,6 @@ async function buildPromptParts(feishuClient, messageId, messageType, rawContent
114083
114087
  }
114084
114088
  return parts;
114085
114089
  }
114086
- var SessionErrorDetected = class extends Error {
114087
- constructor(sessionError) {
114088
- super(sessionError.message);
114089
- this.sessionError = sessionError;
114090
- this.name = "SessionErrorDetected";
114091
- }
114092
- };
114093
114090
  async function pollForResponse(client, sessionId, opts) {
114094
114091
  const { timeout, pollInterval, stablePolls, query, signal } = opts;
114095
114092
  const start = Date.now();
@@ -114145,6 +114142,43 @@ async function replyOrUpdate(feishuClient, chatId, placeholderId, text) {
114145
114142
  await sendTextMessage(feishuClient, chatId, text);
114146
114143
  }
114147
114144
  }
114145
+ var idlePatterns = [
114146
+ /^(无|没有)(任务|变化|进行中)/,
114147
+ /空闲|闲置|等待(指令|中|新|你)/,
114148
+ /随时可(开始|开始新)/,
114149
+ /等你指令/
114150
+ ];
114151
+ function isIdleResponse(text, maxLength = 50) {
114152
+ if (text.length >= maxLength) return false;
114153
+ return idlePatterns.some((p) => p.test(text));
114154
+ }
114155
+ async function runOneAutoPromptIteration(apCtx, iteration, signal) {
114156
+ const { sessionId, chatId, deps } = apCtx;
114157
+ const { config: config2, client, feishuClient, log, directory } = deps;
114158
+ const query = directory ? { directory } : void 0;
114159
+ const { autoPrompt, timeout, pollInterval, stablePolls } = config2;
114160
+ log("info", "\u53D1\u9001\u81EA\u52A8\u63D0\u793A", { sessionKey: apCtx.sessionKey, iteration });
114161
+ clearSessionError(sessionId);
114162
+ await client.session.promptAsync({
114163
+ path: { id: sessionId },
114164
+ query,
114165
+ body: { parts: [{ type: "text", text: autoPrompt.message }] }
114166
+ });
114167
+ const text = await pollForResponse(client, sessionId, {
114168
+ timeout,
114169
+ pollInterval,
114170
+ stablePolls,
114171
+ query,
114172
+ signal
114173
+ });
114174
+ if (!text) return { text: null, isIdle: false };
114175
+ const idle = isIdleResponse(text, autoPrompt.idleMaxLength);
114176
+ if (!idle) {
114177
+ log("info", "\u81EA\u52A8\u63D0\u793A\u54CD\u5E94", { sessionKey: apCtx.sessionKey, iteration, output: text });
114178
+ await sendTextMessage(feishuClient, chatId, text);
114179
+ }
114180
+ return { text, isIdle: idle };
114181
+ }
114148
114182
  function abortableSleep(ms, signal) {
114149
114183
  return new Promise((resolve, reject) => {
114150
114184
  if (signal.aborted) {
@@ -114173,6 +114207,7 @@ function extractLastAssistantText(messages) {
114173
114207
  }
114174
114208
 
114175
114209
  // src/handler/session-queue.ts
114210
+ var QUEUE_MONITOR_INTERVAL_MS = 200;
114176
114211
  var states = /* @__PURE__ */ new Map();
114177
114212
  function getOrCreateState(sessionKey) {
114178
114213
  const existing = states.get(sessionKey);
@@ -114191,6 +114226,9 @@ function cleanupStateIfIdle(sessionKey, state) {
114191
114226
  states.delete(sessionKey);
114192
114227
  }
114193
114228
  }
114229
+ function sleep(ms) {
114230
+ return new Promise((resolve) => setTimeout(resolve, ms));
114231
+ }
114194
114232
  async function enqueueMessage(ctx, deps) {
114195
114233
  if (!ctx.shouldReply) {
114196
114234
  await handleChat(ctx, deps);
@@ -114221,7 +114259,18 @@ async function handleP2PMessage(sessionKey, ctx, deps) {
114221
114259
  const controller = new AbortController();
114222
114260
  state.controller = controller;
114223
114261
  state.processing = true;
114224
- const task = processMessage(ctx, deps, controller.signal).finally(() => {
114262
+ const task = (async () => {
114263
+ const apCtx = await processMessage(ctx, deps, controller.signal);
114264
+ if (apCtx) {
114265
+ await runP2PAutoPrompt(apCtx, controller.signal);
114266
+ }
114267
+ })().catch((err) => {
114268
+ if (err instanceof Error && err.name === "AbortError") return;
114269
+ deps.log("error", "P2P \u6D88\u606F\u6216\u81EA\u52A8\u63D0\u793A\u5904\u7406\u5931\u8D25", {
114270
+ sessionKey,
114271
+ error: err instanceof Error ? err.message : String(err)
114272
+ });
114273
+ }).finally(() => {
114225
114274
  state.processing = false;
114226
114275
  state.controller = null;
114227
114276
  state.currentTask = null;
@@ -114230,6 +114279,31 @@ async function handleP2PMessage(sessionKey, ctx, deps) {
114230
114279
  state.currentTask = task;
114231
114280
  await task;
114232
114281
  }
114282
+ async function runP2PAutoPrompt(apCtx, signal) {
114283
+ const { autoPrompt } = apCtx.deps.config;
114284
+ if (!autoPrompt.enabled) return;
114285
+ let idleCount = 0;
114286
+ for (let i = 0; i < autoPrompt.maxIterations; i++) {
114287
+ await abortableSleep(autoPrompt.intervalSeconds * 1e3, signal);
114288
+ const result = await runOneAutoPromptIteration(apCtx, i + 1, signal);
114289
+ if (result.isIdle) {
114290
+ idleCount++;
114291
+ if (idleCount >= autoPrompt.idleThreshold) {
114292
+ apCtx.deps.log("info", "P2P \u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u68C0\u6D4B\u5230\u7A7A\u95F2\uFF09", {
114293
+ sessionKey: apCtx.sessionKey,
114294
+ iteration: i + 1,
114295
+ idleCount
114296
+ });
114297
+ return;
114298
+ }
114299
+ } else {
114300
+ idleCount = 0;
114301
+ }
114302
+ }
114303
+ apCtx.deps.log("info", "P2P \u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", {
114304
+ sessionKey: apCtx.sessionKey
114305
+ });
114306
+ }
114233
114307
  async function handleGroupMessage(sessionKey, ctx, deps) {
114234
114308
  const state = getOrCreateState(sessionKey);
114235
114309
  state.queue.push({ ctx, deps });
@@ -114238,21 +114312,81 @@ async function handleGroupMessage(sessionKey, ctx, deps) {
114238
114312
  }
114239
114313
  async function drainLoop(sessionKey, state) {
114240
114314
  state.processing = true;
114315
+ let autoPromptCtx;
114316
+ let idleCount = 0;
114317
+ let autoPromptIteration = 0;
114241
114318
  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;
114319
+ while (true) {
114320
+ if (state.queue.length > 0) {
114321
+ const item = state.queue.shift();
114322
+ const controller = new AbortController();
114323
+ state.controller = controller;
114324
+ try {
114325
+ autoPromptCtx = await processMessage(item.ctx, item.deps, controller.signal);
114326
+ idleCount = 0;
114327
+ autoPromptIteration = 0;
114328
+ } catch (err) {
114329
+ item.deps.log("error", "\u7FA4\u804A\u961F\u5217\u6D88\u606F\u5904\u7406\u5931\u8D25", {
114330
+ sessionKey,
114331
+ error: err instanceof Error ? err.message : String(err)
114332
+ });
114333
+ } finally {
114334
+ state.controller = null;
114335
+ }
114336
+ continue;
114337
+ }
114338
+ if (!autoPromptCtx) break;
114339
+ const { autoPrompt } = autoPromptCtx.deps.config;
114340
+ if (!autoPrompt.enabled) break;
114341
+ if (autoPromptIteration >= autoPrompt.maxIterations) {
114342
+ autoPromptCtx.deps.log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", { sessionKey });
114343
+ break;
114344
+ }
114345
+ const intervalSeconds = autoPrompt.intervalSeconds;
114346
+ let interrupted = false;
114347
+ for (let s = 0; s < intervalSeconds; s++) {
114348
+ await sleep(1e3);
114349
+ if (state.queue.length > 0) {
114350
+ interrupted = true;
114351
+ break;
114352
+ }
114353
+ }
114354
+ if (interrupted) continue;
114355
+ const autoPromptController = new AbortController();
114356
+ const monitor = setInterval(() => {
114357
+ if (state.queue.length > 0) autoPromptController.abort();
114358
+ }, QUEUE_MONITOR_INTERVAL_MS);
114247
114359
  try {
114248
- await processMessage(item.ctx, item.deps, controller.signal);
114360
+ const result = await runOneAutoPromptIteration(
114361
+ autoPromptCtx,
114362
+ autoPromptIteration + 1,
114363
+ autoPromptController.signal
114364
+ );
114365
+ autoPromptIteration++;
114366
+ if (result.isIdle) {
114367
+ idleCount++;
114368
+ if (idleCount >= autoPrompt.idleThreshold) {
114369
+ autoPromptCtx.deps.log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u68C0\u6D4B\u5230\u7A7A\u95F2\uFF09", {
114370
+ sessionKey,
114371
+ iteration: autoPromptIteration,
114372
+ idleCount
114373
+ });
114374
+ break;
114375
+ }
114376
+ } else {
114377
+ idleCount = 0;
114378
+ }
114249
114379
  } catch (err) {
114250
- item.deps.log("error", "\u7FA4\u804A\u961F\u5217\u6D88\u606F\u5904\u7406\u5931\u8D25", {
114380
+ if (err instanceof Error && err.name === "AbortError") {
114381
+ continue;
114382
+ }
114383
+ autoPromptCtx.deps.log("error", "\u81EA\u52A8\u63D0\u793A\u8FED\u4EE3\u5F02\u5E38", {
114251
114384
  sessionKey,
114252
114385
  error: err instanceof Error ? err.message : String(err)
114253
114386
  });
114387
+ break;
114254
114388
  } finally {
114255
- state.controller = null;
114389
+ clearInterval(monitor);
114256
114390
  }
114257
114391
  }
114258
114392
  } finally {
@@ -114261,7 +114395,7 @@ async function drainLoop(sessionKey, state) {
114261
114395
  }
114262
114396
  }
114263
114397
  async function processMessage(ctx, deps, signal) {
114264
- await handleChat(ctx, deps, signal);
114398
+ return handleChat(ctx, deps, signal);
114265
114399
  }
114266
114400
  async function abortServerSession(sessionKey, deps) {
114267
114401
  const { client, log, directory } = deps;
@@ -114384,21 +114518,9 @@ var FeishuPlugin = async (ctx) => {
114384
114518
  });
114385
114519
  };
114386
114520
  const configPath = join(homedir(), ".config", "opencode", "plugins", "feishu.json");
114387
- if (!existsSync(configPath)) {
114388
- throw new Error(
114389
- `\u7F3A\u5C11\u98DE\u4E66\u914D\u7F6E\u6587\u4EF6\uFF1A\u8BF7\u521B\u5EFA ${configPath}\uFF0C\u5185\u5BB9\u4E3A {"appId":"cli_xxx","appSecret":"xxx"}`
114390
- );
114391
- }
114392
114521
  let resolvedConfig;
114393
114522
  try {
114394
- const raw = resolveEnvPlaceholders(
114395
- JSON.parse(readFileSync(configPath, "utf-8"))
114396
- );
114397
- const parsed = FeishuConfigSchema.parse(raw);
114398
- resolvedConfig = {
114399
- ...parsed,
114400
- directory: expandDirectoryPath(parsed.directory ?? ctx.directory ?? "")
114401
- };
114523
+ resolvedConfig = loadAndValidateConfig(configPath, ctx.directory ?? "");
114402
114524
  } catch (e) {
114403
114525
  if (e instanceof external_exports.ZodError) {
114404
114526
  const details = e.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
@@ -114477,6 +114599,14 @@ ${details}`);
114477
114599
  };
114478
114600
  return hooks;
114479
114601
  };
114602
+ function loadAndValidateConfig(configPath, ctxDirectory) {
114603
+ if (!existsSync(configPath)) {
114604
+ throw new Error(`\u7F3A\u5C11\u98DE\u4E66\u914D\u7F6E\u6587\u4EF6\uFF1A\u8BF7\u521B\u5EFA ${configPath}\uFF0C\u5185\u5BB9\u4E3A {"appId":"cli_xxx","appSecret":"xxx"}`);
114605
+ }
114606
+ const raw = resolveEnvPlaceholders(JSON.parse(readFileSync(configPath, "utf-8")));
114607
+ const parsed = FeishuConfigSchema.parse(raw);
114608
+ return { ...parsed, directory: expandDirectoryPath(parsed.directory ?? ctxDirectory ?? "") };
114609
+ }
114480
114610
  function expandDirectoryPath(dir) {
114481
114611
  if (!dir) return dir;
114482
114612
  if (dir.startsWith("~")) {