opencode-feishu 0.7.6 → 0.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -99176,130 +99176,11 @@ async function updateMessage(client, messageId, text2) {
99176
99176
  }
99177
99177
  }
99178
99178
 
99179
- // src/session.ts
99180
- var SESSION_KEY_PREFIX = "feishu";
99181
- var TITLE_PREFIX = "Feishu";
99182
- var sessionCache = /* @__PURE__ */ new Map();
99183
- var sessionIdToKeyCache = /* @__PURE__ */ new Map();
99184
- function setCachedSession(sessionKey, session) {
99185
- const oldSession = sessionCache.get(sessionKey);
99186
- if (oldSession && oldSession.id !== session.id) {
99187
- sessionIdToKeyCache.delete(oldSession.id);
99188
- }
99189
- sessionCache.set(sessionKey, session);
99190
- sessionIdToKeyCache.set(session.id, sessionKey);
99191
- }
99192
- function invalidateCachedSession(sessionId) {
99193
- const sessionKey = sessionIdToKeyCache.get(sessionId);
99194
- if (sessionKey) {
99195
- sessionCache.delete(sessionKey);
99196
- sessionIdToKeyCache.delete(sessionId);
99197
- }
99198
- return sessionKey;
99199
- }
99200
- function buildSessionKey(chatType, id) {
99201
- return `${SESSION_KEY_PREFIX}-${chatType}-${id}`;
99202
- }
99203
- function generateSessionTitle(sessionKey) {
99204
- return `${TITLE_PREFIX}-${sessionKey}-${Date.now()}`;
99205
- }
99206
- async function getOrCreateSession(client, sessionKey, directory) {
99207
- const cached = sessionCache.get(sessionKey);
99208
- if (cached) return cached;
99209
- const titlePrefix = `${TITLE_PREFIX}-${sessionKey}-`;
99210
- const query = directory ? { directory } : void 0;
99211
- const { data: sessions } = await client.session.list({ query });
99212
- if (Array.isArray(sessions)) {
99213
- const candidates = sessions.filter(
99214
- (s) => s.title && s.title.startsWith(titlePrefix)
99215
- );
99216
- if (candidates.length > 0) {
99217
- candidates.sort((a, b) => {
99218
- const ca = a.time?.created ?? 0;
99219
- const cb = b.time?.created ?? 0;
99220
- return cb - ca;
99221
- });
99222
- const best = candidates[0];
99223
- if (best?.id) {
99224
- const session2 = { id: best.id, title: best.title };
99225
- setCachedSession(sessionKey, session2);
99226
- return session2;
99227
- }
99228
- }
99229
- }
99230
- const title = generateSessionTitle(sessionKey);
99231
- const createResp = await client.session.create({ query, body: { title } });
99232
- if (!createResp?.data?.id) {
99233
- const err = createResp?.error;
99234
- throw new Error(
99235
- `\u521B\u5EFA OpenCode \u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99236
- );
99237
- }
99238
- const session = { id: createResp.data.id, title: createResp.data.title };
99239
- setCachedSession(sessionKey, session);
99240
- return session;
99241
- }
99242
- async function forkSession(client, oldSessionId, sessionKey, directory) {
99243
- const query = directory ? { directory } : void 0;
99244
- const resp = await client.session.fork({
99245
- path: { id: oldSessionId },
99246
- query,
99247
- body: {}
99248
- });
99249
- if (!resp?.data?.id) {
99250
- const err = resp?.error;
99251
- throw new Error(
99252
- `Fork \u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99253
- );
99254
- }
99255
- const title = generateSessionTitle(sessionKey);
99256
- const updateResp = await client.session.update({
99257
- path: { id: resp.data.id },
99258
- query,
99259
- body: { title }
99260
- });
99261
- if (!updateResp?.data?.id) {
99262
- const err = updateResp?.error;
99263
- throw new Error(
99264
- `\u66F4\u65B0 forked session \u6807\u9898\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99265
- );
99266
- }
99267
- return { id: resp.data.id, title };
99268
- }
99269
- async function createFreshSession(client, sessionKey, directory) {
99270
- const query = directory ? { directory } : void 0;
99271
- const title = generateSessionTitle(sessionKey);
99272
- const resp = await client.session.create({ query, body: { title } });
99273
- if (!resp?.data?.id) {
99274
- const err = resp?.error;
99275
- throw new Error(
99276
- `\u521B\u5EFA\u65B0\u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99277
- );
99278
- }
99279
- return { id: resp.data.id, title: resp.data.title };
99280
- }
99281
- async function forkOrCreateSession(client, oldSessionId, sessionKey, directory, log) {
99282
- try {
99283
- return await forkSession(client, oldSessionId, sessionKey, directory);
99284
- } catch (forkErr) {
99285
- log?.("warn", "Fork \u5931\u8D25\uFF0C\u56DE\u9000\u5230\u521B\u5EFA\u65B0\u4F1A\u8BDD", {
99286
- oldSessionId,
99287
- sessionKey,
99288
- error: forkErr instanceof Error ? forkErr.message : String(forkErr)
99289
- });
99290
- return await createFreshSession(client, sessionKey, directory);
99291
- }
99292
- }
99293
-
99294
99179
  // src/handler/event.ts
