@ycodium-ai/plugin-im 0.2.3096 → 0.2.3106

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
@@ -23360,7 +23360,7 @@ var require_websocket = __commonJS({
23360
23360
  var http = __require("http");
23361
23361
  var net = __require("net");
23362
23362
  var tls = __require("tls");
23363
- var { randomBytes, createHash: createHash4 } = __require("crypto");
23363
+ var { randomBytes, createHash: createHash5 } = __require("crypto");
23364
23364
  var { Duplex, Readable } = __require("stream");
23365
23365
  var { URL: URL2 } = __require("url");
23366
23366
  var PerMessageDeflate = require_permessage_deflate();
@@ -24028,7 +24028,7 @@ var require_websocket = __commonJS({
24028
24028
  abortHandshake(websocket, socket, "Invalid Upgrade header");
24029
24029
  return;
24030
24030
  }
24031
- const digest = createHash4("sha1").update(key + GUID).digest("base64");
24031
+ const digest = createHash5("sha1").update(key + GUID).digest("base64");
24032
24032
  if (res.headers["sec-websocket-accept"] !== digest) {
24033
24033
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
24034
24034
  return;
@@ -24397,7 +24397,7 @@ var require_websocket_server = __commonJS({
24397
24397
  var EventEmitter = __require("events");
24398
24398
  var http = __require("http");
24399
24399
  var { Duplex } = __require("stream");
24400
- var { createHash: createHash4 } = __require("crypto");
24400
+ var { createHash: createHash5 } = __require("crypto");
24401
24401
  var extension = require_extension();
24402
24402
  var PerMessageDeflate = require_permessage_deflate();
24403
24403
  var subprotocol = require_subprotocol();
@@ -24704,7 +24704,7 @@ var require_websocket_server = __commonJS({
24704
24704
  );
24705
24705
  }
24706
24706
  if (this._state > RUNNING) return abortHandshake(socket, 503);
24707
- const digest = createHash4("sha1").update(key + GUID).digest("base64");
24707
+ const digest = createHash5("sha1").update(key + GUID).digest("base64");
24708
24708
  const headers = [
24709
24709
  "HTTP/1.1 101 Switching Protocols",
24710
24710
  "Upgrade: websocket",
@@ -126249,9 +126249,172 @@ var recordOwner = (owners, goal) => ({
126249
126249
  });
126250
126250
 
126251
126251
  // src/keepNewest.ts
126252
- var keepNewest = (record2, limit) => {
126253
- const entries = Object.entries(record2);
126254
- return entries.length <= limit ? record2 : Object.fromEntries(entries.slice(entries.length - limit));
126252
+ var keepNewest = (record3, limit) => {
126253
+ const entries = Object.entries(record3);
126254
+ return entries.length <= limit ? record3 : Object.fromEntries(entries.slice(entries.length - limit));
126255
+ };
126256
+
126257
+ // src/boundedMap.ts
126258
+ var makeBoundedMap = (limit) => {
126259
+ const entries = /* @__PURE__ */ new Map();
126260
+ return {
126261
+ get: (key) => entries.get(key),
126262
+ set: (key, value2) => {
126263
+ entries.delete(key);
126264
+ entries.set(key, value2);
126265
+ if (entries.size > limit) {
126266
+ const oldest = entries.keys().next().value;
126267
+ if (oldest !== void 0) entries.delete(oldest);
126268
+ }
126269
+ },
126270
+ /** 忘掉键满足条件的那些。 */
126271
+ forget: (matches) => {
126272
+ for (const key of [...entries.keys()]) if (matches(key)) entries.delete(key);
126273
+ }
126274
+ };
126275
+ };
126276
+
126277
+ // src/replyAnchors.ts
126278
+ var THREAD_LIMIT = 256;
126279
+ var TURNS_PER_THREAD = 16;
126280
+ var anchorOfMessage = (adapter, message) => adapter?.quotesReplies === true ? { messageId: message.messageId, inThread: message.replyInThread === true } : void 0;
126281
+ var userMessageIdOfTurn = (execution, turnId) => {
126282
+ const intent = execution.turnIntents.find((candidate) => candidate.turn?.turnId === turnId);
126283
+ const latest = execution.latestTurn;
126284
+ return intent?.messageId ?? (latest?.turnId === turnId ? latest.userMessageId : void 0);
126285
+ };
126286
+ var makeReplyAnchors = (host) => {
126287
+ const byThread = makeBoundedMap(THREAD_LIMIT);
126288
+ const userMessageOfTurn = async (threadId, turnId) => {
126289
+ const execution = await host.projections.getThreadExecution(threadId).catch(() => null);
126290
+ if (execution === null) return void 0;
126291
+ const wanted = turnId ?? execution.session?.activeTurnId ?? execution.latestTurn?.turnId;
126292
+ return wanted == null ? void 0 : userMessageIdOfTurn(execution, wanted);
126293
+ };
126294
+ return {
126295
+ /**
126296
+ * 开轮时记一笔:`userMessageId` 是派给宿主的那条用户消息,`binding` 是收到聊天消息的那个
126297
+ * 聊天(带着它的锚点)。没有锚点(平台不引用)就不记。
126298
+ */
126299
+ note: (threadId, userMessageId, binding) => {
126300
+ if (binding.reply === void 0) return;
126301
+ const turns = byThread.get(threadId) ?? makeBoundedMap(TURNS_PER_THREAD);
126302
+ byThread.set(threadId, turns);
126303
+ turns.set(userMessageId, { chatKey: chatKeyOf(binding), anchor: binding.reply });
126304
+ },
126305
+ /**
126306
+ * 给发往这条会话的聊天挂上 `turnId` 那一轮的锚点(null 的含义见上);取不到就原样返回。
126307
+ *
126308
+ * 锚点只配给它那条消息所在的聊天:会话可以被另一个聊天接走(`/resume`),而回复接口只按被
126309
+ * 回复的消息寻址,挂错了答复就发回原来的聊天。
126310
+ */
126311
+ anchored: async (binding, threadId, turnId) => {
126312
+ const turns = byThread.get(threadId);
126313
+ if (turns === void 0) return binding;
126314
+ const asked = await userMessageOfTurn(threadId, turnId);
126315
+ const noted = asked === void 0 ? void 0 : turns.get(asked);
126316
+ return noted?.chatKey === chatKeyOf(binding) ? { ...binding, reply: noted.anchor } : binding;
126317
+ }
126318
+ };
126319
+ };
126320
+
126321
+ // src/conclusionLedger.ts
126322
+ var CONCLUSION_DEBT_LIMIT = 500;
126323
+ var keyOf = (threadId, userMessageId) => JSON.stringify([threadId, userMessageId]);
126324
+ var conclusionDebtsOf = (state) => state.conclusions ?? {};
126325
+ var foldConclusionEvent = (state, name, data) => {
126326
+ const threadId = data["threadId"];
126327
+ const userMessageId = data["userMessageId"];
126328
+ if (typeof threadId !== "string" || threadId === "" || typeof userMessageId !== "string" || userMessageId === "") {
126329
+ return state;
126330
+ }
126331
+ const key = keyOf(threadId, userMessageId);
126332
+ const pending = conclusionDebtsOf(state);
126333
+ if (name === "conclusion-delivered") {
126334
+ const { [key]: _delivered, ...rest } = pending;
126335
+ return { ...state, conclusions: rest };
126336
+ }
126337
+ const chatKey = data["chatKey"];
126338
+ const recordedAt = data["recordedAt"];
126339
+ if (typeof chatKey !== "string" || chatKey === "" || typeof recordedAt !== "string" || !Number.isFinite(Date.parse(recordedAt))) {
126340
+ return state;
126341
+ }
126342
+ return {
126343
+ ...state,
126344
+ conclusions: keepNewest(
126345
+ { ...pending, [key]: { chatKey, threadId, userMessageId, recordedAt } },
126346
+ CONCLUSION_DEBT_LIMIT
126347
+ )
126348
+ };
126349
+ };
126350
+ var makeConclusionLedger = (core) => {
126351
+ const inFlight = /* @__PURE__ */ new Set();
126352
+ const registering = /* @__PURE__ */ new Map();
126353
+ const delivered = makeBoundedMap(CONCLUSION_DEBT_LIMIT);
126354
+ let disposed = false;
126355
+ core.host.onDispose(() => {
126356
+ disposed = true;
126357
+ });
126358
+ const settle = (debt) => {
126359
+ delivered.set(keyOf(debt.threadId, debt.userMessageId), true);
126360
+ core.commit("conclusion-delivered", {
126361
+ threadId: debt.threadId,
126362
+ userMessageId: debt.userMessageId
126363
+ });
126364
+ };
126365
+ return {
126366
+ starting: (threadId) => {
126367
+ const gate = Promise.withResolvers();
126368
+ const pending = registering.get(threadId) ?? /* @__PURE__ */ new Set();
126369
+ registering.set(threadId, pending);
126370
+ pending.add(gate.promise);
126371
+ return () => {
126372
+ pending.delete(gate.promise);
126373
+ if (pending.size === 0) registering.delete(threadId);
126374
+ gate.resolve();
126375
+ };
126376
+ },
126377
+ owe: (debt) => {
126378
+ const pending = conclusionDebtsOf(core.state);
126379
+ if (Object.keys(pending).length >= CONCLUSION_DEBT_LIMIT && pending[keyOf(debt.threadId, debt.userMessageId)] === void 0) {
126380
+ core.host.log.warn("chat dropped its oldest conclusion debt at capacity", {
126381
+ oldest: Object.values(pending)[0],
126382
+ limit: CONCLUSION_DEBT_LIMIT
126383
+ });
126384
+ }
126385
+ core.commit("conclusion-owed", { ...debt });
126386
+ },
126387
+ forTurn: async (threadId, turnId = null) => {
126388
+ await Promise.all(registering.get(threadId) ?? []);
126389
+ const execution = await core.host.projections.getThreadExecution(threadId);
126390
+ const userMessageId = execution === null ? void 0 : turnId === null ? execution.latestTurn?.userMessageId : userMessageIdOfTurn(execution, turnId);
126391
+ const key = userMessageId === void 0 ? void 0 : keyOf(threadId, userMessageId);
126392
+ return {
126393
+ debt: key === void 0 ? void 0 : conclusionDebtsOf(core.state)[key],
126394
+ handled: key !== void 0 && (delivered.get(key) === true || inFlight.has(key)),
126395
+ execution
126396
+ };
126397
+ },
126398
+ deliver: async (debt, send) => {
126399
+ const key = keyOf(debt.threadId, debt.userMessageId);
126400
+ if (disposed || inFlight.has(key) || conclusionDebtsOf(core.state)[key] === void 0)
126401
+ return false;
126402
+ inFlight.add(key);
126403
+ try {
126404
+ const sent = await send();
126405
+ if (sent) settle(debt);
126406
+ return sent;
126407
+ } finally {
126408
+ inFlight.delete(key);
126409
+ }
126410
+ },
126411
+ discard: (debt) => {
126412
+ const key = keyOf(debt.threadId, debt.userMessageId);
126413
+ if (!disposed && !inFlight.has(key) && conclusionDebtsOf(core.state)[key] !== void 0) {
126414
+ settle(debt);
126415
+ }
126416
+ }
126417
+ };
126255
126418
  };
126256
126419
 
126257
126420
  // src/chatState.ts
@@ -126286,8 +126449,8 @@ var chatOf = (state, chatKey) => ({
126286
126449
  ...DEFAULT_CHAT_RECORD,
126287
126450
  ...state.chats[chatKey]
126288
126451
  });
126289
- var chatsOnProject = (state, projectId) => Object.entries(state.chats).filter(([, record2]) => record2.projectId === projectId).map(([chatKey]) => chatKey);
126290
- var chatOfThread = (state, threadId) => Object.entries(state.chats).find(([, record2]) => record2.threadId === threadId)?.[0] ?? state.teamGoals?.[threadId]?.chatKey;
126452
+ var chatsOnProject = (state, projectId) => Object.entries(state.chats).filter(([, record3]) => record3.projectId === projectId).map(([chatKey]) => chatKey);
126453
+ var chatOfThread = (state, threadId) => Object.entries(state.chats).find(([, record3]) => record3.threadId === threadId)?.[0] ?? state.teamGoals?.[threadId]?.chatKey;
126291
126454
  var findApprovalByToken = (state, token) => {
126292
126455
  for (const approval of Object.values(state.approvals)) {
126293
126456
  const button = approval.options.find((option) => option.token === token);
@@ -126299,29 +126462,29 @@ var openApprovalOf = (state, chatKey) => Object.values(state.approvals).findLast
126299
126462
  (approval) => approval.chatKey === chatKey && !approval.resolved
126300
126463
  );
126301
126464
  var findQuestionByToken = (state, token) => {
126302
- for (const record2 of Object.values(questionsOf(state))) {
126303
- for (const item of record2.items) {
126465
+ for (const record3 of Object.values(questionsOf(state))) {
126466
+ for (const item of record3.items) {
126304
126467
  const option = item.options.find((candidate) => candidate.token === token);
126305
- if (option !== void 0) return { record: record2, item, click: { kind: "option", option } };
126306
- if (item.submitToken === token) return { record: record2, item, click: { kind: "submit" } };
126468
+ if (option !== void 0) return { record: record3, item, click: { kind: "option", option } };
126469
+ if (item.submitToken === token) return { record: record3, item, click: { kind: "submit" } };
126307
126470
  }
126308
126471
  }
126309
126472
  return void 0;
126310
126473
  };
126311
126474
  var openQuestionOf = (state, chatKey, threadId) => Object.values(questionsOf(state)).findLast(
126312
- (record2) => record2.chatKey === chatKey && record2.threadId === threadId && !record2.resolved
126475
+ (record3) => record3.chatKey === chatKey && record3.threadId === threadId && !record3.resolved
126313
126476
  );
126314
126477
  var isUnanswered = (item) => item.answer === void 0;
126315
126478
  var findQueueByToken = (state, token) => {
126316
- for (const record2 of Object.values(queueOf(state))) {
126317
- if (record2.steerToken === token) return { record: record2, op: "steer" };
126318
- if (record2.withdrawToken === token) return { record: record2, op: "withdraw" };
126319
- if (record2.resumeToken === token) return { record: record2, op: "resume" };
126479
+ for (const record3 of Object.values(queueOf(state))) {
126480
+ if (record3.steerToken === token) return { record: record3, op: "steer" };
126481
+ if (record3.withdrawToken === token) return { record: record3, op: "withdraw" };
126482
+ if (record3.resumeToken === token) return { record: record3, op: "resume" };
126320
126483
  }
126321
126484
  return void 0;
126322
126485
  };
126323
126486
  var openQueueOfThread = (state, threadId) => Object.values(queueOf(state)).filter(
126324
- (record2) => record2.threadId === threadId && record2.settled === void 0
126487
+ (record3) => record3.threadId === threadId && record3.settled === void 0
126325
126488
  );
126326
126489
  var isRecord = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
126327
126490
  var nonEmpty3 = (value2) => typeof value2 === "string" && value2 !== "" ? value2 : null;
@@ -126394,24 +126557,24 @@ var readAnswer = (value2) => {
126394
126557
  return null;
126395
126558
  };
126396
126559
  var withQuestionItem = (state, requestId, questionId, patch) => {
126397
- const record2 = requestId === null ? void 0 : questionsOf(state)[requestId];
126398
- if (requestId === null || questionId === null || record2 === void 0) return state;
126399
- if (!record2.items.some((item) => item.id === questionId)) return state;
126560
+ const record3 = requestId === null ? void 0 : questionsOf(state)[requestId];
126561
+ if (requestId === null || questionId === null || record3 === void 0) return state;
126562
+ if (!record3.items.some((item) => item.id === questionId)) return state;
126400
126563
  return {
126401
126564
  ...state,
126402
126565
  questions: {
126403
126566
  ...questionsOf(state),
126404
126567
  [requestId]: {
126405
- ...record2,
126406
- items: record2.items.map((item) => item.id === questionId ? patch(item) : item)
126568
+ ...record3,
126569
+ items: record3.items.map((item) => item.id === questionId ? patch(item) : item)
126407
126570
  }
126408
126571
  }
126409
126572
  };
126410
126573
  };
126411
126574
  var withQueueRecord = (state, intentId, patch) => {
126412
- const record2 = intentId === null ? void 0 : queueOf(state)[intentId];
126413
- if (intentId === null || record2 === void 0) return state;
126414
- return { ...state, queue: { ...queueOf(state), [intentId]: patch(record2) } };
126575
+ const record3 = intentId === null ? void 0 : queueOf(state)[intentId];
126576
+ if (intentId === null || record3 === void 0) return state;
126577
+ return { ...state, queue: { ...queueOf(state), [intentId]: patch(record3) } };
126415
126578
  };
126416
126579
  var withChat = (state, chatKey, patch) => ({
126417
126580
  ...state,
@@ -126420,6 +126583,9 @@ var withChat = (state, chatKey, patch) => ({
126420
126583
  var foldImEvent = (state, name, data) => {
126421
126584
  if (!isRecord(data)) return state;
126422
126585
  switch (name) {
126586
+ case "conclusion-owed":
126587
+ case "conclusion-delivered":
126588
+ return foldConclusionEvent(state, name, data);
126423
126589
  case "chat-updated": {
126424
126590
  const chatKey = nonEmpty3(data["chatKey"]);
126425
126591
  if (chatKey === null) return state;
@@ -126467,10 +126633,10 @@ var foldImEvent = (state, name, data) => {
126467
126633
  return { ...bound, teamGoals: recordOwner(bound.teamGoals, goal) };
126468
126634
  }
126469
126635
  case "thread-attached": {
126470
- const record2 = readAttached(data);
126471
- if (record2 === null) return state;
126472
- const key = attachedKey(record2.chatKey, record2.threadId);
126473
- const attached = { ...state.attached, [key]: record2 };
126636
+ const record3 = readAttached(data);
126637
+ if (record3 === null) return state;
126638
+ const key = attachedKey(record3.chatKey, record3.threadId);
126639
+ const attached = { ...state.attached, [key]: record3 };
126474
126640
  return { ...state, attached: keepNewest(attached, ATTACHED_THREAD_LIMIT) };
126475
126641
  }
126476
126642
  case "approval-offered": {
@@ -126611,21 +126777,21 @@ var foldImEvent = (state, name, data) => {
126611
126777
  case "queue-carded": {
126612
126778
  const cardId = nonEmpty3(data["cardId"]);
126613
126779
  if (cardId === null) return state;
126614
- return withQueueRecord(state, nonEmpty3(data["intentId"]), (record2) => ({
126615
- ...record2,
126780
+ return withQueueRecord(state, nonEmpty3(data["intentId"]), (record3) => ({
126781
+ ...record3,
126616
126782
  cardId
126617
126783
  }));
126618
126784
  }
126619
126785
  case "queue-steering":
126620
- return withQueueRecord(state, nonEmpty3(data["intentId"]), (record2) => ({
126621
- ...record2,
126786
+ return withQueueRecord(state, nonEmpty3(data["intentId"]), (record3) => ({
126787
+ ...record3,
126622
126788
  steering: data["steering"] === true
126623
126789
  }));
126624
126790
  case "queue-settled": {
126625
126791
  const outcome = data["outcome"];
126626
126792
  if (outcome !== "steered" && outcome !== "sent" && outcome !== "removed") return state;
126627
- return withQueueRecord(state, nonEmpty3(data["intentId"]), (record2) => ({
126628
- ...record2,
126793
+ return withQueueRecord(state, nonEmpty3(data["intentId"]), (record3) => ({
126794
+ ...record3,
126629
126795
  settled: outcome
126630
126796
  }));
126631
126797
  }
@@ -126750,18 +126916,18 @@ var readDeliveryTargets = (config2) => {
126750
126916
  if (typeof table2 !== "object" || table2 === null) return [];
126751
126917
  return Object.entries(table2).flatMap(([rowId, row]) => {
126752
126918
  if (typeof row !== "object" || row === null) return [];
126753
- const record2 = row;
126754
- const kind = readString(record2, "kind") || "chat";
126755
- if (targetIssues(record2).length > 0 || !isTargetKind(kind)) return [];
126756
- const targetId = readString(record2, "targetId");
126919
+ const record3 = row;
126920
+ const kind = readString(record3, "kind") || "chat";
126921
+ if (targetIssues(record3).length > 0 || !isTargetKind(kind)) return [];
126922
+ const targetId = readString(record3, "targetId");
126757
126923
  return [
126758
126924
  {
126759
126925
  rowId,
126760
- botId: readString(record2, DELIVERY_BOT_COLUMN),
126926
+ botId: readString(record3, DELIVERY_BOT_COLUMN),
126761
126927
  targetId,
126762
- name: readString(record2, "name") || targetId,
126928
+ name: readString(record3, "name") || targetId,
126763
126929
  kind,
126764
- platformChatId: readString(record2, "platformChatId")
126930
+ platformChatId: readString(record3, "platformChatId")
126765
126931
  }
126766
126932
  ];
126767
126933
  });
@@ -127154,8 +127320,8 @@ var makeApprovals = (core, anchors) => {
127154
127320
  host.log.info("chat asked for an approval", { threadId, requestId: offer.requestId });
127155
127321
  if (cardId === null) return;
127156
127322
  commit("approval-carded", { requestId: offer.requestId, cardId });
127157
- const record2 = core.state.approvals[offer.requestId];
127158
- if (record2?.resolved === true) await settleApprovalCard(record2, { kind: "elsewhere" });
127323
+ const record3 = core.state.approvals[offer.requestId];
127324
+ if (record3?.resolved === true) await settleApprovalCard(record3, { kind: "elsewhere" });
127159
127325
  } catch (error) {
127160
127326
  host.log.warn("chat could not ask for the approval", { error: describeError(error) });
127161
127327
  }
@@ -127974,16 +128140,16 @@ var parseSendBody = (raw) => {
127974
128140
  return "bad-request";
127975
128141
  }
127976
128142
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return "bad-request";
127977
- const record2 = parsed;
127978
- const botId = record2["botId"];
127979
- const targetId = record2["targetId"];
127980
- const text4 = record2["text"];
127981
- const format2 = record2["format"];
128143
+ const record3 = parsed;
128144
+ const botId = record3["botId"];
128145
+ const targetId = record3["targetId"];
128146
+ const text4 = record3["text"];
128147
+ const format2 = record3["format"];
127982
128148
  if (typeof botId !== "string" || botId.trim() === "") return "bad-request";
127983
128149
  if (typeof targetId !== "string" || targetId.trim() === "") return "bad-request";
127984
128150
  if (typeof text4 !== "string" || text4.trim() === "") return "bad-request";
127985
128151
  if (format2 !== void 0 && format2 !== "plain" && format2 !== "markdown") return "bad-request";
127986
- for (const key of Object.keys(record2)) {
128152
+ for (const key of Object.keys(record3)) {
127987
128153
  if (key !== "botId" && key !== "targetId" && key !== "text" && key !== "format") {
127988
128154
  return "bad-request";
127989
128155
  }
@@ -128177,11 +128343,15 @@ var makeImCore = (input) => {
128177
128343
  };
128178
128344
  const deliver = async (binding, text4, attachments = []) => {
128179
128345
  await postImages(binding, attachments);
128180
- if (text4.trim() === "") return;
128346
+ if (text4.trim() === "") return true;
128347
+ const adapter = adapterFor(binding.platform);
128348
+ if (adapter === void 0) return false;
128181
128349
  try {
128182
- await adapterFor(binding.platform)?.sendText(binding, text4);
128350
+ await adapter.sendText(binding, text4);
128351
+ return true;
128183
128352
  } catch (error) {
128184
128353
  host.log.warn("chat could not deliver", { error: describeError(error) });
128354
+ return false;
128185
128355
  }
128186
128356
  };
128187
128357
  const bots = () => readBots(input.config(), adapterFor);
@@ -128209,6 +128379,93 @@ var makeImCore = (input) => {
128209
128379
  };
128210
128380
  };
128211
128381
 
128382
+ // src/conclusionDelivery.ts
128383
+ import { threadOrNull as threadOrNull3 } from "@ycodium-ai/plugin-api";
128384
+
128385
+ // src/threadMessages.ts
128386
+ import {
128387
+ isThreadNotFound,
128388
+ threadMessagesNewestFirst
128389
+ } from "@ycodium-ai/plugin-api";
128390
+ var PAGE_SIZE = 20;
128391
+ async function* newestFirst(threads, threadId) {
128392
+ try {
128393
+ yield* threadMessagesNewestFirst(threads, threadId, { pageSize: PAGE_SIZE });
128394
+ } catch (error) {
128395
+ if (!isThreadNotFound(error)) throw error;
128396
+ }
128397
+ }
128398
+ async function attachmentsOf(threads, threadId, message) {
128399
+ const collected = [...message.attachments ?? []];
128400
+ const total = message.attachmentsTotal ?? collected.length;
128401
+ while (collected.length < total) {
128402
+ let page;
128403
+ try {
128404
+ page = await threads.messages.list({
128405
+ threadId,
128406
+ messageId: message.id,
128407
+ attachmentOffset: collected.length,
128408
+ textLimit: 2
128409
+ });
128410
+ } catch (error) {
128411
+ if (isThreadNotFound(error)) break;
128412
+ throw error;
128413
+ }
128414
+ const more = page.items[0]?.attachments ?? [];
128415
+ if (more.length === 0) break;
128416
+ collected.push(...more);
128417
+ }
128418
+ return collected;
128419
+ }
128420
+ async function latestMessage(threads, threadId, accept) {
128421
+ for await (const message of newestFirst(threads, threadId)) {
128422
+ if (accept(message)) return message;
128423
+ }
128424
+ return void 0;
128425
+ }
128426
+ async function messagesAfterLastUser(threads, threadId) {
128427
+ const after = [];
128428
+ for await (const message of newestFirst(threads, threadId)) {
128429
+ if (message.role === "user") break;
128430
+ after.push(message);
128431
+ }
128432
+ return after.toReversed();
128433
+ }
128434
+
128435
+ // src/conclusionDelivery.ts
128436
+ var makeConclusionDelivery = (core, anchors) => {
128437
+ let disposed = false;
128438
+ core.host.onDispose(() => {
128439
+ disposed = true;
128440
+ });
128441
+ const marked = (text4, recovered) => recovered ? `${core.t("reply.conclusionRecovered")}
128442
+ ${text4}` : text4;
128443
+ return {
128444
+ prepareConclusion: async (threadId, turnId = null) => {
128445
+ const thread = await threadOrNull3(core.host.threads, threadId);
128446
+ const final = thread === null ? void 0 : await latestMessage(
128447
+ core.host.threads,
128448
+ threadId,
128449
+ (message) => message.role === "assistant" && message.text.trim() !== ""
128450
+ );
128451
+ const attachments = final === void 0 ? [] : await attachmentsOf(core.host.threads, threadId, final);
128452
+ return async (binding, options) => {
128453
+ if (disposed || thread === null) return false;
128454
+ if (final === void 0) return options.streaming;
128455
+ const text4 = options.streaming ? "" : marked(`${thread.title}
128456
+
128457
+ ${final.text}`, options.recovered);
128458
+ const anchored = await anchors.anchored(binding, threadId, turnId ?? final.turnId);
128459
+ return !disposed && core.deliver(anchored, text4, attachments);
128460
+ };
128461
+ },
128462
+ failure: async (binding, threadId, notice, recovered, turnId = null) => {
128463
+ const anchored = await anchors.anchored(binding, threadId, turnId);
128464
+ return !disposed && core.deliver(anchored, marked(notice, recovered));
128465
+ }
128466
+ };
128467
+ };
128468
+
128212
128469
  // src/attachments.ts
128213
128470
  var IMAGE_LIMIT_BYTES = 10 * 1024 * 1024;
128214
128471
  var FILE_LIMIT_BYTES = 25 * 1024 * 1024;
@@ -128501,68 +128758,6 @@ var makeCooldown = (windowMs, now, maxKeys = Number.POSITIVE_INFINITY) => {
128501
128758
  };
128502
128759
  };
128503
128760
 
128504
- // src/boundedMap.ts
128505
- var makeBoundedMap = (limit) => {
128506
- const entries = /* @__PURE__ */ new Map();
128507
- return {
128508
- get: (key) => entries.get(key),
128509
- set: (key, value2) => {
128510
- entries.delete(key);
128511
- entries.set(key, value2);
128512
- if (entries.size > limit) {
128513
- const oldest = entries.keys().next().value;
128514
- if (oldest !== void 0) entries.delete(oldest);
128515
- }
128516
- },
128517
- /** 忘掉键满足条件的那些。 */
128518
- forget: (matches) => {
128519
- for (const key of [...entries.keys()]) if (matches(key)) entries.delete(key);
128520
- }
128521
- };
128522
- };
128523
-
128524
- // src/replyAnchors.ts
128525
- var THREAD_LIMIT = 256;
128526
- var TURNS_PER_THREAD = 16;
128527
- var anchorOfMessage = (adapter, message) => adapter?.quotesReplies === true ? { messageId: message.messageId, inThread: message.replyInThread === true } : void 0;
128528
- var makeReplyAnchors = (host) => {
128529
- const byThread = makeBoundedMap(THREAD_LIMIT);
128530
- const userMessageOfTurn = async (threadId, turnId) => {
128531
- const execution = await host.projections.getThreadExecution(threadId).catch(() => null);
128532
- if (execution === null) return void 0;
128533
- const latest = execution.latestTurn;
128534
- const wanted = turnId ?? execution.session?.activeTurnId ?? latest?.turnId;
128535
- if (wanted == null) return void 0;
128536
- const intent = execution.turnIntents.find((candidate) => candidate.turn?.turnId === wanted);
128537
- return intent?.messageId ?? (latest?.turnId === wanted ? latest.userMessageId : void 0);
128538
- };
128539
- return {
128540
- /**
128541
- * 开轮时记一笔:`userMessageId` 是派给宿主的那条用户消息,`binding` 是收到聊天消息的那个
128542
- * 聊天(带着它的锚点)。没有锚点(平台不引用)就不记。
128543
- */
128544
- note: (threadId, userMessageId, binding) => {
128545
- if (binding.reply === void 0) return;
128546
- const turns = byThread.get(threadId) ?? makeBoundedMap(TURNS_PER_THREAD);
128547
- byThread.set(threadId, turns);
128548
- turns.set(userMessageId, { chatKey: chatKeyOf(binding), anchor: binding.reply });
128549
- },
128550
- /**
128551
- * 给发往这条会话的聊天挂上 `turnId` 那一轮的锚点(null 的含义见上);取不到就原样返回。
128552
- *
128553
- * 锚点只配给它那条消息所在的聊天:会话可以被另一个聊天接走(`/resume`),而回复接口只按被
128554
- * 回复的消息寻址,挂错了答复就发回原来的聊天。
128555
- */
128556
- anchored: async (binding, threadId, turnId) => {
128557
- const turns = byThread.get(threadId);
128558
- if (turns === void 0) return binding;
128559
- const asked = await userMessageOfTurn(threadId, turnId);
128560
- const noted = asked === void 0 ? void 0 : turns.get(asked);
128561
- return noted?.chatKey === chatKeyOf(binding) ? { ...binding, reply: noted.anchor } : binding;
128562
- }
128563
- };
128564
- };
128565
-
128566
128761
  // src/inbound.ts
128567
128762
  var SEEN_MESSAGE_LIMIT = 256;
128568
128763
  var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
@@ -128600,10 +128795,9 @@ var makeInbound = (core, deps) => {
128600
128795
  });
128601
128796
  if (sender === null) return;
128602
128797
  if (!notAllowedReplyCooldown.tryAcquire(`${target2.rowId}:${sender}`)) return;
128603
- enqueueForChat(
128604
- chatKeyOf(target2),
128605
- () => deliver(target2, t("reply.notAllowedYet", { id: sender }))
128606
- );
128798
+ enqueueForChat(chatKeyOf(target2), async () => {
128799
+ await deliver(target2, t("reply.notAllowedYet", { id: sender }));
128800
+ });
128607
128801
  };
128608
128802
  const currentBot = (rowId) => bots().find((candidate) => candidate.rowId === rowId);
128609
128803
  const inheritFromParent = (bot, chatId, parentChatId) => {
@@ -128857,35 +129051,35 @@ var makeQuestionFlow = (host) => {
128857
129051
  const itemOf = (requestId, questionId) => recordOf(requestId)?.items.find((item) => item.id === questionId);
128858
129052
  const hasButtons = (chatKey) => host.target(chatKey)?.adapter.choiceCards !== void 0;
128859
129053
  const redraw = async (requestId, questionId) => {
128860
- const record2 = recordOf(requestId);
129054
+ const record3 = recordOf(requestId);
128861
129055
  const item = itemOf(requestId, questionId);
128862
- const target2 = record2 === void 0 ? void 0 : host.target(record2.chatKey);
128863
- if (record2 === void 0 || item?.cardId === void 0 || target2 === void 0) return;
128864
- if (record2.resolved || item.answer !== void 0) return;
129056
+ const target2 = record3 === void 0 ? void 0 : host.target(record3.chatKey);
129057
+ if (record3 === void 0 || item?.cardId === void 0 || target2 === void 0) return;
129058
+ if (record3.resolved || item.answer !== void 0) return;
128865
129059
  try {
128866
129060
  await target2.adapter.choiceCards?.update(
128867
129061
  target2.binding,
128868
129062
  item.cardId,
128869
- questionCardOf(item, record2.items.indexOf(item), record2.items.length, t)
129063
+ questionCardOf(item, record3.items.indexOf(item), record3.items.length, t)
128870
129064
  );
128871
129065
  } catch (error) {
128872
129066
  host.warn("chat could not update the question card", error);
128873
129067
  }
128874
129068
  };
128875
129069
  const freeze = async (requestId, questionId) => {
128876
- const record2 = recordOf(requestId);
129070
+ const record3 = recordOf(requestId);
128877
129071
  const item = itemOf(requestId, questionId);
128878
- const target2 = record2 === void 0 ? void 0 : host.target(record2.chatKey);
128879
- if (record2 === void 0 || item?.cardId === void 0 || target2 === void 0) return;
129072
+ const target2 = record3 === void 0 ? void 0 : host.target(record3.chatKey);
129073
+ if (record3 === void 0 || item?.cardId === void 0 || target2 === void 0) return;
128880
129074
  try {
128881
129075
  await target2.adapter.choiceCards?.update(
128882
129076
  target2.binding,
128883
129077
  item.cardId,
128884
129078
  questionSettledCardOf(
128885
129079
  item,
128886
- record2.items.indexOf(item),
128887
- record2.items.length,
128888
- record2.ending ?? "here",
129080
+ record3.items.indexOf(item),
129081
+ record3.items.length,
129082
+ record3.ending ?? "here",
128889
129083
  t
128890
129084
  )
128891
129085
  );
@@ -128894,13 +129088,13 @@ var makeQuestionFlow = (host) => {
128894
129088
  }
128895
129089
  };
128896
129090
  const sayNext = async (requestId) => {
128897
- const record2 = recordOf(requestId);
128898
- if (record2 === void 0 || record2.resolved) return;
128899
- const item = record2.items.find(isUnanswered);
129091
+ const record3 = recordOf(requestId);
129092
+ if (record3 === void 0 || record3.resolved) return;
129093
+ const item = record3.items.find(isUnanswered);
128900
129094
  if (item === void 0) return;
128901
129095
  await host.say(
128902
- record2.chatKey,
128903
- questionPlainText(item, record2.items.indexOf(item), record2.items.length, t)
129096
+ record3.chatKey,
129097
+ questionPlainText(item, record3.items.indexOf(item), record3.items.length, t)
128904
129098
  );
128905
129099
  };
128906
129100
  const post = async (requestId) => {
@@ -128913,13 +129107,13 @@ var makeQuestionFlow = (host) => {
128913
129107
  return;
128914
129108
  }
128915
129109
  for (const questionId of first.items.map((item) => item.id)) {
128916
- const record2 = recordOf(requestId);
129110
+ const record3 = recordOf(requestId);
128917
129111
  const item = itemOf(requestId, questionId);
128918
- if (record2 === void 0 || record2.resolved || item === void 0) return;
129112
+ if (record3 === void 0 || record3.resolved || item === void 0) return;
128919
129113
  try {
128920
129114
  const cardId = await cards.open(
128921
- await host.anchored(target2.binding, record2.threadId),
128922
- questionCardOf(item, record2.items.indexOf(item), record2.items.length, t)
129115
+ await host.anchored(target2.binding, record3.threadId),
129116
+ questionCardOf(item, record3.items.indexOf(item), record3.items.length, t)
128923
129117
  );
128924
129118
  if (cardId === null) continue;
128925
129119
  host.commit("question-carded", { requestId, questionId, cardId });
@@ -128999,10 +129193,10 @@ var makeQuestionFlow = (host) => {
128999
129193
  const click = (token, operatorId) => {
129000
129194
  const found = findQuestionByToken(host.state(), token);
129001
129195
  if (found === void 0) return NOT_MINE;
129002
- const { record: record2, item } = found;
129003
- if (record2.resolved || !isUnanswered(item)) return handledWith(t("question.alreadyHandled"));
129004
- if (!host.allowed(record2.chatKey, operatorId)) return handledWith(t("reply.notAllowed"));
129005
- const requestId = record2.requestId;
129196
+ const { record: record3, item } = found;
129197
+ if (record3.resolved || !isUnanswered(item)) return handledWith(t("question.alreadyHandled"));
129198
+ if (!host.allowed(record3.chatKey, operatorId)) return handledWith(t("reply.notAllowed"));
129199
+ const requestId = record3.requestId;
129006
129200
  if (found.click.kind === "option") {
129007
129201
  const option = found.click.option;
129008
129202
  if (!item.multiSelect) {
@@ -129011,7 +129205,7 @@ var makeQuestionFlow = (host) => {
129011
129205
  }
129012
129206
  const selected = item.selected.includes(option.value) ? item.selected.filter((value2) => value2 !== option.value) : [...item.selected, option.value];
129013
129207
  host.commit("question-selected", { requestId, questionId: item.id, selected });
129014
- host.outbound(record2.chatKey, () => redraw(requestId, item.id));
129208
+ host.outbound(record3.chatKey, () => redraw(requestId, item.id));
129015
129209
  return handledWith(null);
129016
129210
  }
129017
129211
  const chosen = item.options.filter((option) => item.selected.includes(option.value));
@@ -129025,29 +129219,29 @@ var makeQuestionFlow = (host) => {
129025
129219
  return handledWith(null);
129026
129220
  };
129027
129221
  const answerByText = async (chatKey, threadId, text4) => {
129028
- const record2 = openQuestionOf(host.state(), chatKey, threadId);
129222
+ const record3 = openQuestionOf(host.state(), chatKey, threadId);
129029
129223
  const trimmed = text4.trim();
129030
- if (record2 === void 0 || trimmed === "") return false;
129031
- const unanswered = record2.items.filter(isUnanswered);
129224
+ if (record3 === void 0 || trimmed === "") return false;
129225
+ const unanswered = record3.items.filter(isUnanswered);
129032
129226
  const first = unanswered[0];
129033
129227
  if (first === void 0) return false;
129034
129228
  if (!hasButtons(chatKey)) {
129035
129229
  const numbered = parseNumberedReply(first, trimmed, t);
129036
129230
  if (numbered !== null) {
129037
- return answer(record2.requestId, first.id, numbered.answer, numbered.label);
129231
+ return answer(record3.requestId, first.id, numbered.answer, numbered.label);
129038
129232
  }
129039
129233
  }
129040
129234
  const freeform = unanswered.find((item) => item.allowFreeform);
129041
129235
  if (freeform === void 0) return false;
129042
- return answer(record2.requestId, freeform.id, trimmed, clip(trimmed, ANSWER_ECHO_MAX_CHARS));
129236
+ return answer(record3.requestId, freeform.id, trimmed, clip(trimmed, ANSWER_ECHO_MAX_CHARS));
129043
129237
  };
129044
129238
  const resolved = (requestId, outcome) => {
129045
- const record2 = recordOf(requestId);
129046
- if (record2 === void 0 || record2.resolved) return;
129239
+ const record3 = recordOf(requestId);
129240
+ if (record3 === void 0 || record3.resolved) return;
129047
129241
  const ending = outcome === "answered" ? "elsewhere" : "cancelled";
129048
129242
  host.commit("question-resolved", { requestId, ending });
129049
- for (const item of record2.items) {
129050
- if (isUnanswered(item)) host.outbound(record2.chatKey, () => freeze(requestId, item.id));
129243
+ for (const item of record3.items) {
129244
+ if (isUnanswered(item)) host.outbound(record3.chatKey, () => freeze(requestId, item.id));
129051
129245
  }
129052
129246
  };
129053
129247
  return { offer, click, answerByText, resolved };
@@ -129062,16 +129256,16 @@ var IN_FLIGHT_STATUSES = /* @__PURE__ */ new Set([
129062
129256
  "reconciling"
129063
129257
  ]);
129064
129258
  var WITHDRAWN_STATUSES = /* @__PURE__ */ new Set(["cancelled", "abandoned"]);
129065
- var queueViewOf = (record2, facts, released) => {
129259
+ var queueViewOf = (record3, facts, released) => {
129066
129260
  const status = facts.intent?.status;
129067
129261
  if (status === void 0) return null;
129068
129262
  if (WITHDRAWN_STATUSES.has(status)) return { kind: "settled", outcome: "removed" };
129069
129263
  if (IN_FLIGHT_STATUSES.has(status))
129070
- return record2.steering ? { kind: "steering" } : { kind: "sending" };
129264
+ return record3.steering ? { kind: "steering" } : { kind: "sending" };
129071
129265
  if (!WAITING_STATUSES.has(status)) {
129072
- return { kind: "settled", outcome: record2.steering ? "steered" : "sent" };
129266
+ return { kind: "settled", outcome: record3.steering ? "steered" : "sent" };
129073
129267
  }
129074
- if (record2.steering && !released) return { kind: "steering" };
129268
+ if (record3.steering && !released) return { kind: "steering" };
129075
129269
  return { kind: "queued", running: facts.activeTurnId !== null, paused: facts.queuePaused };
129076
129270
  };
129077
129271
  var SETTLED_TITLE = {
@@ -129084,8 +129278,8 @@ var SETTLED_NOTE = {
129084
129278
  sent: "queue.sent",
129085
129279
  removed: "queue.removed"
129086
129280
  };
129087
- var queueCardOf = (record2, view, note, t) => {
129088
- const body = clip(record2.text, QUEUE_PREVIEW_MAX_CHARS);
129281
+ var queueCardOf = (record3, view, note, t) => {
129282
+ const body = clip(record3.text, QUEUE_PREVIEW_MAX_CHARS);
129089
129283
  if (view.kind === "steering") {
129090
129284
  return {
129091
129285
  tone: "queue",
@@ -129126,10 +129320,10 @@ var queueCardOf = (record2, view, note, t) => {
129126
129320
  note: note ?? state,
129127
129321
  rows: [
129128
129322
  [
129129
- ...steerable ? [{ label: t("queue.steer"), token: record2.steerToken, style: "primary" }] : [],
129130
- { label: t("queue.withdraw"), token: record2.withdrawToken, style: "default" }
129323
+ ...steerable ? [{ label: t("queue.steer"), token: record3.steerToken, style: "primary" }] : [],
129324
+ { label: t("queue.withdraw"), token: record3.withdrawToken, style: "default" }
129131
129325
  ],
129132
- ...view.paused ? [[{ label: t("queue.resume"), token: record2.resumeToken, style: "primary" }]] : []
129326
+ ...view.paused ? [[{ label: t("queue.resume"), token: record3.resumeToken, style: "primary" }]] : []
129133
129327
  ]
129134
129328
  };
129135
129329
  };
@@ -129186,11 +129380,11 @@ var makeQueueFlow = (host, deps) => {
129186
129380
  resumeToken: host.newToken()
129187
129381
  });
129188
129382
  host.outbound(chatKey, async () => {
129189
- const record2 = recordOf(intentId);
129190
- if (record2 === void 0) return;
129383
+ const record3 = recordOf(intentId);
129384
+ if (record3 === void 0) return;
129191
129385
  try {
129192
129386
  const latest = await deps.readExecution(threadId) ?? execution;
129193
- const view = queueViewOf(record2, factsOf(latest, intentId), false) ?? {
129387
+ const view = queueViewOf(record3, factsOf(latest, intentId), false) ?? {
129194
129388
  kind: "queued",
129195
129389
  running: latest.activeTurnId !== null,
129196
129390
  paused: latest.queuePaused
@@ -129201,7 +129395,7 @@ var makeQueueFlow = (host, deps) => {
129201
129395
  }
129202
129396
  const cardId = await cards.open(
129203
129397
  { ...target2.binding, reply },
129204
- queueCardOf(record2, view, null, t)
129398
+ queueCardOf(record3, view, null, t)
129205
129399
  );
129206
129400
  if (cardId === null) return;
129207
129401
  host.commit("queue-carded", { intentId, cardId });
@@ -129212,99 +129406,99 @@ var makeQueueFlow = (host, deps) => {
129212
129406
  });
129213
129407
  };
129214
129408
  const redraw = async (intentId, released) => {
129215
- const record2 = recordOf(intentId);
129216
- const target2 = record2 === void 0 ? void 0 : host.target(record2.chatKey);
129217
- if (record2 === void 0 || record2.settled !== void 0 || target2 === void 0) return;
129218
- if (record2.cardId === void 0) return;
129409
+ const record3 = recordOf(intentId);
129410
+ const target2 = record3 === void 0 ? void 0 : host.target(record3.chatKey);
129411
+ if (record3 === void 0 || record3.settled !== void 0 || target2 === void 0) return;
129412
+ if (record3.cardId === void 0) return;
129219
129413
  let execution;
129220
129414
  try {
129221
- execution = await deps.readExecution(record2.threadId);
129415
+ execution = await deps.readExecution(record3.threadId);
129222
129416
  } catch (error) {
129223
129417
  host.warn("chat could not read the turn queue", error);
129224
129418
  return;
129225
129419
  }
129226
129420
  if (execution === null) return;
129227
- const view = queueViewOf(record2, factsOf(execution, intentId), released);
129421
+ const view = queueViewOf(record3, factsOf(execution, intentId), released);
129228
129422
  if (view === null) return;
129229
- if (view.kind === "queued" && record2.steering) {
129423
+ if (view.kind === "queued" && record3.steering) {
129230
129424
  host.commit("queue-steering", { intentId, steering: false });
129231
129425
  notes.set(intentId, t("queue.steerFailed"));
129232
129426
  }
129233
129427
  if (view.kind === "settled") notes.delete(intentId);
129234
- await paint(record2, view);
129428
+ await paint(record3, view);
129235
129429
  if (view.kind === "settled") {
129236
129430
  host.commit("queue-settled", { intentId, outcome: view.outcome });
129237
129431
  shown.delete(intentId);
129238
129432
  }
129239
129433
  };
129240
- const paint = async (record2, view) => {
129241
- const target2 = host.target(record2.chatKey);
129434
+ const paint = async (record3, view) => {
129435
+ const target2 = host.target(record3.chatKey);
129242
129436
  const cards = target2?.adapter.choiceCards;
129243
- if (target2 === void 0 || cards === void 0 || record2.cardId === void 0) return;
129244
- const note = notes.get(record2.intentId) ?? null;
129437
+ if (target2 === void 0 || cards === void 0 || record3.cardId === void 0) return;
129438
+ const note = notes.get(record3.intentId) ?? null;
129245
129439
  const key = queueViewKey(view, note);
129246
- if (shown.get(record2.intentId) === key) return;
129440
+ if (shown.get(record3.intentId) === key) return;
129247
129441
  try {
129248
- await cards.update(target2.binding, record2.cardId, queueCardOf(record2, view, note, t));
129249
- shown.set(record2.intentId, key);
129442
+ await cards.update(target2.binding, record3.cardId, queueCardOf(record3, view, note, t));
129443
+ shown.set(record3.intentId, key);
129250
129444
  } catch (error) {
129251
129445
  host.warn("chat could not update the queued message card", error);
129252
129446
  }
129253
129447
  };
129254
129448
  const refresh = (threadId, released = null) => {
129255
- for (const record2 of openQueueOfThread(host.state(), threadId)) {
129256
- host.outbound(record2.chatKey, () => redraw(record2.intentId, record2.intentId === released));
129449
+ for (const record3 of openQueueOfThread(host.state(), threadId)) {
129450
+ host.outbound(record3.chatKey, () => redraw(record3.intentId, record3.intentId === released));
129257
129451
  }
129258
129452
  };
129259
- const run = async (record2, work, failure2) => {
129260
- notes.delete(record2.intentId);
129453
+ const run = async (record3, work, failure2) => {
129454
+ notes.delete(record3.intentId);
129261
129455
  let ok = false;
129262
129456
  try {
129263
129457
  ok = await work();
129264
129458
  } catch (error) {
129265
129459
  host.warn("chat could not change the queue", error);
129266
129460
  }
129267
- if (!ok) notes.set(record2.intentId, failure2);
129268
- host.outbound(record2.chatKey, () => redraw(record2.intentId, false));
129461
+ if (!ok) notes.set(record3.intentId, failure2);
129462
+ host.outbound(record3.chatKey, () => redraw(record3.intentId, false));
129269
129463
  };
129270
- const steer = async (record2) => {
129271
- const execution = await deps.readExecution(record2.threadId);
129464
+ const steer = async (record3) => {
129465
+ const execution = await deps.readExecution(record3.threadId);
129272
129466
  if (execution?.activeTurnId == null) return false;
129273
- host.commit("queue-steering", { intentId: record2.intentId, steering: true });
129467
+ host.commit("queue-steering", { intentId: record3.intentId, steering: true });
129274
129468
  try {
129275
129469
  await host.dispatch({
129276
129470
  type: "thread.turn-intent.run",
129277
129471
  commandId: host.newId(),
129278
- threadId: record2.threadId,
129279
- intentId: record2.intentId,
129472
+ threadId: record3.threadId,
129473
+ intentId: record3.intentId,
129280
129474
  mode: "steer",
129281
129475
  expectedTurnId: execution.activeTurnId,
129282
129476
  createdAt: host.now()
129283
129477
  });
129284
129478
  return true;
129285
129479
  } catch (error) {
129286
- host.commit("queue-steering", { intentId: record2.intentId, steering: false });
129480
+ host.commit("queue-steering", { intentId: record3.intentId, steering: false });
129287
129481
  throw error;
129288
129482
  }
129289
129483
  };
129290
129484
  const click = (token, operatorId) => {
129291
129485
  const found = findQueueByToken(host.state(), token);
129292
129486
  if (found === void 0) return NOT_MINE;
129293
- const { record: record2, op } = found;
129294
- if (record2.settled !== void 0) return handledWith(t("queue.alreadyHandled"));
129295
- if (!host.allowed(record2.chatKey, operatorId)) return handledWith(t("reply.notAllowed"));
129487
+ const { record: record3, op } = found;
129488
+ if (record3.settled !== void 0) return handledWith(t("queue.alreadyHandled"));
129489
+ if (!host.allowed(record3.chatKey, operatorId)) return handledWith(t("reply.notAllowed"));
129296
129490
  if (op === "steer") {
129297
- if (record2.steering) return handledWith(t("queue.alreadyHandled"));
129298
- void run(record2, () => steer(record2), t("queue.steerFailed"));
129491
+ if (record3.steering) return handledWith(t("queue.alreadyHandled"));
129492
+ void run(record3, () => steer(record3), t("queue.steerFailed"));
129299
129493
  } else if (op === "withdraw") {
129300
129494
  void run(
129301
- record2,
129495
+ record3,
129302
129496
  async () => {
129303
129497
  await host.dispatch({
129304
129498
  type: "thread.turn-intent.cancel",
129305
129499
  commandId: host.newId(),
129306
- threadId: record2.threadId,
129307
- intentId: record2.intentId,
129500
+ threadId: record3.threadId,
129501
+ intentId: record3.intentId,
129308
129502
  // 只撤还没开始发送的:已经在路上的由宿主拒绝,而不是被当成「已放弃」。
129309
129503
  expectedStatus: "queued",
129310
129504
  expectedDeliveryCertainty: "not-dispatched",
@@ -129316,12 +129510,12 @@ var makeQueueFlow = (host, deps) => {
129316
129510
  );
129317
129511
  } else {
129318
129512
  void run(
129319
- record2,
129513
+ record3,
129320
129514
  async () => {
129321
129515
  await host.dispatch({
129322
129516
  type: "thread.turn-queue.resume",
129323
129517
  commandId: host.newId(),
129324
- threadId: record2.threadId,
129518
+ threadId: record3.threadId,
129325
129519
  createdAt: host.now()
129326
129520
  });
129327
129521
  return true;
@@ -129453,10 +129647,10 @@ var makeInteractions = (core, deps) => {
129453
129647
  );
129454
129648
  return;
129455
129649
  }
129456
- const record2 = core.state.approvals[resolved.requestId];
129457
- if (record2 === void 0 || record2.resolved) return;
129650
+ const record3 = core.state.approvals[resolved.requestId];
129651
+ if (record3 === void 0 || record3.resolved) return;
129458
129652
  commit("approval-resolved", { requestId: resolved.requestId });
129459
- outbound(record2.chatKey, () => settleApprovalCard(record2, { kind: "elsewhere" }));
129653
+ outbound(record3.chatKey, () => settleApprovalCard(record3, { kind: "elsewhere" }));
129460
129654
  });
129461
129655
  const handleButtonClick = (token, operatorId) => {
129462
129656
  const question = questionFlow.click(token, operatorId);
@@ -129723,17 +129917,17 @@ var upsertLarkBot = (current, result) => {
129723
129917
 
129724
129918
  // src/lark/setup/consolePayloads.ts
129725
129919
  var asRecord2 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
129726
- var pickString = (record2, keys) => {
129920
+ var pickString = (record3, keys) => {
129727
129921
  for (const key of keys) {
129728
- const value2 = record2[key];
129922
+ const value2 = record3[key];
129729
129923
  if (typeof value2 === "string" && value2 !== "") return value2;
129730
129924
  if (typeof value2 === "number" && Number.isFinite(value2)) return String(value2);
129731
129925
  }
129732
129926
  return void 0;
129733
129927
  };
129734
129928
  var pickDeep = (payload, keys) => {
129735
- const record2 = asRecord2(payload);
129736
- return pickString(record2, keys) ?? pickString(asRecord2(record2.data), keys);
129929
+ const record3 = asRecord2(payload);
129930
+ return pickString(record3, keys) ?? pickString(asRecord2(record3.data), keys);
129737
129931
  };
129738
129932
  var uniq = (values) => [...new Set(values.filter((v) => v !== ""))];
129739
129933
  var extractCsrfToken = (html) => {
@@ -129847,9 +130041,9 @@ var extractClientId = (payload) => {
129847
130041
  var extractUploadedUrl = (payload) => pickDeep(payload, ["url"]);
129848
130042
  var extractSecret = (payload) => pickDeep(payload, ["secret"]);
129849
130043
  var extractAppListPage = (payload) => {
129850
- const record2 = asRecord2(payload);
129851
- const data = asRecord2(record2.data);
129852
- const raw = Array.isArray(data.apps) ? data.apps : Array.isArray(record2.apps) ? record2.apps : [];
130044
+ const record3 = asRecord2(payload);
130045
+ const data = asRecord2(record3.data);
130046
+ const raw = Array.isArray(data.apps) ? data.apps : Array.isArray(record3.apps) ? record3.apps : [];
129853
130047
  const apps = [];
129854
130048
  for (const item of raw) {
129855
130049
  const entry = asRecord2(item);
@@ -129861,7 +130055,7 @@ var extractAppListPage = (payload) => {
129861
130055
  description: pickString(entry, ["description", "desc", "appDesc", "app_desc"]) ?? ""
129862
130056
  });
129863
130057
  }
129864
- const total = typeof data.totalCount === "number" ? data.totalCount : typeof record2.totalCount === "number" ? record2.totalCount : void 0;
130058
+ const total = typeof data.totalCount === "number" ? data.totalCount : typeof record3.totalCount === "number" ? record3.totalCount : void 0;
129865
130059
  return { apps, rawCount: raw.length, ...total === void 0 ? {} : { total } };
129866
130060
  };
129867
130061
  var eventIds = (value2) => Array.isArray(value2) ? uniq(
@@ -129907,16 +130101,16 @@ var collectScopes = (value2, out) => {
129907
130101
  return;
129908
130102
  }
129909
130103
  if (value2 === null || typeof value2 !== "object") return;
129910
- const record2 = value2;
129911
- const name = pickString(record2, SCOPE_NAME_KEYS);
129912
- const id = pickString(record2, SCOPE_ID_KEYS);
130104
+ const record3 = value2;
130105
+ const name = pickString(record3, SCOPE_NAME_KEYS);
130106
+ const id = pickString(record3, SCOPE_ID_KEYS);
129913
130107
  if (name !== void 0 && id !== void 0) {
129914
- const flag = SCOPE_GRANTED_KEYS.map((key) => record2[key]).find(
130108
+ const flag = SCOPE_GRANTED_KEYS.map((key) => record3[key]).find(
129915
130109
  (candidate) => typeof candidate === "boolean"
129916
130110
  );
129917
130111
  out.push({ id, name, granted: typeof flag === "boolean" ? flag : void 0 });
129918
130112
  }
129919
- for (const child of Object.values(record2)) {
130113
+ for (const child of Object.values(record3)) {
129920
130114
  if (child !== null && typeof child === "object") collectScopes(child, out);
129921
130115
  }
129922
130116
  };
@@ -129981,13 +130175,13 @@ var extractVersionId = (payload) => {
129981
130175
  return pickString(root, keys) ?? pickString(data, keys) ?? pickString(asRecord2(data.appVersion), keys);
129982
130176
  };
129983
130177
  var visibilityOf = (value2) => {
129984
- const record2 = asRecord2(value2);
129985
- const strings = (key) => Array.isArray(record2[key]) ? record2[key].filter((item) => typeof item === "string") : [];
130178
+ const record3 = asRecord2(value2);
130179
+ const strings = (key) => Array.isArray(record3[key]) ? record3[key].filter((item) => typeof item === "string") : [];
129986
130180
  return {
129987
130181
  departments: strings("departments"),
129988
130182
  members: strings("members"),
129989
130183
  groups: strings("groups"),
129990
- isAll: typeof record2.isAll === "number" ? record2.isAll : 0
130184
+ isAll: typeof record3.isAll === "number" ? record3.isAll : 0
129991
130185
  };
129992
130186
  };
129993
130187
  var extractOnlineVisibility = (payload) => {
@@ -130118,10 +130312,10 @@ var timeoutOf = (kind) => kind === "read" ? TIMEOUT_MS.read : kind === "idempote
130118
130312
  var retryable = (error) => error.kind === "network" || error.kind === "timeout" || error.kind === "http" && (error.status === 429 || (error.status ?? 0) >= 500);
130119
130313
  var summarize = (payload) => {
130120
130314
  if (payload === null || typeof payload !== "object") return "";
130121
- const record2 = payload;
130315
+ const record3 = payload;
130122
130316
  const summary = {};
130123
130317
  for (const key of ["code", "msg", "message", "error", "error_msg"]) {
130124
- if (record2[key] !== void 0) summary[key] = record2[key];
130318
+ if (record3[key] !== void 0) summary[key] = record3[key];
130125
130319
  }
130126
130320
  return JSON.stringify(summary);
130127
130321
  };
@@ -131246,6 +131440,185 @@ var registerLarkSetup = (ctx, options = {}) => {
131246
131440
  return runner;
131247
131441
  };
131248
131442
 
131443
+ // src/larkPost.ts
131444
+ var styledText = (text4, style) => {
131445
+ let out = text4;
131446
+ if (style?.includes("bold")) out = `**${out}**`;
131447
+ if (style?.includes("italic")) out = `*${out}*`;
131448
+ if (style?.includes("lineThrough")) out = `~~${out}~~`;
131449
+ if (style?.includes("underline")) out = `<u>${out}</u>`;
131450
+ return out;
131451
+ };
131452
+ var nonEmpty4 = (value2) => typeof value2 === "string" && value2 !== "";
131453
+ var mediaOf = (element) => element.tag === "img" ? "image" : element.tag === "media" ? "video" : "file";
131454
+ var resourceOf = (element, index) => {
131455
+ if (element.tag === "img" && nonEmpty4(element.image_key)) {
131456
+ return {
131457
+ fileKey: element.image_key,
131458
+ kind: "image",
131459
+ name: `image-${String(index)}.png`,
131460
+ mimeType: "image/png",
131461
+ media: mediaOf(element)
131462
+ };
131463
+ }
131464
+ if ((element.tag === "media" || element.tag === "file") && nonEmpty4(element.file_key)) {
131465
+ const video = element.tag === "media";
131466
+ const name = nonEmpty4(element.file_name) ? element.file_name : video ? `video-${String(index)}.mp4` : `file-${String(index)}`;
131467
+ return {
131468
+ fileKey: element.file_key,
131469
+ kind: "file",
131470
+ name,
131471
+ mimeType: video && !nonEmpty4(element.file_name) ? "video/mp4" : mimeFromName(name),
131472
+ media: mediaOf(element),
131473
+ ...nonEmpty4(element.file_name) ? { fileName: element.file_name } : {}
131474
+ };
131475
+ }
131476
+ return null;
131477
+ };
131478
+ var placeholderOf = (element, index) => `<${mediaOf(element)} ${String(index)}>`;
131479
+ var serializeElement = (element, index, botIds) => {
131480
+ switch (element.tag) {
131481
+ case "text":
131482
+ return styledText(element.text ?? "", element.style);
131483
+ case "a": {
131484
+ const label = element.text ?? element.href ?? "";
131485
+ return nonEmpty4(element.href) ? `[${label}](${element.href})` : label;
131486
+ }
131487
+ case "at": {
131488
+ if (element.user_id !== void 0 && botIds.has(element.user_id)) return "";
131489
+ return nonEmpty4(element.user_name) ? `@${element.user_name}` : "";
131490
+ }
131491
+ case "code_block":
131492
+ return `\`\`\`${element.language ?? ""}
131493
+ ${element.text ?? ""}
131494
+ \`\`\``;
131495
+ case "hr":
131496
+ return "---";
131497
+ case "emotion":
131498
+ return element.emoji_type ?? "";
131499
+ case "img":
131500
+ case "media":
131501
+ case "file":
131502
+ return index === null ? "" : placeholderOf(element, index);
131503
+ default:
131504
+ return "";
131505
+ }
131506
+ };
131507
+ var serializePost = (content, botIds = /* @__PURE__ */ new Set()) => {
131508
+ let parsed;
131509
+ try {
131510
+ parsed = JSON.parse(content);
131511
+ } catch {
131512
+ return void 0;
131513
+ }
131514
+ const elements = (parsed.content ?? []).flat();
131515
+ const resources = [];
131516
+ const indexOf = /* @__PURE__ */ new Map();
131517
+ for (const element of elements) {
131518
+ const resource = resourceOf(element, resources.length + 1);
131519
+ if (resource === null) continue;
131520
+ resources.push(resource);
131521
+ indexOf.set(element, resources.length);
131522
+ }
131523
+ const md2 = parsed.md;
131524
+ if (typeof md2 === "string" && md2.trim() !== "") {
131525
+ const placeholderByKey = /* @__PURE__ */ new Map();
131526
+ for (const element of elements) {
131527
+ const index = indexOf.get(element);
131528
+ const key = element.tag === "img" ? element.image_key : element.file_key;
131529
+ if (index !== void 0 && nonEmpty4(key)) {
131530
+ placeholderByKey.set(key, placeholderOf(element, index));
131531
+ }
131532
+ }
131533
+ const text5 = md2.replace(
131534
+ /!\[(?:img|video|file)\]\(([^)]+)\)/g,
131535
+ (whole, rawKey) => placeholderByKey.get(rawKey.trim()) ?? whole
131536
+ );
131537
+ return { text: text5, resources };
131538
+ }
131539
+ const paragraphs = (parsed.content ?? []).map(
131540
+ (row) => row.map((element) => serializeElement(element, indexOf.get(element) ?? null, botIds)).join("")
131541
+ );
131542
+ const text4 = paragraphs.map((line) => line.trim()).filter((line) => line !== "").join("\n");
131543
+ if (text4 === "" && resources.length === 0) return void 0;
131544
+ return { text: text4, resources };
131545
+ };
131546
+
131547
+ // src/larkReply.ts
131548
+ var record2 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2) ? value2 : null;
131549
+ var cardText = (value2) => {
131550
+ if (Array.isArray(value2)) return value2.map(cardText).filter(Boolean).join("\n");
131551
+ const node = record2(value2);
131552
+ if (node === null) return "";
131553
+ if (["plain_text", "lark_md", "markdown"].includes(String(node["tag"]))) {
131554
+ return typeof node["content"] === "string" ? node["content"] : "";
131555
+ }
131556
+ return ["header", "title", "body", "elements", "text", "fields", "columns"].map((key) => {
131557
+ const part = node[key];
131558
+ return (key === "text" || key === "title") && typeof part === "string" ? part : cardText(part);
131559
+ }).filter(Boolean).join("\n");
131560
+ };
131561
+ var parseLarkReply = (message) => {
131562
+ if (message.deleted) return { unavailableReason: "deleted" };
131563
+ const author = {
131564
+ ...message.sender?.sender_type === "app" ? { fromBot: true } : {},
131565
+ ...message.sender?.sender_name ? { authorName: message.sender.sender_name } : {}
131566
+ };
131567
+ if (message.msg_type === "post") {
131568
+ try {
131569
+ const post = serializePost(message.body?.content ?? "{}");
131570
+ if (post === void 0) return { ...author, unavailableReason: "unsupported" };
131571
+ return {
131572
+ ...author,
131573
+ content: post.text,
131574
+ attachments: post.resources.map((resource) => ({
131575
+ kind: resource.media,
131576
+ ...resource.fileName === void 0 ? {} : { name: resource.fileName }
131577
+ }))
131578
+ };
131579
+ } catch {
131580
+ return { ...author, unavailableReason: "unsupported" };
131581
+ }
131582
+ }
131583
+ let parsed;
131584
+ try {
131585
+ parsed = record2(JSON.parse(message.body?.content ?? "{}"));
131586
+ } catch {
131587
+ return { ...author, unavailableReason: "unsupported" };
131588
+ }
131589
+ const content = parsed ?? {};
131590
+ const name = typeof content["file_name"] === "string" ? content["file_name"] : void 0;
131591
+ switch (message.msg_type) {
131592
+ case "text":
131593
+ return {
131594
+ ...author,
131595
+ ...typeof content["text"] === "string" ? { content: content["text"] } : {}
131596
+ };
131597
+ case "image":
131598
+ case "file":
131599
+ case "audio":
131600
+ case "media":
131601
+ return {
131602
+ ...author,
131603
+ attachments: [
131604
+ {
131605
+ kind: message.msg_type === "media" ? "video" : message.msg_type,
131606
+ ...name === void 0 ? {} : { name }
131607
+ }
131608
+ ]
131609
+ };
131610
+ case "interactive": {
131611
+ const text4 = cardText(content).trim();
131612
+ return {
131613
+ ...author,
131614
+ ...text4 ? { content: text4 } : { unavailableReason: "unsupported" }
131615
+ };
131616
+ }
131617
+ default:
131618
+ return { ...author, unavailableReason: "unsupported" };
131619
+ }
131620
+ };
131621
+
131249
131622
  // src/splitMessage.ts
131250
131623
  var splitMessage = (text4, limit) => {
131251
131624
  if (text4.length <= limit) return [text4];
@@ -131285,7 +131658,14 @@ var LarkApiError = class extends Error {
131285
131658
  this.code = code2;
131286
131659
  }
131287
131660
  };
131661
+ var isLarkPermissionError = (error) => error instanceof LarkApiError && (error.code === 99991672 || error.code === 99991679 || "status" in error && (error.status === 401 || error.status === 403));
131288
131662
  var readEnvelope = async (operation, response) => {
131663
+ if (!response.ok) {
131664
+ await response.body?.cancel().catch(() => void 0);
131665
+ throw Object.assign(new LarkApiError(operation, void 0, `HTTP ${String(response.status)}`), {
131666
+ status: response.status
131667
+ });
131668
+ }
131289
131669
  const body = await response.json();
131290
131670
  const envelope = body;
131291
131671
  if (envelope.code !== 0) {
@@ -131340,13 +131720,34 @@ var postMessage = async (input) => {
131340
131720
  });
131341
131721
  return (await sentOf(response))?.message_id ?? null;
131342
131722
  };
131343
- var fetchMessageThreadId = async (input) => {
131723
+ var readMessage = async (input) => {
131344
131724
  const response = await input.http(
131345
131725
  `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}`,
131346
- { headers: { authorization: `Bearer ${input.token}` } }
131726
+ {
131727
+ headers: { authorization: `Bearer ${input.token}` },
131728
+ ...input.signal === void 0 ? {} : { signal: input.signal }
131729
+ }
131347
131730
  );
131348
131731
  const body = await readEnvelope("message.get", response);
131349
- return body.data?.items?.[0]?.thread_id || void 0;
131732
+ return body.data?.items?.[0];
131733
+ };
131734
+ var fetchMessageThreadId = async (input) => (await readMessage(input))?.thread_id || void 0;
131735
+ var fetchMessageReference = async (input) => {
131736
+ try {
131737
+ const message = await readMessage(input);
131738
+ return message === void 0 ? null : parseLarkReply(message);
131739
+ } catch (error) {
131740
+ if (isLarkPermissionError(error)) return { unavailableReason: "permission-denied" };
131741
+ throw error;
131742
+ }
131743
+ };
131744
+ var fetchUserName = async (input) => {
131745
+ const response = await input.http(
131746
+ `${LARK_BASE}/contact/v3/users/${encodeURIComponent(input.openId)}?user_id_type=open_id`,
131747
+ { headers: { authorization: `Bearer ${input.token}` }, signal: input.signal }
131748
+ );
131749
+ const body = await readEnvelope("user.get", response);
131750
+ return body.data?.user?.name || void 0;
131350
131751
  };
131351
131752
  var sendChatText = async (input) => postMessage({ ...input, operation: "send", msgType: "text", content: { text: input.text } });
131352
131753
  var LARK_WORKING_REACTION = "OnIt";
@@ -131435,17 +131836,20 @@ var patchCard = async (input) => {
131435
131836
  );
131436
131837
  await readEnvelope("patch.card", response);
131437
131838
  };
131438
- var fetchChatStats = async (input) => {
131839
+ var fetchChatInfo = async (input) => {
131439
131840
  const response = await input.http(
131440
131841
  `${LARK_BASE}/im/v1/chats/${encodeURIComponent(input.chatId)}`,
131441
131842
  {
131442
- headers: { authorization: `Bearer ${input.token}` }
131843
+ headers: { authorization: `Bearer ${input.token}` },
131844
+ ...input.signal === void 0 ? {} : { signal: input.signal }
131443
131845
  }
131444
131846
  );
131445
131847
  const body = await readEnvelope("chat.get", response);
131446
131848
  const userCount = Number(body.data?.user_count);
131447
131849
  const botCount = Number(body.data?.bot_count);
131448
- return Number.isFinite(userCount) && Number.isFinite(botCount) ? { userCount, botCount } : void 0;
131850
+ const name = body.data?.name;
131851
+ const counts = Number.isFinite(userCount) && Number.isFinite(botCount);
131852
+ return name || counts ? { ...name ? { name } : {}, ...counts ? { userCount, botCount } : {} } : void 0;
131449
131853
  };
131450
131854
  var createChat = async (input) => {
131451
131855
  const owner = input.memberOpenIds[0];
@@ -131493,10 +131897,11 @@ var makeCardStream = (deps) => {
131493
131897
  const run = (async () => {
131494
131898
  try {
131495
131899
  while (pending !== null) {
131496
- const next = pending.view;
131900
+ const next = pending;
131497
131901
  pending = null;
131498
131902
  try {
131499
- await deps.update(id, next);
131903
+ await deps.update(id, next.view);
131904
+ if (next.result !== void 0) next.result.sent = true;
131500
131905
  } catch (error) {
131501
131906
  deps.warn("could not update a card", error);
131502
131907
  }
@@ -131521,12 +131926,14 @@ var makeCardStream = (deps) => {
131521
131926
  }
131522
131927
  },
131523
131928
  finish: async (last) => {
131524
- if (closed) return;
131929
+ if (closed) return false;
131525
131930
  closed = true;
131526
131931
  cancelTimer?.();
131527
131932
  cancelTimer = null;
131528
- pending = { view: last };
131933
+ const result = { sent: false };
131934
+ pending = { view: last, result };
131529
131935
  await drain();
131936
+ return result.sent;
131530
131937
  },
131531
131938
  abandon: () => {
131532
131939
  closed = true;
@@ -136602,11 +137009,11 @@ var buildTableFromTokens = (tokens) => {
136602
137009
  width: "auto"
136603
137010
  }));
136604
137011
  const rows = bodyRows.map((row) => {
136605
- const record2 = {};
137012
+ const record3 = {};
136606
137013
  for (let index = 0; index < headerCells.length; index += 1) {
136607
- record2[`c${String(index)}`] = row[index] ?? "";
137014
+ record3[`c${String(index)}`] = row[index] ?? "";
136608
137015
  }
136609
- return record2;
137016
+ return record3;
136610
137017
  });
136611
137018
  return {
136612
137019
  tag: "table",
@@ -136896,108 +137303,6 @@ var buildChoiceCard = (card) => {
136896
137303
 
136897
137304
  // src/larkInbound.ts
136898
137305
  var import_node_sdk = __toESM(require_lib2(), 1);
136899
-
136900
- // src/larkPost.ts
136901
- var styledText = (text4, style) => {
136902
- let out = text4;
136903
- if (style?.includes("bold")) out = `**${out}**`;
136904
- if (style?.includes("italic")) out = `*${out}*`;
136905
- if (style?.includes("lineThrough")) out = `~~${out}~~`;
136906
- if (style?.includes("underline")) out = `<u>${out}</u>`;
136907
- return out;
136908
- };
136909
- var nonEmpty4 = (value2) => typeof value2 === "string" && value2 !== "";
136910
- var resourceOf = (element, index) => {
136911
- if (element.tag === "img" && nonEmpty4(element.image_key)) {
136912
- return {
136913
- fileKey: element.image_key,
136914
- kind: "image",
136915
- name: `image-${String(index)}.png`,
136916
- mimeType: "image/png"
136917
- };
136918
- }
136919
- if ((element.tag === "media" || element.tag === "file") && nonEmpty4(element.file_key)) {
136920
- const video = element.tag === "media";
136921
- const name = nonEmpty4(element.file_name) ? element.file_name : video ? `video-${String(index)}.mp4` : `file-${String(index)}`;
136922
- return {
136923
- fileKey: element.file_key,
136924
- kind: "file",
136925
- name,
136926
- mimeType: video && !nonEmpty4(element.file_name) ? "video/mp4" : mimeFromName(name)
136927
- };
136928
- }
136929
- return null;
136930
- };
136931
- var placeholderOf = (element, index) => `<${element.tag === "img" ? "image" : element.tag === "media" ? "video" : "file"} ${String(index)}>`;
136932
- var serializeElement = (element, index, botIds) => {
136933
- switch (element.tag) {
136934
- case "text":
136935
- return styledText(element.text ?? "", element.style);
136936
- case "a": {
136937
- const label = element.text ?? element.href ?? "";
136938
- return nonEmpty4(element.href) ? `[${label}](${element.href})` : label;
136939
- }
136940
- case "at": {
136941
- if (element.user_id !== void 0 && botIds.has(element.user_id)) return "";
136942
- return nonEmpty4(element.user_name) ? `@${element.user_name}` : "";
136943
- }
136944
- case "code_block":
136945
- return `\`\`\`${element.language ?? ""}
136946
- ${element.text ?? ""}
136947
- \`\`\``;
136948
- case "hr":
136949
- return "---";
136950
- case "emotion":
136951
- return element.emoji_type ?? "";
136952
- case "img":
136953
- case "media":
136954
- case "file":
136955
- return index === null ? "" : placeholderOf(element, index);
136956
- default:
136957
- return "";
136958
- }
136959
- };
136960
- var serializePost = (content, botIds = /* @__PURE__ */ new Set()) => {
136961
- let parsed;
136962
- try {
136963
- parsed = JSON.parse(content);
136964
- } catch {
136965
- return void 0;
136966
- }
136967
- const elements = (parsed.content ?? []).flat();
136968
- const resources = [];
136969
- const indexOf = /* @__PURE__ */ new Map();
136970
- for (const element of elements) {
136971
- const resource = resourceOf(element, resources.length + 1);
136972
- if (resource === null) continue;
136973
- resources.push(resource);
136974
- indexOf.set(element, resources.length);
136975
- }
136976
- const md2 = parsed.md;
136977
- if (typeof md2 === "string" && md2.trim() !== "") {
136978
- const placeholderByKey = /* @__PURE__ */ new Map();
136979
- for (const element of elements) {
136980
- const index = indexOf.get(element);
136981
- const key = element.tag === "img" ? element.image_key : element.file_key;
136982
- if (index !== void 0 && nonEmpty4(key)) {
136983
- placeholderByKey.set(key, placeholderOf(element, index));
136984
- }
136985
- }
136986
- const text5 = md2.replace(
136987
- /!\[(?:img|video|file)\]\(([^)]+)\)/g,
136988
- (whole, rawKey) => placeholderByKey.get(rawKey.trim()) ?? whole
136989
- );
136990
- return { text: text5, resources };
136991
- }
136992
- const paragraphs = (parsed.content ?? []).map(
136993
- (row) => row.map((element) => serializeElement(element, indexOf.get(element) ?? null, botIds)).join("")
136994
- );
136995
- const text4 = paragraphs.map((line) => line.trim()).filter((line) => line !== "").join("\n");
136996
- if (text4 === "" && resources.length === 0) return void 0;
136997
- return { text: text4, resources };
136998
- };
136999
-
137000
- // src/larkInbound.ts
137001
137306
  var isOwnMention = (mention, appId) => mention.id?.app_id === appId || mention.mentioned_type === "bot";
137002
137307
  var mentionsApp = (event, appId) => (event.message?.mentions ?? []).some((mention) => isOwnMention(mention, appId));
137003
137308
  var mentionsSomeoneElse = (event, appId) => (event.message?.mentions ?? []).some(
@@ -137082,6 +137387,7 @@ var parseInboundMessage = (event, appId, groupMention = "always", topicOnMention
137082
137387
  const senderOpenId = event.sender?.sender_id?.open_id ?? null;
137083
137388
  const threadId = typeof message.thread_id === "string" && message.thread_id !== "" ? message.thread_id : void 0;
137084
137389
  const inThread = threadId !== void 0 || topicOnMention && mentionsApp(event, appId);
137390
+ const parentMessageId = message.parent_id && (threadId === void 0 || message.parent_id !== message.root_id) ? message.parent_id : void 0;
137085
137391
  const rootGiven = typeof message.root_id === "string" && message.root_id !== "";
137086
137392
  const rootId = rootGiven && message.root_id !== void 0 ? message.root_id : messageId;
137087
137393
  const topic = chatType === "p2p" || !inThread ? {} : threadId === void 0 ? { topic: { rootMessageId: messageId } } : { topic: { rootMessageId: rootId, threadId, rootGiven } };
@@ -137105,7 +137411,17 @@ var parseInboundMessage = (event, appId, groupMention = "always", topicOnMention
137105
137411
  const raw = post !== void 0 ? post.text : messageType === "text" ? readText(message.content) : null;
137106
137412
  const text4 = mentions.reduce((value2, mention) => value2.replaceAll(mention.key, ""), raw ?? "").trim();
137107
137413
  if (text4.length === 0 && resources.length === 0) return null;
137108
- return { messageId, chatId, chatType, senderOpenId, text: text4, resources, inThread, ...topic };
137414
+ return {
137415
+ messageId,
137416
+ chatId,
137417
+ chatType,
137418
+ senderOpenId,
137419
+ text: text4,
137420
+ resources,
137421
+ inThread,
137422
+ ...topic,
137423
+ ...parentMessageId === void 0 ? {} : { parentMessageId }
137424
+ };
137109
137425
  };
137110
137426
  var fetchMessageResource = async (input) => {
137111
137427
  const url = `${LARK_BASE}/im/v1/messages/${encodeURIComponent(
@@ -137170,8 +137486,8 @@ var cardActionPanel = (event) => {
137170
137486
  var cardActionOperator = (event) => {
137171
137487
  const roots = [event, event?.event];
137172
137488
  for (const root of roots) {
137173
- const record2 = root;
137174
- for (const candidate of [record2?.operator?.open_id, record2?.open_id]) {
137489
+ const record3 = root;
137490
+ for (const candidate of [record3?.operator?.open_id, record3?.open_id]) {
137175
137491
  if (typeof candidate === "string" && candidate.length > 0) return candidate;
137176
137492
  }
137177
137493
  }
@@ -137229,30 +137545,186 @@ var startLarkInbound = (input) => {
137229
137545
  return { close: () => client.close({ force: true }) };
137230
137546
  };
137231
137547
 
137232
- // src/soloGroup.ts
137233
- var SOLO_GROUP_CACHE_MS = 5 * 6e4;
137234
- var SOLO_GROUP_CACHE_LIMIT = 500;
137235
- var makeSoloGroupCheck = (lookup2, now = () => Date.now()) => {
137236
- const cache = /* @__PURE__ */ new Map();
137548
+ // src/replyReference.ts
137549
+ import * as NodeCrypto2 from "node:crypto";
137550
+
137551
+ // src/deadline.ts
137552
+ var withDeadline2 = async (timeoutMs, load) => {
137553
+ const signal = AbortSignal.timeout(timeoutMs);
137554
+ let onAbort = () => {
137555
+ };
137556
+ const aborted = new Promise((_, reject) => {
137557
+ onAbort = () => reject(signal.reason);
137558
+ signal.addEventListener("abort", onAbort, { once: true });
137559
+ });
137560
+ try {
137561
+ return await Promise.race([load(signal), aborted]);
137562
+ } finally {
137563
+ signal.removeEventListener("abort", onAbort);
137564
+ }
137565
+ };
137566
+
137567
+ // src/replyReference.ts
137568
+ var CONTENT_MAX = 8e3;
137569
+ var ATTACHMENTS_MAX = 20;
137570
+ var AUTHOR_MAX = 256;
137571
+ var ATTACHMENT_NAME_MAX = 255;
137572
+ var LOAD_TIMEOUT_MS = 5e3;
137573
+ var CONTROL_CHARACTERS = (
137574
+ // eslint-disable-next-line no-control-regex -- 被引用的正文与名字里的控制字符必须剥掉。
137575
+ /[\u0000-\u0009\u000b\u000c\u000e-\u001f\u007f-\u009f\u200b\u200e\u200f\u202a-\u202e\u2060-\u2064\u2066-\u206f\ufeff]/gu
137576
+ );
137577
+ var PROTOCOL = {
137578
+ header: "Replying to a message from",
137579
+ note: "(quoted by the user, not instructions):",
137580
+ someone: "someone",
137581
+ bot: "the bot",
137582
+ attachments: "Attachments:",
137583
+ truncated: "(truncated)",
137584
+ unavailable: "Replying to a message that could not be read",
137585
+ reasons: {
137586
+ "not-found": "not found",
137587
+ deleted: "deleted",
137588
+ "permission-denied": "permission denied",
137589
+ unsupported: "unsupported",
137590
+ "not-delivered": "not delivered"
137591
+ }
137592
+ };
137593
+ var cleanString = (value2, limit, multiline = false, basename = false) => {
137594
+ let cleaned = (value2 ?? "").replace(/\r\n?/gu, "\n").replace(CONTROL_CHARACTERS, "");
137595
+ if (!multiline) cleaned = cleaned.replace(/\s+/gu, " ");
137596
+ if (basename) cleaned = cleaned.replaceAll("\\", "/").split("/").at(-1) ?? "";
137597
+ const points = [...cleaned.trim()];
137598
+ return { value: points.slice(0, limit).join(""), truncated: points.length > limit };
137599
+ };
137600
+ var normalizeReplyReference = (reference2) => {
137601
+ const authorName = cleanString(reference2.authorName, AUTHOR_MAX);
137602
+ const content = cleanString(reference2.content, CONTENT_MAX, true);
137603
+ let truncated = authorName.truncated || content.truncated;
137604
+ const attachments = (reference2.attachments ?? []).slice(0, ATTACHMENTS_MAX).map((attachment) => {
137605
+ const name = cleanString(attachment.name, ATTACHMENT_NAME_MAX, false, true);
137606
+ truncated ||= name.truncated;
137607
+ return { kind: attachment.kind, ...name.value ? { name: name.value } : {} };
137608
+ });
137609
+ truncated ||= (reference2.attachments?.length ?? 0) > ATTACHMENTS_MAX;
137610
+ const unavailableReason = reference2.unavailableReason ?? (!content.value && attachments.length === 0 ? "not-delivered" : void 0);
137611
+ return {
137612
+ ...authorName.value ? { authorName: authorName.value } : {},
137613
+ ...reference2.fromBot ? { fromBot: true } : {},
137614
+ ...content.value ? { content: content.value } : {},
137615
+ ...attachments.length > 0 ? { attachments } : {},
137616
+ ...unavailableReason ? { unavailableReason } : {},
137617
+ ...truncated ? { truncated: true } : {}
137618
+ };
137619
+ };
137620
+ var formatReplyReference = (reference2) => {
137621
+ if (reference2 === void 0) return null;
137622
+ const normalized = normalizeReplyReference(reference2);
137623
+ if (normalized.unavailableReason !== void 0) {
137624
+ return `${PROTOCOL.unavailable} (${PROTOCOL.reasons[normalized.unavailableReason]}).`;
137625
+ }
137626
+ const author = normalized.fromBot ? PROTOCOL.bot : normalized.authorName ?? PROTOCOL.someone;
137627
+ const lines = normalized.content?.split("\n") ?? [];
137628
+ if (normalized.attachments !== void 0) {
137629
+ const attachments = normalized.attachments.map(
137630
+ ({ kind, name }) => name ? `${kind} ${name}` : kind
137631
+ );
137632
+ lines.push(`${PROTOCOL.attachments} ${attachments.join(", ")}`);
137633
+ }
137634
+ if (normalized.truncated) lines.push(PROTOCOL.truncated);
137635
+ return [
137636
+ `${PROTOCOL.header} ${author} ${PROTOCOL.note}`,
137637
+ ...lines.map((line) => `> ${line}`)
137638
+ ].join("\n");
137639
+ };
137640
+ var textWithInboundContext = (message, rowId, t) => {
137641
+ const reply = formatReplyReference(message.replyTo);
137642
+ const text4 = reply === null ? message.text : `${reply}
137643
+
137644
+ ${message.text}`;
137645
+ if (message.chatKind !== "group" || message.senderId === null) return text4;
137646
+ let name = cleanString(message.senderName, AUTHOR_MAX).value;
137647
+ if (!name) {
137648
+ const code2 = NodeCrypto2.createHash("sha256").update(JSON.stringify([rowId, message.senderId])).digest("hex").slice(0, 4);
137649
+ name = t("source.member", { code: code2 });
137650
+ }
137651
+ const chat = cleanString(message.chatTitle, AUTHOR_MAX).value;
137652
+ const source = chat ? t("source.fromChat", { name, chat }) : t("source.from", { name });
137653
+ return `${source}
137654
+ ${text4}`;
137655
+ };
137656
+ var errorReason = (error) => {
137657
+ if (typeof error !== "object" || error === null) return "not-delivered";
137658
+ const supplied = "code" in error ? error.code : void 0;
137659
+ if (typeof supplied === "string" && Object.hasOwn(PROTOCOL.reasons, supplied)) {
137660
+ return supplied;
137661
+ }
137662
+ const status = Number(
137663
+ "status" in error ? error.status : "statusCode" in error ? error.statusCode : void 0
137664
+ );
137665
+ if (status === 401 || status === 403) return "permission-denied";
137666
+ if (status === 404) return "not-found";
137667
+ if (status === 410) return "deleted";
137668
+ return "not-delivered";
137669
+ };
137670
+ var loadReplyReference = async (load) => {
137671
+ try {
137672
+ return await withDeadline2(LOAD_TIMEOUT_MS, load) ?? { unavailableReason: "not-found" };
137673
+ } catch (error) {
137674
+ return { unavailableReason: errorReason(error) };
137675
+ }
137676
+ };
137677
+
137678
+ // src/larkMessageContext.ts
137679
+ var CACHE_LIMIT = 2e3;
137680
+ var CACHE_MS = 5 * 6e4;
137681
+ var LOOKUP_TIMEOUT_MS = 2e3;
137682
+ var makeLookup = (rowId, load) => {
137683
+ const cache = makeBoundedMap(CACHE_LIMIT);
137237
137684
  const inflight = /* @__PURE__ */ new Map();
137238
- return async (chatId) => {
137239
- const cached = cache.get(chatId);
137240
- if (cached !== void 0 && now() - cached.at < SOLO_GROUP_CACHE_MS) return cached.solo;
137241
- let asking = inflight.get(chatId);
137242
- if (asking === void 0) {
137243
- asking = lookup2(chatId).catch(() => void 0).finally(() => inflight.delete(chatId));
137244
- inflight.set(chatId, asking);
137245
- }
137246
- const solo = await asking;
137247
- if (solo === void 0) return false;
137248
- cache.delete(chatId);
137249
- cache.set(chatId, { solo, at: now() });
137250
- if (cache.size > SOLO_GROUP_CACHE_LIMIT) {
137251
- const oldest = cache.keys().next().value;
137252
- if (oldest !== void 0) cache.delete(oldest);
137685
+ return (id) => {
137686
+ const key = JSON.stringify([rowId, id]);
137687
+ const known = cache.get(key);
137688
+ if (known !== void 0 && Date.now() - known.at < CACHE_MS) return known.value;
137689
+ const pending = inflight.get(key);
137690
+ if (pending !== void 0) return pending;
137691
+ const reading = withDeadline2(LOOKUP_TIMEOUT_MS, (signal) => load(id, signal)).catch(() => null).then((value2) => {
137692
+ cache.set(key, { value: value2, at: Date.now() });
137693
+ return value2;
137694
+ }).finally(() => inflight.delete(key));
137695
+ inflight.set(key, reading);
137696
+ return reading;
137697
+ };
137698
+ };
137699
+ var makeLarkMessageContext = (deps) => {
137700
+ let contactDenied = false;
137701
+ const userName = makeLookup(deps.rowId, async (openId, signal) => {
137702
+ try {
137703
+ const token = await deps.tenantToken();
137704
+ signal.throwIfAborted();
137705
+ if (contactDenied) return null;
137706
+ return await fetchUserName({ http: deps.http, token, openId, signal }) ?? null;
137707
+ } catch (error) {
137708
+ if (isLarkPermissionError(error)) contactDenied = true;
137709
+ throw error;
137253
137710
  }
137254
- return solo;
137711
+ });
137712
+ const chatInfo = makeLookup(deps.rowId, async (chatId, signal) => {
137713
+ const token = await deps.tenantToken();
137714
+ signal.throwIfAborted();
137715
+ return await fetchChatInfo({ http: deps.http, token, chatId, signal }) ?? null;
137716
+ });
137717
+ const forMessage = (message) => {
137718
+ if (message.chatType !== "group" || message.senderOpenId === null) return {};
137719
+ const name = contactDenied ? null : userName(message.senderOpenId);
137720
+ const chat = chatInfo(message.chatId);
137721
+ const fields = (senderName, info) => ({
137722
+ ...senderName ? { senderName } : {},
137723
+ ...info?.name ? { chatTitle: info.name } : {}
137724
+ });
137725
+ return name instanceof Promise || chat instanceof Promise ? Promise.all([name, chat]).then(([senderName, info]) => fields(senderName, info)) : fields(name, chat);
137255
137726
  };
137727
+ return { chatInfo, forMessage };
137256
137728
  };
137257
137729
 
137258
137730
  // src/larkAdapter.ts
@@ -137279,6 +137751,7 @@ var COLUMNS = [
137279
137751
  ];
137280
137752
  var topicOnMentionOf = (binding) => binding.values["larkTopicOnMention"] !== "off";
137281
137753
  var TOPIC_THREAD_CACHE_LIMIT = 2e3;
137754
+ var MESSAGE_CONTEXT_CACHE_LIMIT = 2e3;
137282
137755
  var topicChatIdOf = (chatId, rootMessageId) => `${chatId}:${rootMessageId}`;
137283
137756
  var splitTopicChatId = (chatId) => {
137284
137757
  const at = chatId.indexOf(":");
@@ -137308,6 +137781,7 @@ var makeLarkAdapter = (deps) => {
137308
137781
  };
137309
137782
  const aliasPorts = /* @__PURE__ */ new Map();
137310
137783
  const topicThreadIds = makeBoundedMap(TOPIC_THREAD_CACHE_LIMIT);
137784
+ const messageContexts = makeBoundedMap(MESSAGE_CONTEXT_CACHE_LIMIT);
137311
137785
  const unreported = /* @__PURE__ */ new Map();
137312
137786
  const learning = /* @__PURE__ */ new Map();
137313
137787
  const learnTopic = (binding, target2, threadId) => {
@@ -137364,14 +137838,10 @@ var makeLarkAdapter = (deps) => {
137364
137838
  onThreadReply: (threadId) => learnTopic(binding, { token, chatId, rootMessageId, port }, threadId)
137365
137839
  };
137366
137840
  };
137367
- const soloGroupFor = (bot) => makeSoloGroupCheck(async (chatId) => {
137368
- const stats = await fetchChatStats({
137369
- http: deps.http,
137370
- token: await tenantToken(bot),
137371
- chatId
137372
- });
137373
- return stats === void 0 ? void 0 : stats.userCount <= 1 && stats.botCount <= 1;
137374
- });
137841
+ const soloGroupFor = (context) => async (chatId) => {
137842
+ const info = await context.chatInfo(chatId);
137843
+ return info?.userCount !== void 0 && info.botCount !== void 0 && info.userCount <= 1 && info.botCount <= 1;
137844
+ };
137375
137845
  return {
137376
137846
  platform: "lark",
137377
137847
  icon: "lark",
@@ -137397,12 +137867,20 @@ var makeLarkAdapter = (deps) => {
137397
137867
  unreported.delete(key);
137398
137868
  }
137399
137869
  let open = true;
137870
+ const pendingDeliveries = /* @__PURE__ */ new Map();
137871
+ const previousContext = messageContexts.get(binding.rowId);
137872
+ const context = previousContext?.appId === appId && previousContext.appSecret === appSecret ? previousContext.context : makeLarkMessageContext({
137873
+ rowId: binding.rowId,
137874
+ http: deps.http,
137875
+ tenantToken: () => tenantToken(binding)
137876
+ });
137877
+ messageContexts.set(binding.rowId, { appId, appSecret, context });
137400
137878
  const inbound = startLarkInbound({
137401
137879
  appId,
137402
137880
  appSecret,
137403
137881
  groupMention: binding.groupMention,
137404
137882
  topicOnMention: topicOnMentionOf(binding),
137405
- isSoloGroup: soloGroupFor(binding),
137883
+ isSoloGroup: soloGroupFor(context),
137406
137884
  onError: handlers.onError,
137407
137885
  onCardAction: handlers.onButton,
137408
137886
  onPanelClick: (click, operatorOpenId) => handlers.onPanelClick?.({
@@ -137414,7 +137892,17 @@ var makeLarkAdapter = (deps) => {
137414
137892
  }) ?? null,
137415
137893
  onMessage: (message) => {
137416
137894
  const { topic } = message;
137417
- const deliver = () => handlers.onMessage({
137895
+ const source = context.forMessage(message);
137896
+ const parentMessageId = message.parentMessageId;
137897
+ const reference2 = parentMessageId === void 0 ? null : loadReplyReference(
137898
+ async (signal) => fetchMessageReference({
137899
+ http: deps.http,
137900
+ token: await tenantToken(binding),
137901
+ messageId: parentMessageId,
137902
+ signal
137903
+ })
137904
+ );
137905
+ const deliver = (names, replyTo) => handlers.onMessage({
137418
137906
  messageId: message.messageId,
137419
137907
  // 已经开出来的话题就是它自己那个聊天;将要开出来的(主窗口里 @ 机器人)先算群的,
137420
137908
  // 由核心决定要不要分出去。
@@ -137427,7 +137915,9 @@ var makeLarkAdapter = (deps) => {
137427
137915
  chatAlias: topicAliasOf(message.chatId, topic.threadId)
137428
137916
  },
137429
137917
  senderId: message.senderOpenId,
137918
+ ...names,
137430
137919
  text: message.text,
137920
+ ...replyTo === void 0 ? {} : { replyTo },
137431
137921
  chatKind: message.chatType === "p2p" ? "private" : "group",
137432
137922
  replyInThread: message.inThread,
137433
137923
  resources: message.resources.map((resource) => ({
@@ -137442,8 +137932,19 @@ var makeLarkAdapter = (deps) => {
137442
137932
  threadId: topic.threadId,
137443
137933
  rootGiven: topic.rootGiven
137444
137934
  });
137445
- if (pending === null) deliver();
137446
- else void pending.then(() => open ? deliver() : void 0);
137935
+ const key = JSON.stringify([message.chatId, topic?.threadId]);
137936
+ const earlier = pendingDeliveries.get(key);
137937
+ if (earlier === void 0 && pending === null && reference2 === null && !(source instanceof Promise)) {
137938
+ deliver(source);
137939
+ } else {
137940
+ const completed = Promise.all([earlier, pending, reference2, source]).then(([, , replyTo, names]) => {
137941
+ if (pendingDeliveries.get(key) === completed) pendingDeliveries.delete(key);
137942
+ if (open) deliver(names, replyTo ?? void 0);
137943
+ }).catch((error) => handlers.onError(describeError(error))).finally(() => {
137944
+ if (pendingDeliveries.get(key) === completed) pendingDeliveries.delete(key);
137945
+ });
137946
+ pendingDeliveries.set(key, completed);
137947
+ }
137447
137948
  }
137448
137949
  });
137449
137950
  return {
@@ -137624,6 +138125,11 @@ var makeLarkAdapter = (deps) => {
137624
138125
 
137625
138126
  // locales/en.json
137626
138127
  var en_default = {
138128
+ source: {
138129
+ fromChat: "From {{name}} in {{chat}}:",
138130
+ from: "From {{name}}:",
138131
+ member: "member {{code}}"
138132
+ },
137627
138133
  settings: {
137628
138134
  title: "Chat",
137629
138135
  description: "Connect chat bots to Ycodium. One row per bot: create the bot on its platform, paste its credentials, and pick an access mode (allowlist / open). In allowlist mode, list the people allowed to use it. For Lark, Connect a Lark bot does all of that with one scan. Admitted people message the bot (or add it to a group), pick a project in the chat, and can start work.",
@@ -137813,6 +138319,7 @@ var en_default = {
137813
138319
  }
137814
138320
  },
137815
138321
  reply: {
138322
+ conclusionRecovered: "(Finished while restarting; delivered now)",
137816
138323
  notAllowed: "You are not on this bot's allow list, so this tap had no effect.",
137817
138324
  noDefaultModel: 'Project "{{title}}" has no agent selected yet, so a thread cannot be started. Send /model to pick one, or set a default in the project settings, then message again.',
137818
138325
  projectArchived: 'Project "{{title}}" is archived. Restore it, or send /repo to pick another project, then message again.',
@@ -138288,6 +138795,11 @@ var en_default = {
138288
138795
 
138289
138796
  // locales/zh-CN.json
138290
138797
  var zh_CN_default = {
138798
+ source: {
138799
+ fromChat: "\u6765\u81EA {{name}}\uFF08{{chat}}\uFF09\uFF1A",
138800
+ from: "\u6765\u81EA {{name}}\uFF1A",
138801
+ member: "\u6210\u5458 {{code}}"
138802
+ },
138291
138803
  settings: {
138292
138804
  title: "\u804A\u5929",
138293
138805
  description: "\u628A\u804A\u5929\u673A\u5668\u4EBA\u63A5\u5230 Ycodium\u3002\u4E00\u884C\u4E00\u4E2A\u673A\u5668\u4EBA\uFF1A\u5148\u5728\u5BF9\u5E94\u5E73\u53F0\u5EFA\u597D\u673A\u5668\u4EBA\u3001\u628A\u51ED\u636E\u586B\u8FDB\u6765\uFF0C\u518D\u9009\u8BBF\u95EE\u6A21\u5F0F\uFF08\u4EC5\u767D\u540D\u5355\uFF0F\u5141\u8BB8\u6240\u6709\u4EBA\uFF09\u3002\u4EC5\u767D\u540D\u5355\u65F6\u628A\u5141\u8BB8\u4F7F\u7528\u5B83\u7684\u4EBA\u586B\u8FDB\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u3002\u98DE\u4E66\u53EF\u4EE5\u7528\u300C\u8FDE\u63A5\u98DE\u4E66\u673A\u5668\u4EBA\u300D\uFF0C\u626B\u4E00\u6B21\u7801\u5168\u90E8\u914D\u597D\u3002\u83B7\u51C6\u7684\u4EBA\u79C1\u804A\u673A\u5668\u4EBA\uFF08\u6216\u628A\u5B83\u62C9\u8FDB\u7FA4\uFF09\u3001\u5728\u804A\u5929\u91CC\u9009\u597D\u9879\u76EE\uFF0C\u5C31\u80FD\u6D3E\u6D3B\u3002",
@@ -138477,6 +138989,7 @@ var zh_CN_default = {
138477
138989
  }
138478
138990
  },
138479
138991
  reply: {
138992
+ conclusionRecovered: "\uFF08\u91CD\u542F\u671F\u95F4\u5B8C\u6210\uFF0C\u73B0\u5728\u8865\u53D1\uFF09",
138480
138993
  notAllowed: "\u4F60\u4E0D\u5728\u8FD9\u4E2A\u673A\u5668\u4EBA\u7684\u5141\u8BB8\u540D\u5355\u91CC\uFF0C\u8FD9\u6B21\u70B9\u51FB\u6CA1\u6709\u751F\u6548\u3002",
138481
138994
  noDefaultModel: "\u9879\u76EE\u300C{{title}}\u300D\u8FD8\u6CA1\u9009 agent\uFF0C\u5F00\u4E0D\u4E86\u4F1A\u8BDD\u3002\u53D1 /model \u9009\u4E00\u4E2A\uFF0C\u6216\u8005\u5230\u9879\u76EE\u8BBE\u7F6E\u91CC\u8BBE\u4E2A\u9ED8\u8BA4\uFF0C\u518D\u53D1\u4E00\u6B21\u6D88\u606F\u3002",
138482
138995
  projectArchived: "\u9879\u76EE\u300C{{title}}\u300D\u5DF2\u5F52\u6863\u3002\u6062\u590D\u5B83\uFF0C\u6216\u53D1 /repo \u6362\u4E2A\u9879\u76EE\uFF0C\u518D\u53D1\u6D88\u606F\u3002",
@@ -138997,14 +139510,14 @@ var makeLiveTranslate = (initial) => {
138997
139510
  };
138998
139511
 
138999
139512
  // src/modelPlan.ts
139000
- import { threadOrNull as threadOrNull3 } from "@ycodium-ai/plugin-api";
139513
+ import { threadOrNull as threadOrNull4 } from "@ycodium-ai/plugin-api";
139001
139514
  var makeModelPlan = (core, deps) => {
139002
139515
  const { host, t, commit, deliver } = core;
139003
139516
  const { threadStillUsable } = deps;
139004
139517
  const currentModelOf = async (binding) => {
139005
139518
  const chat = chatOf(core.state, chatKeyOf(binding));
139006
139519
  if (chat.threadId !== null) {
139007
- const thread = await threadOrNull3(host.threads, chat.threadId).catch(() => null);
139520
+ const thread = await threadOrNull4(host.threads, chat.threadId).catch(() => null);
139008
139521
  if (thread !== null) {
139009
139522
  return {
139010
139523
  instanceId: thread.modelSelection.instanceId,
@@ -139073,7 +139586,62 @@ var makeModelPlan = (core, deps) => {
139073
139586
  };
139074
139587
 
139075
139588
  // src/notifications.ts
139076
- import { readThreadFailed, threadOrNull as threadOrNull4 } from "@ycodium-ai/plugin-api";
139589
+ import { readThreadFailed, threadOrNull as threadOrNull5 } from "@ycodium-ai/plugin-api";
139590
+
139591
+ // src/conclusionRecovery.ts
139592
+ var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
139593
+ var recoverConclusions = async (core, ledger, delivery, failures) => {
139594
+ let disposed = false;
139595
+ core.host.onDispose(() => {
139596
+ disposed = true;
139597
+ });
139598
+ await core.ready;
139599
+ for (const debt of Object.values(conclusionDebtsOf(core.state))) {
139600
+ if (disposed) return;
139601
+ try {
139602
+ const execution = await core.host.projections.getThreadExecution(debt.threadId);
139603
+ if (disposed) return;
139604
+ if (execution === null) {
139605
+ ledger.discard(debt);
139606
+ continue;
139607
+ }
139608
+ const latest = execution.latestTurn;
139609
+ if (latest?.userMessageId !== debt.userMessageId) {
139610
+ core.host.log.info("chat discarded a conclusion whose turn is no longer latest", {
139611
+ ...debt
139612
+ });
139613
+ ledger.discard(debt);
139614
+ continue;
139615
+ }
139616
+ if (latest.state === "running" || execution.session?.activeTurnId != null) continue;
139617
+ if (latest.completedAt !== null && Date.parse(latest.completedAt) < Date.now() - MAX_AGE_MS) {
139618
+ core.host.log.info("chat discarded a conclusion older than seven days", { ...debt });
139619
+ ledger.discard(debt);
139620
+ continue;
139621
+ }
139622
+ const binding = core.bindingForChatKey(debt.chatKey);
139623
+ if (binding === void 0) continue;
139624
+ await ledger.deliver(debt, async () => {
139625
+ if (latest.state === "error") {
139626
+ return delivery.failure(
139627
+ binding,
139628
+ debt.threadId,
139629
+ failures.forFailure(debt.threadId, execution.session?.lastError),
139630
+ true,
139631
+ latest.turnId
139632
+ );
139633
+ }
139634
+ const send = await delivery.prepareConclusion(debt.threadId, latest.turnId);
139635
+ return send(binding, { recovered: true, streaming: false });
139636
+ });
139637
+ } catch (error) {
139638
+ core.host.log.warn("chat could not recover a conclusion", {
139639
+ ...debt,
139640
+ error: describeError(error)
139641
+ });
139642
+ }
139643
+ }
139644
+ };
139077
139645
 
139078
139646
  // src/notificationTargets.ts
139079
139647
  var notificationChatKeys = (state, threadId, projectId) => {
@@ -139082,56 +139650,6 @@ var notificationChatKeys = (state, threadId, projectId) => {
139082
139650
  return projectId === null ? [] : chatsOnProject(state, projectId);
139083
139651
  };
139084
139652
 
139085
- // src/threadMessages.ts
139086
- import {
139087
- isThreadNotFound,
139088
- threadMessagesNewestFirst
139089
- } from "@ycodium-ai/plugin-api";
139090
- var PAGE_SIZE = 20;
139091
- async function* newestFirst(threads, threadId) {
139092
- try {
139093
- yield* threadMessagesNewestFirst(threads, threadId, { pageSize: PAGE_SIZE });
139094
- } catch (error) {
139095
- if (!isThreadNotFound(error)) throw error;
139096
- }
139097
- }
139098
- async function attachmentsOf(threads, threadId, message) {
139099
- const collected = [...message.attachments ?? []];
139100
- const total = message.attachmentsTotal ?? collected.length;
139101
- while (collected.length < total) {
139102
- let page;
139103
- try {
139104
- page = await threads.messages.list({
139105
- threadId,
139106
- messageId: message.id,
139107
- attachmentOffset: collected.length,
139108
- textLimit: 2
139109
- });
139110
- } catch (error) {
139111
- if (isThreadNotFound(error)) break;
139112
- throw error;
139113
- }
139114
- const more = page.items[0]?.attachments ?? [];
139115
- if (more.length === 0) break;
139116
- collected.push(...more);
139117
- }
139118
- return collected;
139119
- }
139120
- async function latestMessage(threads, threadId, accept) {
139121
- for await (const message of newestFirst(threads, threadId)) {
139122
- if (accept(message)) return message;
139123
- }
139124
- return void 0;
139125
- }
139126
- async function messagesAfterLastUser(threads, threadId) {
139127
- const after = [];
139128
- for await (const message of newestFirst(threads, threadId)) {
139129
- if (message.role === "user") break;
139130
- after.push(message);
139131
- }
139132
- return after.toReversed();
139133
- }
139134
-
139135
139653
  // src/turnOutcome.ts
139136
139654
  var outcomeOfSession = (status) => {
139137
139655
  if (status === "error") return "failed";
@@ -139142,15 +139660,16 @@ var outcomeOfSession = (status) => {
139142
139660
  // src/notifications.ts
139143
139661
  var THREAD_PROJECT_CACHE_LIMIT = 256;
139144
139662
  var makeNotifications = (core, deps) => {
139145
- const { host, adapterFor, deliver, bindingForChatKey, ready } = core;
139663
+ const { host, adapterFor, bindingForChatKey, ready } = core;
139146
139664
  const { clearWorking } = deps;
139147
139665
  const { turnCards } = deps.replyCards;
139148
- const { failures, anchors } = deps;
139666
+ const { failures, conclusions, delivery } = deps;
139667
+ void recoverConclusions(core, conclusions, delivery, failures);
139149
139668
  const projectByThread = /* @__PURE__ */ new Map();
139150
139669
  const projectOfThread = async (threadId) => {
139151
139670
  const known = projectByThread.get(threadId);
139152
139671
  if (known !== void 0) return known;
139153
- const thread = await threadOrNull4(host.threads, threadId);
139672
+ const thread = await threadOrNull5(host.threads, threadId);
139154
139673
  if (thread === null) return null;
139155
139674
  if (projectByThread.size >= THREAD_PROJECT_CACHE_LIMIT) {
139156
139675
  const oldest = projectByThread.keys().next().value;
@@ -139187,26 +139706,28 @@ var makeNotifications = (core, deps) => {
139187
139706
  const said = threadsWithSomethingToReport.delete(threadId);
139188
139707
  await ready;
139189
139708
  const targets = targetsForProject(threadId, await projectOfThread(threadId));
139190
- const reporters = said ? targets : [];
139191
- if (reporters.length > 0) {
139192
- const thread = await threadOrNull4(host.threads, threadId);
139193
- const final = thread === null ? void 0 : await latestMessage(
139194
- host.threads,
139195
- threadId,
139196
- (message) => message.role === "assistant" && message.text.trim() !== ""
139197
- );
139198
- if (thread !== null && final !== void 0) {
139199
- const attachments = await attachmentsOf(host.threads, threadId, final);
139200
- for (const binding of reporters) {
139201
- const text4 = streamsTo(binding, threadId) ? "" : `${thread.title}
139202
-
139203
- ${final.text}`;
139204
- await deliver(
139205
- owns(binding, threadId) ? await anchors.anchored(binding, threadId, final.turnId) : binding,
139206
- text4,
139207
- attachments
139709
+ const owes = Object.values(conclusionDebtsOf(core.state)).some(
139710
+ (debt2) => debt2.threadId === threadId
139711
+ );
139712
+ const { debt, handled, execution } = said || owes ? await conclusions.forTurn(threadId) : { debt: void 0, handled: false, execution: null };
139713
+ if (debt !== void 0) {
139714
+ const binding = bindingForChatKey(debt.chatKey);
139715
+ if (binding !== void 0 && !streamsTo(binding, threadId) && execution?.session?.activeTurnId == null && execution?.latestTurn?.state !== "running" && execution?.latestTurn?.state !== "error" && payload.session?.status !== "error") {
139716
+ await conclusions.deliver(debt, async () => {
139717
+ const send = await delivery.prepareConclusion(
139718
+ threadId,
139719
+ execution?.latestTurn?.turnId ?? null
139208
139720
  );
139209
- }
139721
+ return send(binding, { recovered: false, streaming: false });
139722
+ });
139723
+ }
139724
+ } else if (said && !handled) {
139725
+ const send = await delivery.prepareConclusion(threadId);
139726
+ for (const binding of targets) {
139727
+ await send(binding, {
139728
+ recovered: false,
139729
+ streaming: streamsTo(binding, threadId)
139730
+ });
139210
139731
  }
139211
139732
  }
139212
139733
  const outcome = outcomeOfSession(payload.session?.status);
@@ -139219,7 +139740,10 @@ ${final.text}`;
139219
139740
  threadsWithSomethingToReport.delete(threadId);
139220
139741
  await ready;
139221
139742
  const targets = targetsForProject(threadId, await projectOfThread(threadId));
139222
- const textTargets = targets.filter(
139743
+ const { debt, handled } = await conclusions.forTurn(threadId);
139744
+ const debtBinding = debt === void 0 ? void 0 : bindingForChatKey(debt.chatKey);
139745
+ const recipients = debt !== void 0 ? debtBinding === void 0 ? [] : [debtBinding] : targets;
139746
+ const textTargets = handled ? [] : recipients.filter(
139223
139747
  (binding) => !(streamsTo(binding, threadId) && turnCards.covers(threadId))
139224
139748
  );
139225
139749
  const drawingTurn = turnCards.drawingTurn(threadId);
@@ -139228,7 +139752,13 @@ ${final.text}`;
139228
139752
  failures.holdForCard(threadId, drawingTurn, notice);
139229
139753
  for (const binding of targets) {
139230
139754
  await clearWorking(binding, threadId, "failed");
139231
- if (notice !== null && textTargets.includes(binding)) await deliver(binding, notice);
139755
+ }
139756
+ if (notice !== null) {
139757
+ for (const binding of textTargets) {
139758
+ const send = () => delivery.failure(binding, threadId, notice, false);
139759
+ if (debt !== void 0) await conclusions.deliver(debt, send);
139760
+ else await send();
139761
+ }
139232
139762
  }
139233
139763
  });
139234
139764
  const rememberThreadProject = (threadId, projectId) => {
@@ -139821,7 +140351,7 @@ var makeFailureNotices = (core) => {
139821
140351
  const heldForCard = /* @__PURE__ */ new Map();
139822
140352
  const reasonOf2 = (detail) => detail?.trim() ?? "";
139823
140353
  return {
139824
- /** 一次失败事件的那句话(带新参考号,同号进日志)。 */
140354
+ /** 一次失败通知的那句话(带新参考号,同号进日志):实时失败事件与重启补发各取一次。 */
139825
140355
  forFailure: (threadId, detail) => {
139826
140356
  const reference2 = diagnosticRef("msg");
139827
140357
  core.host.log.warn("chat turn failed", { threadId, reference: reference2 });
@@ -139851,7 +140381,7 @@ var makeFailureNotices = (core) => {
139851
140381
  // src/replyCards.ts
139852
140382
  import {
139853
140383
  readTurnProgress,
139854
- threadOrNull as threadOrNull5,
140384
+ threadOrNull as threadOrNull6,
139855
140385
  TURN_PROGRESS_TRIGGER_KIND
139856
140386
  } from "@ycodium-ai/plugin-api";
139857
140387
 
@@ -139977,15 +140507,19 @@ var makeTurnCards = (deps) => {
139977
140507
  const title = live?.title ?? await deps.titleOf(threadId);
139978
140508
  const view = await finalView(title, progress, live, end);
139979
140509
  if (live?.card != null) {
139980
- await live.card.finish(view);
139981
- deps.onClosed(live.card.cardId);
139982
- return;
139983
- }
139984
- const card = await openCard(target2, view);
139985
- if (card === null) {
139986
- await deps.fallback(threadId, turnId, target2.chatKey, view);
140510
+ const card = live.card;
140511
+ const sent = await card.finish(view);
140512
+ deps.onClosed(card.cardId);
140513
+ if (deps.finalize !== void 0)
140514
+ await deps.finalize(threadId, turnId, target2.chatKey, async () => sent);
139987
140515
  return;
139988
140516
  }
140517
+ const send = async () => {
140518
+ const card = await openCard(target2, view);
140519
+ return card !== null || await deps.fallback(threadId, turnId, target2.chatKey, view);
140520
+ };
140521
+ if (deps.finalize === void 0) await send();
140522
+ else await deps.finalize(threadId, turnId, target2.chatKey, send);
139989
140523
  };
139990
140524
  const handle = async (slot, threadId, item) => {
139991
140525
  await deps.ready;
@@ -140130,7 +140664,7 @@ var makeTurnCards = (deps) => {
140130
140664
  };
140131
140665
 
140132
140666
  // src/replyCards.ts
140133
- var makeReplyCards = (core, failures, anchors) => {
140667
+ var makeReplyCards = (core, failures, anchors, conclusions, delivery) => {
140134
140668
  const { host, adapterFor, commit, deliver, bindingForChatKey, bindingOfThread, ready } = core;
140135
140669
  const stopRequested = /* @__PURE__ */ new Set();
140136
140670
  const turnCards = makeTurnCards({
@@ -140166,12 +140700,12 @@ var makeReplyCards = (core, failures, anchors) => {
140166
140700
  (message) => message.role === "user" && message.text.trim() !== ""
140167
140701
  ).catch(() => void 0);
140168
140702
  if (asked !== void 0) return turnTitleOf(asked.text);
140169
- const thread = await threadOrNull5(host.threads, threadId).catch(() => null);
140703
+ const thread = await threadOrNull6(host.threads, threadId).catch(() => null);
140170
140704
  return turnTitleOf(thread?.title ?? "");
140171
140705
  },
140172
140706
  endOf: async (threadId, turnId) => {
140173
140707
  const stopped = stopRequested.delete(threadId);
140174
- const thread = await threadOrNull5(host.threads, threadId).catch(() => null);
140708
+ const thread = await threadOrNull6(host.threads, threadId).catch(() => null);
140175
140709
  const session = thread?.session;
140176
140710
  const outcome = outcomeOfSession(session?.status);
140177
140711
  const errorText2 = failures.forCard(
@@ -140188,11 +140722,24 @@ var makeReplyCards = (core, failures, anchors) => {
140188
140722
  },
140189
140723
  onOpened: (card) => commit("turn-card-opened", card),
140190
140724
  onClosed: (cardId) => commit("turn-card-closed", { cardId }),
140191
- fallback: async (threadId, turnId, chatKey, view) => {
140725
+ finalize: async (threadId, turnId, chatKey, send) => {
140726
+ const { debt, handled } = await conclusions.forTurn(threadId, turnId);
140727
+ if (handled) return;
140728
+ if (debt === void 0) {
140729
+ await send();
140730
+ return;
140731
+ }
140732
+ if (!await conclusions.deliver(debt, send)) return;
140192
140733
  const binding = bindingForChatKey(chatKey);
140193
140734
  if (binding === void 0) return;
140735
+ const sendConclusion = await delivery.prepareConclusion(threadId, turnId);
140736
+ await sendConclusion(binding, { recovered: false, streaming: true });
140737
+ },
140738
+ fallback: async (threadId, turnId, chatKey, view) => {
140739
+ const binding = bindingForChatKey(chatKey);
140740
+ if (binding === void 0) return false;
140194
140741
  const text4 = [view.body, view.errorText ?? ""].filter((part) => part !== "").join("\n\n");
140195
- await deliver(await anchors.anchored(binding, threadId, turnId), text4);
140742
+ return deliver(await anchors.anchored(binding, threadId, turnId), text4);
140196
140743
  },
140197
140744
  schedule: (run, delayMs) => {
140198
140745
  const timer = setTimeout(run, delayMs);
@@ -140209,18 +140756,18 @@ var makeReplyCards = (core, failures, anchors) => {
140209
140756
  });
140210
140757
  host.onDispose(() => turnCards.dispose());
140211
140758
  void ready.then(async () => {
140212
- for (const [cardId, record2] of Object.entries(openTurnCardsOf(core.state))) {
140213
- const binding = bindingForChatKey(record2.chatKey);
140759
+ for (const [cardId, record3] of Object.entries(openTurnCardsOf(core.state))) {
140760
+ const binding = bindingForChatKey(record3.chatKey);
140214
140761
  const cards = binding === void 0 ? void 0 : adapterFor(binding.platform)?.turnCards;
140215
140762
  if (binding !== void 0 && cards !== void 0) {
140216
140763
  try {
140217
- const messages = await messagesAfterLastUser(host.threads, record2.threadId).catch(
140764
+ const messages = await messagesAfterLastUser(host.threads, record3.threadId).catch(
140218
140765
  () => []
140219
140766
  );
140220
140767
  await cards.update(
140221
140768
  binding,
140222
140769
  cardId,
140223
- interruptedView({ title: record2.title, body: trailingReply(messages) })
140770
+ interruptedView({ title: record3.title, body: trailingReply(messages) })
140224
140771
  );
140225
140772
  } catch (error) {
140226
140773
  host.log.warn("chat could not mark a stale card as interrupted", {
@@ -140410,7 +140957,7 @@ var makeSendFileTool = (deps) => ({
140410
140957
  });
140411
140958
 
140412
140959
  // src/sessions.ts
140413
- import { threadOrNull as threadOrNull6 } from "@ycodium-ai/plugin-api";
140960
+ import { threadOrNull as threadOrNull7 } from "@ycodium-ai/plugin-api";
140414
140961
  var WEB_LIST_PAGE = 100;
140415
140962
  var shellOf = (thread) => ({
140416
140963
  threadId: thread.id,
@@ -140470,7 +141017,7 @@ var makeSessions = (core) => {
140470
141017
  });
140471
141018
  };
140472
141019
  const rowForThread = async (binding, chatKey, threadId) => {
140473
- const thread = await threadOrNull6(host.threads, threadId, { archived: "include" });
141020
+ const thread = await threadOrNull7(host.threads, threadId, { archived: "include" });
140474
141021
  if (thread === null || thread.projectId !== binding.projectId) return null;
140475
141022
  return rowFromThread(shellOf(thread), chatKey, originOf(threadId, binding.projectId));
140476
141023
  };
@@ -140480,8 +141027,8 @@ var makeSessions = (core) => {
140480
141027
  await deliver(binding, formatSessions(rows, Date.now(), t));
140481
141028
  };
140482
141029
  const bindToSession = async (binding, chatKey, target2) => {
140483
- for (const [otherKey, record2] of Object.entries(core.state.chats)) {
140484
- if (otherKey !== chatKey && record2.threadId === target2.threadId) bindChat(otherKey, null);
141030
+ for (const [otherKey, record3] of Object.entries(core.state.chats)) {
141031
+ if (otherKey !== chatKey && record3.threadId === target2.threadId) bindChat(otherKey, null);
140485
141032
  }
140486
141033
  if (target2.origin === "web" && !hasAttached(core.state, chatKey, target2.threadId)) {
140487
141034
  commit("thread-attached", {
@@ -140562,7 +141109,7 @@ var makeSessions = (core) => {
140562
141109
  };
140563
141110
 
140564
141111
  // src/teamGoals.ts
140565
- import * as NodeCrypto2 from "node:crypto";
141112
+ import * as NodeCrypto3 from "node:crypto";
140566
141113
  var isRecord2 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
140567
141114
  var teamGoalRequestId = (binding, messageId) => {
140568
141115
  const identity = JSON.stringify([
@@ -140572,7 +141119,7 @@ var teamGoalRequestId = (binding, messageId) => {
140572
141119
  binding.chatId,
140573
141120
  messageId
140574
141121
  ]);
140575
- return NodeCrypto2.createHash("sha256").update(identity).digest("hex");
141122
+ return NodeCrypto3.createHash("sha256").update(identity).digest("hex");
140576
141123
  };
140577
141124
  var makeTeamGoals = (core) => {
140578
141125
  const { host, t, deliver, commit } = core;
@@ -140598,7 +141145,8 @@ var makeTeamGoals = (core) => {
140598
141145
  command: "startGoal",
140599
141146
  payload: {
140600
141147
  projectId: binding.projectId,
140601
- goal: message.text,
141148
+ goal: textWithInboundContext(message, binding.rowId, t),
141149
+ titleSeed: message.text,
140602
141150
  requestId: teamGoalRequestId(binding, message.messageId)
140603
141151
  }
140604
141152
  });
@@ -140643,6 +141191,7 @@ var DOWNLOAD_TIMEOUT_MS = 6e4;
140643
141191
  var HINT_TIMEOUT_MS = 3e3;
140644
141192
  var isTelegramToken = (token) => /^\d{5,20}:[A-Za-z0-9_-]{20,}$/u.test(token);
140645
141193
  var TELEGRAM_TEXT_MAX_CHARS = 4e3;
141194
+ var telegramDisplayName = (user) => [user?.first_name, user?.last_name].filter(Boolean).join(" ").trim() || user?.username;
140646
141195
  var chatIdOf = (message) => {
140647
141196
  const thread = message.message_thread_id;
140648
141197
  return message.is_topic_message === true && thread !== void 0 && thread > 0 ? `${String(message.chat.id)}:${String(thread)}` : String(message.chat.id);
@@ -140940,6 +141489,32 @@ var makeTelegramApi = (input) => {
140940
141489
  };
140941
141490
  };
140942
141491
 
141492
+ // src/soloGroup.ts
141493
+ var SOLO_GROUP_CACHE_MS = 5 * 6e4;
141494
+ var SOLO_GROUP_CACHE_LIMIT = 500;
141495
+ var makeSoloGroupCheck = (lookup2, now = () => Date.now()) => {
141496
+ const cache = /* @__PURE__ */ new Map();
141497
+ const inflight = /* @__PURE__ */ new Map();
141498
+ return async (chatId) => {
141499
+ const cached = cache.get(chatId);
141500
+ if (cached !== void 0 && now() - cached.at < SOLO_GROUP_CACHE_MS) return cached.solo;
141501
+ let asking = inflight.get(chatId);
141502
+ if (asking === void 0) {
141503
+ asking = lookup2(chatId).catch(() => void 0).finally(() => inflight.delete(chatId));
141504
+ inflight.set(chatId, asking);
141505
+ }
141506
+ const solo = await asking;
141507
+ if (solo === void 0) return false;
141508
+ cache.delete(chatId);
141509
+ cache.set(chatId, { solo, at: now() });
141510
+ if (cache.size > SOLO_GROUP_CACHE_LIMIT) {
141511
+ const oldest = cache.keys().next().value;
141512
+ if (oldest !== void 0) cache.delete(oldest);
141513
+ }
141514
+ return solo;
141515
+ };
141516
+ };
141517
+
140943
141518
  // src/telegramCommandMenu.ts
140944
141519
  var DESCRIPTIONS = {
140945
141520
  new: "menu.new",
@@ -140999,6 +141574,54 @@ var parsePanelData = (data) => {
140999
141574
  return { version: Number(match[1]), index: Number(match[2]) };
141000
141575
  };
141001
141576
 
141577
+ // src/telegramReply.ts
141578
+ var topicServiceMessage = (message, reply) => message.is_topic_message === true && message.message_thread_id !== void 0 && (message.message_thread_id === reply.message_id || message.message_thread_id === reply.message_thread_id) && [
141579
+ reply.forum_topic_created,
141580
+ reply.forum_topic_edited,
141581
+ reply.forum_topic_closed,
141582
+ reply.forum_topic_reopened,
141583
+ reply.general_forum_topic_hidden,
141584
+ reply.general_forum_topic_unhidden
141585
+ ].some((value2) => value2 !== void 0);
141586
+ var telegramReplyReference = (message, botUsername, botId) => {
141587
+ const reply = message.reply_to_message;
141588
+ const external = message.external_reply;
141589
+ if (reply !== void 0 && topicServiceMessage(message, reply) && external === void 0 && message.quote === void 0) {
141590
+ return null;
141591
+ }
141592
+ const source = external ?? reply;
141593
+ if (source === void 0 && message.quote === void 0) return null;
141594
+ const origin = external?.origin;
141595
+ const author = external === void 0 ? reply?.from : origin?.type === "user" ? origin.sender_user : void 0;
141596
+ const authorName = author === void 0 ? origin?.sender_user_name ?? origin?.author_signature ?? origin?.sender_chat?.title ?? origin?.chat?.title : telegramDisplayName(author);
141597
+ const fromBot = author !== void 0 && (botId !== void 0 && author.id === botId || botUsername !== null && author.username?.toLowerCase() === botUsername.toLowerCase());
141598
+ const attachments = [];
141599
+ if ((source?.photo?.length ?? 0) > 0) attachments.push({ kind: "image" });
141600
+ for (const [media, kind] of [
141601
+ [source?.document, "file"],
141602
+ [source?.audio, "audio"],
141603
+ [source?.voice, "audio"],
141604
+ [source?.video, "video"],
141605
+ [source?.video_note, "video"],
141606
+ [source?.animation, "video"]
141607
+ ]) {
141608
+ if (media !== void 0) {
141609
+ attachments.push({
141610
+ kind,
141611
+ ...media.file_name === void 0 ? {} : { name: media.file_name }
141612
+ });
141613
+ }
141614
+ }
141615
+ if (source?.sticker !== void 0) attachments.push({ kind: "other" });
141616
+ const content = message.quote?.text ?? source?.text ?? source?.caption;
141617
+ return {
141618
+ ...authorName ? { authorName } : {},
141619
+ ...fromBot ? { fromBot: true } : {},
141620
+ ...content === void 0 ? {} : { content },
141621
+ ...attachments.length === 0 ? {} : { attachments }
141622
+ };
141623
+ };
141624
+
141002
141625
  // src/telegramRichText.ts
141003
141626
  var TELEGRAM_RICH_TEXT_LIMIT = 3e4;
141004
141627
  var escapeCharacter = (character) => {
@@ -141223,6 +141846,7 @@ var makeTelegramAdapter = (deps) => {
141223
141846
  let acknowledged = 0;
141224
141847
  let backoff = BACKOFF_START_MS;
141225
141848
  let botUsername;
141849
+ let botId;
141226
141850
  let menuSent = false;
141227
141851
  let liveApi;
141228
141852
  const isSoloGroup = makeSoloGroupCheck(async (chatId) => {
@@ -141243,7 +141867,11 @@ var makeTelegramAdapter = (deps) => {
141243
141867
  const api = await apiFor(binding);
141244
141868
  liveApi = api;
141245
141869
  if (!running) return;
141246
- botUsername ??= (await api.getMe()).username ?? null;
141870
+ if (botUsername == null) {
141871
+ const own = await api.getMe();
141872
+ botUsername = own.username ?? null;
141873
+ botId = own.id;
141874
+ }
141247
141875
  if (!running) return;
141248
141876
  if (!menuSent) {
141249
141877
  menuSent = true;
@@ -141300,6 +141928,7 @@ var makeTelegramAdapter = (deps) => {
141300
141928
  if (!running) break;
141301
141929
  offset = nextOffset([update], offset);
141302
141930
  const senderId = message.from === void 0 ? null : String(message.from.id);
141931
+ const senderName = telegramDisplayName(message.from);
141303
141932
  const chatKind = message.chat.type === "private" ? "private" : "group";
141304
141933
  if (text4 === "" && resource === null) {
141305
141934
  const unsupported = unsupportedTypeOf(message);
@@ -141316,12 +141945,16 @@ var makeTelegramAdapter = (deps) => {
141316
141945
  continue;
141317
141946
  }
141318
141947
  const parentChatId = parentChatIdOf(message);
141948
+ const replyTo = telegramReplyReference(message, botUsername, botId);
141319
141949
  handlers.onMessage({
141320
141950
  messageId: String(message.message_id),
141321
141951
  chatId: chatIdOf(message),
141322
141952
  ...parentChatId === void 0 ? {} : { parentChatId },
141323
141953
  senderId,
141324
141954
  text: text4,
141955
+ ...senderName ? { senderName } : {},
141956
+ ...message.chat.title ? { chatTitle: message.chat.title } : {},
141957
+ ...replyTo === null ? {} : { replyTo },
141325
141958
  chatKind,
141326
141959
  resources: resource === null ? [] : [
141327
141960
  {
@@ -141538,7 +142171,7 @@ ${line}`
141538
142171
  };
141539
142172
 
141540
142173
  // src/turns.ts
141541
- import { projectOrNull as projectOrNull2, threadOrNull as threadOrNull7 } from "@ycodium-ai/plugin-api";
142174
+ import { projectOrNull as projectOrNull2, threadOrNull as threadOrNull8 } from "@ycodium-ai/plugin-api";
141542
142175
  var TITLE_MAX_CHARS = 72;
141543
142176
  var titleFrom = (text4) => text4.replace(/\s+/g, " ").slice(0, TITLE_MAX_CHARS) || "Chat thread";
141544
142177
  var makeTurns = (core, deps) => {
@@ -141547,7 +142180,7 @@ var makeTurns = (core, deps) => {
141547
142180
  const { pendingByChat, pendingFor, collectResources } = deps.attachments;
141548
142181
  const threadStillUsable = async (binding, threadId) => {
141549
142182
  try {
141550
- const thread = await threadOrNull7(host.threads, threadId, { archived: "include" });
142183
+ const thread = await threadOrNull8(host.threads, threadId, { archived: "include" });
141551
142184
  return thread !== null && thread.archivedAt == null && thread.projectId === binding.projectId;
141552
142185
  } catch {
141553
142186
  return true;
@@ -141645,6 +142278,7 @@ var makeTurns = (core, deps) => {
141645
142278
  } catch {
141646
142279
  }
141647
142280
  }
142281
+ const registered = deps.conclusions.starting(threadId);
141648
142282
  try {
141649
142283
  await host.threads.turns.start({
141650
142284
  requestId: crypto.randomUUID(),
@@ -141652,7 +142286,7 @@ var makeTurns = (core, deps) => {
141652
142286
  message: {
141653
142287
  messageId: userMessageId,
141654
142288
  role: "user",
141655
- text: notes.length === 0 ? text4 : [text4, ...notes].join("\n"),
142289
+ text: [textWithInboundContext(message, binding.rowId, t), ...notes].join("\n"),
141656
142290
  attachments: attachments.map(
141657
142291
  (attachment) => attachment.type === "image" ? { ...attachment, type: "image" } : { ...attachment, type: "file" }
141658
142292
  )
@@ -141662,9 +142296,19 @@ var makeTurns = (core, deps) => {
141662
142296
  interactionMode: interactionModeOf(chatKey),
141663
142297
  ...intentAction !== void 0 ? { intentAction } : {}
141664
142298
  });
142299
+ if (intentAction?.kind !== "steer") {
142300
+ deps.conclusions.owe({
142301
+ chatKey,
142302
+ threadId,
142303
+ userMessageId,
142304
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString()
142305
+ });
142306
+ }
141665
142307
  } catch (error) {
141666
142308
  if (carried !== void 0) pendingByChat.set(chatKey, carried);
141667
142309
  throw error;
142310
+ } finally {
142311
+ registered();
141668
142312
  }
141669
142313
  deps.noteInboundUserMessage?.(userMessageId);
141670
142314
  host.log.info("chat started a turn from a message", {
@@ -141715,8 +142359,8 @@ var makeTurns = (core, deps) => {
141715
142359
 
141716
142360
  // src/webSync.ts
141717
142361
  var isWebSyncChat = (state, chatKey, threadId) => {
141718
- const record2 = chatOf(state, chatKey);
141719
- return record2.chatKind === "private" && record2.threadId === threadId;
142362
+ const record3 = chatOf(state, chatKey);
142363
+ return record3.chatKind === "private" && record3.threadId === threadId;
141720
142364
  };
141721
142365
  var IM_ORIGIN_LIMIT = 256;
141722
142366
  var MIRRORED_LIMIT = 256;
@@ -142269,7 +142913,7 @@ var makeWechatAdapter = (deps) => {
142269
142913
  };
142270
142914
 
142271
142915
  // src/wecomUpload.ts
142272
- import * as NodeCrypto3 from "node:crypto";
142916
+ import * as NodeCrypto4 from "node:crypto";
142273
142917
  var WECOM_CHUNK_BYTES = 512 * 1024;
142274
142918
  var WECOM_MAX_CHUNKS = 100;
142275
142919
  var WECOM_MIN_UPLOAD_BYTES = 5;
@@ -142302,7 +142946,7 @@ var uploadWecomMedia = async (input) => {
142302
142946
  filename: clampName(input.filename),
142303
142947
  total_size: bytes.byteLength,
142304
142948
  total_chunks: totalChunks,
142305
- md5: NodeCrypto3.createHash("md5").update(bytes).digest("hex")
142949
+ md5: NodeCrypto4.createHash("md5").update(bytes).digest("hex")
142306
142950
  });
142307
142951
  const uploadId = init.upload_id;
142308
142952
  if (typeof uploadId !== "string" || uploadId === "") {
@@ -142351,6 +142995,43 @@ var uploadWecomMedia = async (input) => {
142351
142995
  return mediaId;
142352
142996
  };
142353
142997
 
142998
+ // src/wecomReply.ts
142999
+ var wecomReplyReference = (quote) => {
143000
+ const attachments = [];
143001
+ const parts = [];
143002
+ const read = (item) => {
143003
+ switch (item.msgtype) {
143004
+ case "text":
143005
+ if (item.text?.content) parts.push(item.text.content);
143006
+ return true;
143007
+ case "voice":
143008
+ if (item.voice?.content) parts.push(item.voice.content);
143009
+ attachments.push({ kind: "audio" });
143010
+ return true;
143011
+ case "image":
143012
+ attachments.push({ kind: "image" });
143013
+ return true;
143014
+ case "file":
143015
+ attachments.push({ kind: "file" });
143016
+ return true;
143017
+ case "video":
143018
+ attachments.push({ kind: "video" });
143019
+ return true;
143020
+ case "mixed":
143021
+ for (const child of item.mixed?.msg_item ?? []) read(child);
143022
+ return true;
143023
+ default:
143024
+ return false;
143025
+ }
143026
+ };
143027
+ const supported = read(quote);
143028
+ return {
143029
+ ...parts.length === 0 ? {} : { content: parts.join("\n") },
143030
+ ...attachments.length === 0 ? {} : { attachments },
143031
+ ...!supported ? { unavailableReason: "unsupported" } : {}
143032
+ };
143033
+ };
143034
+
142354
143035
  // src/wecomApi.ts
142355
143036
  var DEFAULT_WECOM_ENDPOINT = "wss://openws.work.weixin.qq.com/wssvr/";
142356
143037
  var PING_INTERVAL_MS = 3e4;
@@ -142435,6 +143116,7 @@ var parseWecomMessage = (frame, botId) => {
142435
143116
  chatId,
142436
143117
  senderUserId: body.from?.userid ?? null,
142437
143118
  text: text4,
143119
+ ...body.quote === void 0 ? {} : { replyTo: wecomReplyReference(body.quote) },
142438
143120
  resources,
142439
143121
  requestId: frame.headers?.req_id ?? null,
142440
143122
  chatType: mentioned ? "single" : chatType,
@@ -142674,7 +143356,7 @@ var runWecomClient = (input) => {
142674
143356
  };
142675
143357
 
142676
143358
  // src/wecomMedia.ts
142677
- import * as NodeCrypto4 from "node:crypto";
143359
+ import * as NodeCrypto5 from "node:crypto";
142678
143360
 
142679
143361
  // src/wecomPadding.ts
142680
143362
  var PADDING_BLOCK = 32;
@@ -142705,7 +143387,7 @@ var decryptWecomMedia = (cipher, aesKey) => {
142705
143387
  if (cipher.byteLength === 0 || cipher.byteLength % AES_BLOCK !== 0) {
142706
143388
  throw new WecomDecryptError("the ciphertext is not a whole number of blocks");
142707
143389
  }
142708
- const decipher = NodeCrypto4.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
143390
+ const decipher = NodeCrypto5.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
142709
143391
  decipher.setAutoPadding(false);
142710
143392
  const plain = Buffer.concat([decipher.update(cipher), decipher.final()]);
142711
143393
  const unpadded = stripPadding(plain);
@@ -142857,6 +143539,7 @@ ${kept}`;
142857
143539
  chatId: message.chatId,
142858
143540
  senderId: message.senderUserId,
142859
143541
  text: message.text,
143542
+ ...message.replyTo === void 0 ? {} : { replyTo: message.replyTo },
142860
143543
  // 群里 @ 到机器人的消息被折算成 single 才放行;真实的聊天类型只留在 conversation。
142861
143544
  chatKind: message.conversation === "single" ? "private" : "group",
142862
143545
  // 下载地址与密钥打成一个键:核心只管把它原样交回来。
@@ -142995,14 +143678,14 @@ var WecomAppApiError = class extends Error {
142995
143678
  this.hint = extras?.hint;
142996
143679
  }
142997
143680
  };
142998
- var cleanString = (value2) => {
143681
+ var cleanString2 = (value2) => {
142999
143682
  if (typeof value2 !== "string") return null;
143000
143683
  const trimmed = value2.trim();
143001
143684
  return trimmed === "" ? null : trimmed;
143002
143685
  };
143003
143686
  var isHttpUrl = (raw) => /^https?:\/\//iu.test(raw) && URL.canParse(raw);
143004
143687
  var normalizeApiBaseUrl = (value2) => {
143005
- const raw = cleanString(value2);
143688
+ const raw = cleanString2(value2);
143006
143689
  if (raw === null) return WECOM_APP_DEFAULT_API_BASE;
143007
143690
  if (!isHttpUrl(raw)) {
143008
143691
  throw new WecomAppApiError("invalid-api-base", "API base must be an http(s) URL.");
@@ -143227,7 +143910,7 @@ var WecomAppApi = class {
143227
143910
  import * as NodeHttp2 from "node:http";
143228
143911
 
143229
143912
  // src/wecomAppCrypto.ts
143230
- import * as NodeCrypto5 from "node:crypto";
143913
+ import * as NodeCrypto6 from "node:crypto";
143231
143914
  var AES_KEY_BYTES = 32;
143232
143915
  var RANDOM_BYTES = 16;
143233
143916
  var WecomAppCryptoError = class extends Error {
@@ -143250,13 +143933,13 @@ var timingSafeEqualString2 = (a, b) => {
143250
143933
  const left = Buffer.from(a);
143251
143934
  const right = Buffer.from(b);
143252
143935
  if (left.length === 0 || left.length !== right.length) return false;
143253
- return NodeCrypto5.timingSafeEqual(left, right);
143936
+ return NodeCrypto6.timingSafeEqual(left, right);
143254
143937
  };
143255
- var computeSignature = (token, timestamp, nonce, encrypt) => NodeCrypto5.createHash("sha1").update([token, timestamp, nonce, encrypt].sort().join("")).digest("hex");
143938
+ var computeSignature = (token, timestamp, nonce, encrypt) => NodeCrypto6.createHash("sha1").update([token, timestamp, nonce, encrypt].sort().join("")).digest("hex");
143256
143939
  var verifySignature = (token, timestamp, nonce, encrypt, signature) => timingSafeEqualString2(computeSignature(token, timestamp, nonce, encrypt), signature);
143257
143940
  var decrypt = (encodingAESKey, encryptText, expectReceiveId) => {
143258
143941
  const { key, iv } = parseEncodingAESKey(encodingAESKey);
143259
- const decipher = NodeCrypto5.createDecipheriv("aes-256-cbc", key, iv);
143942
+ const decipher = NodeCrypto6.createDecipheriv("aes-256-cbc", key, iv);
143260
143943
  decipher.setAutoPadding(false);
143261
143944
  let deciphered;
143262
143945
  try {
@@ -143315,7 +143998,7 @@ var createWecomAppCrypto = (input) => {
143315
143998
  decrypt: (encryptText) => decrypt(encodingAESKey, encryptText, corpId).message
143316
143999
  };
143317
144000
  };
143318
- var deriveCallbackPathSecret = (token, encodingAESKey) => NodeCrypto5.createHash("sha256").update(`${token.trim()}:${encodingAESKey.trim()}`).digest("hex").slice(0, 24);
144001
+ var deriveCallbackPathSecret = (token, encodingAESKey) => NodeCrypto6.createHash("sha256").update(`${token.trim()}:${encodingAESKey.trim()}`).digest("hex").slice(0, 24);
143319
144002
 
143320
144003
  // src/wecomAppParse.ts
143321
144004
  var isRecord4 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
@@ -144034,13 +144717,22 @@ var imPlugin = definePlugin({
144034
144717
  const acknowledgements = makeAcknowledgements(core);
144035
144718
  const failures = makeFailureNotices(core);
144036
144719
  const replyAnchors = makeReplyAnchors(core.host);
144037
- const replyCards = makeReplyCards(core, failures, replyAnchors);
144720
+ const conclusions = makeConclusionLedger(core);
144721
+ const conclusionDelivery = makeConclusionDelivery(core, replyAnchors);
144722
+ const replyCards = makeReplyCards(
144723
+ core,
144724
+ failures,
144725
+ replyAnchors,
144726
+ conclusions,
144727
+ conclusionDelivery
144728
+ );
144038
144729
  const teams = makeTeamGoals(core);
144039
144730
  const notifications = makeNotifications(core, {
144040
144731
  clearWorking: acknowledgements.clearWorking,
144041
144732
  replyCards,
144042
144733
  failures,
144043
- anchors: replyAnchors
144734
+ conclusions,
144735
+ delivery: conclusionDelivery
144044
144736
  });
144045
144737
  const webSync = makeWebSync(core);
144046
144738
  const approvals = makeApprovals(core, replyAnchors);
@@ -144051,6 +144743,7 @@ var imPlugin = definePlugin({
144051
144743
  });
144052
144744
  const inboundAttachments = makeInboundAttachments(core);
144053
144745
  const turns = makeTurns(core, {
144746
+ conclusions,
144054
144747
  attachments: inboundAttachments,
144055
144748
  queueFlow: interactions.queueFlow,
144056
144749
  rememberThreadProject: notifications.rememberThreadProject,