@ycodium-ai/plugin-im 0.2.3216 → 0.2.3230

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
@@ -126258,67 +126258,96 @@ var keepNewest = (record4, limit) => {
126258
126258
  return entries.length <= limit ? record4 : Object.fromEntries(entries.slice(entries.length - limit));
126259
126259
  };
126260
126260
 
126261
- // src/boundedMap.ts
126262
- var makeBoundedMap = (limit) => {
126263
- const entries = /* @__PURE__ */ new Map();
126264
- return {
126265
- get: (key) => entries.get(key),
126266
- set: (key, value2) => {
126267
- entries.delete(key);
126268
- entries.set(key, value2);
126269
- if (entries.size > limit) {
126270
- const oldest = entries.keys().next().value;
126271
- if (oldest !== void 0) entries.delete(oldest);
126272
- }
126273
- },
126274
- /** 忘掉键满足条件的那些。 */
126275
- forget: (matches) => {
126276
- for (const key of [...entries.keys()]) if (matches(key)) entries.delete(key);
126261
+ // src/executionTurn.ts
126262
+ var indexExecution = (execution) => {
126263
+ const intentsByMessageId = /* @__PURE__ */ new Map();
126264
+ const intentsById = /* @__PURE__ */ new Map();
126265
+ const turnsById = /* @__PURE__ */ new Map();
126266
+ for (const intent of execution.turnIntents) {
126267
+ if (!intentsByMessageId.has(intent.messageId)) intentsByMessageId.set(intent.messageId, intent);
126268
+ if (!intentsById.has(intent.intentId)) intentsById.set(intent.intentId, intent);
126269
+ if (intent.turn?.turnId != null && !turnsById.has(intent.turn.turnId)) {
126270
+ turnsById.set(intent.turn.turnId, intent.turn);
126277
126271
  }
126278
- };
126272
+ }
126273
+ if (execution.latestTurn != null && !turnsById.has(execution.latestTurn.turnId)) {
126274
+ turnsById.set(execution.latestTurn.turnId, execution.latestTurn);
126275
+ }
126276
+ return { execution, intentsByMessageId, intentsById, turnsById };
126279
126277
  };
126278
+ var executionTurnFor = (snapshot, turnId) => snapshot.turnsById.get(turnId);
126280
126279
 
126281
- // src/replyAnchors.ts
126282
- var THREAD_LIMIT = 256;
126283
- var TURNS_PER_THREAD = 16;
126284
- var anchorOfMessage = (adapter, message) => adapter?.quotesReplies === true ? { messageId: message.messageId, inThread: message.replyInThread === true } : void 0;
126285
- var userMessageIdOfTurn = (execution, turnId) => {
126286
- const intent = execution.turnIntents.find((candidate) => candidate.turn?.turnId === turnId);
126287
- const latest = execution.latestTurn;
126288
- return intent?.messageId ?? (latest?.turnId === turnId ? latest.userMessageId : void 0);
126280
+ // src/intentStatus.ts
126281
+ var isLiveIntentStatus = (status) => status === "queued" || status === "preparing" || status === "dispatching" || status === "running" || status === "reconciling" || status === "blocked";
126282
+
126283
+ // src/conclusionTurn.ts
126284
+ var conclusionOf = (turn) => turn.state === "completed" || turn.state === "interrupted" || turn.state === "error" ? { kind: "settled", ...turn, state: turn.state } : { kind: "pending" };
126285
+ var conclusionTurnFor = (snapshot, userMessageId) => {
126286
+ const { execution } = snapshot;
126287
+ const initial = snapshot.intentsByMessageId.get(userMessageId);
126288
+ if (initial === void 0) {
126289
+ const latest = execution.latestTurn;
126290
+ return latest?.userMessageId === userMessageId ? conclusionOf(latest) : { kind: "gone" };
126291
+ }
126292
+ let intent = initial;
126293
+ const visited = /* @__PURE__ */ new Set();
126294
+ while (intent.status === "cancelled" || intent.status === "abandoned") {
126295
+ if (visited.has(intent.intentId)) return { kind: "gone" };
126296
+ visited.add(intent.intentId);
126297
+ const replacementId = intent.replacementIntentId;
126298
+ const replacement = replacementId == null ? void 0 : snapshot.intentsById.get(replacementId);
126299
+ if (replacement === void 0) return { kind: "gone" };
126300
+ intent = replacement;
126301
+ }
126302
+ const action = intent.action;
126303
+ const turn = intent.turn;
126304
+ if (action?.kind === "steer" && intent.deliveredTurnId === action.turnId) {
126305
+ return {
126306
+ kind: "merged",
126307
+ turnId: action.turnId,
126308
+ turn: executionTurnFor(snapshot, action.turnId)
126309
+ };
126310
+ }
126311
+ if (turn !== null) return conclusionOf(turn);
126312
+ if (action?.kind === "steer") return { kind: "pending" };
126313
+ return { kind: isLiveIntentStatus(intent.status) ? "pending" : "gone" };
126289
126314
  };
126290
- var makeReplyAnchors = (host) => {
126291
- const byThread = makeBoundedMap(THREAD_LIMIT);
126292
- const userMessageOfTurn = async (threadId, turnId) => {
126293
- const execution = await host.projections.getThreadExecution(threadId).catch(() => null);
126294
- if (execution === null) return void 0;
126295
- const wanted = turnId ?? execution.session?.activeTurnId ?? execution.latestTurn?.turnId;
126296
- return wanted == null ? void 0 : userMessageIdOfTurn(execution, wanted);
126297
- };
126315
+ var groupConclusionDebts = (snapshot, threadId, debts) => {
126316
+ const resolved = new Map(
126317
+ debts.filter((debt) => debt.threadId === threadId).map((debt) => [
126318
+ debt.userMessageId,
126319
+ { debt, result: conclusionTurnFor(snapshot, debt.userMessageId) }
126320
+ ])
126321
+ );
126322
+ const byTurn = /* @__PURE__ */ new Map();
126323
+ for (const { debt, result: result2 } of resolved.values()) {
126324
+ if (result2.kind !== "settled" && result2.kind !== "merged") continue;
126325
+ const group = byTurn.get(result2.turnId) ?? [];
126326
+ group.push(debt);
126327
+ byTurn.set(result2.turnId, group);
126328
+ }
126329
+ for (const [turnId, candidates] of byTurn) {
126330
+ const ownerId = turnId === null ? void 0 : executionTurnFor(snapshot, turnId)?.userMessageId;
126331
+ const owner = ownerId === void 0 ? void 0 : resolved.get(ownerId);
126332
+ if (owner !== void 0 && owner.result.kind !== "gone" && !candidates.includes(owner.debt))
126333
+ candidates.push(owner.debt);
126334
+ }
126335
+ return { resolved, byTurn };
126336
+ };
126337
+ var conclusionDebtForTurn = (snapshot, turnId, candidates, isDelivered) => {
126338
+ const turn = turnId === null ? void 0 : executionTurnFor(snapshot, turnId);
126339
+ const own = candidates.find((debt2) => debt2.userMessageId === turn?.userMessageId);
126340
+ const handled = turn?.userMessageId !== void 0 && isDelivered(turn.userMessageId) || candidates.length === 0 && snapshot.execution.turnIntents.some((intent) => {
126341
+ if (!isDelivered(intent.messageId)) return false;
126342
+ const result2 = conclusionTurnFor(snapshot, intent.messageId);
126343
+ return (result2.kind === "merged" || result2.kind === "settled") && result2.turnId === turnId;
126344
+ });
126345
+ const debt = handled ? void 0 : own ?? candidates[0];
126298
126346
  return {
126299
- /**
126300
- * 开轮时记一笔:`userMessageId` 是派给宿主的那条用户消息,`binding` 是收到聊天消息的那个
126301
- * 聊天(带着它的锚点)。没有锚点(平台不引用)就不记。
126302
- */
126303
- note: (threadId, userMessageId, binding) => {
126304
- if (binding.reply === void 0) return;
126305
- const turns = byThread.get(threadId) ?? makeBoundedMap(TURNS_PER_THREAD);
126306
- byThread.set(threadId, turns);
126307
- turns.set(userMessageId, { chatKey: chatKeyOf(binding), anchor: binding.reply });
126308
- },
126309
- /**
126310
- * 给发往这条会话的聊天挂上 `turnId` 那一轮的锚点(null 的含义见上);取不到就原样返回。
126311
- *
126312
- * 锚点只配给它那条消息所在的聊天:会话可以被另一个聊天接走(`/resume`),而回复接口只按被
126313
- * 回复的消息寻址,挂错了答复就发回原来的聊天。
126314
- */
126315
- anchored: async (binding, threadId, turnId) => {
126316
- const turns = byThread.get(threadId);
126317
- if (turns === void 0) return binding;
126318
- const asked = await userMessageOfTurn(threadId, turnId);
126319
- const noted = asked === void 0 ? void 0 : turns.get(asked);
126320
- return noted?.chatKey === chatKeyOf(binding) ? { ...binding, reply: noted.anchor } : binding;
126321
- }
126347
+ debt,
126348
+ handled,
126349
+ userMessageId: turn?.userMessageId,
126350
+ redundant: candidates.filter((candidate) => candidate !== debt)
126322
126351
  };
126323
126352
  };
126324
126353
 
@@ -126336,11 +126365,14 @@ var foldConclusionEvent = (state, name, data) => {
126336
126365
  const pending = conclusionDebtsOf(state);
126337
126366
  if (name === "conclusion-delivered") {
126338
126367
  const { [key]: _delivered, ...rest } = pending;
126368
+ const turnUserMessageId = data["turnUserMessageId"];
126369
+ const turnKey2 = typeof turnUserMessageId === "string" && turnUserMessageId !== "" ? keyOf(threadId, turnUserMessageId) : key;
126370
+ const { [turnKey2]: _turnDelivered, ...remaining } = rest;
126339
126371
  return {
126340
126372
  ...state,
126341
- conclusions: rest,
126373
+ conclusions: remaining,
126342
126374
  deliveredConclusions: keepNewest(
126343
- { ...state.deliveredConclusions, [key]: true },
126375
+ { ...state.deliveredConclusions, [key]: true, [turnKey2]: true },
126344
126376
  CONCLUSION_DEBT_LIMIT
126345
126377
  )
126346
126378
  };
@@ -126368,13 +126400,35 @@ var makeConclusionLedger = (core) => {
126368
126400
  core.host.onDispose(() => {
126369
126401
  disposed = true;
126370
126402
  });
126371
- const settle = (debt) => {
126403
+ const settle = ({ debt, turnUserMessageId }) => {
126372
126404
  core.commit("conclusion-delivered", {
126373
126405
  threadId: debt.threadId,
126374
- userMessageId: debt.userMessageId
126406
+ userMessageId: debt.userMessageId,
126407
+ ...turnUserMessageId !== void 0 && turnUserMessageId !== debt.userMessageId ? { turnUserMessageId } : {}
126375
126408
  });
126376
126409
  };
126410
+ const forExecutionTurn = (snapshot, threadId, turnId, grouped) => {
126411
+ if (disposed) return { selection: void 0, handled: true };
126412
+ const pending = conclusionDebtsOf(core.state);
126413
+ const candidates = (grouped ?? groupConclusionDebts(snapshot, threadId, Object.values(pending))).byTurn.get(turnId);
126414
+ const currentCandidates = candidates?.filter(
126415
+ (debt) => pending[keyOf(threadId, debt.userMessageId)] !== void 0
126416
+ );
126417
+ if (currentCandidates?.length === 0) return { selection: void 0, handled: true };
126418
+ const selected = conclusionDebtForTurn(
126419
+ snapshot,
126420
+ turnId,
126421
+ currentCandidates ?? [],
126422
+ (messageId) => delivered(keyOf(threadId, messageId))
126423
+ );
126424
+ for (const debt of selected.redundant) settle({ debt, turnUserMessageId: void 0 });
126425
+ return {
126426
+ selection: selected.debt === void 0 ? void 0 : { debt: selected.debt, turnUserMessageId: selected.userMessageId },
126427
+ handled: selected.handled || selected.debt !== void 0 && inFlight.has(keyOf(threadId, selected.debt.userMessageId))
126428
+ };
126429
+ };
126377
126430
  return {
126431
+ forExecutionTurn,
126378
126432
  starting: (threadId) => {
126379
126433
  const gate = Promise.withResolvers();
126380
126434
  const pending = registering.get(threadId) ?? /* @__PURE__ */ new Set();
@@ -126406,18 +126460,32 @@ var makeConclusionLedger = (core) => {
126406
126460
  unpersisted.delete(key);
126407
126461
  if (added) await recover?.(debt);
126408
126462
  },
126463
+ /** selection 为 null 表示执行视图不可读且本会话仍有欠账,须等恢复器判定后投递。 */
126409
126464
  forTurn: async (threadId, turnId = null) => {
126410
126465
  await Promise.all(registering.get(threadId) ?? []);
126411
- const execution = await core.host.projections.getThreadExecution(threadId);
126412
- const userMessageId = execution === null ? void 0 : turnId === null ? execution.latestTurn?.userMessageId : userMessageIdOfTurn(execution, turnId);
126413
- const key = userMessageId === void 0 ? void 0 : keyOf(threadId, userMessageId);
126466
+ const execution = await core.host.projections.getThreadExecution(threadId).catch((error) => {
126467
+ const owes = Object.values(conclusionDebtsOf(core.state)).some(
126468
+ (debt) => debt.threadId === threadId
126469
+ );
126470
+ if (!owes) return null;
126471
+ core.host.log.warn("chat deferred a conclusion until its execution can be read", {
126472
+ threadId,
126473
+ error: describeError(error)
126474
+ });
126475
+ return void 0;
126476
+ });
126477
+ if (execution === void 0) return { selection: null, handled: false, execution: null };
126414
126478
  return {
126415
- debt: key === void 0 ? void 0 : conclusionDebtsOf(core.state)[key],
126416
- handled: key !== void 0 && (delivered(key) || inFlight.has(key)),
126417
- execution
126479
+ execution,
126480
+ ...execution === null ? { selection: void 0, handled: false } : forExecutionTurn(
126481
+ indexExecution(execution),
126482
+ threadId,
126483
+ turnId ?? execution.latestTurn?.turnId ?? null
126484
+ )
126418
126485
  };
126419
126486
  },
126420
- deliver: async (debt, send) => {
126487
+ deliver: async (selection, send) => {
126488
+ const { debt } = selection;
126421
126489
  const key = keyOf(debt.threadId, debt.userMessageId);
126422
126490
  if (disposed || delivered(key) || inFlight.has(key) || conclusionDebtsOf(core.state)[key] === void 0)
126423
126491
  return false;
@@ -126426,16 +126494,17 @@ var makeConclusionLedger = (core) => {
126426
126494
  const sent = await send(
126427
126495
  () => !delivered(key) && conclusionDebtsOf(core.state)[key] !== void 0
126428
126496
  );
126429
- if (sent) settle(debt);
126497
+ if (sent) settle(selection);
126430
126498
  return sent;
126431
126499
  } finally {
126432
126500
  inFlight.delete(key);
126433
126501
  }
126434
126502
  },
126435
- discard: (debt) => {
126503
+ discard: (selection) => {
126504
+ const { debt } = selection;
126436
126505
  const key = keyOf(debt.threadId, debt.userMessageId);
126437
126506
  if (!disposed && !delivered(key) && !inFlight.has(key) && conclusionDebtsOf(core.state)[key] !== void 0) {
126438
- settle(debt);
126507
+ settle(selection);
126439
126508
  }
126440
126509
  }
126441
126510
  };
@@ -127450,6 +127519,26 @@ var makeChatQueue = (core) => {
127450
127519
  // src/chatSetup.ts
127451
127520
  import { threadOrNull } from "@ycodium-ai/plugin-api";
127452
127521
 
127522
+ // src/boundedMap.ts
127523
+ var makeBoundedMap = (limit) => {
127524
+ const entries = /* @__PURE__ */ new Map();
127525
+ return {
127526
+ get: (key) => entries.get(key),
127527
+ set: (key, value2) => {
127528
+ entries.delete(key);
127529
+ entries.set(key, value2);
127530
+ if (entries.size > limit) {
127531
+ const oldest = entries.keys().next().value;
127532
+ if (oldest !== void 0) entries.delete(oldest);
127533
+ }
127534
+ },
127535
+ /** 忘掉键满足条件的那些。 */
127536
+ forget: (matches) => {
127537
+ for (const key of [...entries.keys()]) if (matches(key)) entries.delete(key);
127538
+ }
127539
+ };
127540
+ };
127541
+
127453
127542
  // src/cooldown.ts
127454
127543
  var makeCooldown = (windowMs, now, maxKeys = Number.POSITIVE_INFINITY) => {
127455
127544
  const lastAt = /* @__PURE__ */ new Map();
@@ -133277,6 +133366,87 @@ var makeCommands = (core, deps) => {
133277
133366
  return { handleCommand, executeCommand, targetOf };
133278
133367
  };
133279
133368
 
133369
+ // src/connectionError.ts
133370
+ var NETWORK_HINTS = {
133371
+ ENOTFOUND: "networkDns",
133372
+ EAI_AGAIN: "networkDns",
133373
+ ECONNREFUSED: "networkRefused",
133374
+ ECONNRESET: "networkReset",
133375
+ EPIPE: "networkReset",
133376
+ UND_ERR_SOCKET: "networkReset",
133377
+ ENETUNREACH: "networkUnreachable",
133378
+ EHOSTUNREACH: "networkUnreachable",
133379
+ ERR_NETWORK_ACCESS_DENIED: "networkUnreachable",
133380
+ CERT_HAS_EXPIRED: "networkTls",
133381
+ CERT_NOT_YET_VALID: "networkTls",
133382
+ DEPTH_ZERO_SELF_SIGNED_CERT: "networkTls",
133383
+ SELF_SIGNED_CERT_IN_CHAIN: "networkTls",
133384
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: "networkTls",
133385
+ UNABLE_TO_GET_ISSUER_CERT_LOCALLY: "networkTls",
133386
+ ERR_TLS_CERT_ALTNAME_INVALID: "networkTls",
133387
+ ERR_SSL_WRONG_VERSION_NUMBER: "networkTls",
133388
+ ERR_TLS_HANDSHAKE_TIMEOUT: "networkTls",
133389
+ ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR: "networkTls",
133390
+ ETIMEDOUT: "networkTimeout",
133391
+ UND_ERR_CONNECT_TIMEOUT: "networkTimeout",
133392
+ UND_ERR_HEADERS_TIMEOUT: "networkTimeout",
133393
+ UND_ERR_BODY_TIMEOUT: "networkTimeout",
133394
+ timeout: "networkTimeout",
133395
+ ERR_PROXY_CONNECTION_FAILED: "networkProxy"
133396
+ };
133397
+ var NETWORK_REASONS = new Set(Object.keys(NETWORK_HINTS));
133398
+ var GENERIC_REASONS = /* @__PURE__ */ new Set(["timeout"]);
133399
+ var MAX_NODES = 16;
133400
+ var recordOf = (value2) => value2 !== null && typeof value2 === "object" ? value2 : void 0;
133401
+ var httpStatusOf = (value2) => typeof value2 === "number" && Number.isInteger(value2) && value2 >= 100 && value2 <= 599 ? value2 : void 0;
133402
+ var networkReason = (value2) => typeof value2 === "string" && NETWORK_REASONS.has(value2) ? value2 : void 0;
133403
+ var canonicalCode = (entry) => entry["name"] === "TimeoutError" ? "timeout" : entry["code"];
133404
+ var visitedErrors = (error) => {
133405
+ const queue = [error];
133406
+ const seen = /* @__PURE__ */ new Set();
133407
+ const chain = [];
133408
+ while (queue.length > 0 && chain.length < MAX_NODES) {
133409
+ const entry = recordOf(queue.shift());
133410
+ if (entry === void 0 || seen.has(entry)) continue;
133411
+ seen.add(entry);
133412
+ chain.push(entry);
133413
+ if (recordOf(entry["cause"]) !== void 0) queue.push(entry["cause"]);
133414
+ if (Array.isArray(entry["errors"])) queue.push(...entry["errors"].slice(0, MAX_NODES));
133415
+ }
133416
+ return chain;
133417
+ };
133418
+ var connectionFacts = (error) => {
133419
+ const nodes = visitedErrors(error).map((entry) => {
133420
+ const response = recordOf(entry["response"]);
133421
+ const fromCode = networkReason(canonicalCode(entry));
133422
+ return {
133423
+ reasons: fromCode === void 0 ? [] : [fromCode],
133424
+ httpStatus: httpStatusOf(entry["status"] ?? entry["statusCode"] ?? response?.["status"])
133425
+ };
133426
+ });
133427
+ const all2 = [...new Set(nodes.flatMap((node) => node.reasons))];
133428
+ const precise = all2.filter((reason) => !GENERIC_REASONS.has(reason));
133429
+ const httpStatus = nodes.find((node) => node.httpStatus !== void 0)?.httpStatus;
133430
+ return {
133431
+ reasons: precise.length > 0 ? precise : all2,
133432
+ ...httpStatus !== void 0 ? { httpStatus } : {}
133433
+ };
133434
+ };
133435
+ var connectionHint = (error) => {
133436
+ const { reasons, httpStatus } = connectionFacts(error);
133437
+ if (reasons.length > 1) return "networkMultipleCauses";
133438
+ const mapped = reasons[0] !== void 0 ? NETWORK_HINTS[reasons[0]] : void 0;
133439
+ if (mapped !== void 0) return mapped;
133440
+ if (httpStatus === 407) return "networkProxy";
133441
+ if (httpStatus === 429) return "platformRateLimited";
133442
+ if (httpStatus !== void 0 && httpStatus >= 500) return "platformUnavailable";
133443
+ return void 0;
133444
+ };
133445
+ var connectionHintFields = (error, platformHint) => {
133446
+ const hint = platformHint ?? connectionHint(error);
133447
+ return hint === void 0 ? {} : { hint };
133448
+ };
133449
+
133280
133450
  // src/inboundStore.ts
133281
133451
  import * as NodeCrypto from "node:crypto";
133282
133452
  import * as NodeFSP from "node:fs/promises";
@@ -133604,18 +133774,14 @@ var makeInboundStore = (input) => {
133604
133774
  write(rowId, row);
133605
133775
  return result2.promise;
133606
133776
  };
133607
- const claim = (rowId, id, message, receivedAt = 0) => {
133777
+ const claimReceipt = (rowId, id, message, receivedAt = 0) => {
133608
133778
  const row = rowOf(rowId);
133609
- if (!row.loaded) return load(rowId).then(() => claim(rowId, id, message, receivedAt));
133610
- try {
133611
- assertReadable(row);
133612
- } catch (error) {
133613
- return Promise.reject(error);
133614
- }
133779
+ assertReadable(row);
133615
133780
  const pending = row.claims.get(id);
133616
- if (pending !== void 0) return pending.then(() => false);
133781
+ if (pending !== void 0) return { first: false, persisted: pending };
133617
133782
  const seen = row.data.seenIds.includes(id);
133618
- if (seen && (row.claimRevisions.get(id) ?? 0) <= row.persisted) return Promise.resolve(false);
133783
+ if (seen && (row.claimRevisions.get(id) ?? 0) <= row.persisted)
133784
+ return { first: false, persisted: Promise.resolve() };
133619
133785
  if (!seen) {
133620
133786
  if (message !== void 0) {
133621
133787
  const inbox = row.data.inbox ?? [];
@@ -133659,7 +133825,17 @@ var makeInboundStore = (input) => {
133659
133825
  row.claims.delete(id);
133660
133826
  };
133661
133827
  void result2.then(forget, forget);
133662
- return result2.then(() => !seen);
133828
+ return { first: !seen, persisted: result2 };
133829
+ };
133830
+ const claim = (rowId, id, message, receivedAt = 0) => {
133831
+ const row = rowOf(rowId);
133832
+ if (!row.loaded) return load(rowId).then(() => claim(rowId, id, message, receivedAt));
133833
+ try {
133834
+ const receipt = claimReceipt(rowId, id, message, receivedAt);
133835
+ return receipt.persisted.then(() => receipt.first);
133836
+ } catch (error) {
133837
+ return Promise.reject(error);
133838
+ }
133663
133839
  };
133664
133840
  const removeEntry = (rowId, key, failure2) => {
133665
133841
  const row = rowOf(rowId);
@@ -133785,6 +133961,7 @@ var makeInboundStore = (input) => {
133785
133961
  return {
133786
133962
  load,
133787
133963
  claim,
133964
+ claimReceipt,
133788
133965
  get: (rowId, key) => {
133789
133966
  const row = rowOf(rowId);
133790
133967
  assertReadable(row);
@@ -133822,6 +133999,8 @@ var makeInboundStore = (input) => {
133822
133999
  assertReadable(row);
133823
134000
  if (row.data.inbox !== void 0)
133824
134001
  row.data.inbox = row.data.inbox.filter((e) => e.key !== key);
134002
+ row.data.seenIds = row.data.seenIds.filter((id) => id !== key);
134003
+ row.claimRevisions.delete(key);
133825
134004
  row.revision++;
133826
134005
  },
133827
134006
  reset,
@@ -133962,7 +134141,9 @@ var startConnections = (core, inboundHandlersFor, inboundState, recovery) => {
133962
134141
  ...inbound,
133963
134142
  // 停用先封接收,保持发送连接直到已接收工作 drain 完成。
133964
134143
  onMessage: (message) => {
133965
- if (!closed && !disposed) inbound.onMessage(message);
134144
+ if (closed || disposed)
134145
+ return Promise.reject(new Error("inbound connection is closed"));
134146
+ return inbound.onMessage(message);
133966
134147
  },
133967
134148
  onButton: (...args2) => closed || disposed ? null : inbound.onButton(...args2),
133968
134149
  onPanelClick: (click) => closed || disposed ? null : inbound.onPanelClick?.(click) ?? null,
@@ -133980,7 +134161,7 @@ var startConnections = (core, inboundHandlersFor, inboundState, recovery) => {
133980
134161
  state: "failed",
133981
134162
  ...status.reason === void 0 ? {} : { reason: status.reason },
133982
134163
  ...status.error === void 0 ? {} : { error: redactor.redact(describeError(status.error)) },
133983
- ...status.hint === void 0 ? {} : { hint: status.hint }
134164
+ ...connectionHintFields(status.error, status.hint)
133984
134165
  });
133985
134166
  }
133986
134167
  });
@@ -133993,7 +134174,7 @@ var startConnections = (core, inboundHandlersFor, inboundState, recovery) => {
133993
134174
  record4(bot.rowId, {
133994
134175
  state: "failed",
133995
134176
  error: detail,
133996
- ...error instanceof InboxUnreadableError ? { reason: "inbox-unreadable", hint: "inboxUnreadable" } : {}
134177
+ ...error instanceof InboxUnreadableError ? { reason: "inbox-unreadable", hint: "inboxUnreadable" } : connectionHintFields(error)
133997
134178
  });
133998
134179
  host.log.warn("chat could not start an inbound connection", {
133999
134180
  bot: bot.displayName,
@@ -135109,6 +135290,45 @@ var makeGreetings = (core, enqueueForChat, now) => {
135109
135290
  };
135110
135291
  };
135111
135292
 
135293
+ // src/replyAnchors.ts
135294
+ var THREAD_LIMIT = 256;
135295
+ var TURNS_PER_THREAD = 16;
135296
+ var anchorOfMessage = (adapter, message) => adapter?.quotesReplies === true ? { messageId: message.messageId, inThread: message.replyInThread === true } : void 0;
135297
+ var makeReplyAnchors = (host) => {
135298
+ const byThread = makeBoundedMap(THREAD_LIMIT);
135299
+ const userMessageOfTurn = async (threadId, turnId) => {
135300
+ const execution = await host.projections.getThreadExecution(threadId).catch(() => null);
135301
+ if (execution === null) return void 0;
135302
+ const wanted = turnId ?? execution.session?.activeTurnId ?? execution.latestTurn?.turnId;
135303
+ return wanted == null ? void 0 : executionTurnFor(indexExecution(execution), wanted)?.userMessageId;
135304
+ };
135305
+ return {
135306
+ /**
135307
+ * 开轮时记一笔:`userMessageId` 是派给宿主的那条用户消息,`binding` 是收到聊天消息的那个
135308
+ * 聊天(带着它的锚点)。没有锚点(平台不引用)就不记。
135309
+ */
135310
+ note: (threadId, userMessageId, binding) => {
135311
+ if (binding.reply === void 0) return;
135312
+ const turns = byThread.get(threadId) ?? makeBoundedMap(TURNS_PER_THREAD);
135313
+ byThread.set(threadId, turns);
135314
+ turns.set(userMessageId, { chatKey: chatKeyOf(binding), anchor: binding.reply });
135315
+ },
135316
+ /**
135317
+ * 给发往这条会话的聊天挂上 `turnId` 那一轮的锚点(null 的含义见上);取不到就原样返回。
135318
+ *
135319
+ * 锚点只配给它那条消息所在的聊天:会话可以被另一个聊天接走(`/resume`),而回复接口只按被
135320
+ * 回复的消息寻址,挂错了答复就发回原来的聊天。
135321
+ */
135322
+ anchored: async (binding, threadId, turnId) => {
135323
+ const turns = byThread.get(threadId);
135324
+ if (turns === void 0) return binding;
135325
+ const asked = await userMessageOfTurn(threadId, turnId);
135326
+ const noted = asked === void 0 ? void 0 : turns.get(asked);
135327
+ return noted?.chatKey === chatKeyOf(binding) ? { ...binding, reply: noted.anchor } : binding;
135328
+ }
135329
+ };
135330
+ };
135331
+
135112
135332
  // src/inbound.ts
135113
135333
  var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
135114
135334
  var UNSUPPORTED_REPLY_COOLDOWN_MS = 6e4;
@@ -135336,40 +135556,44 @@ var makeInbound = (core, deps) => {
135336
135556
  noteChatAlias: (alias, chatId) => noteChatAlias(bot, alias, chatId),
135337
135557
  knowsChatAlias: (alias) => chatAliasOf(core.state, bot.rowId, alias) !== void 0,
135338
135558
  onGreeting: (chatId, senderId) => greet(bot.rowId, chatId, senderId),
135339
- onMessage: (received) => {
135559
+ onMessage: async (received) => {
135340
135560
  const current = currentBot(bot.rowId);
135341
- if (!accepting || current === void 0) return;
135561
+ if (!accepting || current === void 0) throw new Error("inbound is not accepting messages");
135342
135562
  const message = settleChat(current, received);
135343
135563
  const work = workForMessage(message);
135344
- if (inboundState.hasSeen(current.rowId, work.key)) return;
135564
+ const adapter = adapterFor(current.platform);
135345
135565
  let saved;
135346
135566
  try {
135347
- saved = persistInbound(adapterFor(current.platform), message);
135567
+ saved = persistInbound(adapter, message);
135348
135568
  } catch (error) {
135349
135569
  host.log.warn("chat could not serialize inbound; processing without restart protection", {
135350
135570
  rowId: current.rowId,
135351
135571
  error: describeError(error)
135352
135572
  });
135353
135573
  }
135574
+ const claim = inboundState.claimReceipt(current.rowId, work.key, saved, now());
135575
+ if (!claim.first) return claim.persisted;
135576
+ const receipt = claim.persisted.catch((error) => {
135577
+ inboundState.abandon(current.rowId, work.key);
135578
+ host.log.warn(
135579
+ adapter?.redeliversUnacknowledged ? "chat could not save inbound state; waiting for platform redelivery" : "chat could not save inbound state; processing without restart protection",
135580
+ { rowId: current.rowId, error: describeError(error) }
135581
+ );
135582
+ throw error;
135583
+ });
135584
+ const enqueue = () => {
135585
+ const arrived = bindingOf2(current, message.chatId);
135586
+ if (message.chatKind !== void 0 && chatOf(core.state, chatKeyOf(arrived)).chatKind !== message.chatKind) {
135587
+ commit("chat-updated", { chatKey: chatKeyOf(arrived), chatKind: message.chatKind });
135588
+ }
135589
+ enqueueForChat(chatKeyOf(arrived), () => resumeMessage(current, message, work));
135590
+ };
135354
135591
  track(
135355
- inboundState.claim(current.rowId, work.key, saved, now()).then(
135356
- () => {
135357
- },
135358
- (error) => {
135359
- inboundState.abandon(current.rowId, work.key);
135360
- host.log.warn(
135361
- "chat could not save inbound state; processing without restart protection",
135362
- { rowId: current.rowId, error: describeError(error) }
135363
- );
135364
- }
135365
- ).then(() => {
135366
- const arrived = bindingOf2(current, message.chatId);
135367
- if (message.chatKind !== void 0 && chatOf(core.state, chatKeyOf(arrived)).chatKind !== message.chatKind) {
135368
- commit("chat-updated", { chatKey: chatKeyOf(arrived), chatKind: message.chatKind });
135369
- }
135370
- enqueueForChat(chatKeyOf(arrived), () => resumeMessage(current, message, work));
135592
+ receipt.then(enqueue, () => {
135593
+ if (!adapter?.redeliversUnacknowledged) enqueue();
135371
135594
  })
135372
135595
  );
135596
+ return receipt;
135373
135597
  }
135374
135598
  });
135375
135599
  const replay = makeInboxReplay(
@@ -135509,11 +135733,11 @@ var parseNumberedReply = (item, text4, t) => {
135509
135733
  // src/questionFlow.ts
135510
135734
  var makeQuestionFlow = (host) => {
135511
135735
  const { t } = host;
135512
- const recordOf = (requestId) => questionsOf(host.state())[requestId];
135513
- const itemOf = (requestId, questionId) => recordOf(requestId)?.items.find((item) => item.id === questionId);
135736
+ const recordOf2 = (requestId) => questionsOf(host.state())[requestId];
135737
+ const itemOf = (requestId, questionId) => recordOf2(requestId)?.items.find((item) => item.id === questionId);
135514
135738
  const hasButtons = (chatKey) => host.target(chatKey)?.adapter.choiceCards !== void 0;
135515
135739
  const redraw = async (requestId, questionId) => {
135516
- const record4 = recordOf(requestId);
135740
+ const record4 = recordOf2(requestId);
135517
135741
  const item = itemOf(requestId, questionId);
135518
135742
  const target2 = record4 === void 0 ? void 0 : host.target(record4.chatKey);
135519
135743
  if (record4 === void 0 || item?.cardId === void 0 || target2 === void 0) return;
@@ -135529,7 +135753,7 @@ var makeQuestionFlow = (host) => {
135529
135753
  }
135530
135754
  };
135531
135755
  const freeze = async (requestId, questionId) => {
135532
- const record4 = recordOf(requestId);
135756
+ const record4 = recordOf2(requestId);
135533
135757
  const item = itemOf(requestId, questionId);
135534
135758
  const target2 = record4 === void 0 ? void 0 : host.target(record4.chatKey);
135535
135759
  if (record4 === void 0 || item?.cardId === void 0 || target2 === void 0) return;
@@ -135550,7 +135774,7 @@ var makeQuestionFlow = (host) => {
135550
135774
  }
135551
135775
  };
135552
135776
  const sayNext = async (requestId) => {
135553
- const record4 = recordOf(requestId);
135777
+ const record4 = recordOf2(requestId);
135554
135778
  if (record4 === void 0 || record4.resolved) return;
135555
135779
  const item = record4.items.find(isUnanswered);
135556
135780
  if (item === void 0) return;
@@ -135560,7 +135784,7 @@ var makeQuestionFlow = (host) => {
135560
135784
  );
135561
135785
  };
135562
135786
  const post = async (requestId) => {
135563
- const first = recordOf(requestId);
135787
+ const first = recordOf2(requestId);
135564
135788
  const target2 = first === void 0 ? void 0 : host.target(first.chatKey);
135565
135789
  if (first === void 0 || target2 === void 0) return;
135566
135790
  const cards = target2.adapter.choiceCards;
@@ -135569,7 +135793,7 @@ var makeQuestionFlow = (host) => {
135569
135793
  return;
135570
135794
  }
135571
135795
  for (const questionId of first.items.map((item) => item.id)) {
135572
- const record4 = recordOf(requestId);
135796
+ const record4 = recordOf2(requestId);
135573
135797
  const item = itemOf(requestId, questionId);
135574
135798
  if (record4 === void 0 || record4.resolved || item === void 0) return;
135575
135799
  try {
@@ -135579,7 +135803,7 @@ var makeQuestionFlow = (host) => {
135579
135803
  );
135580
135804
  if (cardId === null) continue;
135581
135805
  host.commit("question-carded", { requestId, questionId, cardId });
135582
- const after = recordOf(requestId);
135806
+ const after = recordOf2(requestId);
135583
135807
  if (after?.resolved === true || itemOf(requestId, questionId)?.answer !== void 0) {
135584
135808
  await freeze(requestId, questionId);
135585
135809
  }
@@ -135596,7 +135820,7 @@ var makeQuestionFlow = (host) => {
135596
135820
  host.warn("chat cannot show questions on this platform");
135597
135821
  return;
135598
135822
  }
135599
- const fresh = pending.filter((request) => recordOf(request.requestId) === void 0);
135823
+ const fresh = pending.filter((request) => recordOf2(request.requestId) === void 0);
135600
135824
  for (const request of buttons ? fresh : fresh.slice(0, 1)) {
135601
135825
  host.commit("question-offered", {
135602
135826
  chatKey,
@@ -135619,18 +135843,18 @@ var makeQuestionFlow = (host) => {
135619
135843
  ...question.multiSelect ? { submitToken: host.newToken() } : {}
135620
135844
  }))
135621
135845
  });
135622
- if (recordOf(request.requestId) === void 0) continue;
135846
+ if (recordOf2(request.requestId) === void 0) continue;
135623
135847
  host.outbound(chatKey, () => post(request.requestId));
135624
135848
  }
135625
135849
  };
135626
135850
  const answer = async (requestId, questionId, value2, label) => {
135627
- const before = recordOf(requestId);
135851
+ const before = recordOf2(requestId);
135628
135852
  const item = itemOf(requestId, questionId);
135629
135853
  if (before === void 0 || before.resolved || item === void 0 || !isUnanswered(item)) {
135630
135854
  return false;
135631
135855
  }
135632
135856
  host.commit("question-answered", { requestId, questionId, answer: value2, label });
135633
- const after = recordOf(requestId);
135857
+ const after = recordOf2(requestId);
135634
135858
  if (after === void 0) return false;
135635
135859
  const complete = after.items.every((entry) => !isUnanswered(entry));
135636
135860
  if (complete) host.commit("question-resolved", { requestId, ending: "here" });
@@ -135709,7 +135933,7 @@ var makeQuestionFlow = (host) => {
135709
135933
  };
135710
135934
  const answerText = (input) => answer(input.requestId, input.questionId, input.value, input.label);
135711
135935
  const resolved = (requestId, outcome) => {
135712
- const record4 = recordOf(requestId);
135936
+ const record4 = recordOf2(requestId);
135713
135937
  if (record4 === void 0 || record4.resolved) return;
135714
135938
  const ending = outcome === "answered" ? "elsewhere" : "cancelled";
135715
135939
  host.commit("question-resolved", { requestId, ending });
@@ -135803,19 +136027,11 @@ var queueCardOf = (record4, view, note, t) => {
135803
136027
  var queueViewKey = (view, note) => JSON.stringify([view, note]);
135804
136028
 
135805
136029
  // src/queueFlow.ts
135806
- var LIVE_STATUSES = /* @__PURE__ */ new Set([
135807
- "queued",
135808
- "preparing",
135809
- "dispatching",
135810
- "running",
135811
- "reconciling",
135812
- "blocked"
135813
- ]);
135814
136030
  var makeQueueFlow = (host, deps) => {
135815
136031
  const { t } = host;
135816
136032
  const shown = /* @__PURE__ */ new Map();
135817
136033
  const notes = /* @__PURE__ */ new Map();
135818
- const recordOf = (intentId) => queueOf(host.state())[intentId];
136034
+ const recordOf2 = (intentId) => queueOf(host.state())[intentId];
135819
136035
  const factsOf = (execution, intentId) => ({
135820
136036
  activeTurnId: execution.activeTurnId,
135821
136037
  queuePaused: execution.queuePaused,
@@ -135834,7 +136050,7 @@ var makeQueueFlow = (host, deps) => {
135834
136050
  const intent = execution?.intents.find((entry) => entry.messageId === messageId);
135835
136051
  if (execution === null || intent === void 0 || intent.status !== "queued") return;
135836
136052
  const waiting = execution.activeTurnId !== null || execution.queuePaused || execution.intents.some(
135837
- (other) => other.intentId !== intent.intentId && LIVE_STATUSES.has(other.status) && other.sequence < intent.sequence
136053
+ (other) => other.intentId !== intent.intentId && isLiveIntentStatus(other.status) && other.sequence < intent.sequence
135838
136054
  );
135839
136055
  if (!waiting) return;
135840
136056
  const cards = target2.adapter.choiceCards;
@@ -135853,7 +136069,7 @@ var makeQueueFlow = (host, deps) => {
135853
136069
  resumeToken: host.newToken()
135854
136070
  });
135855
136071
  host.outbound(chatKey, async () => {
135856
- const record4 = recordOf(intentId);
136072
+ const record4 = recordOf2(intentId);
135857
136073
  if (record4 === void 0) return;
135858
136074
  try {
135859
136075
  const latest = await deps.readExecution(threadId) ?? execution;
@@ -135879,7 +136095,7 @@ var makeQueueFlow = (host, deps) => {
135879
136095
  });
135880
136096
  };
135881
136097
  const redraw = async (intentId, released) => {
135882
- const record4 = recordOf(intentId);
136098
+ const record4 = recordOf2(intentId);
135883
136099
  const target2 = record4 === void 0 ? void 0 : host.target(record4.chatKey);
135884
136100
  if (record4 === void 0 || record4.settled !== void 0 || target2 === void 0) return;
135885
136101
  if (record4.cardId === void 0) return;
@@ -138283,6 +138499,9 @@ var fetchUserName = async (input) => {
138283
138499
  };
138284
138500
  var sendChatText = async (input) => postMessage({ ...input, operation: "send", msgType: "text", content: { text: input.text } });
138285
138501
  var LARK_WORKING_REACTION = "OnIt";
138502
+ var LARK_DONE_REACTION = "DONE";
138503
+ var LARK_FAILED_REACTION = "ERROR";
138504
+ var LARK_REACTION_TIMEOUT_MS = 2e3;
138286
138505
  var addReaction = async (input) => {
138287
138506
  const response = await input.http(
138288
138507
  `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}/reactions`,
@@ -138292,7 +138511,8 @@ var addReaction = async (input) => {
138292
138511
  authorization: `Bearer ${input.token}`,
138293
138512
  "content-type": "application/json; charset=utf-8"
138294
138513
  },
138295
- body: JSON.stringify({ reaction_type: { emoji_type: input.emojiType } })
138514
+ body: JSON.stringify({ reaction_type: { emoji_type: input.emojiType } }),
138515
+ ...input.signal === void 0 ? {} : { signal: input.signal }
138296
138516
  }
138297
138517
  );
138298
138518
  const body = await readEnvelope("reactions.create", response);
@@ -138303,7 +138523,8 @@ var removeReaction = async (input) => {
138303
138523
  `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}/reactions/${encodeURIComponent(input.reactionId)}`,
138304
138524
  {
138305
138525
  method: "DELETE",
138306
- headers: { authorization: `Bearer ${input.token}` }
138526
+ headers: { authorization: `Bearer ${input.token}` },
138527
+ ...input.signal === void 0 ? {} : { signal: input.signal }
138307
138528
  }
138308
138529
  );
138309
138530
  await readEnvelope("reactions.delete", response);
@@ -144071,7 +144292,7 @@ var cardActionOperator = (event) => {
144071
144292
  };
144072
144293
  var startLarkInbound = (input) => {
144073
144294
  const dispatcher = new import_node_sdk.EventDispatcher({}).register({
144074
- "im.message.receive_v1": (event) => {
144295
+ "im.message.receive_v1": async (event) => {
144075
144296
  const mode = input.groupMention ?? "always";
144076
144297
  const parse = (groupMention) => parseInboundMessage(
144077
144298
  event,
@@ -144081,17 +144302,16 @@ var startLarkInbound = (input) => {
144081
144302
  );
144082
144303
  const parsed = parse(mode);
144083
144304
  if (parsed !== null) {
144084
- input.onMessage(parsed);
144305
+ await input.onMessage(parsed);
144085
144306
  return {};
144086
144307
  }
144087
144308
  const chat = event.message;
144088
144309
  if (mode === "always" && input.isSoloGroup !== void 0 && chat?.chat_type !== "p2p") {
144089
144310
  const chatId = chat?.chat_id;
144090
144311
  if (typeof chatId === "string") {
144091
- void input.isSoloGroup(chatId).then((solo) => {
144092
- const relaxed = solo ? parse("never") : null;
144093
- if (relaxed !== null) input.onMessage(relaxed);
144094
- });
144312
+ const solo = await input.isSoloGroup(chatId).catch(() => false);
144313
+ const relaxed = solo ? parse("never") : null;
144314
+ if (relaxed !== null) await input.onMessage(relaxed);
144095
144315
  }
144096
144316
  }
144097
144317
  return {};
@@ -144129,7 +144349,7 @@ var startLarkInbound = (input) => {
144129
144349
  state: "failed",
144130
144350
  reason: hint === "larkCredentials" ? "credentials-rejected" : "disconnected",
144131
144351
  error,
144132
- ...hint === void 0 ? {} : { hint }
144352
+ ...connectionHintFields(error, hint)
144133
144353
  });
144134
144354
  }
144135
144355
  };
@@ -144188,6 +144408,22 @@ var larkInboundPersistence = (contextFor) => ({
144188
144408
  }
144189
144409
  });
144190
144410
 
144411
+ // src/deadline.ts
144412
+ var withDeadline2 = async (timeoutMs, load) => {
144413
+ const signal = AbortSignal.timeout(timeoutMs);
144414
+ let onAbort = () => {
144415
+ };
144416
+ const aborted = new Promise((_, reject) => {
144417
+ onAbort = () => reject(signal.reason);
144418
+ signal.addEventListener("abort", onAbort, { once: true });
144419
+ });
144420
+ try {
144421
+ return await Promise.race([load(signal), aborted]);
144422
+ } finally {
144423
+ signal.removeEventListener("abort", onAbort);
144424
+ }
144425
+ };
144426
+
144191
144427
  // src/boundedSeen.ts
144192
144428
  var makeBoundedSeen = (limit) => {
144193
144429
  const seen = /* @__PURE__ */ new Set();
@@ -144206,22 +144442,6 @@ var makeBoundedSeen = (limit) => {
144206
144442
  };
144207
144443
  };
144208
144444
 
144209
- // src/deadline.ts
144210
- var withDeadline2 = async (timeoutMs, load) => {
144211
- const signal = AbortSignal.timeout(timeoutMs);
144212
- let onAbort = () => {
144213
- };
144214
- const aborted = new Promise((_, reject) => {
144215
- onAbort = () => reject(signal.reason);
144216
- signal.addEventListener("abort", onAbort, { once: true });
144217
- });
144218
- try {
144219
- return await Promise.race([load(signal), aborted]);
144220
- } finally {
144221
- signal.removeEventListener("abort", onAbort);
144222
- }
144223
- };
144224
-
144225
144445
  // src/replyReference.ts
144226
144446
  import * as NodeCrypto4 from "node:crypto";
144227
144447
 
@@ -144670,10 +144890,12 @@ var makeLarkAdapter = (deps) => {
144670
144890
  };
144671
144891
  const soloGroupFor = (context2) => async (chatId) => {
144672
144892
  const info = await context2.chatInfo(chatId);
144673
- return info?.userCount !== void 0 && info.botCount !== void 0 && info.userCount <= 1 && info.botCount <= 1;
144893
+ if (info?.userCount === void 0 || info.botCount === void 0) return false;
144894
+ return info.userCount <= 1 && info.botCount <= 1;
144674
144895
  };
144675
144896
  return {
144676
144897
  platform: "lark",
144898
+ redeliversUnacknowledged: true,
144677
144899
  ...larkInboundPersistence((rowId) => messageContexts.get(rowId)?.context),
144678
144900
  botIdentity: (bot) => larkCredentials(bot).appId,
144679
144901
  icon: "lark",
@@ -144781,15 +145003,27 @@ var makeLarkAdapter = (deps) => {
144781
145003
  const key = JSON.stringify([message.chatId, topic?.threadId]);
144782
145004
  const earlier = pendingDeliveries.get(key);
144783
145005
  if (earlier === void 0 && pending === null && reference2 === null && !(prepared instanceof Promise) && !(source instanceof Promise)) {
144784
- deliver(prepared, source);
145006
+ return deliver(prepared, source);
144785
145007
  } else {
144786
- const completed = Promise.all([earlier, pending, reference2, source, prepared]).then(([, , replyTo, names, ready]) => {
145008
+ const completed = Promise.all([
145009
+ earlier?.catch(() => {
145010
+ }),
145011
+ pending,
145012
+ reference2,
145013
+ source,
145014
+ prepared
145015
+ ]).then(([, , replyTo, names, ready]) => {
144787
145016
  if (pendingDeliveries.get(key) === completed) pendingDeliveries.delete(key);
144788
- if (open) deliver(ready, names, replyTo ?? void 0);
144789
- }).catch((error) => handlers.onError(describeError(error))).finally(() => {
145017
+ if (!open) throw new Error("lark inbound connection is closed");
145018
+ return deliver(ready, names, replyTo ?? void 0);
145019
+ }).catch((error) => {
145020
+ handlers.onError(describeError(error));
145021
+ throw error;
145022
+ }).finally(() => {
144790
145023
  if (pendingDeliveries.get(key) === completed) pendingDeliveries.delete(key);
144791
145024
  });
144792
145025
  pendingDeliveries.set(key, completed);
145026
+ return completed;
144793
145027
  }
144794
145028
  }
144795
145029
  });
@@ -144841,12 +145075,11 @@ var makeLarkAdapter = (deps) => {
144841
145075
  detail: `token ok; expiresIn=${String(expiresInSeconds)}s`
144842
145076
  };
144843
145077
  } catch (error) {
144844
- const hint = larkConnectionHint(error);
144845
145078
  return {
144846
145079
  stage: "tenant_access_token",
144847
145080
  ok: false,
144848
145081
  detail: describeError(error),
144849
- ...hint === void 0 ? {} : { hint }
145082
+ ...connectionHintFields(error, larkConnectionHint(error))
144850
145083
  };
144851
145084
  }
144852
145085
  },
@@ -144951,13 +145184,22 @@ var makeLarkAdapter = (deps) => {
144951
145184
  messageId,
144952
145185
  emojiType: LARK_WORKING_REACTION
144953
145186
  }),
144954
- clearAcknowledge: async (binding, messageId, ackId) => {
144955
- await removeReaction({
144956
- http: deps.http,
144957
- token: await tenantToken(binding),
144958
- messageId,
144959
- reactionId: ackId
144960
- });
145187
+ clearAcknowledge: async (binding, messageId, ackId, outcome) => {
145188
+ const token = await tenantToken(binding);
145189
+ await withDeadline2(
145190
+ LARK_REACTION_TIMEOUT_MS,
145191
+ (signal) => removeReaction({ http: deps.http, token, messageId, reactionId: ackId, signal })
145192
+ );
145193
+ const emojiType = outcome === "done" ? LARK_DONE_REACTION : outcome === "failed" ? LARK_FAILED_REACTION : null;
145194
+ if (emojiType === null) return;
145195
+ try {
145196
+ await withDeadline2(
145197
+ LARK_REACTION_TIMEOUT_MS,
145198
+ (signal) => addReaction({ http: deps.http, token, messageId, emojiType, signal })
145199
+ );
145200
+ } catch (error) {
145201
+ deps.log.warn("lark could not react to the message", { error: describeError(error) });
145202
+ }
144961
145203
  },
144962
145204
  fetchResource: async (binding, input) => {
144963
145205
  const resource = input.resource;
@@ -145194,6 +145436,13 @@ var en_default = {
145194
145436
  turnFailedNoDetail: "This turn didn't finish. Reference {{ref}}. Send the message again; if it keeps failing, send /new to start a new conversation.",
145195
145437
  turnFailedBrief: "This turn didn't finish: {{detail}}\nSend the message again; if it keeps failing, send /new to start a new conversation.",
145196
145438
  turnFailedBriefNoDetail: "This turn didn't finish. Send the message again; if it keeps failing, send /new to start a new conversation.",
145439
+ turnFailedWithReference: "{{message}}\nReference {{ref}}.",
145440
+ turnFailedQuota: "The model quota or balance is insufficient, so this turn didn't finish. Send /model to switch to another model, or ask an administrator to add quota, then try again.",
145441
+ turnFailedRateLimit: "The model service is rate-limiting requests, so this turn didn't finish. Please try again later.",
145442
+ turnFailedContextLimit: "This conversation exceeds the model context limit, so this turn didn't finish. Send /new to start a new conversation, then send your request again.",
145443
+ turnFailedAuth: "The model credentials are missing or invalid, so this turn didn't finish. Ask an administrator to check the model settings, then try again.",
145444
+ turnFailedModelUnavailable: "The current model does not exist or does not support the selected settings, so this turn didn't finish. Send /model to list models, then send /model followed by a number (for example /model 2) to pick one for this chat, and send your request again.",
145445
+ turnFailedTransport: "The model service is temporarily unreachable, so this turn didn't finish. Please try again later.",
145197
145446
  attachmentReceived: "Got {{names}} ({{count}} waiting). Tell me what to do with them.",
145198
145447
  attachmentFailed: "Could not take {{name}}: {{reason}}.",
145199
145448
  attachmentReason: {
@@ -145545,6 +145794,16 @@ var en_default = {
145545
145794
  failed: "Connection failed",
145546
145795
  notSaved: "Save this row first (credentials are read from saved config only).",
145547
145796
  hint: {
145797
+ networkMultipleCauses: "This failure has more than one network cause. Check this machine's network and this bot's Proxy setting, then check again.",
145798
+ networkDns: "The service's domain name could not be resolved. Check this machine's DNS and network settings, then check again.",
145799
+ networkRefused: "The connection was refused. Check whether a firewall or security software on this machine or its network is blocking it, then check again.",
145800
+ networkReset: "The connection was cut off by the other side. Check this machine's network and whether the target service is running normally, then check again.",
145801
+ networkUnreachable: "The target network is unreachable. Check this machine's outbound network access and access restrictions, then check again.",
145802
+ networkTls: "TLS or certificate validation failed. Check this machine's system clock, certificate chain and network settings, and whether a proxy or security software is intercepting HTTPS, then check again.",
145803
+ networkTimeout: "The request timed out. Check whether the target service is running normally and check this machine's network, then check again.",
145804
+ networkProxy: "Proxy connection or authentication failed. Check this row's Proxy setting and the selected proxy's configuration on this machine; if no proxy is set, check other proxies on this machine or its network, then check again.",
145805
+ platformRateLimited: "The platform is rate limiting requests. Try again later.",
145806
+ platformUnavailable: "The target service is temporarily having problems. Try again later.",
145548
145807
  connecting: "Still connecting. There is usually a result within 20 seconds; check again in a moment.",
145549
145808
  badToken: "This does not look like a bot token. Copy it again from BotFather: digits, a colon, then the key.",
145550
145809
  unauthorized: "Telegram does not accept this token. It may have been revoked; get the current one from BotFather. Save the corrected token here to reconnect automatically; if nothing needs changing, use Reconnect below.",
@@ -145964,6 +146223,13 @@ var zh_CN_default = {
145964
146223
  turnFailedNoDetail: "\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\u3002\u53C2\u8003\u53F7 {{ref}}\u3002\u53EF\u4EE5\u76F4\u63A5\u518D\u53D1\u4E00\u6B21\uFF1B\u4E00\u76F4\u4E0D\u884C\u5C31\u53D1 /new \u5F00\u4E2A\u65B0\u4F1A\u8BDD\u3002",
145965
146224
  turnFailedBrief: "\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\uFF1A{{detail}}\n\u53EF\u4EE5\u76F4\u63A5\u518D\u53D1\u4E00\u6B21\uFF1B\u4E00\u76F4\u4E0D\u884C\u5C31\u53D1 /new \u5F00\u4E2A\u65B0\u4F1A\u8BDD\u3002",
145966
146225
  turnFailedBriefNoDetail: "\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\u3002\u53EF\u4EE5\u76F4\u63A5\u518D\u53D1\u4E00\u6B21\uFF1B\u4E00\u76F4\u4E0D\u884C\u5C31\u53D1 /new \u5F00\u4E2A\u65B0\u4F1A\u8BDD\u3002",
146226
+ turnFailedWithReference: "{{message}}\n\u53C2\u8003\u53F7 {{ref}}\u3002",
146227
+ turnFailedQuota: "\u6A21\u578B\u989D\u5EA6\u6216\u4F59\u989D\u4E0D\u8DB3\uFF0C\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\u3002\u53EF\u4EE5\u53D1 /model \u6362\u4E2A\u6A21\u578B\u518D\u8BD5\uFF0C\u6216\u8BF7\u7BA1\u7406\u5458\u8865\u5145\u989D\u5EA6\u540E\u91CD\u8BD5\u3002",
146228
+ turnFailedRateLimit: "\u6A21\u578B\u670D\u52A1\u6B63\u5728\u9650\u6D41\uFF0C\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
146229
+ turnFailedContextLimit: "\u5F53\u524D\u4F1A\u8BDD\u5185\u5BB9\u8D85\u8FC7\u6A21\u578B\u4E0A\u4E0B\u6587\u4E0A\u9650\uFF0C\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\u3002\u8BF7\u53D1 /new \u5F00\u4E2A\u65B0\u4F1A\u8BDD\uFF0C\u518D\u628A\u8BF7\u6C42\u91CD\u65B0\u53D1\u4E00\u904D\u3002",
146230
+ turnFailedAuth: "\u6A21\u578B\u51ED\u636E\u7F3A\u5931\u6216\u65E0\u6548\uFF0C\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\u3002\u8BF7\u7BA1\u7406\u5458\u68C0\u67E5\u6A21\u578B\u914D\u7F6E\uFF0C\u4E4B\u540E\u4F60\u518D\u53D1\u4E00\u6B21\u3002",
146231
+ turnFailedModelUnavailable: "\u5F53\u524D\u6A21\u578B\u4E0D\u5B58\u5728\u6216\u4E0D\u652F\u6301\u6240\u9009\u914D\u7F6E\uFF0C\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\u3002\u8BF7\u53D1 /model \u67E5\u770B\u53EF\u9009\u6A21\u578B\uFF0C\u518D\u53D1 /model \u52A0\u7F16\u53F7\uFF08\u4F8B\u5982 /model 2\uFF09\u4E3A\u8FD9\u4E2A\u804A\u5929\u9009\u4E00\u4E2A\uFF0C\u7136\u540E\u91CD\u53D1\u8BF7\u6C42\u3002",
146232
+ turnFailedTransport: "\u6682\u65F6\u65E0\u6CD5\u8FDE\u63A5\u6A21\u578B\u670D\u52A1\uFF0C\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
145967
146233
  attachmentReceived: "\u6536\u5230 {{names}}\uFF08\u73B0\u6709 {{count}} \u4E2A\u5F85\u5904\u7406\uFF09\u3002\u8BF4\u4E00\u53E5\u60F3\u8BA9\u6211\u600E\u4E48\u5904\u7406\u3002",
145968
146234
  attachmentFailed: "\u6CA1\u80FD\u6536\u4E0B {{name}}\uFF1A{{reason}}\u3002",
145969
146235
  attachmentReason: {
@@ -146315,6 +146581,16 @@ var zh_CN_default = {
146315
146581
  failed: "\u8FDE\u63A5\u5931\u8D25",
146316
146582
  notSaved: "\u8BF7\u5148\u4FDD\u5B58\u8FD9\u4E00\u884C\u518D\u68C0\u67E5\u8FDE\u63A5\uFF08\u51ED\u636E\u53EA\u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u8BFB\u53D6\uFF09\u3002",
146317
146583
  hint: {
146584
+ networkMultipleCauses: "\u8FD9\u6B21\u5931\u8D25\u80CC\u540E\u4E0D\u6B62\u4E00\u79CD\u7F51\u7EDC\u95EE\u9898\u3002\u8BF7\u68C0\u67E5\u8FD9\u53F0\u673A\u5668\u7684\u7F51\u7EDC\u548C\u8FD9\u4E2A\u673A\u5668\u4EBA\u7684\u300C\u4EE3\u7406\u300D\u8BBE\u7F6E\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
146585
+ networkDns: "\u670D\u52A1\u57DF\u540D\u89E3\u6790\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u8FD9\u53F0\u673A\u5668\u7684 DNS \u548C\u7F51\u7EDC\u8BBE\u7F6E\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
146586
+ networkRefused: "\u76EE\u6807\u62D2\u7EDD\u4E86\u8FDE\u63A5\u3002\u8BF7\u68C0\u67E5\u8FD9\u53F0\u673A\u5668\u548C\u6240\u5728\u7F51\u7EDC\u7684\u9632\u706B\u5899\u3001\u5B89\u5168\u8F6F\u4EF6\u662F\u5426\u62E6\u4E86\u8FD9\u4E2A\u8FDE\u63A5\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
146587
+ networkReset: "\u8FDE\u63A5\u88AB\u5BF9\u65B9\u4E2D\u65AD\u3002\u8BF7\u68C0\u67E5\u8FD9\u53F0\u673A\u5668\u7684\u7F51\u7EDC\uFF0C\u4EE5\u53CA\u76EE\u6807\u670D\u52A1\u662F\u5426\u5728\u6B63\u5E38\u8FD0\u884C\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
146588
+ networkUnreachable: "\u76EE\u6807\u7F51\u7EDC\u4E0D\u53EF\u8FBE\u3002\u8BF7\u68C0\u67E5\u8FD9\u53F0\u673A\u5668\u7684\u7F51\u7EDC\u51FA\u53E3\u548C\u8BBF\u95EE\u9650\u5236\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
146589
+ networkTls: "TLS \u6216\u8BC1\u4E66\u6821\u9A8C\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u8FD9\u53F0\u673A\u5668\u7684\u7CFB\u7EDF\u65F6\u95F4\u3001\u8BC1\u4E66\u94FE\u548C\u7F51\u7EDC\u8BBE\u7F6E\uFF0C\u4EE5\u53CA\u4EE3\u7406\u6216\u5B89\u5168\u8F6F\u4EF6\u662F\u5426\u62E6\u622A\u4E86 HTTPS \u8FDE\u63A5\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
146590
+ networkTimeout: "\u8BF7\u6C42\u8D85\u65F6\u3002\u8BF7\u786E\u8BA4\u76EE\u6807\u670D\u52A1\u662F\u5426\u5728\u6B63\u5E38\u8FD0\u884C\uFF0C\u5E76\u68C0\u67E5\u8FD9\u53F0\u673A\u5668\u7684\u7F51\u7EDC\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
146591
+ networkProxy: "\u4EE3\u7406\u8FDE\u63A5\u6216\u8BA4\u8BC1\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u8FD9\u4E00\u884C\u7684\u300C\u4EE3\u7406\u300D\u8BBE\u7F6E\u548C\u6240\u9009\u4EE3\u7406\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u7684\u914D\u7F6E\uFF1B\u6CA1\u8BBE\u4EE3\u7406\u7684\u8BDD\uFF0C\u68C0\u67E5\u8FD9\u53F0\u673A\u5668\u6216\u6240\u5728\u7F51\u7EDC\u4E0A\u7684\u5176\u4ED6\u4EE3\u7406\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
146592
+ platformRateLimited: "\u5E73\u53F0\u6B63\u5728\u9650\u6D41\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
146593
+ platformUnavailable: "\u76EE\u6807\u670D\u52A1\u6682\u65F6\u5F02\u5E38\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
146318
146594
  connecting: "\u8FD8\u5728\u8FDE\u63A5\uFF0C\u4E00\u822C 20 \u79D2\u5185\u4F1A\u6709\u7ED3\u679C\uFF0C\u7A0D\u540E\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
146319
146595
  badToken: "\u8FD9\u4E0D\u50CF\u673A\u5668\u4EBA\u7684 token\u3002\u5230 BotFather \u91CD\u65B0\u590D\u5236\u4E00\u4EFD\uFF1A\u6570\u5B57\u3001\u5192\u53F7\uFF0C\u540E\u9762\u662F\u5BC6\u94A5\u3002",
146320
146596
  unauthorized: "Telegram \u4E0D\u8BA4\u8FD9\u4E2A token\uFF0C\u53EF\u80FD\u5DF2\u7ECF\u4F5C\u5E9F\u3002\u5230 BotFather \u53D6\u73B0\u5728\u7684\u90A3\u4E00\u4EFD\u3002\u6539\u597D\u540E\u5728\u8FD9\u91CC\u4FDD\u5B58\uFF0C\u673A\u5668\u4EBA\u4F1A\u81EA\u52A8\u91CD\u65B0\u8FDE\u63A5\uFF1B\u6CA1\u6709\u8981\u6539\u7684\u4E5F\u53EF\u4EE5\u70B9\u4E0B\u65B9\u7684\u300C\u91CD\u65B0\u8FDE\u63A5\u300D\u3002",
@@ -146643,13 +146919,13 @@ var makeModelPlan = (core, deps) => {
146643
146919
  import { readThreadFailed, threadOrNull as threadOrNull5 } from "@ycodium-ai/plugin-api";
146644
146920
 
146645
146921
  // src/conclusionOutbox.ts
146646
- var enqueueConclusion = (core, ledger, binding, debt, send) => core.outbound(
146922
+ var enqueueConclusion = (core, ledger, binding, selection, send) => core.outbound(
146647
146923
  chatKeyOf(binding),
146648
146924
  async () => {
146649
- if (debt === void 0) await send();
146650
- else await ledger.deliver(debt, send);
146925
+ if (selection === void 0) await send();
146926
+ else await ledger.deliver(selection, send);
146651
146927
  },
146652
- { droppable: debt !== void 0 }
146928
+ { droppable: selection !== void 0 }
146653
146929
  );
146654
146930
 
146655
146931
  // src/conclusionRecovery.ts
@@ -146665,60 +146941,100 @@ var makeConclusionRecovery = (core, ledger, delivery, failures) => {
146665
146941
  error: describeError(error)
146666
146942
  });
146667
146943
  };
146668
- const recoverConclusion = async (debt) => {
146944
+ const recoverThread = async (threadId, debts, requestedMessageId) => {
146669
146945
  await core.ready;
146670
146946
  if (disposed) return;
146671
146947
  try {
146672
- const execution = await core.host.projections.getThreadExecution(debt.threadId);
146948
+ const execution = await core.host.projections.getThreadExecution(threadId);
146673
146949
  if (disposed) return;
146674
146950
  if (execution === null) {
146675
- ledger.discard(debt);
146676
- return;
146677
- }
146678
- const latest = execution.latestTurn;
146679
- if (latest?.userMessageId !== debt.userMessageId) {
146680
- core.host.log.info("chat discarded a conclusion whose turn is no longer latest", {
146681
- ...debt
146682
- });
146683
- ledger.discard(debt);
146684
- return;
146685
- }
146686
- if (latest.state === "running" || execution.session?.activeTurnId != null) return;
146687
- if (latest.completedAt !== null && Date.parse(latest.completedAt) < Date.now() - MAX_AGE_MS) {
146688
- core.host.log.info("chat discarded a conclusion older than seven days", { ...debt });
146689
- ledger.discard(debt);
146951
+ for (const debt of debts) {
146952
+ if (requestedMessageId === void 0 || debt.userMessageId === requestedMessageId)
146953
+ ledger.discard({ debt, turnUserMessageId: void 0 });
146954
+ }
146690
146955
  return;
146691
146956
  }
146692
- const binding = core.bindingForChatKey(debt.chatKey);
146693
- if (binding === void 0) return;
146694
- enqueueConclusion(core, ledger, binding, debt, async (owed) => {
146695
- try {
146696
- if (latest.state === "error") {
146697
- return delivery.sendText(
146698
- binding,
146699
- debt.threadId,
146700
- failures.forFailure(debt.threadId, execution.session?.lastError),
146701
- true,
146702
- latest.turnId,
146703
- owed
146704
- );
146957
+ const snapshot = indexExecution(execution);
146958
+ const grouped = groupConclusionDebts(snapshot, threadId, debts);
146959
+ const { resolved, byTurn } = grouped;
146960
+ for (const { debt, result: result2 } of resolved.values()) {
146961
+ if (result2.kind === "gone") {
146962
+ if (requestedMessageId === void 0 || debt.userMessageId === requestedMessageId) {
146963
+ core.host.log.info("chat discarded a conclusion whose message has no turn", {
146964
+ ...debt
146965
+ });
146966
+ ledger.discard({ debt, turnUserMessageId: void 0 });
146705
146967
  }
146706
- const send = await delivery.prepareConclusion(debt.threadId, latest.turnId);
146707
- return send(binding, { recovered: true, streaming: false, owed });
146708
- } catch (error) {
146709
- couldNotRecover(debt, error);
146710
- return false;
146711
146968
  }
146712
- });
146969
+ }
146970
+ const requested = requestedMessageId === void 0 ? void 0 : resolved.get(requestedMessageId)?.result;
146971
+ for (const turnId of byTurn.keys()) {
146972
+ if (requestedMessageId !== void 0 && (requested?.kind !== "settled" && requested?.kind !== "merged" || requested.turnId !== turnId))
146973
+ continue;
146974
+ const { selection, handled } = ledger.forExecutionTurn(snapshot, threadId, turnId, grouped);
146975
+ if (handled || selection === void 0 || requestedMessageId !== void 0 && selection.debt.userMessageId !== requestedMessageId)
146976
+ continue;
146977
+ const result2 = resolved.get(selection.debt.userMessageId).result;
146978
+ if (result2.kind === "pending" || result2.kind === "gone") continue;
146979
+ recoverTurn(selection, result2.kind === "settled" ? result2 : result2.turn, execution);
146980
+ }
146713
146981
  } catch (error) {
146714
- couldNotRecover(debt, error);
146982
+ for (const debt of debts) couldNotRecover(debt, error);
146983
+ }
146984
+ };
146985
+ const recoverTurn = (selection, turn, execution) => {
146986
+ const { debt } = selection;
146987
+ if (turn === void 0 || turn.state === "pending" || turn.state === "running" || execution.session?.activeTurnId === turn.turnId)
146988
+ return;
146989
+ if (turn.completedAt !== null && Date.parse(turn.completedAt) < Date.now() - MAX_AGE_MS) {
146990
+ core.host.log.info("chat discarded a conclusion older than seven days", { ...debt });
146991
+ ledger.discard(selection);
146992
+ return;
146715
146993
  }
146994
+ const binding = core.bindingForChatKey(selection.debt.chatKey);
146995
+ if (binding === void 0) return;
146996
+ enqueueConclusion(core, ledger, binding, selection, async (owed) => {
146997
+ try {
146998
+ if (turn.state === "error") {
146999
+ const session = execution.latestTurn?.state === "error" && execution.latestTurn.turnId === turn.turnId ? execution.session : void 0;
147000
+ return delivery.sendText(
147001
+ binding,
147002
+ debt.threadId,
147003
+ failures.forFailure(debt.threadId, session?.lastError, session?.failureCategory),
147004
+ true,
147005
+ turn.turnId,
147006
+ owed
147007
+ );
147008
+ }
147009
+ const send = await delivery.prepareConclusion(debt.threadId, turn.turnId);
147010
+ return send(binding, { recovered: true, streaming: false, owed });
147011
+ } catch (error) {
147012
+ couldNotRecover(debt, error);
147013
+ return false;
147014
+ }
147015
+ });
147016
+ };
147017
+ const recoverConclusion = async (debt) => {
147018
+ await core.ready;
147019
+ await recoverThread(
147020
+ debt.threadId,
147021
+ Object.values(conclusionDebtsOf(core.state)).filter(
147022
+ (candidate) => candidate.threadId === debt.threadId
147023
+ ),
147024
+ debt.userMessageId
147025
+ );
146716
147026
  };
146717
147027
  const recoverConclusions = async () => {
146718
147028
  await core.ready;
147029
+ const byThread = /* @__PURE__ */ new Map();
146719
147030
  for (const debt of Object.values(conclusionDebtsOf(core.state))) {
147031
+ const group = byThread.get(debt.threadId) ?? [];
147032
+ group.push(debt);
147033
+ byThread.set(debt.threadId, group);
147034
+ }
147035
+ for (const [threadId, debts] of byThread) {
146720
147036
  if (disposed) return;
146721
- await recoverConclusion(debt);
147037
+ await recoverThread(threadId, debts);
146722
147038
  }
146723
147039
  };
146724
147040
  return { recoverConclusion, recoverConclusions };
@@ -146799,19 +147115,19 @@ var makeNotifications = (core, deps) => {
146799
147115
  await ready;
146800
147116
  const targets = targetsForProject(threadId, await projectOfThread(threadId));
146801
147117
  const owes = Object.values(conclusionDebtsOf(core.state)).some(
146802
- (debt2) => debt2.threadId === threadId
147118
+ (debt) => debt.threadId === threadId
146803
147119
  );
146804
- const { debt, handled, execution } = said || owes || targets.length > 0 ? await conclusions.forTurn(threadId) : { debt: void 0, handled: false, execution: null };
146805
- if (debt !== void 0) {
146806
- const binding = bindingForChatKey(debt.chatKey);
147120
+ const { selection, handled, execution } = said || owes || targets.length > 0 ? await conclusions.forTurn(threadId) : { selection: void 0, handled: false, execution: null };
147121
+ if (selection !== void 0 && selection !== null) {
147122
+ const binding = bindingForChatKey(selection.debt.chatKey);
146807
147123
  if (binding !== void 0 && !streamsTo(binding, threadId) && execution?.session?.activeTurnId == null && execution?.latestTurn?.state !== "running" && execution?.latestTurn?.state !== "error" && payload.session?.status !== "error") {
146808
147124
  const turnId = execution?.latestTurn?.turnId ?? null;
146809
- enqueueConclusion(core, conclusions, binding, debt, async (owed) => {
147125
+ enqueueConclusion(core, conclusions, binding, selection, async (owed) => {
146810
147126
  const send = await delivery.prepareConclusion(threadId, turnId);
146811
147127
  return send(binding, { recovered: false, streaming: false, owed });
146812
147128
  });
146813
147129
  }
146814
- } else if (said && !handled) {
147130
+ } else if (selection !== null && said && !handled) {
146815
147131
  const send = await delivery.prepareConclusion(threadId);
146816
147132
  for (const binding of targets) {
146817
147133
  const streaming = streamsTo(binding, threadId);
@@ -146841,20 +147157,21 @@ var makeNotifications = (core, deps) => {
146841
147157
  threadsWithSomethingToReport.delete(threadId);
146842
147158
  await ready;
146843
147159
  const targets = targetsForProject(threadId, await projectOfThread(threadId));
146844
- const { debt, handled, execution } = await conclusions.forTurn(threadId);
146845
- const debtBinding = debt === void 0 ? void 0 : bindingForChatKey(debt.chatKey);
146846
- const recipients = debt !== void 0 ? debtBinding === void 0 ? [] : [debtBinding] : targets;
146847
- const textTargets = handled ? [] : recipients.filter(
147160
+ const { selection, handled, execution } = await conclusions.forTurn(threadId);
147161
+ const debtBinding = selection == null ? void 0 : bindingForChatKey(selection.debt.chatKey);
147162
+ const recipients = selection == null ? targets : debtBinding === void 0 ? [] : [debtBinding];
147163
+ const textTargets = handled || selection === null ? [] : recipients.filter(
146848
147164
  (binding) => !(streamsTo(binding, threadId) && turnCards.covers(threadId))
146849
147165
  );
146850
147166
  const drawingTurn = turnCards.drawingTurn(threadId);
146851
- const notice = textTargets.length > 0 || drawingTurn !== null ? failures.forFailure(threadId, readThreadFailed(event)?.detail) : null;
147167
+ const failure2 = readThreadFailed(event);
147168
+ const notice = textTargets.length > 0 || drawingTurn !== null ? failures.forFailure(threadId, failure2?.detail, failure2?.failureCategory) : null;
146852
147169
  if (notice !== null && drawingTurn !== null)
146853
147170
  failures.holdForCard(threadId, drawingTurn, notice);
146854
- if (notice !== null) {
147171
+ if (notice !== null && selection !== null) {
146855
147172
  for (const binding of textTargets) {
146856
147173
  const send = (owed) => delivery.sendText(binding, threadId, notice, false, null, owed);
146857
- enqueueConclusion(core, conclusions, binding, debt, send);
147174
+ enqueueConclusion(core, conclusions, binding, selection, send);
146858
147175
  }
146859
147176
  }
146860
147177
  for (const binding of targets) {
@@ -147426,6 +147743,17 @@ var makeProjectRows = (core) => {
147426
147743
  };
147427
147744
 
147428
147745
  // src/failureNotices.ts
147746
+ var PROVIDER_FAILURES = {
147747
+ quota: { code: "MODEL_QUOTA", messageKey: "reply.turnFailedQuota" },
147748
+ "rate-limit": { code: "MODEL_RATE_LIMIT", messageKey: "reply.turnFailedRateLimit" },
147749
+ "context-limit": { code: "MODEL_CONTEXT_LIMIT", messageKey: "reply.turnFailedContextLimit" },
147750
+ auth: { code: "MODEL_AUTH", messageKey: "reply.turnFailedAuth" },
147751
+ "model-unavailable": {
147752
+ code: "MODEL_UNAVAILABLE",
147753
+ messageKey: "reply.turnFailedModelUnavailable"
147754
+ },
147755
+ transport: { code: "MODEL_TRANSPORT", messageKey: "reply.turnFailedTransport" }
147756
+ };
147429
147757
  var excerptSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
147430
147758
  var graphemeExcerpt = (value2) => {
147431
147759
  const characters = Array.from(
@@ -147463,9 +147791,20 @@ var makeFailureNotices = (core) => {
147463
147791
  return core.t("reply.turnRetrying", { ref: reference2 });
147464
147792
  },
147465
147793
  /** 一次失败通知的那句话(带新参考号,同号进日志):实时失败事件与重启补发各取一次。 */
147466
- forFailure: (threadId, detail) => {
147794
+ forFailure: (threadId, detail, category) => {
147467
147795
  const reference2 = diagnosticRef("msg");
147468
- core.host.log.warn("chat turn failed", { threadId, reference: reference2 });
147796
+ const failure2 = category === void 0 || category === "unknown" ? null : PROVIDER_FAILURES[category];
147797
+ core.host.log.warn("chat turn failed", {
147798
+ threadId,
147799
+ reference: reference2,
147800
+ code: failure2?.code ?? null,
147801
+ detail
147802
+ });
147803
+ if (failure2 !== null)
147804
+ return core.t("reply.turnFailedWithReference", {
147805
+ message: core.t(failure2.messageKey),
147806
+ ref: reference2
147807
+ });
147469
147808
  const said = reasonOf2(detail);
147470
147809
  const text4 = said === "" ? core.t("reply.turnFailedNoDetail", { ref: reference2 }) : core.t("reply.turnFailed", { detail: said, ref: reference2 });
147471
147810
  return text4;
@@ -147478,11 +147817,13 @@ var makeFailureNotices = (core) => {
147478
147817
  * 卡收尾(无论怎么收的都调):清掉这条会话的寄存。失败收尾时,寄存是这一轮的就用它(同一个号),
147479
147818
  * 否则只写原因与下一步,不另取号。不是失败收尾回 null。
147480
147819
  */
147481
- forCard: (threadId, turnId, detail, failed) => {
147820
+ forCard: (threadId, turnId, detail, failed, category) => {
147482
147821
  const held = heldForCard.get(threadId);
147483
147822
  heldForCard.delete(threadId);
147484
147823
  if (!failed) return null;
147485
147824
  if (held !== void 0 && held.turnId === turnId) return held.text;
147825
+ if (category !== void 0 && category !== "unknown")
147826
+ return core.t(PROVIDER_FAILURES[category].messageKey);
147486
147827
  const said = reasonOf2(detail);
147487
147828
  return said === "" ? core.t("reply.turnFailedBriefNoDetail") : core.t("reply.turnFailedBrief", { detail: said });
147488
147829
  }
@@ -147833,7 +148174,8 @@ var makeReplyCards = (core, failures, anchors, conclusions, delivery) => {
147833
148174
  threadId,
147834
148175
  turnId,
147835
148176
  session?.lastError,
147836
- outcome === "failed"
148177
+ outcome === "failed",
148178
+ session?.failureCategory
147837
148179
  );
147838
148180
  if (errorText2 !== null) return { status: "error", errorText: errorText2 };
147839
148181
  if (stopped || outcome === "stopped") {
@@ -147844,10 +148186,11 @@ var makeReplyCards = (core, failures, anchors, conclusions, delivery) => {
147844
148186
  onOpened: (card) => commit("turn-card-opened", card),
147845
148187
  onClosed: (cardId) => commit("turn-card-closed", { cardId }),
147846
148188
  finalize: async (threadId, turnId, chatKey, send) => {
147847
- const { debt, handled } = await conclusions.forTurn(threadId, turnId);
148189
+ const { selection, handled } = await conclusions.forTurn(threadId, turnId);
147848
148190
  if (handled) return true;
147849
- if (debt === void 0) return send();
147850
- if (!await conclusions.deliver(debt, send)) return false;
148191
+ if (selection === null) return false;
148192
+ if (selection === void 0) return send();
148193
+ if (!await conclusions.deliver(selection, send)) return false;
147851
148194
  const binding = bindingForChatKey(chatKey);
147852
148195
  if (binding === void 0) return true;
147853
148196
  const sendConclusion = await delivery.prepareConclusion(threadId, turnId);
@@ -147855,8 +148198,8 @@ var makeReplyCards = (core, failures, anchors, conclusions, delivery) => {
147855
148198
  return true;
147856
148199
  },
147857
148200
  fallback: async (threadId, turnId, chatKey, view) => {
147858
- const { debt, handled } = await conclusions.forTurn(threadId, turnId);
147859
- if (handled) return;
148201
+ const { selection, handled } = await conclusions.forTurn(threadId, turnId);
148202
+ if (handled || selection === null) return;
147860
148203
  const binding = bindingForChatKey(chatKey);
147861
148204
  if (binding === void 0) return;
147862
148205
  const text4 = [view.body, view.errorText ?? ""].filter((part) => part !== "").join("\n\n");
@@ -147864,7 +148207,7 @@ var makeReplyCards = (core, failures, anchors, conclusions, delivery) => {
147864
148207
  core,
147865
148208
  conclusions,
147866
148209
  binding,
147867
- debt,
148210
+ selection,
147868
148211
  (owed) => delivery.sendText(binding, threadId, text4, false, turnId, owed)
147869
148212
  );
147870
148213
  },
@@ -148323,12 +148666,14 @@ import * as NodeCrypto6 from "node:crypto";
148323
148666
  // src/telegramApiResponse.ts
148324
148667
  var TelegramApiError = class extends Error {
148325
148668
  code;
148669
+ status;
148326
148670
  /** 限流(429)时平台给的等待秒数;别的错误没有。 */
148327
148671
  retryAfterSeconds;
148328
- constructor(method, code2, description, retryAfterSeconds) {
148329
- super(`telegram ${method} failed (${String(code2 ?? "?")}): ${description}`);
148672
+ constructor(method, code2, description, retryAfterSeconds, options) {
148673
+ super(`telegram ${method} failed (${String(code2 ?? "?")}): ${description}`, options);
148330
148674
  this.name = "TelegramApiError";
148331
148675
  this.code = code2;
148676
+ this.status = options?.status;
148332
148677
  this.retryAfterSeconds = retryAfterSeconds;
148333
148678
  }
148334
148679
  };
@@ -148343,7 +148688,8 @@ var readTelegramResponse = async (method, response) => {
148343
148688
  method,
148344
148689
  response.status === 401 ? 401 : envelope?.error_code ?? (response.ok ? void 0 : response.status),
148345
148690
  envelope?.description ?? "no reason",
148346
- envelope?.parameters?.retry_after
148691
+ envelope?.parameters?.retry_after,
148692
+ { status: response.status }
148347
148693
  );
148348
148694
  }
148349
148695
  return envelope.result;
@@ -148477,7 +148823,13 @@ var makeTelegramApi = (input) => {
148477
148823
  return await input.http(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
148478
148824
  } catch (error) {
148479
148825
  if (error instanceof Error && error.name === "TimeoutError") {
148480
- throw new TelegramApiError(method, void 0, `timed out after ${String(timeoutMs)} ms`);
148826
+ throw new TelegramApiError(
148827
+ method,
148828
+ void 0,
148829
+ `timed out after ${String(timeoutMs)} ms`,
148830
+ void 0,
148831
+ { cause: error }
148832
+ );
148481
148833
  }
148482
148834
  throw error;
148483
148835
  }
@@ -148702,6 +149054,98 @@ var TELEGRAM_MENU = [
148702
149054
  ];
148703
149055
  var telegramCommandMenu = (t) => TELEGRAM_MENU.map((item) => ({ command: item.command, description: t(item.key) }));
148704
149056
 
149057
+ // src/telegramConnectionCheck.ts
149058
+ var webhookHost = (url) => {
149059
+ try {
149060
+ return new URL(url).host;
149061
+ } catch {
149062
+ return "(unreadable)";
149063
+ }
149064
+ };
149065
+ var NOT_BOT_DETAIL = "token is not a bot's";
149066
+ var webhookActiveDetail = (url) => `webhook is set (host ${webhookHost(url)})`;
149067
+ var transportFailure = (stage, error) => ({
149068
+ ok: false,
149069
+ stage,
149070
+ detail: describeError(error),
149071
+ error
149072
+ });
149073
+ var probeTelegramBot = async (api) => {
149074
+ let own;
149075
+ try {
149076
+ own = await api.getMe();
149077
+ } catch (error) {
149078
+ return transportFailure("getMe", error);
149079
+ }
149080
+ if (own.is_bot !== true) {
149081
+ return { ok: false, stage: "getMe", hint: "notBot", detail: NOT_BOT_DETAIL };
149082
+ }
149083
+ let webhook = "";
149084
+ try {
149085
+ webhook = (await api.getWebhookInfo()).url ?? "";
149086
+ } catch (error) {
149087
+ return transportFailure("getWebhookInfo", error);
149088
+ }
149089
+ if (webhook !== "") {
149090
+ return {
149091
+ ok: false,
149092
+ stage: "getWebhookInfo",
149093
+ hint: "webhookActive",
149094
+ detail: webhookActiveDetail(webhook)
149095
+ };
149096
+ }
149097
+ return { ok: true, username: own.username, id: own.id };
149098
+ };
149099
+ var TelegramConnectGateError = class extends Error {
149100
+ hint;
149101
+ constructor(hint, detail) {
149102
+ super(detail);
149103
+ this.name = "TelegramConnectGateError";
149104
+ this.hint = hint;
149105
+ }
149106
+ };
149107
+ var assertTelegramPollingReady = async (api) => {
149108
+ const probed = await probeTelegramBot(api);
149109
+ if (!probed.ok) {
149110
+ if ("error" in probed) throw probed.error;
149111
+ throw new TelegramConnectGateError(probed.hint, probed.detail);
149112
+ }
149113
+ return { username: probed.username ?? null, id: probed.id };
149114
+ };
149115
+ var telegramPollingFailureHint = (error) => error instanceof TelegramConnectGateError ? error.hint : error instanceof TelegramApiError && error.code === 409 ? "webhookActive" : void 0;
149116
+ var checkTelegramConnection = async (bot, token, apiFor) => {
149117
+ if (token === "") {
149118
+ return { stage: "credentials", ok: false, detail: "missing bot token in saved config" };
149119
+ }
149120
+ if (!isTelegramToken(token)) {
149121
+ return {
149122
+ stage: "token-format",
149123
+ ok: false,
149124
+ detail: "saved token is not in the BotFather format",
149125
+ hint: "badToken"
149126
+ };
149127
+ }
149128
+ const api = await apiFor(bot);
149129
+ const probed = await probeTelegramBot(api);
149130
+ if (probed.ok) {
149131
+ const name = probed.username;
149132
+ const username = name !== void 0 && name !== "" ? `@${name}` : "(no username)";
149133
+ return { stage: "getMe", ok: true, detail: `username=${username} webhook=none` };
149134
+ }
149135
+ if ("error" in probed) {
149136
+ return {
149137
+ stage: probed.stage,
149138
+ ok: false,
149139
+ detail: probed.detail,
149140
+ ...connectionHintFields(
149141
+ probed.error,
149142
+ probed.stage === "getMe" && isTelegramCredentialsRejected(probed.error) ? "unauthorized" : void 0
149143
+ )
149144
+ };
149145
+ }
149146
+ return { stage: probed.stage, ok: false, detail: probed.detail, hint: probed.hint };
149147
+ };
149148
+
148705
149149
  // src/telegramChoice.ts
148706
149150
  var renderChoiceCard = (card) => ({
148707
149151
  text: [card.title, card.heading ?? "", card.body, card.note ?? ""].filter((part) => part !== "").join("\n\n"),
@@ -148893,15 +149337,13 @@ var tokenFingerprint = (bot) => NodeCrypto6.createHash("sha256").update(tokenOf(
148893
149337
  var REACTION_WORKING = "\u{1F440}";
148894
149338
  var REACTION_DONE = "\u{1F44D}";
148895
149339
  var REACTION_FAILED = "\u{1F44E}";
148896
- var telegramConfigIssues = (binding) => tokenOf(binding) === "" ? [{ column: "telegramToken", code: "required" }] : [];
148897
- var defaultSleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
148898
- var webhookHost = (url) => {
148899
- try {
148900
- return new URL(url).host;
148901
- } catch {
148902
- return "(unreadable)";
148903
- }
149340
+ var telegramConfigIssues = (binding) => {
149341
+ const token = tokenOf(binding);
149342
+ if (token === "") return [{ column: "telegramToken", code: "required" }];
149343
+ if (!isTelegramToken(token)) return [{ column: "telegramToken", code: "format" }];
149344
+ return [];
148904
149345
  };
149346
+ var defaultSleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
148905
149347
  var makeTelegramAdapter = (deps) => {
148906
149348
  const apis = /* @__PURE__ */ new Map();
148907
149349
  const sleep4 = deps.sleep ?? defaultSleep;
@@ -148996,13 +149438,14 @@ var makeTelegramAdapter = (deps) => {
148996
149438
  });
148997
149439
  return {
148998
149440
  platform: "telegram",
149441
+ redeliversUnacknowledged: true,
148999
149442
  botIdentity: tokenFingerprint,
149000
149443
  icon: "telegram",
149001
149444
  label: "Telegram",
149002
149445
  quotesReplies: true,
149003
149446
  columns: COLUMNS2,
149004
149447
  validate: telegramConfigIssues,
149005
- isConfigured: (binding) => telegramConfigIssues(binding).length === 0,
149448
+ isConfigured: (binding) => telegramConfigIssues(binding).every((issue) => issue.code !== "required"),
149006
149449
  connect: async (binding, handlers) => {
149007
149450
  let stop = () => {
149008
149451
  };
@@ -149040,8 +149483,8 @@ var makeTelegramAdapter = (deps) => {
149040
149483
  liveApi = api;
149041
149484
  if (!running) return;
149042
149485
  if (botUsername == null) {
149043
- const own = await api.getMe();
149044
- botUsername = own.username ?? null;
149486
+ const own = await assertTelegramPollingReady(api);
149487
+ botUsername = own.username;
149045
149488
  botId = own.id;
149046
149489
  if (running) handlers.onConnectionState?.({ state: "connected" });
149047
149490
  }
@@ -149107,14 +149550,16 @@ var makeTelegramAdapter = (deps) => {
149107
149550
  }
149108
149551
  }
149109
149552
  if (!running) break;
149110
- advanceOffset(update);
149111
149553
  const senderId = message.from === void 0 ? null : String(message.from.id);
149112
149554
  const senderName2 = telegramDisplayName(message.from);
149113
149555
  const chatKind = message.chat.type === "private" ? "private" : "group";
149114
149556
  if (message.voice !== void 0 || text4 === "" && resource === null) {
149115
149557
  const unsupported = unsupportedTypeOf(message);
149116
- if (unsupported === null) continue;
149117
- handlers.onMessage({
149558
+ if (unsupported === null) {
149559
+ advanceOffset(update);
149560
+ continue;
149561
+ }
149562
+ await handlers.onMessage({
149118
149563
  messageId: String(message.message_id),
149119
149564
  chatId: chatIdOf(message),
149120
149565
  senderId,
@@ -149123,11 +149568,13 @@ var makeTelegramAdapter = (deps) => {
149123
149568
  resources: [],
149124
149569
  unsupportedType: unsupported
149125
149570
  });
149571
+ if (!running) break;
149572
+ advanceOffset(update);
149126
149573
  continue;
149127
149574
  }
149128
149575
  const parentChatId = parentChatIdOf(message);
149129
149576
  const replyTo = telegramReplyReference(message, botUsername, botId);
149130
- handlers.onMessage({
149577
+ await handlers.onMessage({
149131
149578
  messageId: String(message.message_id),
149132
149579
  chatId: chatIdOf(message),
149133
149580
  ...parentChatId === void 0 ? {} : { parentChatId },
@@ -149147,6 +149594,8 @@ var makeTelegramAdapter = (deps) => {
149147
149594
  }
149148
149595
  ]
149149
149596
  });
149597
+ if (!running) break;
149598
+ advanceOffset(update);
149150
149599
  }
149151
149600
  } catch (error) {
149152
149601
  if (!running) return;
@@ -149161,7 +149610,12 @@ var makeTelegramAdapter = (deps) => {
149161
149610
  });
149162
149611
  return;
149163
149612
  }
149164
- if (checkingConnection) handlers.onConnectionState?.({ state: "failed", error });
149613
+ if (checkingConnection)
149614
+ handlers.onConnectionState?.({
149615
+ state: "failed",
149616
+ error,
149617
+ ...connectionHintFields(error, telegramPollingFailureHint(error))
149618
+ });
149165
149619
  handlers.onError(error instanceof Error ? error.message : String(error));
149166
149620
  await Promise.race([(deps.sleep ?? defaultSleep)(backoff), stopped]);
149167
149621
  backoff = Math.min(backoff * 2, BACKOFF_MAX_MS);
@@ -149183,50 +149637,7 @@ var makeTelegramAdapter = (deps) => {
149183
149637
  const api = await apiFor(binding);
149184
149638
  await api.sendMessage(binding.chatId, text4, options?.retry, binding.reply?.messageId);
149185
149639
  },
149186
- checkConnection: async (bot) => {
149187
- const token = tokenOf(bot);
149188
- if (token === "") {
149189
- return { stage: "credentials", ok: false, detail: "missing bot token in saved config" };
149190
- }
149191
- if (!isTelegramToken(token)) {
149192
- return {
149193
- stage: "token-format",
149194
- ok: false,
149195
- detail: "saved token is not in the BotFather format",
149196
- hint: "badToken"
149197
- };
149198
- }
149199
- const api = await apiFor(bot);
149200
- let username;
149201
- try {
149202
- const me = await api.getMe();
149203
- if (me.is_bot !== true) {
149204
- return { stage: "getMe", ok: false, detail: "token is not a bot's", hint: "notBot" };
149205
- }
149206
- username = me.username !== void 0 && me.username !== "" ? `@${me.username}` : "(no username)";
149207
- } catch (error) {
149208
- return {
149209
- stage: "getMe",
149210
- ok: false,
149211
- detail: describeError(error),
149212
- ...isTelegramCredentialsRejected(error) ? { hint: "unauthorized" } : {}
149213
- };
149214
- }
149215
- try {
149216
- const webhook = (await api.getWebhookInfo()).url ?? "";
149217
- if (webhook !== "") {
149218
- return {
149219
- stage: "getWebhookInfo",
149220
- ok: false,
149221
- detail: `webhook is set (host ${webhookHost(webhook)})`,
149222
- hint: "webhookActive"
149223
- };
149224
- }
149225
- } catch (error) {
149226
- return { stage: "getWebhookInfo", ok: false, detail: describeError(error) };
149227
- }
149228
- return { stage: "getMe", ok: true, detail: `username=${username} webhook=none` };
149229
- },
149640
+ checkConnection: (bot) => checkTelegramConnection(bot, tokenOf(bot), apiFor),
149230
149641
  sendTyping: async (binding) => {
149231
149642
  await (await apiFor(binding)).sendTyping(binding.chatId);
149232
149643
  },
@@ -149727,12 +150138,10 @@ var makeTurns = (core, deps) => {
149727
150138
  );
149728
150139
  if (!dispatched) return null;
149729
150140
  topicRoot?.confirmConsumed();
149730
- if (intentAction?.kind !== "steer") {
149731
- await deps.conclusions.owe(
149732
- { chatKey, threadId, userMessageId, recordedAt: (/* @__PURE__ */ new Date()).toISOString() },
149733
- replayed ? deps.recoverConclusion : void 0
149734
- );
149735
- }
150141
+ await deps.conclusions.owe(
150142
+ { chatKey, threadId, userMessageId, recordedAt: (/* @__PURE__ */ new Date()).toISOString() },
150143
+ replayed ? deps.recoverConclusion : void 0
150144
+ );
149736
150145
  } finally {
149737
150146
  registered();
149738
150147
  }
@@ -149969,7 +150378,13 @@ var probe = async (adapters, bot) => {
149969
150378
  result2.hint
149970
150379
  );
149971
150380
  } catch (error) {
149972
- return reportOf(bot, "check", false, redactor.redact(describeError(error)));
150381
+ return reportOf(
150382
+ bot,
150383
+ "check",
150384
+ false,
150385
+ redactor.redact(describeError(error)),
150386
+ connectionHint(error)
150387
+ );
149973
150388
  }
149974
150389
  };
149975
150390
  var emitReport = (emit, report, action) => {
@@ -150495,6 +150910,16 @@ var makeWechatAdapter = (deps) => {
150495
150910
  };
150496
150911
  };
150497
150912
 
150913
+ // src/wecomAckError.ts
150914
+ var WecomAckError = class extends Error {
150915
+ code;
150916
+ constructor(code2, msg) {
150917
+ super(`WeCom ack failed: ${String(code2)} ${msg}`);
150918
+ this.name = "WecomAckError";
150919
+ this.code = code2;
150920
+ }
150921
+ };
150922
+
150498
150923
  // src/wecomUpload.ts
150499
150924
  import * as NodeCrypto8 from "node:crypto";
150500
150925
  var WECOM_CHUNK_BYTES = 512 * 1024;
@@ -150707,15 +151132,12 @@ var parseWecomMessage = (frame, botId) => {
150707
151132
  };
150708
151133
  };
150709
151134
  var splitForWecom = (text4, limit = WECOM_TEXT_MAX_CHARS) => splitMessage(text4, limit);
150710
- var decoder = new TextDecoder();
150711
- var WecomAckError = class extends Error {
150712
- code;
150713
- constructor(code2, msg) {
150714
- super(`WeCom ack failed: ${String(code2)} ${msg}`);
150715
- this.name = "WecomAckError";
150716
- this.code = code2;
151135
+ var sendWecomText = async (text4, sendChunk) => {
151136
+ for (const [index, content] of splitForWecom(text4).entries()) {
151137
+ await sendChunk(content, index === 0);
150717
151138
  }
150718
151139
  };
151140
+ var decoder = new TextDecoder();
150719
151141
  var subscriptionHint = (error) => {
150720
151142
  if (!(error instanceof WecomAckError)) return void 0;
150721
151143
  switch (error.code) {
@@ -150848,41 +151270,39 @@ var runWecomClient = (input) => {
150848
151270
  return false;
150849
151271
  }
150850
151272
  );
150851
- const sendText = async (chatId, text4) => {
150852
- for (const [index, content] of splitForWecom(text4).entries()) {
150853
- const inboundRequestId = index === 0 ? pendingRequests.get(chatId) : void 0;
150854
- if (inboundRequestId !== void 0) {
150855
- pendingRequests.delete(chatId);
150856
- await send({
150857
- cmd: "aibot_respond_msg",
150858
- headers: { req_id: inboundRequestId },
150859
- body: {
150860
- msgtype: "stream",
150861
- stream: { id: nextRequestId("stream"), finish: true, content }
150862
- }
150863
- });
150864
- continue;
150865
- }
150866
- const requestId = nextRequestId("aibot_send_msg");
150867
- const ack = new Promise((resolve2, reject) => {
150868
- pendingAcks.set(requestId, { resolve: () => resolve2(), reject });
150869
- setTimeout(() => {
150870
- if (pendingAcks.delete(requestId)) resolve2();
150871
- }, SEND_ACK_TIMEOUT_MS);
151273
+ const sendTextChunk = async (chatId, content, firstChunk) => {
151274
+ const inboundRequestId = firstChunk ? pendingRequests.get(chatId) : void 0;
151275
+ if (inboundRequestId !== void 0) {
151276
+ pendingRequests.delete(chatId);
151277
+ await send({
151278
+ cmd: "aibot_respond_msg",
151279
+ headers: { req_id: inboundRequestId },
151280
+ body: {
151281
+ msgtype: "stream",
151282
+ stream: { id: nextRequestId("stream"), finish: true, content }
151283
+ }
150872
151284
  });
150873
- try {
150874
- await Promise.all([
150875
- send({
150876
- cmd: "aibot_send_msg",
150877
- headers: { req_id: requestId },
150878
- body: { chatid: chatId, msgtype: "markdown", markdown: { content } }
150879
- }),
150880
- ack
150881
- ]);
150882
- } catch (error) {
150883
- pendingAcks.delete(requestId);
150884
- throw error;
150885
- }
151285
+ return;
151286
+ }
151287
+ const requestId = nextRequestId("aibot_send_msg");
151288
+ const ack = new Promise((resolve2, reject) => {
151289
+ pendingAcks.set(requestId, { resolve: () => resolve2(), reject });
151290
+ setTimeout(() => {
151291
+ if (pendingAcks.delete(requestId)) resolve2();
151292
+ }, SEND_ACK_TIMEOUT_MS);
151293
+ });
151294
+ try {
151295
+ await Promise.all([
151296
+ send({
151297
+ cmd: "aibot_send_msg",
151298
+ headers: { req_id: requestId },
151299
+ body: { chatid: chatId, msgtype: "markdown", markdown: { content } }
151300
+ }),
151301
+ ack
151302
+ ]);
151303
+ } catch (error) {
151304
+ pendingAcks.delete(requestId);
151305
+ throw error;
150886
151306
  }
150887
151307
  };
150888
151308
  const request = async (cmd, body) => {
@@ -150959,7 +151379,8 @@ var runWecomClient = (input) => {
150959
151379
  };
150960
151380
  };
150961
151381
  return {
150962
- sendText,
151382
+ sendText: (chatId, text4) => sendWecomText(text4, (content, firstChunk) => sendTextChunk(chatId, content, firstChunk)),
151383
+ sendTextChunk,
150963
151384
  sendMedia,
150964
151385
  rememberConversation: (chatId, conversation) => {
150965
151386
  conversations.set(chatId, conversation);
@@ -151064,6 +151485,38 @@ var fetchWecomMedia = async (input) => {
151064
151485
  return { bytes, name };
151065
151486
  };
151066
151487
 
151488
+ // src/wecomSendError.ts
151489
+ var WecomSendError = class extends Error {
151490
+ operation;
151491
+ code;
151492
+ providerCode;
151493
+ constructor(cause, operation, code2, providerCode) {
151494
+ super(
151495
+ providerCode === void 0 && cause instanceof Error ? cause.message : `WeCom ${operation} failed (${code2}, provider=${providerCode ?? "-"})`,
151496
+ { cause }
151497
+ );
151498
+ this.name = "WecomSendError";
151499
+ this.operation = operation;
151500
+ this.code = code2;
151501
+ this.providerCode = providerCode;
151502
+ }
151503
+ };
151504
+ var wecomSendError = (error, operation) => {
151505
+ if (error instanceof ImNotConnectedError || error instanceof WecomSendError || error instanceof Error && error.name === "AbortError" || error instanceof TypeError || error instanceof ReferenceError || error instanceof SyntaxError)
151506
+ return error;
151507
+ const providerCode = error instanceof WecomAckError ? error.code : void 0;
151508
+ let code2 = providerCode === void 0 ? "channel-delivery-uncertain" : "channel-delivery-failed";
151509
+ if (providerCode === 48002) code2 = "channel-permission";
151510
+ if (providerCode === 45009) code2 = "channel-rate-limit";
151511
+ return new WecomSendError(error, operation, code2, providerCode);
151512
+ };
151513
+ var wecomSendDiagnostic = (error) => error instanceof WecomSendError ? {
151514
+ operation: error.operation,
151515
+ code: error.code,
151516
+ providerCode: error.providerCode
151517
+ } : void 0;
151518
+ var wecomSendVerdict = (error) => error instanceof ImNotConnectedError || error instanceof WecomSendError && error.code === "channel-rate-limit" ? { retry: true } : NO_RETRY;
151519
+
151067
151520
  // src/wecomAdapter.ts
151068
151521
  var WECOM_CARD_THROTTLE_MS = 400;
151069
151522
  var LIVE_STREAM_LIMIT = 200;
@@ -151090,6 +151543,16 @@ var makeWecomAdapter = (deps) => {
151090
151543
  const clients = /* @__PURE__ */ new Map();
151091
151544
  const managedConnections = /* @__PURE__ */ new Map();
151092
151545
  const t = deps.translate;
151546
+ const sending = async (operation, request) => {
151547
+ try {
151548
+ return await request();
151549
+ } catch (error) {
151550
+ const failure2 = wecomSendError(error, operation);
151551
+ const diagnostic = wecomSendDiagnostic(failure2);
151552
+ if (diagnostic !== void 0) deps.log.warn("wecom send failed", diagnostic);
151553
+ throw failure2;
151554
+ }
151555
+ };
151093
151556
  const liveStreams = /* @__PURE__ */ new Map();
151094
151557
  const rememberStream = (key, entry) => {
151095
151558
  liveStreams.set(key, entry);
@@ -151110,19 +151573,19 @@ var makeWecomAdapter = (deps) => {
151110
151573
  shown = `${t("card.truncated", { count: body.length - kept.length })}
151111
151574
  ${kept}`;
151112
151575
  }
151113
- await entry.stream.send(joinParts(head, shown, foot), false);
151576
+ await sending("replyStream", () => entry.stream.send(joinParts(head, shown, foot), false));
151114
151577
  return;
151115
151578
  }
151116
151579
  const chunks = splitForWecom(joinParts(head, body, foot));
151117
- const delivered = await entry.stream.send(chunks[0] ?? "", true);
151580
+ const delivered = await sending("replyStream", () => entry.stream.send(chunks[0] ?? "", true));
151118
151581
  for (const chunk of delivered ? chunks.slice(1) : chunks) {
151119
- await entry.client.sendText(entry.chatId, chunk);
151582
+ await sending("sendMessage", () => entry.client.sendText(entry.chatId, chunk));
151120
151583
  }
151121
151584
  };
151122
151585
  const sendMedia = async (binding, kind, media) => {
151123
151586
  const client = clients.get(binding.rowId);
151124
151587
  if (client === void 0) throw new Error("WeCom is not connected right now");
151125
- await client.sendMedia(binding.chatId, kind, media);
151588
+ await sending("sendMedia", () => client.sendMedia(binding.chatId, kind, media));
151126
151589
  deps.log.info("wecom delivered a file", { name: media.name, as: kind });
151127
151590
  };
151128
151591
  return {
@@ -151167,7 +151630,7 @@ ${kept}`;
151167
151630
  if (message.chatType !== "single") return;
151168
151631
  client.rememberRequest(message.chatId, message.requestId);
151169
151632
  client.rememberConversation(message.chatId, message.conversation);
151170
- handlers.onMessage({
151633
+ void handlers.onMessage({
151171
151634
  // 不保存短效 req_id:重启后的回话走新连接的 aibot_send_msg 主动发送,
151172
151635
  // 不冒充原消息的回答,也不复用旧连接的 rememberRequest / 流状态。
151173
151636
  messageId: message.messageId,
@@ -151184,6 +151647,10 @@ ${kept}`;
151184
151647
  mimeType: resource.mimeType,
151185
151648
  kind: resource.kind
151186
151649
  }))
151650
+ }).catch((error) => {
151651
+ deps.log.warn("wecom could not persist inbound message", {
151652
+ error: describeError(error)
151653
+ });
151187
151654
  });
151188
151655
  }
151189
151656
  }
@@ -151233,13 +151700,26 @@ ${kept}`;
151233
151700
  }
151234
151701
  };
151235
151702
  },
151236
- // 不看 `options.retry`:长连接的回执没有核实过的限流码,也没有幂等键,失败一律说不清发没发出去。
151237
- sendText: async (binding, text4) => {
151238
- const client = clients.get(binding.rowId);
151239
- if (client === void 0) {
151240
- throw new ImNotConnectedError(`wecom bot '${binding.displayName}' has no connection.`);
151241
- }
151242
- await client.sendText(binding.chatId, text4);
151703
+ sendText: async (binding, text4, options) => {
151704
+ await sendWecomText(
151705
+ text4,
151706
+ (chunk, firstChunk) => sendWithRetry(
151707
+ async () => {
151708
+ const client = clients.get(binding.rowId);
151709
+ if (client === void 0) {
151710
+ throw new ImNotConnectedError(
151711
+ `wecom bot '${binding.displayName}' has no connection.`
151712
+ );
151713
+ }
151714
+ await sending(
151715
+ "sendMessage",
151716
+ () => client.sendTextChunk(binding.chatId, chunk, firstChunk)
151717
+ );
151718
+ },
151719
+ wecomSendVerdict,
151720
+ options?.retry
151721
+ )
151722
+ );
151243
151723
  },
151244
151724
  checkConnection: async (bot) => {
151245
151725
  const managed = managedConnections.get(bot.rowId);
@@ -151313,7 +151793,10 @@ ${kept}`;
151313
151793
  }
151314
151794
  },
151315
151795
  askApproval: async (binding, request) => {
151316
- await (clients.get(binding.rowId)?.sendText(binding.chatId, numberedApprovalText(request)) ?? Promise.resolve());
151796
+ const client = clients.get(binding.rowId);
151797
+ const text4 = numberedApprovalText(request);
151798
+ if (client !== void 0)
151799
+ await sending("sendMessage", () => client.sendText(binding.chatId, text4));
151317
151800
  return null;
151318
151801
  },
151319
151802
  turnCards: {
@@ -151370,6 +151853,7 @@ var REQUEST_TIMEOUT_MS = 15e3;
151370
151853
  var MEDIA_TIMEOUT_MS = 6e4;
151371
151854
  var WecomAppApiError = class extends Error {
151372
151855
  code;
151856
+ status;
151373
151857
  providerCode;
151374
151858
  hint;
151375
151859
  invalidUsers;
@@ -151377,6 +151861,7 @@ var WecomAppApiError = class extends Error {
151377
151861
  super(message);
151378
151862
  this.name = "WecomAppApiError";
151379
151863
  this.code = code2;
151864
+ this.status = extras?.status;
151380
151865
  this.providerCode = extras?.providerCode;
151381
151866
  this.hint = extras?.hint;
151382
151867
  this.invalidUsers = extras?.invalidUsers ?? [];
@@ -151586,30 +152071,31 @@ var WecomAppApi = class {
151586
152071
  });
151587
152072
  }
151588
152073
  }
151589
- #apiError(errcode, errmsg, invalidUsers) {
152074
+ #apiError(errcode, errmsg, invalidUsers, status) {
151590
152075
  const code2 = String(errcode);
152076
+ const details = { providerCode: code2, status };
151591
152077
  const message = typeof errmsg === "string" && errmsg !== "" ? `WeCom API failed (${code2}): ${errmsg}` : `WeCom API failed (${code2}).`;
151592
152078
  if (TOKEN_INVALID_CODES.has(code2)) this.invalidateToken();
151593
152079
  if (errcode === 60020) {
151594
152080
  return new WecomAppApiError("trusted-ip", "WeCom rejected the outbound IP (errcode 60020).", {
151595
- providerCode: code2,
152081
+ ...details,
151596
152082
  hint: "wecomTrustedIp"
151597
152083
  });
151598
152084
  }
151599
152085
  if (errcode === 81013) {
151600
152086
  return new WecomAppApiError("invalid-user", message, {
151601
- providerCode: code2,
152087
+ ...details,
151602
152088
  hint: "wecomInvalidUser",
151603
152089
  invalidUsers: parseInvalidUsers(invalidUsers)
151604
152090
  });
151605
152091
  }
151606
152092
  if (errcode === 40056) {
151607
152093
  return new WecomAppApiError("invalid-agent", message, {
151608
- providerCode: code2,
152094
+ ...details,
151609
152095
  hint: "wecomInvalidAgent"
151610
152096
  });
151611
152097
  }
151612
- return new WecomAppApiError("api-failed", message, { providerCode: code2 });
152098
+ return new WecomAppApiError("api-failed", message, details);
151613
152099
  }
151614
152100
  async #withRequest(url, init, timeoutMs, read) {
151615
152101
  const deadline = new AbortController();
@@ -151636,15 +152122,29 @@ var WecomAppApi = class {
151636
152122
  } catch {
151637
152123
  throw new WecomAppApiError(
151638
152124
  "bad-response",
151639
- `WeCom returned non-JSON (HTTP ${String(response.status)}).`
152125
+ `WeCom returned non-JSON (HTTP ${String(response.status)}).`,
152126
+ { status: response.status }
151640
152127
  );
151641
152128
  }
151642
152129
  if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
151643
- throw new WecomAppApiError("bad-response", "WeCom returned a non-object JSON body.");
152130
+ throw new WecomAppApiError("bad-response", "WeCom returned a non-object JSON body.", {
152131
+ status: response.status
152132
+ });
151644
152133
  }
151645
152134
  const result2 = payload;
151646
152135
  const errcode = Number(result2["errcode"] ?? 0);
151647
- if (errcode !== 0) throw this.#apiError(errcode, result2["errmsg"], result2["invaliduser"]);
152136
+ if (errcode !== 0) {
152137
+ throw this.#apiError(errcode, result2["errmsg"], result2["invaliduser"], response.status);
152138
+ }
152139
+ if (!response.ok) {
152140
+ throw new WecomAppApiError(
152141
+ "bad-response",
152142
+ `WeCom returned HTTP ${String(response.status)}.`,
152143
+ {
152144
+ status: response.status
152145
+ }
152146
+ );
152147
+ }
151648
152148
  return result2;
151649
152149
  });
151650
152150
  }
@@ -152279,12 +152779,19 @@ var makeWecomAppAdapter = (deps) => {
152279
152779
  }
152280
152780
  const messageId = message.msgid ?? `${chatId}:${message.createTime ?? Date.now()}`;
152281
152781
  const base2 = { messageId, chatId, senderId: chatId };
152782
+ const receive = (inbound) => {
152783
+ void handlers.onMessage(inbound).catch((error) => {
152784
+ deps.log.warn("wecom-app could not persist inbound message", {
152785
+ error: describeError(error)
152786
+ });
152787
+ });
152788
+ };
152282
152789
  if (message.msgtype === "text") {
152283
- handlers.onMessage({ ...base2, text: message.text?.content?.trim() ?? "", resources: [] });
152790
+ receive({ ...base2, text: message.text?.content?.trim() ?? "", resources: [] });
152284
152791
  return;
152285
152792
  }
152286
152793
  if (message.msgtype === "image" && message.mediaId !== void 0 && message.mediaId !== "") {
152287
- handlers.onMessage({
152794
+ receive({
152288
152795
  ...base2,
152289
152796
  text: "",
152290
152797
  resources: [
@@ -152293,7 +152800,7 @@ var makeWecomAppAdapter = (deps) => {
152293
152800
  });
152294
152801
  return;
152295
152802
  }
152296
- handlers.onMessage({
152803
+ receive({
152297
152804
  ...base2,
152298
152805
  text: "",
152299
152806
  resources: [],
@@ -152373,7 +152880,10 @@ var makeWecomAppAdapter = (deps) => {
152373
152880
  stage: "gettoken",
152374
152881
  ok: false,
152375
152882
  detail: describeError(error),
152376
- ...error instanceof WecomAppApiError && error.hint !== void 0 ? { hint: error.hint } : {}
152883
+ ...connectionHintFields(
152884
+ error,
152885
+ error instanceof WecomAppApiError ? error.hint : void 0
152886
+ )
152377
152887
  };
152378
152888
  }
152379
152889
  },
@@ -152392,7 +152902,7 @@ var makeWecomAppAdapter = (deps) => {
152392
152902
  const report = (state) => {
152393
152903
  if (closed) return;
152394
152904
  handlers.onConnectionState?.(
152395
- state.state === "failed" && state.error instanceof WecomAppApiError && state.error.hint !== void 0 ? { ...state, hint: state.error.hint } : state
152905
+ state.state === "failed" && state.error instanceof WecomAppApiError && state.error.hint !== void 0 ? { ...state, hint: state.error.hint } : state.state === "failed" ? { ...state, ...connectionHintFields(state.error, state.hint) } : state
152396
152906
  );
152397
152907
  };
152398
152908
  apis.set(bot.rowId, { api, report });
@@ -152503,7 +153013,7 @@ var makeWecomAppAdapter = (deps) => {
152503
153013
  // src/index.ts
152504
153014
  var definition = {
152505
153015
  id: "im",
152506
- apiVersion: 22,
153016
+ apiVersion: 23,
152507
153017
  // 第二个参数只给插件自己的测试夹具:换掉文件 IO 与派发重试的等待。宿主只传 ctx。
152508
153018
  activate: async (ctx, testing = {}) => {
152509
153019
  const { dispatchSleep } = testing;