99295
- function maskKey(sessionKey) {
99296
- return sessionKey.replace(/-[^-]+$/, "-***");
99297
- }
99298
99180
  var pendingBySession = /* @__PURE__ */ new Map();
99299
99181
  var sessionErrors = /* @__PURE__ */ new Map();
99300
99182
  var sessionErrorTimeouts = /* @__PURE__ */ new Map();
99301
99183
  var SESSION_ERROR_TTL_MS = 3e4;
99302
- var modelOverrides = /* @__PURE__ */ new Map();
99303
99184
  var forkAttempts = /* @__PURE__ */ new Map();
99304
99185
  var forkAttemptTimeouts = /* @__PURE__ */ new Map();
99305
99186
  var MAX_FORK_ATTEMPTS = 2;
@@ -99312,6 +99193,9 @@ function clearForkAttempts(sessionKey) {
99312
99193
  forkAttemptTimeouts.delete(sessionKey);
99313
99194
  }
99314
99195
  }
99196
+ function getForkAttempts(sessionKey) {
99197
+ return forkAttempts.get(sessionKey) ?? 0;
99198
+ }
99315
99199
  function setForkAttempts(sessionKey, count) {
99316
99200
  forkAttempts.set(sessionKey, count);
99317
99201
  const existing = forkAttemptTimeouts.get(sessionKey);
@@ -99322,12 +99206,6 @@ function setForkAttempts(sessionKey, count) {
99322
99206
  }, FORK_ATTEMPTS_TTL_MS);
99323
99207
  forkAttemptTimeouts.set(sessionKey, timeoutId);
99324
99208
  }
99325
- function getModelOverride(sessionKey) {
99326
- return modelOverrides.get(sessionKey);
99327
- }
99328
- function clearModelOverride(sessionKey) {
99329
- modelOverrides.delete(sessionKey);
99330
- }
99331
99209
  function getSessionError(sessionId) {
99332
99210
  return sessionErrors.get(sessionId);
99333
99211
  }
@@ -99339,12 +99217,12 @@ function clearSessionError(sessionId) {
99339
99217
  }
99340
99218
  sessionErrors.delete(sessionId);
99341
99219
  }
99342
- function setSessionError(sessionId, errMsg) {
99220
+ function setSessionError(sessionId, message, fields) {
99343
99221
  const existing = sessionErrorTimeouts.get(sessionId);
99344
99222
  if (existing) {
99345
99223
  clearTimeout(existing);
99346
99224
  }
99347
- sessionErrors.set(sessionId, errMsg);
99225
+ sessionErrors.set(sessionId, { message, fields });
99348
99226
  const timeoutId = setTimeout(() => {
99349
99227
  sessionErrors.delete(sessionId);
99350
99228
  sessionErrorTimeouts.delete(sessionId);
@@ -99357,13 +99235,6 @@ function registerPending(sessionId, payload) {
99357
99235
  function unregisterPending(sessionId) {
99358
99236
  pendingBySession.delete(sessionId);
99359
99237
  }
99360
- function migratePending(oldSessionId, newSessionId) {
99361
- const payload = pendingBySession.get(oldSessionId);
99362
- if (payload) {
99363
- pendingBySession.delete(oldSessionId);
99364
- pendingBySession.set(newSessionId, payload);
99365
- }
99366
- }
99367
99238
  function extractErrorFields(error) {
99368
99239
  if (typeof error === "string") return [error];
99369
99240
  if (error && typeof error === "object") {
@@ -99377,14 +99248,12 @@ function extractErrorFields(error) {
99377
99248
  }
99378
99249
  return [String(error)];
99379
99250
  }
99380
- function isModelError(errMsg, rawError) {
99251
+ function isModelError(fields) {
99381
99252
  const check = (s) => {
99382
99253
  const l = s.toLowerCase();
99383
99254
  return l.includes("model not found") || l.includes("modelnotfound");
99384
99255
  };
99385
- if (check(errMsg)) return true;
99386
- if (rawError) return extractErrorFields(rawError).some(check);
99387
- return false;
99256
+ return fields.some(check);
99388
99257
  }
99389
99258
  async function handleEvent(event, deps) {
99390
99259
  switch (event.type) {
@@ -99426,77 +99295,13 @@ async function handleEvent(event, deps) {
99426
99295
  } else {
99427
99296
  errMsg = String(error);
99428
99297
  }
99298
+ const fields = extractErrorFields(error);
99429
99299
  deps.log("warn", "\u6536\u5230 session.error \u4E8B\u4EF6", { sessionId, errMsg });
99430
- setSessionError(sessionId, errMsg);
99431
- if (isModelError(errMsg, props.error)) {
99432
- const sessionKey = invalidateCachedSession(sessionId);
99433
- if (sessionKey) {
99434
- const attempts = forkAttempts.get(sessionKey) ?? 0;
99435
- if (attempts >= MAX_FORK_ATTEMPTS) {
99436
- deps.log("warn", "\u5DF2\u8FBE fork \u4E0A\u9650\uFF0C\u653E\u5F03\u6062\u590D", { sessionKey: maskKey(sessionKey), attempts });
99437
- } else {
99438
- setForkAttempts(sessionKey, attempts + 1);
99439
- try {
99440
- const newSession = await forkOrCreateSession(deps.client, sessionId, sessionKey, deps.directory, deps.log);
99441
- setCachedSession(sessionKey, newSession);
99442
- modelOverrides.delete(sessionKey);
99443
- try {
99444
- const fallbackModel = await resolveLatestModel(deps.client, props.error ?? errMsg, deps.directory);
99445
- if (fallbackModel) {
99446
- modelOverrides.set(sessionKey, fallbackModel);
99447
- deps.log("info", "\u5DF2\u89E3\u6790\u964D\u7EA7\u6A21\u578B", {
99448
- sessionKey: maskKey(sessionKey),
99449
- providerID: fallbackModel.providerID,
99450
- modelID: fallbackModel.modelID
99451
- });
99452
- }
99453
- } catch (modelErr) {
99454
- deps.log("warn", "\u89E3\u6790\u964D\u7EA7\u6A21\u578B\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u9ED8\u8BA4\u6A21\u578B", {
99455
- sessionKey: maskKey(sessionKey),
99456
- error: modelErr instanceof Error ? modelErr.message : String(modelErr)
99457
- });
99458
- }
99459
- deps.log("warn", "\u6A21\u578B\u4E0D\u517C\u5BB9\uFF0C\u5DF2\u6062\u590D\u4F1A\u8BDD", {
99460
- oldSessionId: sessionId,
99461
- newSessionId: newSession.id,
99462
- sessionKey: maskKey(sessionKey),
99463
- forkAttempt: attempts + 1
99464
- });
99465
- migratePending(sessionId, newSession.id);
99466
- } catch (recoverErr) {
99467
- deps.log("error", "\u4F1A\u8BDD\u6062\u590D\u5931\u8D25", {
99468
- sessionId,
99469
- sessionKey: maskKey(sessionKey),
99470
- error: recoverErr instanceof Error ? recoverErr.message : String(recoverErr)
99471
- });
99472
- }
99473
- }
99474
- }
99475
- }
99300
+ setSessionError(sessionId, errMsg, fields);
99476
99301
  break;
99477
99302
  }
99478
99303
  }
99479
99304
  }
99480
- async function resolveLatestModel(client, rawError, directory) {
99481
- const pattern = /model not found:?\s*(\w[\w-]*)\//i;
99482
- const fields = extractErrorFields(rawError);
99483
- const rawProviderID = fields.map((f) => pattern.exec(f)?.[1]).find(Boolean);
99484
- if (!rawProviderID) return void 0;
99485
- const providerID = rawProviderID.toLowerCase();
99486
- const query = directory ? { directory } : void 0;
99487
- const { data } = await client.provider.list({ query });
99488
- if (!data) return void 0;
99489
- const defaultModelID = data.default?.[providerID];
99490
- if (defaultModelID) {
99491
- return { providerID, modelID: defaultModelID };
99492
- }
99493
- const provider = data.all?.find((p) => p.id === providerID);
99494
- if (!provider?.models) return void 0;
99495
- const sortedModels = Object.values(provider.models).filter((m) => m.status !== "deprecated").sort((a, b) => b.release_date.localeCompare(a.release_date));
99496
- if (sortedModels.length === 0) return void 0;
99497
- const best = sortedModels.find((m) => m.tool_call) ?? sortedModels[0];
99498
- return { providerID, modelID: best.id };
99499
- }
99500
99305
  function extractPartText(part) {
99501
99306
  if (part.type === "text") return part.text ?? "";
99502
99307
  if (part.type === "reasoning" && part.text) return `\u{1F914} \u601D\u8003: ${part.text}
@@ -99505,6 +99310,121 @@ function extractPartText(part) {
99505
99310
  return "";
99506
99311
  }
99507
99312
 
99313
+ // src/session.ts
99314
+ var SESSION_KEY_PREFIX = "feishu";
99315
+ var TITLE_PREFIX = "Feishu";
99316
+ var sessionCache = /* @__PURE__ */ new Map();
99317
+ var sessionIdToKeyCache = /* @__PURE__ */ new Map();
99318
+ function setCachedSession(sessionKey, session) {
99319
+ const oldSession = sessionCache.get(sessionKey);
99320
+ if (oldSession && oldSession.id !== session.id) {
99321
+ sessionIdToKeyCache.delete(oldSession.id);
99322
+ }
99323
+ sessionCache.set(sessionKey, session);
99324
+ sessionIdToKeyCache.set(session.id, sessionKey);
99325
+ }
99326
+ function invalidateCachedSession(sessionId) {
99327
+ const sessionKey = sessionIdToKeyCache.get(sessionId);
99328
+ if (sessionKey) {
99329
+ sessionCache.delete(sessionKey);
99330
+ sessionIdToKeyCache.delete(sessionId);
99331
+ }
99332
+ return sessionKey;
99333
+ }
99334
+ function buildSessionKey(chatType, id) {
99335
+ return `${SESSION_KEY_PREFIX}-${chatType}-${id}`;
99336
+ }
99337
+ function generateSessionTitle(sessionKey) {
99338
+ return `${TITLE_PREFIX}-${sessionKey}-${Date.now()}`;
99339
+ }
99340
+ async function getOrCreateSession(client, sessionKey, directory) {
99341
+ const cached = sessionCache.get(sessionKey);
99342
+ if (cached) return cached;
99343
+ const titlePrefix = `${TITLE_PREFIX}-${sessionKey}-`;
99344
+ const query = directory ? { directory } : void 0;
99345
+ const { data: sessions } = await client.session.list({ query });
99346
+ if (Array.isArray(sessions)) {
99347
+ const candidates = sessions.filter(
99348
+ (s) => s.title && s.title.startsWith(titlePrefix)
99349
+ );
99350
+ if (candidates.length > 0) {
99351
+ candidates.sort((a, b) => {
99352
+ const ca = a.time?.created ?? 0;
99353
+ const cb = b.time?.created ?? 0;
99354
+ return cb - ca;
99355
+ });
99356
+ const best = candidates[0];
99357
+ if (best?.id) {
99358
+ const session2 = { id: best.id, title: best.title };
99359
+ setCachedSession(sessionKey, session2);
99360
+ return session2;
99361
+ }
99362
+ }
99363
+ }
99364
+ const title = generateSessionTitle(sessionKey);
99365
+ const createResp = await client.session.create({ query, body: { title } });
99366
+ if (!createResp?.data?.id) {
99367
+ const err = createResp?.error;
99368
+ throw new Error(
99369
+ `\u521B\u5EFA OpenCode \u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99370
+ );
99371
+ }
99372
+ const session = { id: createResp.data.id, title: createResp.data.title };
99373
+ setCachedSession(sessionKey, session);
99374
+ return session;
99375
+ }
99376
+ async function forkSession(client, oldSessionId, sessionKey, directory) {
99377
+ const query = directory ? { directory } : void 0;
99378
+ const resp = await client.session.fork({
99379
+ path: { id: oldSessionId },
99380
+ query,
99381
+ body: {}
99382
+ });
99383
+ if (!resp?.data?.id) {
99384
+ const err = resp?.error;
99385
+ throw new Error(
99386
+ `Fork \u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99387
+ );
99388
+ }
99389
+ const title = generateSessionTitle(sessionKey);
99390
+ const updateResp = await client.session.update({
99391
+ path: { id: resp.data.id },
99392
+ query,
99393
+ body: { title }
99394
+ });
99395
+ if (!updateResp?.data?.id) {
99396
+ const err = updateResp?.error;
99397
+ throw new Error(
99398
+ `\u66F4\u65B0 forked session \u6807\u9898\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99399
+ );
99400
+ }
99401
+ return { id: resp.data.id, title };
99402
+ }
99403
+ async function createFreshSession(client, sessionKey, directory) {
99404
+ const query = directory ? { directory } : void 0;
99405
+ const title = generateSessionTitle(sessionKey);
99406
+ const resp = await client.session.create({ query, body: { title } });
99407
+ if (!resp?.data?.id) {
99408
+ const err = resp?.error;
99409
+ throw new Error(
99410
+ `\u521B\u5EFA\u65B0\u4F1A\u8BDD\u5931\u8D25: ${err ? JSON.stringify(err) : "unknown"}`
99411
+ );
99412
+ }
99413
+ return { id: resp.data.id, title: resp.data.title };
99414
+ }
99415
+ async function forkOrCreateSession(client, oldSessionId, sessionKey, directory, log) {
99416
+ try {
99417
+ return await forkSession(client, oldSessionId, sessionKey, directory);
99418
+ } catch (forkErr) {
99419
+ log?.("warn", "Fork \u5931\u8D25\uFF0C\u56DE\u9000\u5230\u521B\u5EFA\u65B0\u4F1A\u8BDD", {
99420
+ oldSessionId,
99421
+ sessionKey,
99422
+ error: forkErr instanceof Error ? forkErr.message : String(forkErr)
99423
+ });
99424
+ return await createFreshSession(client, sessionKey, directory);
99425
+ }
99426
+ }
99427
+
99508
99428
  // src/handler/chat.ts
99509
99429
  var SSE_RACE_WAIT_MS = 100;
99510
99430
  var activeAutoPrompts = /* @__PURE__ */ new Map();
@@ -99530,10 +99450,10 @@ async function handleChat(ctx, deps) {
99530
99450
  senderId,
99531
99451
  messageType,
99532
99452
  shouldReply,
99453
+ content,
99533
99454
  parts
99534
99455
  });
99535
- const modelOverride = getModelOverride(sessionKey);
99536
- const baseBody = { parts, ...modelOverride ? { model: modelOverride } : {} };
99456
+ const baseBody = { parts };
99537
99457
  if (!shouldReply) {
99538
99458
  try {
99539
99459
  await client.session.prompt({
@@ -99554,6 +99474,7 @@ async function handleChat(ctx, deps) {
99554
99474
  const stablePolls = config.stablePolls;
99555
99475
  let placeholderId = "";
99556
99476
  let done = false;
99477
+ let activeSessionId = session.id;
99557
99478
  const timer = thinkingDelay > 0 ? setTimeout(async () => {
99558
99479
  if (done) return;
99559
99480
  try {
@@ -99561,7 +99482,7 @@ async function handleChat(ctx, deps) {
99561
99482
  if (done) return;
99562
99483
  if (res.ok && res.messageId) {
99563
99484
  placeholderId = res.messageId;
99564
- registerPending(session.id, { chatId, placeholderId, feishuClient });
99485
+ registerPending(activeSessionId, { chatId, placeholderId, feishuClient });
99565
99486
  }
99566
99487
  } catch (err) {
99567
99488
  log("warn", "\u53D1\u9001\u5360\u4F4D\u6D88\u606F\u5931\u8D25", {
@@ -99570,6 +99491,45 @@ async function handleChat(ctx, deps) {
99570
99491
  });
99571
99492
  }
99572
99493
  }, thinkingDelay) : null;
99494
+ async function runAutoPromptLoop(activeId) {
99495
+ const { autoPrompt } = config;
99496
+ if (!autoPrompt.enabled || !shouldReply) return;
99497
+ const ac = new AbortController();
99498
+ activeAutoPrompts.set(sessionKey, ac);
99499
+ log("info", "\u542F\u52A8\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF", { sessionKey, maxIterations: autoPrompt.maxIterations });
99500
+ try {
99501
+ for (let i = 0; i < autoPrompt.maxIterations; i++) {
99502
+ await abortableSleep(autoPrompt.intervalSeconds * 1e3, ac.signal);
99503
+ log("info", "\u53D1\u9001\u81EA\u52A8\u63D0\u793A", { sessionKey, iteration: i + 1 });
99504
+ await client.session.prompt({
99505
+ path: { id: activeId },
99506
+ query,
99507
+ body: { parts: [{ type: "text", text: autoPrompt.message }] }
99508
+ });
99509
+ const text2 = await pollForResponse(client, activeId, { timeout, pollInterval, stablePolls, query, signal: ac.signal });
99510
+ if (text2) {
99511
+ log("info", "\u81EA\u52A8\u63D0\u793A\u54CD\u5E94", {
99512
+ sessionKey,
99513
+ iteration: i + 1,
99514
+ output: text2
99515
+ });
99516
+ await sendTextMessage(feishuClient, chatId, text2);
99517
+ }
99518
+ }
99519
+ log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", { sessionKey });
99520
+ } catch (loopErr) {
99521
+ if (loopErr.name === "AbortError") {
99522
+ log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u88AB\u4E2D\u65AD", { sessionKey });
99523
+ } else {
99524
+ log("error", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u5F02\u5E38", {
99525
+ sessionKey,
99526
+ error: loopErr instanceof Error ? loopErr.message : String(loopErr)
99527
+ });
99528
+ }
99529
+ } finally {
99530
+ activeAutoPrompts.delete(sessionKey);
99531
+ }
99532
+ }
99573
99533
  try {
99574
99534
  await client.session.prompt({
99575
99535
  path: { id: session.id },
@@ -99577,62 +99537,129 @@ async function handleChat(ctx, deps) {
99577
99537
  body: baseBody
99578
99538
  });
99579
99539
  const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query });
99540
+ log("info", "\u6A21\u578B\u54CD\u5E94\u5B8C\u6210", {
99541
+ sessionKey,
99542
+ sessionId: session.id,
99543
+ output: finalText || "(empty)"
99544
+ });
99580
99545
  clearForkAttempts(sessionKey);
99581
- clearModelOverride(sessionKey);
99582
99546
  await replyOrUpdate(feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
99583
- const { autoPrompt } = config;
99584
- if (autoPrompt.enabled && shouldReply) {
99585
- const ac = new AbortController();
99586
- activeAutoPrompts.set(sessionKey, ac);
99587
- log("info", "\u542F\u52A8\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF", { sessionKey, maxIterations: autoPrompt.maxIterations });
99588
- try {
99589
- for (let i = 0; i < autoPrompt.maxIterations; i++) {
99590
- await abortableSleep(autoPrompt.intervalSeconds * 1e3, ac.signal);
99591
- log("info", "\u53D1\u9001\u81EA\u52A8\u63D0\u793A", { sessionKey, iteration: i + 1 });
99547
+ await runAutoPromptLoop(session.id);
99548
+ } catch (err) {
99549
+ await new Promise((r) => setTimeout(r, SSE_RACE_WAIT_MS));
99550
+ let sessionError = getSessionError(session.id);
99551
+ clearSessionError(session.id);
99552
+ if (!sessionError) {
99553
+ const thrownFields = extractErrorFields(err);
99554
+ if (isModelError(thrownFields)) {
99555
+ const thrownMsg = err instanceof Error ? err.message : String(err);
99556
+ sessionError = { message: thrownMsg, fields: thrownFields };
99557
+ }
99558
+ }
99559
+ if (sessionError && isModelError(sessionError.fields)) {
99560
+ const attempts = getForkAttempts(sessionKey);
99561
+ if (attempts < MAX_FORK_ATTEMPTS) {
99562
+ setForkAttempts(sessionKey, attempts + 1);
99563
+ try {
99564
+ invalidateCachedSession(session.id);
99565
+ const newSession = await forkOrCreateSession(client, session.id, sessionKey, directory, log);
99566
+ setCachedSession(sessionKey, newSession);
99567
+ activeSessionId = newSession.id;
99568
+ let modelOverride;
99569
+ try {
99570
+ modelOverride = await resolveLatestModel(client, sessionError.fields, directory);
99571
+ if (modelOverride) {
99572
+ log("info", "\u5DF2\u89E3\u6790\u964D\u7EA7\u6A21\u578B", {
99573
+ sessionKey,
99574
+ providerID: modelOverride.providerID,
99575
+ modelID: modelOverride.modelID
99576
+ });
99577
+ }
99578
+ } catch (modelErr) {
99579
+ log("warn", "\u89E3\u6790\u964D\u7EA7\u6A21\u578B\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u9ED8\u8BA4\u6A21\u578B", {
99580
+ sessionKey,
99581
+ error: modelErr instanceof Error ? modelErr.message : String(modelErr)
99582
+ });
99583
+ }
99584
+ unregisterPending(session.id);
99585
+ if (placeholderId) {
99586
+ registerPending(newSession.id, { chatId, placeholderId, feishuClient });
99587
+ }
99588
+ const retryBody = { ...baseBody, ...modelOverride ? { model: modelOverride } : {} };
99592
99589
  await client.session.prompt({
99593
- path: { id: session.id },
99590
+ path: { id: newSession.id },
99594
99591
  query,
99595
- body: { parts: [{ type: "text", text: autoPrompt.message }] }
99592
+ body: retryBody
99596
99593
  });
99597
- const text2 = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query, signal: ac.signal });
99598
- if (text2) {
99599
- await sendTextMessage(feishuClient, chatId, text2);
99600
- }
99601
- }
99602
- log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", { sessionKey });
99603
- } catch (err) {
99604
- if (err.name === "AbortError") {
99605
- log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u88AB\u4E2D\u65AD", { sessionKey });
99606
- } else {
99607
- log("error", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u5F02\u5E38", {
99594
+ const finalText = await pollForResponse(client, newSession.id, { timeout, pollInterval, stablePolls, query });
99595
+ log("info", "\u6062\u590D\u540E\u6A21\u578B\u54CD\u5E94\u5B8C\u6210", {
99596
+ sessionKey,
99597
+ newSessionId: newSession.id,
99598
+ output: finalText || "(empty)"
99599
+ });
99600
+ clearForkAttempts(sessionKey);
99601
+ await replyOrUpdate(feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
99602
+ log("info", "\u6A21\u578B\u4E0D\u517C\u5BB9\u6062\u590D\u6210\u529F", {
99603
+ oldSessionId: session.id,
99604
+ newSessionId: newSession.id,
99605
+ sessionKey,
99606
+ forkAttempt: attempts + 1
99607
+ });
99608
+ await runAutoPromptLoop(newSession.id);
99609
+ return;
99610
+ } catch (recoveryErr) {
99611
+ const recoveryErrMsg = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
99612
+ const newSessionError = activeSessionId !== session.id ? getSessionError(activeSessionId) : void 0;
99613
+ if (newSessionError) clearSessionError(activeSessionId);
99614
+ sessionError = newSessionError ?? { message: recoveryErrMsg, fields: [] };
99615
+ log("error", "\u6A21\u578B\u6062\u590D\u5931\u8D25", {
99616
+ sessionId: session.id,
99608
99617
  sessionKey,
99609
- error: err instanceof Error ? err.message : String(err)
99618
+ error: recoveryErrMsg
99610
99619
  });
99611
99620
  }
99612
- } finally {
99613
- activeAutoPrompts.delete(sessionKey);
99621
+ } else {
99622
+ log("warn", "\u5DF2\u8FBE fork \u4E0A\u9650\uFF0C\u653E\u5F03\u6062\u590D", {
99623
+ sessionKey,
99624
+ attempts
99625
+ });
99614
99626
  }
99615
99627
  }
99616
- } catch (err) {
99617
- await new Promise((r) => setTimeout(r, SSE_RACE_WAIT_MS));
99618
- const sessionError = getSessionError(session.id);
99619
- clearSessionError(session.id);
99620
99628
  const thrownError = err instanceof Error ? err.message : String(err);
99621
- const errorMessage = sessionError || thrownError;
99629
+ const errorMessage = sessionError?.message || thrownError;
99622
99630
  log("error", "\u5BF9\u8BDD\u5904\u7406\u5931\u8D25", {
99623
99631
  sessionId: session.id,
99624
- sessionKey: sessionKey.replace(/-[^-]+$/, "-***"),
99632
+ sessionKey,
99625
99633
  chatType,
99626
99634
  error: thrownError,
99627
- ...sessionError ? { sessionError } : { sseRaceMiss: true }
99635
+ ...sessionError ? { sessionError: sessionError.message } : { sseRaceMiss: true }
99628
99636
  });
99629
99637
  const msg = "\u274C " + errorMessage;
99630
99638
  await replyOrUpdate(feishuClient, chatId, placeholderId, msg);
99631
99639
  } finally {
99632
99640
  done = true;
99633
99641
  if (timer) clearTimeout(timer);
99634
- unregisterPending(session.id);
99642
+ unregisterPending(activeSessionId);
99643
+ }
99644
+ }
99645
+ async function resolveLatestModel(client, errorFields, directory) {
99646
+ const pattern = /model not found:?\s*(\w[\w-]*)\//i;
99647
+ const rawProviderID = errorFields.map((f) => pattern.exec(f)?.[1]).find(Boolean);
99648
+ if (!rawProviderID) return void 0;
99649
+ const providerID = rawProviderID.toLowerCase();
99650
+ const query = directory ? { directory } : void 0;
99651
+ const { data } = await client.provider.list({ query });
99652
+ if (!data) return void 0;
99653
+ const defaultModelID = data.default?.[providerID];
99654
+ if (defaultModelID) {
99655
+ return { providerID, modelID: defaultModelID };
99635
99656
  }
99657
+ const provider = data.all?.find((p) => p.id === providerID);
99658
+ if (!provider?.models) return void 0;
99659
+ const sortedModels = Object.values(provider.models).filter((m) => m.status !== "deprecated").sort((a, b) => b.release_date.localeCompare(a.release_date));
99660
+ if (sortedModels.length === 0) return void 0;
99661
+ const best = sortedModels.find((m) => m.tool_call) ?? sortedModels[0];
99662
+ return { providerID, modelID: best.id };
99636
99663
  }
99637
99664
  async function buildPromptParts(feishuClient, messageId, messageType, rawContent, textContent, chatType, senderId, log) {
99638
99665
  if (messageType === "text") {