@ycodium-ai/plugin-im 0.2.2972 → 0.2.2976

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
@@ -126225,6 +126225,26 @@ var readAttached = (data) => {
126225
126225
  return chatKey === null || threadId === null || projectId === null || attachedAt === null ? null : { chatKey, threadId, projectId, attachedAt };
126226
126226
  };
126227
126227
 
126228
+ // src/chatAliases.ts
126229
+ var aliasKey = (rowId, alias) => `${rowId}:${alias}`;
126230
+ var chatAliasOf = (state, rowId, alias) => state.aliases?.[aliasKey(rowId, alias)];
126231
+ var nonEmpty2 = (value2) => typeof value2 === "string" && value2 !== "" ? value2 : null;
126232
+ var foldAliased = (state, data) => {
126233
+ const rowId = nonEmpty2(data["rowId"]);
126234
+ const alias = nonEmpty2(data["alias"]);
126235
+ const chatId = nonEmpty2(data["chatId"]);
126236
+ if (rowId === null || alias === null || chatId === null) return state;
126237
+ const key = aliasKey(rowId, alias);
126238
+ if (state.aliases?.[key] !== void 0) return state;
126239
+ return { ...state, aliases: { ...state.aliases, [key]: chatId } };
126240
+ };
126241
+
126242
+ // src/keepNewest.ts
126243
+ var keepNewest = (record2, limit) => {
126244
+ const entries = Object.entries(record2);
126245
+ return entries.length <= limit ? record2 : Object.fromEntries(entries.slice(entries.length - limit));
126246
+ };
126247
+
126228
126248
  // src/chatState.ts
126229
126249
  var IM_STATE_KEY = "chats";
126230
126250
  var IM_STATE_VERSION = 1;
@@ -126236,7 +126256,8 @@ var emptyState = () => ({
126236
126256
  approvals: {},
126237
126257
  turnCards: {},
126238
126258
  questions: {},
126239
- queue: {}
126259
+ queue: {},
126260
+ aliases: {}
126240
126261
  });
126241
126262
  var questionsOf = (state) => state.questions ?? {};
126242
126263
  var queueOf = (state) => state.queue ?? {};
@@ -126294,11 +126315,11 @@ var openQueueOfThread = (state, threadId) => Object.values(queueOf(state)).filte
126294
126315
  (record2) => record2.threadId === threadId && record2.settled === void 0
126295
126316
  );
126296
126317
  var isRecord = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
126297
- var nonEmpty2 = (value2) => typeof value2 === "string" && value2 !== "" ? value2 : null;
126318
+ var nonEmpty3 = (value2) => typeof value2 === "string" && value2 !== "" ? value2 : null;
126298
126319
  var readTarget = (value2) => {
126299
126320
  if (!isRecord(value2)) return null;
126300
126321
  if (value2["kind"] === "thread") return { kind: "thread" };
126301
- const teamId = nonEmpty2(value2["teamId"]);
126322
+ const teamId = nonEmpty3(value2["teamId"]);
126302
126323
  return value2["kind"] === "team" && teamId !== null ? { kind: "team", teamId } : null;
126303
126324
  };
126304
126325
  var readButtons = (value2) => {
@@ -126306,8 +126327,8 @@ var readButtons = (value2) => {
126306
126327
  const buttons = [];
126307
126328
  for (const entry of value2) {
126308
126329
  if (!isRecord(entry)) return null;
126309
- const token = nonEmpty2(entry["token"]);
126310
- const decision = nonEmpty2(entry["decision"]);
126330
+ const token = nonEmpty3(entry["token"]);
126331
+ const decision = nonEmpty3(entry["decision"]);
126311
126332
  const label = typeof entry["label"] === "string" ? entry["label"] : null;
126312
126333
  if (token === null || decision === null || label === null) return null;
126313
126334
  buttons.push({ token, label, decision });
@@ -126319,8 +126340,8 @@ var readQuestionOptions = (value2) => {
126319
126340
  const options = [];
126320
126341
  for (const entry of value2) {
126321
126342
  if (!isRecord(entry)) return null;
126322
- const token = nonEmpty2(entry["token"]);
126323
- const optionValue = nonEmpty2(entry["value"]);
126343
+ const token = nonEmpty3(entry["token"]);
126344
+ const optionValue = nonEmpty3(entry["value"]);
126324
126345
  if (token === null || optionValue === null || typeof entry["label"] !== "string") return null;
126325
126346
  const planAction = entry["planAction"];
126326
126347
  options.push({
@@ -126338,10 +126359,10 @@ var readQuestionItems = (value2) => {
126338
126359
  const items = [];
126339
126360
  for (const entry of value2) {
126340
126361
  if (!isRecord(entry)) return null;
126341
- const id = nonEmpty2(entry["id"]);
126362
+ const id = nonEmpty3(entry["id"]);
126342
126363
  const options = readQuestionOptions(entry["options"]);
126343
126364
  if (id === null || options === null || typeof entry["question"] !== "string") return null;
126344
- const submitToken = nonEmpty2(entry["submitToken"]);
126365
+ const submitToken = nonEmpty3(entry["submitToken"]);
126345
126366
  items.push({
126346
126367
  id,
126347
126368
  header: typeof entry["header"] === "string" ? entry["header"] : "",
@@ -126384,10 +126405,6 @@ var withQueueRecord = (state, intentId, patch) => {
126384
126405
  if (intentId === null || record2 === void 0) return state;
126385
126406
  return { ...state, queue: { ...queueOf(state), [intentId]: patch(record2) } };
126386
126407
  };
126387
- var keepNewest = (record2, limit) => {
126388
- const entries = Object.entries(record2);
126389
- return entries.length <= limit ? record2 : Object.fromEntries(entries.slice(entries.length - limit));
126390
- };
126391
126408
  var withChat = (state, chatKey, patch) => ({
126392
126409
  ...state,
126393
126410
  chats: { ...state.chats, [chatKey]: { ...chatOf(state, chatKey), ...patch } }
@@ -126396,19 +126413,19 @@ var foldImEvent = (state, name, data) => {
126396
126413
  if (!isRecord(data)) return state;
126397
126414
  switch (name) {
126398
126415
  case "chat-updated": {
126399
- const chatKey = nonEmpty2(data["chatKey"]);
126416
+ const chatKey = nonEmpty3(data["chatKey"]);
126400
126417
  if (chatKey === null) return state;
126401
126418
  const patch = {};
126402
126419
  if (data["projectId"] === null) patch.projectId = null;
126403
- else if (nonEmpty2(data["projectId"]) !== null) patch.projectId = nonEmpty2(data["projectId"]);
126420
+ else if (nonEmpty3(data["projectId"]) !== null) patch.projectId = nonEmpty3(data["projectId"]);
126404
126421
  if (data["threadId"] === null) patch.threadId = null;
126405
- else if (nonEmpty2(data["threadId"]) !== null) patch.threadId = nonEmpty2(data["threadId"]);
126422
+ else if (nonEmpty3(data["threadId"]) !== null) patch.threadId = nonEmpty3(data["threadId"]);
126406
126423
  const target2 = readTarget(data["target"]);
126407
126424
  if (target2 !== null) patch.target = target2;
126408
126425
  if (data["model"] === null) patch.model = null;
126409
126426
  else if (isRecord(data["model"])) {
126410
- const instanceId = nonEmpty2(data["model"]["instanceId"]);
126411
- const model = nonEmpty2(data["model"]["model"]);
126427
+ const instanceId = nonEmpty3(data["model"]["instanceId"]);
126428
+ const model = nonEmpty3(data["model"]["model"]);
126412
126429
  if (instanceId !== null && model !== null) patch.model = { instanceId, model };
126413
126430
  }
126414
126431
  if (typeof data["plan"] === "boolean") patch.plan = data["plan"];
@@ -126419,10 +126436,10 @@ var foldImEvent = (state, name, data) => {
126419
126436
  return withChat(state, chatKey, patch);
126420
126437
  }
126421
126438
  case "thread-opened": {
126422
- const chatKey = nonEmpty2(data["chatKey"]);
126423
- const threadId = nonEmpty2(data["threadId"]);
126424
- const projectId = nonEmpty2(data["projectId"]);
126425
- const openedAt = nonEmpty2(data["openedAt"]);
126439
+ const chatKey = nonEmpty3(data["chatKey"]);
126440
+ const threadId = nonEmpty3(data["threadId"]);
126441
+ const projectId = nonEmpty3(data["projectId"]);
126442
+ const openedAt = nonEmpty3(data["openedAt"]);
126426
126443
  if (chatKey === null || threadId === null || projectId === null || openedAt === null) {
126427
126444
  return state;
126428
126445
  }
@@ -126443,9 +126460,9 @@ var foldImEvent = (state, name, data) => {
126443
126460
  return { ...state, attached: keepNewest(attached, ATTACHED_THREAD_LIMIT) };
126444
126461
  }
126445
126462
  case "approval-offered": {
126446
- const chatKey = nonEmpty2(data["chatKey"]);
126447
- const threadId = nonEmpty2(data["threadId"]);
126448
- const requestId = nonEmpty2(data["requestId"]);
126463
+ const chatKey = nonEmpty3(data["chatKey"]);
126464
+ const threadId = nonEmpty3(data["threadId"]);
126465
+ const requestId = nonEmpty3(data["requestId"]);
126449
126466
  const options = readButtons(data["options"]);
126450
126467
  if (chatKey === null || threadId === null || requestId === null || options === null) {
126451
126468
  return state;
@@ -126462,7 +126479,7 @@ var foldImEvent = (state, name, data) => {
126462
126479
  };
126463
126480
  }
126464
126481
  case "approval-resolved": {
126465
- const requestId = nonEmpty2(data["requestId"]);
126482
+ const requestId = nonEmpty3(data["requestId"]);
126466
126483
  const existing = requestId === null ? void 0 : state.approvals[requestId];
126467
126484
  if (requestId === null || existing === void 0) return state;
126468
126485
  return {
@@ -126471,8 +126488,8 @@ var foldImEvent = (state, name, data) => {
126471
126488
  };
126472
126489
  }
126473
126490
  case "approval-carded": {
126474
- const requestId = nonEmpty2(data["requestId"]);
126475
- const cardId = nonEmpty2(data["cardId"]);
126491
+ const requestId = nonEmpty3(data["requestId"]);
126492
+ const cardId = nonEmpty3(data["cardId"]);
126476
126493
  const existing = requestId === null ? void 0 : state.approvals[requestId];
126477
126494
  if (requestId === null || cardId === null || existing === void 0) return state;
126478
126495
  return {
@@ -126481,9 +126498,9 @@ var foldImEvent = (state, name, data) => {
126481
126498
  };
126482
126499
  }
126483
126500
  case "question-offered": {
126484
- const chatKey = nonEmpty2(data["chatKey"]);
126485
- const threadId = nonEmpty2(data["threadId"]);
126486
- const requestId = nonEmpty2(data["requestId"]);
126501
+ const chatKey = nonEmpty3(data["chatKey"]);
126502
+ const threadId = nonEmpty3(data["threadId"]);
126503
+ const requestId = nonEmpty3(data["requestId"]);
126487
126504
  const items = readQuestionItems(data["items"]);
126488
126505
  if (chatKey === null || threadId === null || requestId === null || items === null) {
126489
126506
  return state;
@@ -126500,12 +126517,12 @@ var foldImEvent = (state, name, data) => {
126500
126517
  };
126501
126518
  }
126502
126519
  case "question-carded": {
126503
- const cardId = nonEmpty2(data["cardId"]);
126520
+ const cardId = nonEmpty3(data["cardId"]);
126504
126521
  if (cardId === null) return state;
126505
126522
  return withQuestionItem(
126506
126523
  state,
126507
- nonEmpty2(data["requestId"]),
126508
- nonEmpty2(data["questionId"]),
126524
+ nonEmpty3(data["requestId"]),
126525
+ nonEmpty3(data["questionId"]),
126509
126526
  (item) => ({ ...item, cardId })
126510
126527
  );
126511
126528
  }
@@ -126514,8 +126531,8 @@ var foldImEvent = (state, name, data) => {
126514
126531
  if (!Array.isArray(selected)) return state;
126515
126532
  return withQuestionItem(
126516
126533
  state,
126517
- nonEmpty2(data["requestId"]),
126518
- nonEmpty2(data["questionId"]),
126534
+ nonEmpty3(data["requestId"]),
126535
+ nonEmpty3(data["questionId"]),
126519
126536
  (item) => ({ ...item, selected })
126520
126537
  );
126521
126538
  }
@@ -126525,13 +126542,13 @@ var foldImEvent = (state, name, data) => {
126525
126542
  const label = typeof data["label"] === "string" ? data["label"] : "";
126526
126543
  return withQuestionItem(
126527
126544
  state,
126528
- nonEmpty2(data["requestId"]),
126529
- nonEmpty2(data["questionId"]),
126545
+ nonEmpty3(data["requestId"]),
126546
+ nonEmpty3(data["questionId"]),
126530
126547
  (item) => ({ ...item, answer, answerLabel: label })
126531
126548
  );
126532
126549
  }
126533
126550
  case "question-resolved": {
126534
- const requestId = nonEmpty2(data["requestId"]);
126551
+ const requestId = nonEmpty3(data["requestId"]);
126535
126552
  const existing = requestId === null ? void 0 : questionsOf(state)[requestId];
126536
126553
  if (requestId === null || existing === void 0) return state;
126537
126554
  const ending = data["ending"];
@@ -126548,12 +126565,12 @@ var foldImEvent = (state, name, data) => {
126548
126565
  };
126549
126566
  }
126550
126567
  case "queue-offered": {
126551
- const chatKey = nonEmpty2(data["chatKey"]);
126552
- const threadId = nonEmpty2(data["threadId"]);
126553
- const intentId = nonEmpty2(data["intentId"]);
126554
- const steerToken = nonEmpty2(data["steerToken"]);
126555
- const withdrawToken = nonEmpty2(data["withdrawToken"]);
126556
- const resumeToken = nonEmpty2(data["resumeToken"]);
126568
+ const chatKey = nonEmpty3(data["chatKey"]);
126569
+ const threadId = nonEmpty3(data["threadId"]);
126570
+ const intentId = nonEmpty3(data["intentId"]);
126571
+ const steerToken = nonEmpty3(data["steerToken"]);
126572
+ const withdrawToken = nonEmpty3(data["withdrawToken"]);
126573
+ const resumeToken = nonEmpty3(data["resumeToken"]);
126557
126574
  if (chatKey === null || threadId === null || intentId === null || steerToken === null || withdrawToken === null || resumeToken === null) {
126558
126575
  return state;
126559
126576
  }
@@ -126578,30 +126595,30 @@ var foldImEvent = (state, name, data) => {
126578
126595
  };
126579
126596
  }
126580
126597
  case "queue-carded": {
126581
- const cardId = nonEmpty2(data["cardId"]);
126598
+ const cardId = nonEmpty3(data["cardId"]);
126582
126599
  if (cardId === null) return state;
126583
- return withQueueRecord(state, nonEmpty2(data["intentId"]), (record2) => ({
126600
+ return withQueueRecord(state, nonEmpty3(data["intentId"]), (record2) => ({
126584
126601
  ...record2,
126585
126602
  cardId
126586
126603
  }));
126587
126604
  }
126588
126605
  case "queue-steering":
126589
- return withQueueRecord(state, nonEmpty2(data["intentId"]), (record2) => ({
126606
+ return withQueueRecord(state, nonEmpty3(data["intentId"]), (record2) => ({
126590
126607
  ...record2,
126591
126608
  steering: data["steering"] === true
126592
126609
  }));
126593
126610
  case "queue-settled": {
126594
126611
  const outcome = data["outcome"];
126595
126612
  if (outcome !== "steered" && outcome !== "sent" && outcome !== "removed") return state;
126596
- return withQueueRecord(state, nonEmpty2(data["intentId"]), (record2) => ({
126613
+ return withQueueRecord(state, nonEmpty3(data["intentId"]), (record2) => ({
126597
126614
  ...record2,
126598
126615
  settled: outcome
126599
126616
  }));
126600
126617
  }
126601
126618
  case "turn-card-opened": {
126602
- const cardId = nonEmpty2(data["cardId"]);
126603
- const chatKey = nonEmpty2(data["chatKey"]);
126604
- const threadId = nonEmpty2(data["threadId"]);
126619
+ const cardId = nonEmpty3(data["cardId"]);
126620
+ const chatKey = nonEmpty3(data["chatKey"]);
126621
+ const threadId = nonEmpty3(data["threadId"]);
126605
126622
  if (cardId === null || chatKey === null || threadId === null) return state;
126606
126623
  const title = typeof data["title"] === "string" ? data["title"] : "";
126607
126624
  return {
@@ -126613,11 +126630,13 @@ var foldImEvent = (state, name, data) => {
126613
126630
  };
126614
126631
  }
126615
126632
  case "turn-card-closed": {
126616
- const cardId = nonEmpty2(data["cardId"]);
126633
+ const cardId = nonEmpty3(data["cardId"]);
126617
126634
  if (cardId === null) return state;
126618
126635
  const { [cardId]: _closed, ...rest } = openTurnCardsOf(state);
126619
126636
  return { ...state, turnCards: rest };
126620
126637
  }
126638
+ case "chat-aliased":
126639
+ return foldAliased(state, data);
126621
126640
  default:
126622
126641
  return state;
126623
126642
  }
@@ -126990,6 +127009,7 @@ var readBots = (config2, adapterFor) => {
126990
127009
  };
126991
127010
  var bindingOf = (bot, chatId, state) => ({
126992
127011
  ...bot,
127012
+ reply: void 0,
126993
127013
  chatId,
126994
127014
  projectId: chatOf(state, chatKeyOf({ rowId: bot.rowId, chatId })).projectId ?? ""
126995
127015
  });
@@ -127044,7 +127064,7 @@ var numberedApprovalText = (request) => [
127044
127064
  ].join("\n");
127045
127065
 
127046
127066
  // src/approvals.ts
127047
- var makeApprovals = (core) => {
127067
+ var makeApprovals = (core, anchors) => {
127048
127068
  const { host, t, adapterFor, commit, outbound, bindingForChatKey, bindingOfThread, ready } = core;
127049
127069
  const settleApprovalCard = async (approval, settlement) => {
127050
127070
  if (approval.cardId === void 0) return;
@@ -127080,7 +127100,7 @@ var makeApprovals = (core) => {
127080
127100
  };
127081
127101
  const sendApprovalCard = async (binding, adapter, threadId, offer) => {
127082
127102
  try {
127083
- const cardId = await adapter.askApproval(binding, {
127103
+ const cardId = await adapter.askApproval(await anchors.anchored(binding, threadId, null), {
127084
127104
  title: offer.title,
127085
127105
  body: offer.body,
127086
127106
  options: offer.buttons,
@@ -127103,13 +127123,15 @@ var makeApprovals = (core) => {
127103
127123
  if (binding === void 0) return;
127104
127124
  const adapter = adapterFor(binding.platform);
127105
127125
  if (adapter === void 0) return;
127106
- const pending = await host.projections.listPendingApprovals(threadId);
127126
+ const pending = await host.approvals.pending.list({ threadId });
127107
127127
  const fresh = pending.filter(
127108
127128
  (candidate) => core.state.approvals[candidate.requestId] === void 0
127109
127129
  );
127110
127130
  const offers = (adapter.answerByText === void 0 ? fresh : fresh.slice(0, 1)).map(
127111
127131
  (approval) => {
127112
- const offered = approval.options.filter((option) => option.decision !== "acceptAlways");
127132
+ const offered = (approval.options ?? []).filter(
127133
+ (option) => option.decision !== "acceptAlways"
127134
+ );
127113
127135
  const options = offered.length > 0 ? offered : [
127114
127136
  { decision: "accept", label: t("approval.allow") },
127115
127137
  { decision: "decline", label: t("approval.deny") }
@@ -127224,7 +127246,7 @@ var makeChatSetup = (core, deps) => {
127224
127246
  listedProjectsByChat.set(chatKey, listedProjectIds(rows));
127225
127247
  if (rows.length > 0 && hasPanel(binding)) {
127226
127248
  try {
127227
- await deps.openPicker(chatKey, rows);
127249
+ await deps.openPicker(chatKey, rows, binding.reply);
127228
127250
  return;
127229
127251
  } catch (error) {
127230
127252
  host.log.warn("chat could not open the project picker card", {
@@ -127574,7 +127596,7 @@ var makeCommands = (core, deps) => {
127574
127596
  case "panel":
127575
127597
  if (hasPanel(binding)) {
127576
127598
  try {
127577
- await openPanel(chatKey);
127599
+ await openPanel(chatKey, binding.reply);
127578
127600
  return true;
127579
127601
  } catch (error) {
127580
127602
  host.log.warn("chat could not open the command panel", {
@@ -127694,51 +127716,339 @@ var makeCommands = (core, deps) => {
127694
127716
  return { handleCommand, targetOf };
127695
127717
  };
127696
127718
 
127719
+ // src/keyedQueue.ts
127720
+ var makeKeyedQueue = (onError) => {
127721
+ const tails = /* @__PURE__ */ new Map();
127722
+ return (key, work) => {
127723
+ const run = (tails.get(key) ?? Promise.resolve()).then(work).catch(onError).then(() => {
127724
+ if (tails.get(key) === run) tails.delete(key);
127725
+ });
127726
+ tails.set(key, run);
127727
+ };
127728
+ };
127729
+
127697
127730
  // src/connections.ts
127731
+ var connectionKey = (adapter, bot) => JSON.stringify([
127732
+ adapter.platform,
127733
+ bot.displayName,
127734
+ // 「跟机器默认」与「不加代理」是两种选择,分开记。
127735
+ bot.proxy === void 0 ? [] : [bot.proxy],
127736
+ bot.groupMention,
127737
+ [...adapter.columns.map((column) => column.key), ...adapter.unlistedValues ?? []].map(
127738
+ (key) => bot.values[key] ?? ""
127739
+ )
127740
+ ]);
127698
127741
  var startConnections = (core, inboundHandlersFor) => {
127699
127742
  const { host, adapterFor, ready } = core;
127700
- const bots = core.bots();
127701
- const connections = [];
127702
- const started = /* @__PURE__ */ new Set();
127743
+ const live = /* @__PURE__ */ new Map();
127744
+ const enqueueForRow = makeKeyedQueue((error) => {
127745
+ host.log.warn("chat could not start an inbound connection", { error: describeError(error) });
127746
+ });
127703
127747
  let disposed = false;
127704
- for (const bot of bots) {
127748
+ const open = (bot, key) => {
127705
127749
  const adapter = adapterFor(bot.platform);
127706
- if (adapter === void 0) continue;
127707
- const fingerprint = `${bot.platform}:${adapter.columns.map((column) => bot.values[column.key] ?? "").join(" ")}`;
127708
- if (started.has(fingerprint)) {
127709
- host.log.warn("chat did not connect a bot that repeats another bot's credentials", {
127710
- bot: bot.displayName,
127711
- platform: bot.platform
127712
- });
127713
- continue;
127714
- }
127715
- started.add(fingerprint);
127716
- void ready.then(() => adapter.connect(bot, inboundHandlersFor(bot))).then((connection) => {
127717
- if (disposed) connection.close();
127718
- else connections.push(connection);
127719
- }).catch((error) => {
127720
- host.log.warn("chat could not start an inbound connection", {
127721
- bot: bot.displayName,
127722
- platform: bot.platform,
127723
- error: describeError(error)
127724
- });
127750
+ let closed = false;
127751
+ let closeConnection;
127752
+ enqueueForRow(bot.rowId, async () => {
127753
+ try {
127754
+ await ready;
127755
+ if (closed || adapter === void 0) return;
127756
+ const connection = await adapter.connect(bot, inboundHandlersFor(bot));
127757
+ if (closed) connection.close();
127758
+ else closeConnection = () => connection.close();
127759
+ } catch (error) {
127760
+ host.log.warn("chat could not start an inbound connection", {
127761
+ bot: bot.displayName,
127762
+ platform: bot.platform,
127763
+ error: describeError(error)
127764
+ });
127765
+ }
127725
127766
  });
127726
- }
127767
+ return {
127768
+ key,
127769
+ close: () => {
127770
+ closed = true;
127771
+ closeConnection?.();
127772
+ }
127773
+ };
127774
+ };
127775
+ const reconcile = () => {
127776
+ const bots = disposed ? [] : core.bots();
127777
+ const wanted = /* @__PURE__ */ new Map();
127778
+ const credentials3 = /* @__PURE__ */ new Set();
127779
+ for (const bot of bots) {
127780
+ const adapter = adapterFor(bot.platform);
127781
+ if (adapter === void 0) continue;
127782
+ const fingerprint = `${bot.platform}:${adapter.columns.map((column) => bot.values[column.key] ?? "").join(" ")}`;
127783
+ if (credentials3.has(fingerprint)) {
127784
+ host.log.warn("chat did not connect a bot that repeats another bot's credentials", {
127785
+ bot: bot.displayName,
127786
+ platform: bot.platform
127787
+ });
127788
+ continue;
127789
+ }
127790
+ credentials3.add(fingerprint);
127791
+ wanted.set(bot.rowId, { bot, key: connectionKey(adapter, bot) });
127792
+ }
127793
+ for (const [rowId, connection] of live) {
127794
+ if (wanted.get(rowId)?.key === connection.key) continue;
127795
+ connection.close();
127796
+ live.delete(rowId);
127797
+ }
127798
+ for (const [rowId, { bot, key }] of wanted) {
127799
+ if (!live.has(rowId)) live.set(rowId, open(bot, key));
127800
+ }
127801
+ return { bots: bots.length, connecting: wanted.size };
127802
+ };
127727
127803
  host.onDispose(() => {
127728
127804
  disposed = true;
127729
- for (const connection of connections) connection.close();
127805
+ reconcile();
127730
127806
  });
127731
- return { bots: bots.length, connecting: started.size };
127807
+ return { reconcile };
127732
127808
  };
127733
127809
 
127734
- // src/keyedQueue.ts
127735
- var makeKeyedQueue = (onError) => {
127736
- const tails = /* @__PURE__ */ new Map();
127737
- return (key, work) => {
127738
- const run = (tails.get(key) ?? Promise.resolve()).then(work).catch(onError).then(() => {
127739
- if (tails.get(key) === run) tails.delete(key);
127810
+ // src/deliveryHttp.ts
127811
+ import * as NodeCrypto from "node:crypto";
127812
+ import * as NodeHttp from "node:http";
127813
+
127814
+ // src/boundedBody.ts
127815
+ var readBoundedBody = (req, limit) => new Promise((resolve) => {
127816
+ const chunks = [];
127817
+ let size = 0;
127818
+ let settled = false;
127819
+ const finish = (result) => {
127820
+ if (settled) return;
127821
+ settled = true;
127822
+ resolve(result);
127823
+ };
127824
+ req.on("data", (chunk) => {
127825
+ if (settled) return;
127826
+ size += chunk.length;
127827
+ if (size > limit) {
127828
+ finish({ kind: "too-large" });
127829
+ req.destroy();
127830
+ return;
127831
+ }
127832
+ chunks.push(chunk);
127833
+ });
127834
+ req.on("end", () => finish({ kind: "ok", bytes: Buffer.concat(chunks) }));
127835
+ req.on("error", () => finish({ kind: "read-failed" }));
127836
+ req.on("close", () => finish({ kind: "read-failed" }));
127837
+ });
127838
+
127839
+ // src/deliveryContract.ts
127840
+ var DELIVERY_SEND_ERROR_CODES = [
127841
+ "bad-request",
127842
+ "unknown-bot",
127843
+ "unknown-target",
127844
+ "bot-not-connected",
127845
+ "delivery-failed"
127846
+ ];
127847
+ var isDeliverySendErrorCode = (value2) => typeof value2 === "string" && DELIVERY_SEND_ERROR_CODES.includes(value2);
127848
+ var DeliverySendError = class extends Error {
127849
+ code;
127850
+ constructor(code2, message) {
127851
+ super(message);
127852
+ this.name = "DeliverySendError";
127853
+ this.code = code2;
127854
+ }
127855
+ };
127856
+
127857
+ // src/deliveryHttp.ts
127858
+ var LOOPBACK = "127.0.0.1";
127859
+ var MAX_BODY_BYTES = 1 * 1024 * 1024;
127860
+ var PATH = "/v1/delivery/messages";
127861
+ var statusFor = (code2) => {
127862
+ switch (code2) {
127863
+ case "bad-request":
127864
+ return 400;
127865
+ case "unauthorized":
127866
+ return 401;
127867
+ case "unknown-bot":
127868
+ case "unknown-target":
127869
+ return 404;
127870
+ case "bot-not-connected":
127871
+ return 503;
127872
+ case "delivery-failed":
127873
+ return 502;
127874
+ case "method-not-allowed":
127875
+ return 405;
127876
+ case "unsupported-media-type":
127877
+ return 415;
127878
+ case "payload-too-large":
127879
+ return 413;
127880
+ }
127881
+ };
127882
+ var writeJson = (res, status, body) => {
127883
+ const payload = JSON.stringify(body);
127884
+ res.writeHead(status, {
127885
+ "content-type": "application/json; charset=utf-8",
127886
+ "content-length": Buffer.byteLength(payload),
127887
+ "cache-control": "no-store"
127888
+ });
127889
+ res.end(payload);
127890
+ };
127891
+ var writeError = (res, code2, message) => {
127892
+ writeJson(res, statusFor(code2), { error: { code: code2, message } });
127893
+ };
127894
+ var isLoopbackRemote = (remote) => {
127895
+ if (remote === void 0 || remote === "") return false;
127896
+ return remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
127897
+ };
127898
+ var timingSafeEqualString = (a, b) => {
127899
+ const left = Buffer.from(a);
127900
+ const right = Buffer.from(b);
127901
+ if (left.length !== right.length) return false;
127902
+ return NodeCrypto.timingSafeEqual(left, right);
127903
+ };
127904
+ var parseSendBody = (raw) => {
127905
+ let parsed;
127906
+ try {
127907
+ parsed = JSON.parse(raw.toString("utf8"));
127908
+ } catch {
127909
+ return "bad-request";
127910
+ }
127911
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return "bad-request";
127912
+ const record2 = parsed;
127913
+ const botId = record2["botId"];
127914
+ const targetId = record2["targetId"];
127915
+ const text3 = record2["text"];
127916
+ const format2 = record2["format"];
127917
+ if (typeof botId !== "string" || botId.trim() === "") return "bad-request";
127918
+ if (typeof targetId !== "string" || targetId.trim() === "") return "bad-request";
127919
+ if (typeof text3 !== "string" || text3.trim() === "") return "bad-request";
127920
+ if (format2 !== void 0 && format2 !== "plain" && format2 !== "markdown") return "bad-request";
127921
+ for (const key of Object.keys(record2)) {
127922
+ if (key !== "botId" && key !== "targetId" && key !== "text" && key !== "format") {
127923
+ return "bad-request";
127924
+ }
127925
+ }
127926
+ return {
127927
+ botId: botId.trim(),
127928
+ targetId: targetId.trim(),
127929
+ text: text3,
127930
+ format: format2 === "markdown" ? "markdown" : format2 === "plain" ? "plain" : void 0
127931
+ };
127932
+ };
127933
+ var startDeliveryHttpServer = async (options) => {
127934
+ const server = NodeHttp.createServer((req, res) => {
127935
+ void (async () => {
127936
+ if (!isLoopbackRemote(req.socket.remoteAddress)) {
127937
+ writeError(res, "unauthorized", "Only loopback clients are accepted.");
127938
+ return;
127939
+ }
127940
+ const url = new URL(req.url ?? "/", `http://${LOOPBACK}`);
127941
+ if (url.pathname !== PATH) {
127942
+ writeError(res, "bad-request", `Unknown path '${url.pathname}'.`);
127943
+ return;
127944
+ }
127945
+ if (req.method !== "POST") {
127946
+ res.setHeader("allow", "POST");
127947
+ writeError(res, "method-not-allowed", "Only POST is accepted.");
127948
+ return;
127949
+ }
127950
+ const contentType = (req.headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase();
127951
+ if (contentType !== "application/json") {
127952
+ writeError(res, "unsupported-media-type", "Content-Type must be application/json.");
127953
+ return;
127954
+ }
127955
+ if (options.token !== "") {
127956
+ const header = req.headers.authorization ?? "";
127957
+ const prefix = "Bearer ";
127958
+ if (!header.startsWith(prefix) || !timingSafeEqualString(header.slice(prefix.length), options.token)) {
127959
+ writeError(res, "unauthorized", "Bearer token required.");
127960
+ return;
127961
+ }
127962
+ }
127963
+ const read = await readBoundedBody(req, MAX_BODY_BYTES);
127964
+ if (read.kind !== "ok") {
127965
+ if (read.kind === "too-large") {
127966
+ writeError(res, "payload-too-large", "JSON body exceeds 1 MiB.");
127967
+ } else {
127968
+ writeError(res, "bad-request", "Failed to read body.");
127969
+ }
127970
+ return;
127971
+ }
127972
+ const parsed = parseSendBody(read.bytes);
127973
+ if (typeof parsed === "string") {
127974
+ writeError(res, parsed, "Invalid JSON body.");
127975
+ return;
127976
+ }
127977
+ try {
127978
+ const result = await options.send(parsed);
127979
+ writeJson(res, 200, { sent: result.sent });
127980
+ } catch (error) {
127981
+ const rawCode = error instanceof Error ? error.code : void 0;
127982
+ const code2 = isDeliverySendErrorCode(rawCode) ? rawCode : "delivery-failed";
127983
+ const message = error instanceof Error ? error.message : String(error);
127984
+ options.log?.warn("delivery http send failed", { code: code2, message });
127985
+ writeError(res, code2, message);
127986
+ }
127987
+ })().catch((error) => {
127988
+ options.log?.warn("delivery http handler crashed", {
127989
+ error: error instanceof Error ? error.message : String(error)
127990
+ });
127991
+ if (!res.headersSent) writeError(res, "delivery-failed", "Internal error.");
127740
127992
  });
127741
- tails.set(key, run);
127993
+ });
127994
+ await new Promise((resolve, reject) => {
127995
+ server.once("error", reject);
127996
+ server.listen(options.port, LOOPBACK, () => resolve());
127997
+ });
127998
+ const address = server.address();
127999
+ const port = address.port;
128000
+ options.log?.info("delivery http listening", { host: LOOPBACK, port, path: PATH });
128001
+ return {
128002
+ port,
128003
+ origin: `http://${LOOPBACK}:${String(port)}`,
128004
+ close: () => new Promise((resolve, reject) => {
128005
+ server.close((error) => error ? reject(error) : resolve());
128006
+ })
128007
+ };
128008
+ };
128009
+
128010
+ // src/deliveryHttpLifecycle.ts
128011
+ var makeDeliveryHttp = (input) => {
128012
+ const start = input.start ?? startDeliveryHttpServer;
128013
+ let running;
128014
+ let disposed = false;
128015
+ const stop = async () => {
128016
+ const current = running;
128017
+ running = void 0;
128018
+ if (current === void 0) return;
128019
+ await current.server.close().catch((error) => {
128020
+ input.log.warn("delivery http could not close", { error: describeError(error) });
128021
+ });
128022
+ };
128023
+ input.onDispose(() => {
128024
+ disposed = true;
128025
+ void stop();
128026
+ });
128027
+ return {
128028
+ apply: async (config2) => {
128029
+ const port = readDeliveryHttpPort(config2);
128030
+ const token = readDeliveryHttpToken(config2);
128031
+ const wanted = readDeliveryHttpEnabled(config2) ? `${String(port)}
128032
+ ${token}` : void 0;
128033
+ if (wanted === running?.listening) return;
128034
+ await stop();
128035
+ if (wanted === void 0 || disposed) return;
128036
+ try {
128037
+ const server = await start({
128038
+ port,
128039
+ token,
128040
+ send: input.send,
128041
+ log: {
128042
+ info: (message, fields) => input.log.info(message, fields),
128043
+ warn: (message, fields) => input.log.warn(message, fields)
128044
+ }
128045
+ });
128046
+ running = { listening: wanted, server };
128047
+ if (disposed) await stop();
128048
+ } catch (error) {
128049
+ input.log.warn("delivery http failed to listen", { port, error: describeError(error) });
128050
+ }
128051
+ }
127742
128052
  };
127743
128053
  };
127744
128054
 
@@ -127809,7 +128119,7 @@ var makeImCore = (input) => {
127809
128119
  host.log.warn("chat could not deliver", { error: describeError(error) });
127810
128120
  }
127811
128121
  };
127812
- const bots = () => readBots(input.config, adapterFor);
128122
+ const bots = () => readBots(input.config(), adapterFor);
127813
128123
  const bindingForChatKey = (chatKey) => findBindingByChatKey(bots(), state, chatKey);
127814
128124
  return {
127815
128125
  host,
@@ -128123,6 +128433,64 @@ var makeCooldown = (windowMs, now, maxKeys = Number.POSITIVE_INFINITY) => {
128123
128433
  };
128124
128434
  };
128125
128435
 
128436
+ // src/boundedMap.ts
128437
+ var makeBoundedMap = (limit) => {
128438
+ const entries = /* @__PURE__ */ new Map();
128439
+ return {
128440
+ get: (key) => entries.get(key),
128441
+ set: (key, value2) => {
128442
+ entries.delete(key);
128443
+ entries.set(key, value2);
128444
+ if (entries.size > limit) {
128445
+ const oldest = entries.keys().next().value;
128446
+ if (oldest !== void 0) entries.delete(oldest);
128447
+ }
128448
+ }
128449
+ };
128450
+ };
128451
+
128452
+ // src/replyAnchors.ts
128453
+ var THREAD_LIMIT = 256;
128454
+ var TURNS_PER_THREAD = 16;
128455
+ var anchorOfMessage = (adapter, message) => adapter?.quotesReplies === true ? { messageId: message.messageId, inThread: message.replyInThread === true } : void 0;
128456
+ var makeReplyAnchors = (host) => {
128457
+ const byThread = makeBoundedMap(THREAD_LIMIT);
128458
+ const userMessageOfTurn = async (threadId, turnId) => {
128459
+ const execution = await host.projections.getThreadExecution(threadId).catch(() => null);
128460
+ if (execution === null) return void 0;
128461
+ const latest = execution.latestTurn;
128462
+ const wanted = turnId ?? execution.session?.activeTurnId ?? latest?.turnId;
128463
+ if (wanted == null) return void 0;
128464
+ const intent = execution.turnIntents.find((candidate) => candidate.turn?.turnId === wanted);
128465
+ return intent?.messageId ?? (latest?.turnId === wanted ? latest.userMessageId : void 0);
128466
+ };
128467
+ return {
128468
+ /**
128469
+ * 开轮时记一笔:`userMessageId` 是派给宿主的那条用户消息,`binding` 是收到聊天消息的那个
128470
+ * 聊天(带着它的锚点)。没有锚点(平台不引用)就不记。
128471
+ */
128472
+ note: (threadId, userMessageId, binding) => {
128473
+ if (binding.reply === void 0) return;
128474
+ const turns = byThread.get(threadId) ?? makeBoundedMap(TURNS_PER_THREAD);
128475
+ byThread.set(threadId, turns);
128476
+ turns.set(userMessageId, { chatKey: chatKeyOf(binding), anchor: binding.reply });
128477
+ },
128478
+ /**
128479
+ * 给发往这条会话的聊天挂上 `turnId` 那一轮的锚点(null 的含义见上);取不到就原样返回。
128480
+ *
128481
+ * 锚点只配给它那条消息所在的聊天:会话可以被另一个聊天接走(`/resume`),而回复接口只按被
128482
+ * 回复的消息寻址,挂错了答复就发回原来的聊天。
128483
+ */
128484
+ anchored: async (binding, threadId, turnId) => {
128485
+ const turns = byThread.get(threadId);
128486
+ if (turns === void 0) return binding;
128487
+ const asked = await userMessageOfTurn(threadId, turnId);
128488
+ const noted = asked === void 0 ? void 0 : turns.get(asked);
128489
+ return noted?.chatKey === chatKeyOf(binding) ? { ...binding, reply: noted.anchor } : binding;
128490
+ }
128491
+ };
128492
+ };
128493
+
128126
128494
  // src/inbound.ts
128127
128495
  var SEEN_MESSAGE_LIMIT = 256;
128128
128496
  var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
@@ -128139,7 +128507,7 @@ var makeInbound = (core, deps) => {
128139
128507
  const { questionFlow, handleButtonClick } = deps.interactions;
128140
128508
  const { answerByToken } = deps.approvals;
128141
128509
  const { handlePanelClick } = deps.panelWiring;
128142
- const { startTurn, receiveAttachmentsOnly } = deps.turns;
128510
+ const { startTurn, receiveAttachmentsOnly, hasWaitingAttachments } = deps.turns;
128143
128511
  const { fetchTeams, startTeamRun } = deps.teams;
128144
128512
  const { markWorking } = deps.acknowledgements;
128145
128513
  const archivedReplyCooldown = makeCooldown(ARCHIVED_REPLY_COOLDOWN_MS, () => Date.now());
@@ -128166,6 +128534,34 @@ var makeInbound = (core, deps) => {
128166
128534
  () => deliver(target2, t("reply.notAllowedYet", { id: sender }))
128167
128535
  );
128168
128536
  };
128537
+ const currentBot = (rowId) => bots().find((candidate) => candidate.rowId === rowId);
128538
+ const inheritFromParent = (bot, chatId, parentChatId) => {
128539
+ if (parentChatId === void 0) return;
128540
+ const chatKey = chatKeyOf({ rowId: bot.rowId, chatId });
128541
+ if (chatOf(core.state, chatKey).projectId) return;
128542
+ const parent = chatOf(core.state, chatKeyOf({ rowId: bot.rowId, chatId: parentChatId }));
128543
+ if (!parent.projectId) return;
128544
+ commit("chat-updated", {
128545
+ chatKey,
128546
+ projectId: parent.projectId,
128547
+ model: parent.model,
128548
+ plan: parent.plan
128549
+ });
128550
+ };
128551
+ const noteChatAlias = (bot, alias, chatId) => {
128552
+ if (chatAliasOf(core.state, bot.rowId, alias) !== void 0) return;
128553
+ commit("chat-aliased", { rowId: bot.rowId, alias, chatId });
128554
+ };
128555
+ const settleChat = (bot, message) => {
128556
+ if (message.chatAlias === void 0) return message;
128557
+ const known = chatAliasOf(core.state, bot.rowId, message.chatAlias);
128558
+ if (known === void 0) noteChatAlias(bot, message.chatAlias, message.chatId);
128559
+ return known === void 0 || known === message.chatId ? message : { ...message, chatId: known };
128560
+ };
128561
+ const openTurn = async (speaker, message) => {
128562
+ const threadId = await startTurn(speaker, message);
128563
+ if (threadId !== null) await markWorking(speaker, threadId, message.messageId);
128564
+ };
128169
128565
  const inboundHandlersFor = (bot) => ({
128170
128566
  onError: (message) => host.log.warn("chat inbound connection failed", {
128171
128567
  platform: bot.platform,
@@ -128173,10 +128569,16 @@ var makeInbound = (core, deps) => {
128173
128569
  message
128174
128570
  }),
128175
128571
  onButton: handleButtonClick,
128176
- onPanelClick: (click) => handlePanelClick(bot, click),
128177
- onMessage: (message) => {
128178
- const current = bots().find((candidate) => candidate.rowId === bot.rowId);
128572
+ onPanelClick: (click) => {
128573
+ const current = currentBot(bot.rowId);
128574
+ return current === void 0 ? null : handlePanelClick(current, click);
128575
+ },
128576
+ noteChatAlias: (alias, chatId) => noteChatAlias(bot, alias, chatId),
128577
+ knowsChatAlias: (alias) => chatAliasOf(core.state, bot.rowId, alias) !== void 0,
128578
+ onMessage: (received) => {
128579
+ const current = currentBot(bot.rowId);
128179
128580
  if (current === void 0) return;
128581
+ const message = settleChat(current, received);
128180
128582
  const arrived = bindingOf2(current, message.chatId);
128181
128583
  if (alreadyHandled(arrived, message.messageId)) return;
128182
128584
  if (message.chatKind !== void 0 && chatOf(core.state, chatKeyOf(arrived)).chatKind !== message.chatKind) {
@@ -128206,7 +128608,11 @@ var makeInbound = (core, deps) => {
128206
128608
  return;
128207
128609
  }
128208
128610
  enqueueForChat(chatKeyOf(arrived), async () => {
128209
- const target2 = bindingOf2(current, message.chatId);
128611
+ inheritFromParent(current, message.chatId, message.parentChatId);
128612
+ const target2 = {
128613
+ ...bindingOf2(current, message.chatId),
128614
+ reply: anchorOfMessage(adapterFor(current.platform), message)
128615
+ };
128210
128616
  const chatKey = chatKeyOf(target2);
128211
128617
  const teams = await fetchTeams(target2);
128212
128618
  if (await handleCommand(target2, message.text, teams ?? [], message.senderId)) return;
@@ -128261,8 +128667,15 @@ var makeInbound = (core, deps) => {
128261
128667
  await receiveAttachmentsOnly(target2, message);
128262
128668
  return;
128263
128669
  }
128264
- const threadId = await startTurn(target2, message);
128265
- if (threadId !== null) await markWorking(target2, threadId, message.messageId);
128670
+ const branchChatId = hasWaitingAttachments(chatKey) ? void 0 : message.branchChatId;
128671
+ if (branchChatId === void 0) {
128672
+ await openTurn(target2, message);
128673
+ return;
128674
+ }
128675
+ enqueueForChat(chatKeyOf({ rowId: current.rowId, chatId: branchChatId }), async () => {
128676
+ inheritFromParent(current, branchChatId, message.chatId);
128677
+ await openTurn({ ...bindingOf2(current, branchChatId), reply: target2.reply }, message);
128678
+ });
128266
128679
  });
128267
128680
  }
128268
128681
  });
@@ -128440,7 +128853,7 @@ var makeQuestionFlow = (host) => {
128440
128853
  if (record2 === void 0 || record2.resolved || item === void 0) return;
128441
128854
  try {
128442
128855
  const cardId = await cards.open(
128443
- target2.binding,
128856
+ await host.anchored(target2.binding, record2.threadId),
128444
128857
  questionCardOf(item, record2.items.indexOf(item), record2.items.length, t)
128445
128858
  );
128446
128859
  if (cardId === null) continue;
@@ -128678,7 +129091,7 @@ var makeQueueFlow = (host, deps) => {
128678
129091
  queuePaused: execution.queuePaused,
128679
129092
  intent: execution.intents.find((intent) => intent.intentId === intentId)
128680
129093
  });
128681
- const noteQueued = async (chatKey, threadId, messageId, text3) => {
129094
+ const noteQueued = async (chatKey, threadId, messageId, text3, reply) => {
128682
129095
  const target2 = host.target(chatKey);
128683
129096
  if (target2 === void 0) return;
128684
129097
  let execution;
@@ -128723,7 +129136,10 @@ var makeQueueFlow = (host, deps) => {
128723
129136
  host.commit("queue-settled", { intentId, outcome: view.outcome });
128724
129137
  return;
128725
129138
  }
128726
- const cardId = await cards.open(target2.binding, queueCardOf(record2, view, null, t));
129139
+ const cardId = await cards.open(
129140
+ { ...target2.binding, reply },
129141
+ queueCardOf(record2, view, null, t)
129142
+ );
128727
129143
  if (cardId === null) return;
128728
129144
  host.commit("queue-carded", { intentId, cardId });
128729
129145
  shown.set(intentId, queueViewKey(view, null));
@@ -128883,6 +129299,8 @@ var makeInteractions = (core, deps) => {
128883
129299
  const adapter = binding === void 0 ? void 0 : adapterFor(binding.platform);
128884
129300
  return binding === void 0 || adapter === void 0 ? void 0 : { binding, adapter };
128885
129301
  },
129302
+ // 提问是在跑的那一轮问的。
129303
+ anchored: (binding, threadId) => deps.anchors.anchored(binding, threadId, null),
128886
129304
  allowed: (chatKey, operatorId) => {
128887
129305
  const binding = bindingForChatKey(chatKey);
128888
129306
  if (binding !== void 0 && isAllowedSender(binding, operatorId)) return true;
@@ -128961,6 +129379,9 @@ var makeInteractions = (core, deps) => {
128961
129379
  const resolved = readRequestResolved(event);
128962
129380
  if (resolved === null) return;
128963
129381
  await ready;
129382
+ if (resolved.outcome !== "cancelled" && event.threadId !== null) {
129383
+ deps.rotateTurnCard(event.threadId);
129384
+ }
128964
129385
  if (resolved.requestKind === "user-input") {
128965
129386
  questionFlow.resolved(
128966
129387
  resolved.requestId,
@@ -130779,24 +131200,50 @@ var fetchTenantToken = async (input) => {
130779
131200
  }
130780
131201
  return { token: body.tenant_access_token, expiresInSeconds: body.expire ?? 0 };
130781
131202
  };
130782
- var sendChatText = async (input) => {
131203
+ var postMessage = async (input) => {
131204
+ const headers = {
131205
+ authorization: `Bearer ${input.token}`,
131206
+ "content-type": "application/json; charset=utf-8"
131207
+ };
131208
+ const message = { msg_type: input.msgType, content: JSON.stringify(input.content) };
131209
+ const sentOf = async (response2) => (await readEnvelope(input.operation, response2)).data;
131210
+ if (input.reply !== void 0) {
131211
+ try {
131212
+ const response2 = await input.http(
131213
+ `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.reply.messageId)}/reply`,
131214
+ {
131215
+ method: "POST",
131216
+ headers,
131217
+ body: JSON.stringify({
131218
+ ...message,
131219
+ ...input.reply.inThread ? { reply_in_thread: true } : {}
131220
+ })
131221
+ }
131222
+ );
131223
+ const sent = await sentOf(response2);
131224
+ if (input.reply.inThread) input.onThreadReply?.(sent?.thread_id || void 0);
131225
+ return sent?.message_id ?? null;
131226
+ } catch (error) {
131227
+ if (!(error instanceof LarkApiError)) throw error;
131228
+ }
131229
+ }
130783
131230
  const idType = input.receiveIdType ?? "chat_id";
130784
131231
  const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=${idType}`, {
130785
131232
  method: "POST",
130786
- headers: {
130787
- authorization: `Bearer ${input.token}`,
130788
- "content-type": "application/json; charset=utf-8"
130789
- },
130790
- body: JSON.stringify({
130791
- receive_id: input.chatId,
130792
- msg_type: "text",
130793
- // content 是一个 JSON **字符串**,不是对象——飞书这条接口就是这么定的。
130794
- content: JSON.stringify({ text: input.text })
130795
- })
131233
+ headers,
131234
+ body: JSON.stringify({ receive_id: input.chatId, ...message })
130796
131235
  });
130797
- const body = await readEnvelope("send", response);
130798
- return body.data?.message_id ?? null;
131236
+ return (await sentOf(response))?.message_id ?? null;
131237
+ };
131238
+ var fetchMessageThreadId = async (input) => {
131239
+ const response = await input.http(
131240
+ `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}`,
131241
+ { headers: { authorization: `Bearer ${input.token}` } }
131242
+ );
131243
+ const body = await readEnvelope("message.get", response);
131244
+ return body.data?.items?.[0]?.thread_id || void 0;
130799
131245
  };
131246
+ var sendChatText = async (input) => postMessage({ ...input, operation: "send", msgType: "text", content: { text: input.text } });
130800
131247
  var LARK_WORKING_REACTION = "OnIt";
130801
131248
  var addReaction = async (input) => {
130802
131249
  const response = await input.http(
@@ -130856,54 +131303,19 @@ var uploadFile = async (input) => {
130856
131303
  }
130857
131304
  return body.data.file_key;
130858
131305
  };
130859
- var sendChatFile = async (input) => {
130860
- const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=chat_id`, {
130861
- method: "POST",
130862
- headers: {
130863
- authorization: `Bearer ${input.token}`,
130864
- "content-type": "application/json; charset=utf-8"
130865
- },
130866
- body: JSON.stringify({
130867
- receive_id: input.chatId,
130868
- msg_type: "file",
130869
- content: JSON.stringify({ file_key: input.fileKey })
130870
- })
130871
- });
130872
- const body = await readEnvelope("send.file", response);
130873
- return body.data?.message_id ?? null;
130874
- };
130875
- var sendChatImage = async (input) => {
130876
- const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=chat_id`, {
130877
- method: "POST",
130878
- headers: {
130879
- authorization: `Bearer ${input.token}`,
130880
- "content-type": "application/json; charset=utf-8"
130881
- },
130882
- body: JSON.stringify({
130883
- receive_id: input.chatId,
130884
- msg_type: "image",
130885
- content: JSON.stringify({ image_key: input.imageKey })
130886
- })
130887
- });
130888
- const body = await readEnvelope("send.image", response);
130889
- return body.data?.message_id ?? null;
130890
- };
130891
- var sendCard = async (input) => {
130892
- const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=chat_id`, {
130893
- method: "POST",
130894
- headers: {
130895
- authorization: `Bearer ${input.token}`,
130896
- "content-type": "application/json; charset=utf-8"
130897
- },
130898
- body: JSON.stringify({
130899
- receive_id: input.chatId,
130900
- msg_type: "interactive",
130901
- content: JSON.stringify(input.card)
130902
- })
130903
- });
130904
- const body = await readEnvelope("send.card", response);
130905
- return body.data?.message_id ?? null;
130906
- };
131306
+ var sendChatFile = async (input) => postMessage({
131307
+ ...input,
131308
+ operation: "send.file",
131309
+ msgType: "file",
131310
+ content: { file_key: input.fileKey }
131311
+ });
131312
+ var sendChatImage = async (input) => postMessage({
131313
+ ...input,
131314
+ operation: "send.image",
131315
+ msgType: "image",
131316
+ content: { image_key: input.imageKey }
131317
+ });
131318
+ var sendCard = async (input) => postMessage({ ...input, operation: "send.card", msgType: "interactive", content: input.card });
130907
131319
  var patchCard = async (input) => {
130908
131320
  const response = await input.http(
130909
131321
  `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}`,
@@ -136204,7 +136616,8 @@ var STATUS_TEMPLATE = {
136204
136616
  working: "wathet",
136205
136617
  done: "green",
136206
136618
  error: "red",
136207
- interrupted: "orange"
136619
+ interrupted: "orange",
136620
+ continued: "grey"
136208
136621
  };
136209
136622
  var encoder = new TextEncoder();
136210
136623
  var stripAngleBrackets = (text3) => text3.replaceAll("<", "").replaceAll(">", "");
@@ -136388,9 +136801,9 @@ var styledText = (text3, style) => {
136388
136801
  if (style?.includes("underline")) out = `<u>${out}</u>`;
136389
136802
  return out;
136390
136803
  };
136391
- var nonEmpty3 = (value2) => typeof value2 === "string" && value2 !== "";
136804
+ var nonEmpty4 = (value2) => typeof value2 === "string" && value2 !== "";
136392
136805
  var resourceOf = (element, index) => {
136393
- if (element.tag === "img" && nonEmpty3(element.image_key)) {
136806
+ if (element.tag === "img" && nonEmpty4(element.image_key)) {
136394
136807
  return {
136395
136808
  fileKey: element.image_key,
136396
136809
  kind: "image",
@@ -136398,14 +136811,14 @@ var resourceOf = (element, index) => {
136398
136811
  mimeType: "image/png"
136399
136812
  };
136400
136813
  }
136401
- if ((element.tag === "media" || element.tag === "file") && nonEmpty3(element.file_key)) {
136814
+ if ((element.tag === "media" || element.tag === "file") && nonEmpty4(element.file_key)) {
136402
136815
  const video = element.tag === "media";
136403
- const name = nonEmpty3(element.file_name) ? element.file_name : video ? `video-${String(index)}.mp4` : `file-${String(index)}`;
136816
+ const name = nonEmpty4(element.file_name) ? element.file_name : video ? `video-${String(index)}.mp4` : `file-${String(index)}`;
136404
136817
  return {
136405
136818
  fileKey: element.file_key,
136406
136819
  kind: "file",
136407
136820
  name,
136408
- mimeType: video && !nonEmpty3(element.file_name) ? "video/mp4" : mimeFromName(name)
136821
+ mimeType: video && !nonEmpty4(element.file_name) ? "video/mp4" : mimeFromName(name)
136409
136822
  };
136410
136823
  }
136411
136824
  return null;
@@ -136417,11 +136830,11 @@ var serializeElement = (element, index, botIds) => {
136417
136830
  return styledText(element.text ?? "", element.style);
136418
136831
  case "a": {
136419
136832
  const label = element.text ?? element.href ?? "";
136420
- return nonEmpty3(element.href) ? `[${label}](${element.href})` : label;
136833
+ return nonEmpty4(element.href) ? `[${label}](${element.href})` : label;
136421
136834
  }
136422
136835
  case "at": {
136423
136836
  if (element.user_id !== void 0 && botIds.has(element.user_id)) return "";
136424
- return nonEmpty3(element.user_name) ? `@${element.user_name}` : "";
136837
+ return nonEmpty4(element.user_name) ? `@${element.user_name}` : "";
136425
136838
  }
136426
136839
  case "code_block":
136427
136840
  return `\`\`\`${element.language ?? ""}
@@ -136461,7 +136874,7 @@ var serializePost = (content, botIds = /* @__PURE__ */ new Set()) => {
136461
136874
  for (const element of elements) {
136462
136875
  const index = indexOf.get(element);
136463
136876
  const key = element.tag === "img" ? element.image_key : element.file_key;
136464
- if (index !== void 0 && nonEmpty3(key)) {
136877
+ if (index !== void 0 && nonEmpty4(key)) {
136465
136878
  placeholderByKey.set(key, placeholderOf(element, index));
136466
136879
  }
136467
136880
  }
@@ -136552,7 +136965,7 @@ var readText = (content) => {
136552
136965
  const text3 = readJson(content)?.["text"];
136553
136966
  return typeof text3 === "string" ? text3 : null;
136554
136967
  };
136555
- var parseInboundMessage = (event, appId, groupMention = "always") => {
136968
+ var parseInboundMessage = (event, appId, groupMention = "always", topicOnMention = true) => {
136556
136969
  if (event.sender?.sender_type === "app") return null;
136557
136970
  const message = event.message;
136558
136971
  if (message === void 0) return null;
@@ -136562,6 +136975,11 @@ var parseInboundMessage = (event, appId, groupMention = "always") => {
136562
136975
  const messageType = message.message_type ?? "";
136563
136976
  if (!passesGroupGate(event, appId, groupMention, messageType)) return null;
136564
136977
  const senderOpenId = event.sender?.sender_id?.open_id ?? null;
136978
+ const threadId = typeof message.thread_id === "string" && message.thread_id !== "" ? message.thread_id : void 0;
136979
+ const inThread = threadId !== void 0 || topicOnMention && mentionsApp(event, appId);
136980
+ const rootGiven = typeof message.root_id === "string" && message.root_id !== "";
136981
+ const rootId = rootGiven && message.root_id !== void 0 ? message.root_id : messageId;
136982
+ const topic = chatType === "p2p" || !inThread ? {} : threadId === void 0 ? { topic: { rootMessageId: messageId } } : { topic: { rootMessageId: rootId, threadId, rootGiven } };
136565
136983
  if (UNSUPPORTED_MESSAGE_TYPES.has(messageType)) {
136566
136984
  return {
136567
136985
  messageId,
@@ -136570,6 +136988,8 @@ var parseInboundMessage = (event, appId, groupMention = "always") => {
136570
136988
  senderOpenId,
136571
136989
  text: "",
136572
136990
  resources: [],
136991
+ inThread,
136992
+ ...topic,
136573
136993
  unsupportedType: messageType
136574
136994
  };
136575
136995
  }
@@ -136580,7 +137000,7 @@ var parseInboundMessage = (event, appId, groupMention = "always") => {
136580
137000
  const raw = post !== void 0 ? post.text : messageType === "text" ? readText(message.content) : null;
136581
137001
  const text3 = mentions.reduce((value2, mention) => value2.replaceAll(mention.key, ""), raw ?? "").trim();
136582
137002
  if (text3.length === 0 && resources.length === 0) return null;
136583
- return { messageId, chatId, chatType, senderOpenId, text: text3, resources };
137003
+ return { messageId, chatId, chatType, senderOpenId, text: text3, resources, inThread, ...topic };
136584
137004
  };
136585
137005
  var fetchMessageResource = async (input) => {
136586
137006
  const url = `${LARK_BASE}/im/v1/messages/${encodeURIComponent(
@@ -136656,7 +137076,13 @@ var startLarkInbound = (input) => {
136656
137076
  const dispatcher = new import_node_sdk.EventDispatcher({}).register({
136657
137077
  "im.message.receive_v1": (event) => {
136658
137078
  const mode = input.groupMention ?? "always";
136659
- const parsed = parseInboundMessage(event, input.appId, mode);
137079
+ const parse = (groupMention) => parseInboundMessage(
137080
+ event,
137081
+ input.appId,
137082
+ groupMention,
137083
+ input.topicOnMention
137084
+ );
137085
+ const parsed = parse(mode);
136660
137086
  if (parsed !== null) {
136661
137087
  input.onMessage(parsed);
136662
137088
  return {};
@@ -136666,7 +137092,7 @@ var startLarkInbound = (input) => {
136666
137092
  const chatId = chat?.chat_id;
136667
137093
  if (typeof chatId === "string") {
136668
137094
  void input.isSoloGroup(chatId).then((solo) => {
136669
- const relaxed = solo ? parseInboundMessage(event, input.appId, "never") : null;
137095
+ const relaxed = solo ? parse("never") : null;
136670
137096
  if (relaxed !== null) input.onMessage(relaxed);
136671
137097
  });
136672
137098
  }
@@ -136733,8 +137159,27 @@ var COLUMNS = [
136733
137159
  labelKey: "column.larkAppId.label",
136734
137160
  placeholderKey: "column.larkAppId.placeholder"
136735
137161
  },
136736
- { key: "larkAppSecret", kind: "password", labelKey: "column.larkAppSecret.label" }
137162
+ { key: "larkAppSecret", kind: "password", labelKey: "column.larkAppSecret.label" },
137163
+ // 被 @ 时答复要不要开话题。只有这个适配器读它,没填过就是开(见 `topicOnMentionOf`)。
137164
+ {
137165
+ key: "larkTopicOnMention",
137166
+ kind: "select",
137167
+ labelKey: "column.larkTopicOnMention.label",
137168
+ descriptionKey: "column.larkTopicOnMention.description",
137169
+ options: [
137170
+ { value: "on", labelKey: "settings.onOff.on" },
137171
+ { value: "off", labelKey: "settings.onOff.off" }
137172
+ ]
137173
+ }
136737
137174
  ];
137175
+ var topicOnMentionOf = (binding) => binding.values["larkTopicOnMention"] !== "off";
137176
+ var TOPIC_THREAD_CACHE_LIMIT = 2e3;
137177
+ var topicChatIdOf = (chatId, rootMessageId) => `${chatId}:${rootMessageId}`;
137178
+ var splitTopicChatId = (chatId) => {
137179
+ const at = chatId.indexOf(":");
137180
+ return at < 0 ? { chatId } : { chatId: chatId.slice(0, at), rootMessageId: chatId.slice(at + 1) };
137181
+ };
137182
+ var topicAliasOf = (chatId, threadId) => `${chatId}:${threadId}`;
136738
137183
  var credentials = (binding) => ({
136739
137184
  appId: binding.values["larkAppId"] ?? "",
136740
137185
  appSecret: binding.values["larkAppSecret"] ?? ""
@@ -136749,6 +137194,60 @@ var makeLarkAdapter = (deps) => {
136749
137194
  await deps.cacheSet(key, { token: minted.token });
136750
137195
  return minted.token;
136751
137196
  };
137197
+ const aliasPorts = /* @__PURE__ */ new Map();
137198
+ const topicThreadIds = makeBoundedMap(TOPIC_THREAD_CACHE_LIMIT);
137199
+ const learning = /* @__PURE__ */ new Map();
137200
+ const learnTopic = (binding, target2, threadId) => {
137201
+ const key = chatKeyOf(binding);
137202
+ const report = (id) => {
137203
+ topicThreadIds.set(key, id);
137204
+ target2.port?.noteChatAlias?.(topicAliasOf(target2.chatId, id), binding.chatId);
137205
+ };
137206
+ const known = threadId ?? topicThreadIds.get(key);
137207
+ if (known !== void 0) {
137208
+ report(known);
137209
+ return;
137210
+ }
137211
+ const group = chatKeyOf({ rowId: binding.rowId, chatId: target2.chatId });
137212
+ const inflight = learning.get(group) ?? /* @__PURE__ */ new Map();
137213
+ if (inflight.has(key)) return;
137214
+ learning.set(group, inflight);
137215
+ const reading = fetchMessageThreadId({
137216
+ http: deps.http,
137217
+ token: target2.token,
137218
+ messageId: target2.rootMessageId
137219
+ }).then((id) => {
137220
+ if (id !== void 0) report(id);
137221
+ }).catch((error) => {
137222
+ deps.log.warn("lark could not learn the topic of a chat", { error: describeError(error) });
137223
+ }).finally(() => {
137224
+ inflight.delete(key);
137225
+ if (inflight.size === 0) learning.delete(group);
137226
+ });
137227
+ inflight.set(key, reading);
137228
+ };
137229
+ const topicUnsettled = (rowId, groupId, topic) => {
137230
+ if (topic.rootGiven === true) return null;
137231
+ const inflight = learning.get(chatKeyOf({ rowId, chatId: groupId }));
137232
+ if (inflight === void 0) return null;
137233
+ const known = aliasPorts.get(rowId)?.knowsChatAlias?.(topicAliasOf(groupId, topic.threadId));
137234
+ return known === true ? null : Promise.all(inflight.values());
137235
+ };
137236
+ const sendTarget = async (binding) => {
137237
+ const port = aliasPorts.get(binding.rowId);
137238
+ const token = await tenantToken(binding);
137239
+ const { chatId, rootMessageId } = splitTopicChatId(binding.chatId);
137240
+ if (rootMessageId === void 0) {
137241
+ return { http: deps.http, token, chatId, reply: binding.reply };
137242
+ }
137243
+ return {
137244
+ http: deps.http,
137245
+ token,
137246
+ chatId,
137247
+ reply: binding.reply ?? { messageId: rootMessageId, inThread: true },
137248
+ onThreadReply: (threadId) => learnTopic(binding, { token, chatId, rootMessageId, port }, threadId)
137249
+ };
137250
+ };
136752
137251
  const soloGroupFor = (bot) => makeSoloGroupCheck(async (chatId) => {
136753
137252
  const stats = await fetchChatStats({
136754
137253
  http: deps.http,
@@ -136762,16 +137261,28 @@ var makeLarkAdapter = (deps) => {
136762
137261
  icon: "lark",
136763
137262
  label: "Lark",
136764
137263
  columns: COLUMNS,
137264
+ quotesReplies: true,
137265
+ parentChatIdOf: (chatId) => {
137266
+ const { chatId: groupId, rootMessageId } = splitTopicChatId(chatId);
137267
+ return rootMessageId === void 0 ? void 0 : groupId;
137268
+ },
136765
137269
  isConfigured: (binding) => {
136766
137270
  const { appId, appSecret } = credentials(binding);
136767
137271
  return appId !== "" && appSecret !== "";
136768
137272
  },
136769
137273
  connect: (binding, handlers) => {
136770
137274
  const { appId, appSecret } = credentials(binding);
136771
- return startLarkInbound({
137275
+ const port = {
137276
+ noteChatAlias: handlers.noteChatAlias,
137277
+ knowsChatAlias: handlers.knowsChatAlias
137278
+ };
137279
+ aliasPorts.set(binding.rowId, port);
137280
+ let open = true;
137281
+ const inbound = startLarkInbound({
136772
137282
  appId,
136773
137283
  appSecret,
136774
137284
  groupMention: binding.groupMention,
137285
+ topicOnMention: topicOnMentionOf(binding),
136775
137286
  isSoloGroup: soloGroupFor(binding),
136776
137287
  onError: handlers.onError,
136777
137288
  onCardAction: handlers.onButton,
@@ -136783,12 +137294,23 @@ var makeLarkAdapter = (deps) => {
136783
137294
  arg: click.arg
136784
137295
  }) ?? null,
136785
137296
  onMessage: (message) => {
136786
- handlers.onMessage({
137297
+ const { topic } = message;
137298
+ const deliver = () => handlers.onMessage({
136787
137299
  messageId: message.messageId,
136788
- chatId: message.chatId,
137300
+ // 已经开出来的话题就是它自己那个聊天;将要开出来的(主窗口里 @ 机器人)先算群的,
137301
+ // 由核心决定要不要分出去。
137302
+ ...topic === void 0 ? { chatId: message.chatId } : topic.threadId === void 0 ? {
137303
+ chatId: message.chatId,
137304
+ branchChatId: topicChatIdOf(message.chatId, topic.rootMessageId)
137305
+ } : {
137306
+ chatId: topicChatIdOf(message.chatId, topic.rootMessageId),
137307
+ parentChatId: message.chatId,
137308
+ chatAlias: topicAliasOf(message.chatId, topic.threadId)
137309
+ },
136789
137310
  senderId: message.senderOpenId,
136790
137311
  text: message.text,
136791
137312
  chatKind: message.chatType === "p2p" ? "private" : "group",
137313
+ replyInThread: message.inThread,
136792
137314
  resources: message.resources.map((resource) => ({
136793
137315
  key: resource.fileKey,
136794
137316
  name: resource.name,
@@ -136797,8 +137319,21 @@ var makeLarkAdapter = (deps) => {
136797
137319
  })),
136798
137320
  ...message.unsupportedType === void 0 ? {} : { unsupportedType: message.unsupportedType }
136799
137321
  });
137322
+ const pending = topic?.threadId === void 0 ? null : topicUnsettled(binding.rowId, message.chatId, {
137323
+ threadId: topic.threadId,
137324
+ rootGiven: topic.rootGiven
137325
+ });
137326
+ if (pending === null) deliver();
137327
+ else void pending.then(() => open ? deliver() : void 0);
136800
137328
  }
136801
137329
  });
137330
+ return {
137331
+ close: () => {
137332
+ open = false;
137333
+ if (aliasPorts.get(binding.rowId) === port) aliasPorts.delete(binding.rowId);
137334
+ inbound.close();
137335
+ }
137336
+ };
136802
137337
  },
136803
137338
  createGroup: async (binding, input) => createChat({
136804
137339
  http: deps.http,
@@ -136807,13 +137342,11 @@ var makeLarkAdapter = (deps) => {
136807
137342
  memberOpenIds: input.memberIds
136808
137343
  }),
136809
137344
  sendText: async (binding, text3, options) => {
136810
- const token = await tenantToken(binding);
137345
+ const target2 = await sendTarget(binding);
136811
137346
  const receiveIdType = options?.targetKind === "user" ? "open_id" : "chat_id";
136812
137347
  for (const chunk of splitForLark(text3)) {
136813
137348
  const messageId = await sendChatText({
136814
- http: deps.http,
136815
- token,
136816
- chatId: binding.chatId,
137349
+ ...target2,
136817
137350
  receiveIdType,
136818
137351
  text: chunk
136819
137352
  });
@@ -136841,35 +137374,39 @@ var makeLarkAdapter = (deps) => {
136841
137374
  }
136842
137375
  },
136843
137376
  sendImage: async (binding, image2) => {
136844
- const token = await tenantToken(binding);
137377
+ const target2 = await sendTarget(binding);
136845
137378
  const imageKey = await uploadImage({
136846
137379
  http: deps.http,
136847
- token,
137380
+ token: target2.token,
136848
137381
  bytes: image2.bytes,
136849
137382
  name: image2.name,
136850
137383
  mimeType: image2.mimeType
136851
137384
  });
136852
- await sendChatImage({ http: deps.http, token, chatId: binding.chatId, imageKey });
137385
+ await sendChatImage({
137386
+ ...target2,
137387
+ imageKey
137388
+ });
136853
137389
  deps.log.info("lark delivered an image", { name: image2.name });
136854
137390
  },
136855
137391
  sendFile: async (binding, file) => {
136856
- const token = await tenantToken(binding);
137392
+ const target2 = await sendTarget(binding);
136857
137393
  const fileKey = await uploadFile({
136858
137394
  http: deps.http,
136859
- token,
137395
+ token: target2.token,
136860
137396
  bytes: file.bytes,
136861
137397
  name: file.name,
136862
137398
  mimeType: file.mimeType
136863
137399
  });
136864
- await sendChatFile({ http: deps.http, token, chatId: binding.chatId, fileKey });
137400
+ await sendChatFile({
137401
+ ...target2,
137402
+ fileKey
137403
+ });
136865
137404
  deps.log.info("lark delivered a file", { name: file.name });
136866
137405
  },
136867
137406
  // 图 10 MB、文件 30 MB(飞书开放平台文档:上传图片接口、上传文件接口;空文件会被拒)。
136868
137407
  outboundLimits: { imageBytes: 10 * 1024 * 1024, fileBytes: 30 * 1024 * 1024 },
136869
137408
  askApproval: async (binding, request) => sendCard({
136870
- http: deps.http,
136871
- token: await tenantToken(binding),
136872
- chatId: binding.chatId,
137409
+ ...await sendTarget(binding),
136873
137410
  card: buildApprovalCard({
136874
137411
  title: request.title,
136875
137412
  body: request.body,
@@ -136889,10 +137426,9 @@ var makeLarkAdapter = (deps) => {
136889
137426
  },
136890
137427
  turnCards: {
136891
137428
  throttleMs: LARK_CARD_THROTTLE_MS,
137429
+ rotatesOnInteraction: true,
136892
137430
  open: async (binding, view) => sendCard({
136893
- http: deps.http,
136894
- token: await tenantToken(binding),
136895
- chatId: binding.chatId,
137431
+ ...await sendTarget(binding),
136896
137432
  card: buildTurnCard(view, deps.translate)
136897
137433
  }),
136898
137434
  update: async (binding, cardId, view) => {
@@ -136906,9 +137442,7 @@ var makeLarkAdapter = (deps) => {
136906
137442
  },
136907
137443
  panel: {
136908
137444
  open: async (binding, card) => sendCard({
136909
- http: deps.http,
136910
- token: await tenantToken(binding),
136911
- chatId: binding.chatId,
137445
+ ...await sendTarget(binding),
136912
137446
  card: buildPanelCard(card)
136913
137447
  }),
136914
137448
  update: async (binding, cardId, card) => {
@@ -136922,9 +137456,7 @@ var makeLarkAdapter = (deps) => {
136922
137456
  },
136923
137457
  choiceCards: {
136924
137458
  open: async (binding, card) => sendCard({
136925
- http: deps.http,
136926
- token: await tenantToken(binding),
136927
- chatId: binding.chatId,
137459
+ ...await sendTarget(binding),
136928
137460
  card: buildChoiceCard(card)
136929
137461
  }),
136930
137462
  update: async (binding, cardId, card) => {
@@ -136976,7 +137508,7 @@ var en_default = {
136976
137508
  bots: "Bots",
136977
137509
  language: {
136978
137510
  label: "Reply language",
136979
- description: "The language of the bot's own sentences in the chat: help, status, confirmations and notices. Chinese shortcut commands only work when this is Chinese. Saving restarts the plugin.",
137511
+ description: "The language of the bot's own sentences in the chat: help, status, confirmations and notices. Chinese shortcut commands only work when this is Chinese. A change applies from the bot's next sentence.",
136980
137512
  zhCN: "\u4E2D\u6587",
136981
137513
  en: "English"
136982
137514
  },
@@ -137108,6 +137640,10 @@ var en_default = {
137108
137640
  label: "Command panel",
137109
137641
  description: "Which commands /panel (or typing /) lists. Minimal hides low-frequency system items; typing the same command still works."
137110
137642
  },
137643
+ larkTopicOnMention: {
137644
+ label: "Open a topic when mentioned",
137645
+ description: "When someone @-mentions the bot, its answer opens a Lark topic under that message, so the question and the answer stay together. Messages already inside a topic are answered in that topic either way."
137646
+ },
137111
137647
  wecomNumberedHints: {
137112
137648
  label: "Numbered-reply hints",
137113
137649
  description: "WeCom has no buttons; approvals and questions use reply numbers. When on, include fuller usage hints."
@@ -137337,7 +137873,8 @@ var en_default = {
137337
137873
  working: "Working on it\u2026",
137338
137874
  done: "Done",
137339
137875
  error: "Something went wrong",
137340
- interrupted: "Interrupted"
137876
+ interrupted: "Interrupted",
137877
+ continued: "Continues in the card below"
137341
137878
  },
137342
137879
  step: {
137343
137880
  think: "think",
@@ -137582,7 +138119,7 @@ var zh_CN_default = {
137582
138119
  bots: "\u673A\u5668\u4EBA",
137583
138120
  language: {
137584
138121
  label: "\u56DE\u8BDD\u8BED\u8A00",
137585
- description: "\u673A\u5668\u4EBA\u5728\u804A\u5929\u91CC\u81EA\u5DF1\u8BF4\u7684\u8BDD\u7528\u4EC0\u4E48\u8BED\u8A00\uFF1A\u5E2E\u52A9\u3001\u72B6\u6001\u3001\u786E\u8BA4\u548C\u5404\u79CD\u63D0\u793A\u3002\u4E2D\u6587\u5FEB\u6377\u547D\u4EE4\u53EA\u5728\u9009\u4E2D\u6587\u65F6\u8BA4\u3002\u4FDD\u5B58\u540E\u63D2\u4EF6\u4F1A\u91CD\u542F\u3002",
138122
+ description: "\u673A\u5668\u4EBA\u5728\u804A\u5929\u91CC\u81EA\u5DF1\u8BF4\u7684\u8BDD\u7528\u4EC0\u4E48\u8BED\u8A00\uFF1A\u5E2E\u52A9\u3001\u72B6\u6001\u3001\u786E\u8BA4\u548C\u5404\u79CD\u63D0\u793A\u3002\u4E2D\u6587\u5FEB\u6377\u547D\u4EE4\u53EA\u5728\u9009\u4E2D\u6587\u65F6\u8BA4\u3002\u6539\u4E86\u4E4B\u540E\u4ECE\u673A\u5668\u4EBA\u7684\u4E0B\u4E00\u53E5\u8BDD\u8D77\u751F\u6548\u3002",
137586
138123
  zhCN: "\u4E2D\u6587",
137587
138124
  en: "English"
137588
138125
  },
@@ -137714,6 +138251,10 @@ var zh_CN_default = {
137714
138251
  label: "\u6307\u4EE4\u9762\u677F",
137715
138252
  description: "\u804A\u5929\u91CC /panel \u6216\u6253 / \u65F6\u5217\u51FA\u7684\u547D\u4EE4\u8303\u56F4\u3002\u7CBE\u7B80\u9690\u85CF\u4F4E\u9891\u7CFB\u7EDF\u9879\uFF1B\u624B\u52A8\u8F93\u5165\u540C\u4E00\u547D\u4EE4\u4ECD\u53EF\u7528\u3002"
137716
138253
  },
138254
+ larkTopicOnMention: {
138255
+ label: "\u88AB @ \u65F6\u5F00\u8BDD\u9898",
138256
+ description: "\u6709\u4EBA @ \u673A\u5668\u4EBA\u63D0\u95EE\u65F6\uFF0C\u7B54\u590D\u5728\u90A3\u6761\u6D88\u606F\u4E0B\u5F00\u4E00\u4E2A\u98DE\u4E66\u8BDD\u9898\uFF0C\u4E00\u95EE\u4E00\u7B54\u6536\u5728\u8BDD\u9898\u91CC\u3002\u5DF2\u7ECF\u5728\u8BDD\u9898\u91CC\u7684\u6D88\u606F\uFF0C\u4E0D\u7BA1\u5F00\u4E0D\u5F00\uFF0C\u7B54\u590D\u90FD\u7559\u5728\u8BE5\u8BDD\u9898\u91CC\u3002"
138257
+ },
137717
138258
  wecomNumberedHints: {
137718
138259
  label: "\u7F16\u53F7\u5E94\u7B54\u63D0\u793A",
137719
138260
  description: "\u4F01\u5FAE\u6CA1\u6709\u6309\u94AE\uFF0C\u5BA1\u6279\u4E0E\u63D0\u95EE\u9760\u56DE\u7F16\u53F7\u3002\u5F00\u542F\u65F6\u9644\u5E26\u66F4\u5B8C\u6574\u7684\u7528\u6CD5\u8BF4\u660E\u3002"
@@ -137943,7 +138484,8 @@ var zh_CN_default = {
137943
138484
  working: "\u5904\u7406\u4E2D\u2026",
137944
138485
  done: "\u5DF2\u5B8C\u6210",
137945
138486
  error: "\u51FA\u9519\u4E86",
137946
- interrupted: "\u5DF2\u4E2D\u65AD"
138487
+ interrupted: "\u5DF2\u4E2D\u65AD",
138488
+ continued: "\u540E\u7EED\u89C1\u4E0B\u65B9\u5361\u7247"
137947
138489
  },
137948
138490
  step: {
137949
138491
  think: "\u601D\u8003",
@@ -138207,6 +138749,24 @@ var makeTranslate = (language) => {
138207
138749
  { optional, language }
138208
138750
  );
138209
138751
  };
138752
+ var makeLiveTranslate = (initial) => {
138753
+ let current = makeTranslate(initial);
138754
+ const t = Object.defineProperties(
138755
+ (key, params) => current(key, params),
138756
+ {
138757
+ optional: {
138758
+ value: (key, params) => current.optional(key, params)
138759
+ },
138760
+ language: { get: () => current.language }
138761
+ }
138762
+ );
138763
+ return {
138764
+ t,
138765
+ setLanguage: (language) => {
138766
+ if (language !== current.language) current = makeTranslate(language);
138767
+ }
138768
+ };
138769
+ };
138210
138770
 
138211
138771
  // src/modelPlan.ts
138212
138772
  var makeModelPlan = (core, deps) => {
@@ -138284,6 +138844,13 @@ var makeModelPlan = (core, deps) => {
138284
138844
  return { currentModelOf, handleModel, handlePlan };
138285
138845
  };
138286
138846
 
138847
+ // src/notificationTargets.ts
138848
+ var notificationChatKeys = (state, threadId, projectId) => {
138849
+ const owner = chatOfThread(state, threadId);
138850
+ if (owner !== void 0) return [owner];
138851
+ return projectId === null ? [] : chatsOnProject(state, projectId);
138852
+ };
138853
+
138287
138854
  // src/turnOutcome.ts
138288
138855
  var outcomeOfSession = (status) => {
138289
138856
  if (status === "error") return "failed";
@@ -138298,7 +138865,7 @@ var makeNotifications = (core, deps) => {
138298
138865
  const { host, adapterFor, deliver, bindingForChatKey, ready } = core;
138299
138866
  const { clearWorking } = deps;
138300
138867
  const { turnCards } = deps.replyCards;
138301
- const { failures } = deps;
138868
+ const { failures, anchors } = deps;
138302
138869
  const projectByThread = /* @__PURE__ */ new Map();
138303
138870
  const projectOfThread = async (threadId) => {
138304
138871
  const known = projectByThread.get(threadId);
@@ -138312,7 +138879,7 @@ var makeNotifications = (core, deps) => {
138312
138879
  projectByThread.set(threadId, thread.projectId);
138313
138880
  return thread.projectId;
138314
138881
  };
138315
- const targetsForProject = (projectId) => projectId === null ? [] : chatsOnProject(core.state, projectId).flatMap((chatKey) => {
138882
+ const targetsForProject = (threadId, projectId) => notificationChatKeys(core.state, threadId, projectId).flatMap((chatKey) => {
138316
138883
  const binding = bindingForChatKey(chatKey);
138317
138884
  return binding === void 0 ? [] : [binding];
138318
138885
  });
@@ -138338,7 +138905,7 @@ var makeNotifications = (core, deps) => {
138338
138905
  if (payload.session?.activeTurnId != null) return;
138339
138906
  const said = threadsWithSomethingToReport.delete(threadId);
138340
138907
  await ready;
138341
- const targets = targetsForProject(await projectOfThread(threadId));
138908
+ const targets = targetsForProject(threadId, await projectOfThread(threadId));
138342
138909
  const reporters = said ? targets : [];
138343
138910
  if (reporters.length > 0) {
138344
138911
  const thread = await host.projections.getThread(threadId);
@@ -138348,7 +138915,12 @@ var makeNotifications = (core, deps) => {
138348
138915
  const text3 = streamsTo(binding, threadId) ? "" : `${thread.title}
138349
138916
 
138350
138917
  ${final.text}`;
138351
- await deliver(binding, text3, final.attachments);
138918
+ const owns = chatOf(core.state, chatKeyOf(binding)).threadId === threadId;
138919
+ await deliver(
138920
+ owns ? await anchors.anchored(binding, threadId, final.turnId) : binding,
138921
+ text3,
138922
+ final.attachments
138923
+ );
138352
138924
  }
138353
138925
  }
138354
138926
  }
@@ -138361,7 +138933,7 @@ ${final.text}`;
138361
138933
  if (threadId === null) return;
138362
138934
  threadsWithSomethingToReport.delete(threadId);
138363
138935
  await ready;
138364
- const targets = targetsForProject(await projectOfThread(threadId));
138936
+ const targets = targetsForProject(threadId, await projectOfThread(threadId));
138365
138937
  const textTargets = targets.filter(
138366
138938
  (binding) => !(streamsTo(binding, threadId) && turnCards.covers(threadId))
138367
138939
  );
@@ -138809,8 +139381,8 @@ ${t("panel.projects.hint")}`;
138809
139381
  };
138810
139382
  return {
138811
139383
  /** 开一张新面板卡(`/help`)。发不出就抛,调用方退回文字版。 */
138812
- open: async (chatKey) => {
138813
- const surface = deps.surface(chatKey);
139384
+ open: async (chatKey, reply) => {
139385
+ const surface = deps.surface(chatKey, reply);
138814
139386
  if (surface === null) throw new Error("this chat has no panel");
138815
139387
  const entry = { cardId: "", chatKey, stack: [{ kind: "menu" }], busy: false };
138816
139388
  const cardId = await surface.open(menuCard(chatKey));
@@ -138822,8 +139394,8 @@ ${t("panel.projects.hint")}`;
138822
139394
  * 单独发一张选项目卡(聊天还没选项目时的第一句话、`/repo`)。发不出就抛,调用方退回文字
138823
139395
  * 列表。选完不回面板菜单。
138824
139396
  */
138825
- openProjects: async (chatKey, rows) => {
138826
- const surface = deps.surface(chatKey);
139397
+ openProjects: async (chatKey, rows, reply) => {
139398
+ const surface = deps.surface(chatKey, reply);
138827
139399
  if (surface === null) throw new Error("this chat has no panel");
138828
139400
  const entry = {
138829
139401
  cardId: "",
@@ -138837,6 +139409,11 @@ ${t("panel.projects.hint")}`;
138837
139409
  entry.cardId = cardId;
138838
139410
  remember(entry);
138839
139411
  },
139412
+ /**
139413
+ * 这张卡开在哪个聊天上。没记过,或已经被淘汰,就是 undefined。
139414
+ * 只读:不改 `cards`,也不改淘汰顺序。
139415
+ */
139416
+ chatKeyOfCard: (cardId) => cards.get(cardId)?.chatKey,
138840
139417
  /**
138841
139418
  * 处理一次点击,同步回给点击者的一句提示(null 就是没话说)。取数据、跑命令、改卡都
138842
139419
  * 在回调应答之后做。
@@ -138908,12 +139485,12 @@ var makePanelWiring = (core, deps) => {
138908
139485
  const { projectRowsFor, listedProjectsByChat } = deps.rows;
138909
139486
  const panel = makePanelController({
138910
139487
  t,
138911
- surface: (chatKey) => {
139488
+ surface: (chatKey, reply) => {
138912
139489
  const binding = bindingForChatKey(chatKey);
138913
139490
  const surface = binding === void 0 ? void 0 : adapterFor(binding.platform)?.panel;
138914
139491
  if (binding === void 0 || surface === void 0) return null;
138915
139492
  return {
138916
- open: (card) => surface.open(binding, card),
139493
+ open: (card) => surface.open({ ...binding, reply }, card),
138917
139494
  update: (cardId, card) => surface.update(binding, cardId, card)
138918
139495
  };
138919
139496
  },
@@ -138965,7 +139542,10 @@ var makePanelWiring = (core, deps) => {
138965
139542
  }
138966
139543
  });
138967
139544
  const handlePanelClick = (bot, click) => {
138968
- const target2 = bindingOf2(bot, click.chatId);
139545
+ const remembered = panel.chatKeyOfCard(click.cardId);
139546
+ const opener = remembered === void 0 ? void 0 : bindingForChatKey(remembered);
139547
+ const chatId = opener !== void 0 && opener.rowId === bot.rowId && adapterFor(opener.platform)?.parentChatIdOf?.(opener.chatId) === click.chatId ? opener.chatId : click.chatId;
139548
+ const target2 = bindingOf2(bot, chatId);
138969
139549
  if (!isAllowedSender(target2, click.operatorId)) {
138970
139550
  host.log.warn("chat ignored a panel click from someone not on the allow list", {
138971
139551
  bot: target2.displayName,
@@ -139111,6 +139691,8 @@ var trailingReply = (messages) => {
139111
139691
  };
139112
139692
 
139113
139693
  // src/turnCards.ts
139694
+ var ROTATE = "rotate";
139695
+ var isDrawing = (live) => live.card !== null || live.reopen;
139114
139696
  var RECENT_ENDED_LIMIT = 256;
139115
139697
  var makeTurnCards = (deps) => {
139116
139698
  const slots = /* @__PURE__ */ new Map();
@@ -139147,12 +139729,37 @@ var makeTurnCards = (deps) => {
139147
139729
  status: end.status,
139148
139730
  errorText: end.errorText
139149
139731
  });
139732
+ const openCard = (target2, view) => makeCardStream({
139733
+ open: target2.open,
139734
+ update: target2.update,
139735
+ schedule: deps.schedule,
139736
+ throttleMs: target2.throttleMs,
139737
+ warn: deps.warn
139738
+ }).open(view);
139739
+ const seal = async (slot) => {
139740
+ await deps.ready;
139741
+ const live = slot.live;
139742
+ if (disposed || live === null || live.card === null || !live.target.rotates) return;
139743
+ const card = live.card;
139744
+ await card.finish(
139745
+ buildTurnView({
139746
+ title: live.title,
139747
+ progress: live.lastProgress,
139748
+ texts: await readTexts(live, live.lastProgress),
139749
+ status: "continued"
139750
+ })
139751
+ );
139752
+ deps.onClosed(card.cardId);
139753
+ live.card = null;
139754
+ live.reopen = true;
139755
+ };
139150
139756
  const finalize = async (slot, threadId, progress, end) => {
139151
139757
  const live = slot.live;
139152
139758
  slot.live = null;
139153
- const target2 = live?.target ?? deps.resolveTarget(threadId);
139759
+ const turnId = progress.turnId ?? live?.turnId ?? null;
139760
+ const target2 = live?.target ?? deps.resolveTarget(threadId, turnId);
139154
139761
  if (target2 === null) return;
139155
- rememberEnded(threadId, progress.turnId ?? live?.turnId ?? null);
139762
+ rememberEnded(threadId, turnId);
139156
139763
  if (end.status === "error") erroredThreads.add(threadId);
139157
139764
  const title = live?.title ?? await deps.titleOf(threadId);
139158
139765
  const view = await finalView(title, progress, live, end);
@@ -139161,15 +139768,9 @@ var makeTurnCards = (deps) => {
139161
139768
  deps.onClosed(live.card.cardId);
139162
139769
  return;
139163
139770
  }
139164
- const card = await makeCardStream({
139165
- open: target2.open,
139166
- update: target2.update,
139167
- schedule: deps.schedule,
139168
- throttleMs: target2.throttleMs,
139169
- warn: deps.warn
139170
- }).open(view);
139771
+ const card = await openCard(target2, view);
139171
139772
  if (card === null) {
139172
- await deps.fallback(threadId, target2.chatKey, view);
139773
+ await deps.fallback(threadId, turnId, target2.chatKey, view);
139173
139774
  return;
139174
139775
  }
139175
139776
  };
@@ -139188,38 +139789,33 @@ var makeTurnCards = (deps) => {
139188
139789
  }
139189
139790
  }
139190
139791
  if (item.phase === "ended") {
139191
- if (live === null && deps.resolveTarget(threadId) === null) return;
139792
+ if (live === null && deps.resolveTarget(threadId, item.turnId) === null) return;
139192
139793
  await finalize(slot, threadId, item, await deps.endOf(threadId, item.turnId));
139193
139794
  return;
139194
139795
  }
139195
139796
  erroredThreads.delete(threadId);
139196
139797
  if (live === null) {
139197
- const target2 = deps.resolveTarget(threadId);
139798
+ const target2 = deps.resolveTarget(threadId, item.turnId);
139198
139799
  if (target2 === null) return;
139199
139800
  const title = await deps.titleOf(threadId);
139200
139801
  const fresh = {
139201
139802
  chatKey: target2.chatKey,
139202
139803
  title,
139203
139804
  card: null,
139805
+ reopen: false,
139204
139806
  target: target2,
139205
139807
  turnId: item.turnId,
139206
139808
  lastSeq: item.seq,
139207
139809
  lastProgress: item,
139208
139810
  settledTexts: /* @__PURE__ */ new Map()
139209
139811
  };
139210
- const view = buildTurnView({
139812
+ const view2 = buildTurnView({
139211
139813
  title,
139212
139814
  progress: item,
139213
139815
  texts: await readTexts(fresh, item),
139214
139816
  status: "working"
139215
139817
  });
139216
- const card = await makeCardStream({
139217
- open: target2.open,
139218
- update: target2.update,
139219
- schedule: deps.schedule,
139220
- throttleMs: target2.throttleMs,
139221
- warn: deps.warn
139222
- }).open(view);
139818
+ const card = await openCard(target2, view2);
139223
139819
  slot.live = { ...fresh, card };
139224
139820
  if (card !== null) {
139225
139821
  deps.onOpened({ cardId: card.cardId, chatKey: target2.chatKey, threadId, title });
@@ -139229,15 +139825,27 @@ var makeTurnCards = (deps) => {
139229
139825
  live.turnId ??= item.turnId;
139230
139826
  live.lastSeq = item.seq;
139231
139827
  live.lastProgress = item;
139232
- if (live.card === null) return;
139233
- live.card.push(
139234
- buildTurnView({
139235
- title: live.title,
139236
- progress: item,
139237
- texts: await readTexts(live, item),
139238
- status: "working"
139239
- })
139240
- );
139828
+ if (live.card === null && !live.reopen) return;
139829
+ const view = buildTurnView({
139830
+ title: live.title,
139831
+ progress: item,
139832
+ texts: await readTexts(live, item),
139833
+ status: "working"
139834
+ });
139835
+ if (live.card !== null) {
139836
+ live.card.push(view);
139837
+ return;
139838
+ }
139839
+ live.reopen = false;
139840
+ live.card = await openCard(live.target, view);
139841
+ if (live.card !== null) {
139842
+ deps.onOpened({
139843
+ cardId: live.card.cardId,
139844
+ chatKey: live.chatKey,
139845
+ threadId,
139846
+ title: live.title
139847
+ });
139848
+ }
139241
139849
  };
139242
139850
  const pump = async (threadId, slot) => {
139243
139851
  try {
@@ -139245,7 +139853,8 @@ var makeTurnCards = (deps) => {
139245
139853
  const item = slot.queue.shift();
139246
139854
  if (item === void 0) break;
139247
139855
  try {
139248
- await handle(slot, threadId, item);
139856
+ if (item === ROTATE) await seal(slot);
139857
+ else await handle(slot, threadId, item);
139249
139858
  } catch (error) {
139250
139859
  deps.warn("could not draw a turn card", error);
139251
139860
  }
@@ -139265,7 +139874,7 @@ var makeTurnCards = (deps) => {
139265
139874
  slots.set(threadId, slot);
139266
139875
  }
139267
139876
  const last = slot.queue.at(-1);
139268
- if (last !== void 0 && last.phase === "running" && last.turnId === progress.turnId) {
139877
+ if (last !== void 0 && last !== ROTATE && last.phase === "running" && last.turnId === progress.turnId) {
139269
139878
  slot.queue[slot.queue.length - 1] = progress;
139270
139879
  } else {
139271
139880
  slot.queue.push(progress);
@@ -139275,13 +139884,29 @@ var makeTurnCards = (deps) => {
139275
139884
  void pump(threadId, slot);
139276
139885
  }
139277
139886
  },
139278
- /** 这条会话正在画卡的那一轮;没有在画的卡是 null。 */
139887
+ /**
139888
+ * 审批或提问答完了:封存这条会话开着的卡,下一拍另开一张。立刻返回;没有在画的卡、或
139889
+ * 平台不换卡,就什么都不做。
139890
+ */
139891
+ rotate: (threadId) => {
139892
+ const slot = slots.get(threadId);
139893
+ if (disposed || slot === void 0) return;
139894
+ slot.queue.push(ROTATE);
139895
+ if (!slot.running) {
139896
+ slot.running = true;
139897
+ void pump(threadId, slot);
139898
+ }
139899
+ },
139900
+ /** 这条会话正在画卡的那一轮(封存后等着另开的也算);没有在画的卡是 null。 */
139279
139901
  drawingTurn: (threadId) => {
139280
139902
  const live = slots.get(threadId)?.live;
139281
- return live?.card != null ? live.turnId : null;
139903
+ return live != null && isDrawing(live) ? live.turnId : null;
139282
139904
  },
139283
139905
  /** 这条会话现在有一张在画的卡,或者上一轮刚以失败收了卡:失败原因已经在卡上了。 */
139284
- covers: (threadId) => slots.get(threadId)?.live?.card != null || erroredThreads.has(threadId),
139906
+ covers: (threadId) => {
139907
+ const live = slots.get(threadId)?.live;
139908
+ return live != null && isDrawing(live) || erroredThreads.has(threadId);
139909
+ },
139285
139910
  /** 插件停用:放下所有在画的卡,不再发任何更新。卡会停在最后一拍,重启时被标「已中断」。 */
139286
139911
  dispose: () => {
139287
139912
  disposed = true;
@@ -139292,19 +139917,21 @@ var makeTurnCards = (deps) => {
139292
139917
  };
139293
139918
 
139294
139919
  // src/replyCards.ts
139295
- var makeReplyCards = (core, failures) => {
139920
+ var makeReplyCards = (core, failures, anchors) => {
139296
139921
  const { host, adapterFor, commit, deliver, bindingForChatKey, bindingOfThread, ready } = core;
139297
139922
  const stopRequested = /* @__PURE__ */ new Set();
139298
139923
  const turnCards = makeTurnCards({
139299
139924
  ready,
139300
- resolveTarget: (threadId) => {
139925
+ resolveTarget: (threadId, turnId) => {
139301
139926
  const binding = bindingOfThread(threadId);
139302
139927
  const cards = binding === void 0 ? void 0 : adapterFor(binding.platform)?.turnCards;
139303
139928
  if (binding === void 0 || cards === void 0) return null;
139929
+ let anchored;
139304
139930
  return {
139305
139931
  chatKey: chatKeyOf(binding),
139306
139932
  throttleMs: cards.throttleMs,
139307
- open: (view) => cards.open(binding, view),
139933
+ rotates: cards.rotatesOnInteraction === true,
139934
+ open: async (view) => cards.open(await (anchored ??= anchors.anchored(binding, threadId, turnId)), view),
139308
139935
  update: (cardId, view) => cards.update(binding, cardId, view)
139309
139936
  };
139310
139937
  },
@@ -139335,11 +139962,11 @@ var makeReplyCards = (core, failures) => {
139335
139962
  },
139336
139963
  onOpened: (card) => commit("turn-card-opened", card),
139337
139964
  onClosed: (cardId) => commit("turn-card-closed", { cardId }),
139338
- fallback: async (_threadId, chatKey, view) => {
139965
+ fallback: async (threadId, turnId, chatKey, view) => {
139339
139966
  const binding = bindingForChatKey(chatKey);
139340
139967
  if (binding === void 0) return;
139341
139968
  const text3 = [view.body, view.errorText ?? ""].filter((part) => part !== "").join("\n\n");
139342
- await deliver(binding, text3);
139969
+ await deliver(await anchors.anchored(binding, threadId, turnId), text3);
139343
139970
  },
139344
139971
  schedule: (run, delayMs) => {
139345
139972
  const timer = setTimeout(run, delayMs);
@@ -139390,24 +140017,6 @@ var ImNotConnectedError = class extends Error {
139390
140017
  }
139391
140018
  };
139392
140019
 
139393
- // src/deliveryContract.ts
139394
- var DELIVERY_SEND_ERROR_CODES = [
139395
- "bad-request",
139396
- "unknown-bot",
139397
- "unknown-target",
139398
- "bot-not-connected",
139399
- "delivery-failed"
139400
- ];
139401
- var isDeliverySendErrorCode = (value2) => typeof value2 === "string" && DELIVERY_SEND_ERROR_CODES.includes(value2);
139402
- var DeliverySendError = class extends Error {
139403
- code;
139404
- constructor(code2, message) {
139405
- super(message);
139406
- this.name = "DeliverySendError";
139407
- this.code = code2;
139408
- }
139409
- };
139410
-
139411
140020
  // src/delivery.ts
139412
140021
  var makeDeliverySender = (core, config2) => {
139413
140022
  return async (input) => {
@@ -139436,188 +140045,6 @@ var makeDeliverySender = (core, config2) => {
139436
140045
  };
139437
140046
  };
139438
140047
 
139439
- // src/deliveryHttp.ts
139440
- import * as NodeCrypto from "node:crypto";
139441
- import * as NodeHttp from "node:http";
139442
-
139443
- // src/boundedBody.ts
139444
- var readBoundedBody = (req, limit) => new Promise((resolve) => {
139445
- const chunks = [];
139446
- let size = 0;
139447
- let settled = false;
139448
- const finish = (result) => {
139449
- if (settled) return;
139450
- settled = true;
139451
- resolve(result);
139452
- };
139453
- req.on("data", (chunk) => {
139454
- if (settled) return;
139455
- size += chunk.length;
139456
- if (size > limit) {
139457
- finish({ kind: "too-large" });
139458
- req.destroy();
139459
- return;
139460
- }
139461
- chunks.push(chunk);
139462
- });
139463
- req.on("end", () => finish({ kind: "ok", bytes: Buffer.concat(chunks) }));
139464
- req.on("error", () => finish({ kind: "read-failed" }));
139465
- req.on("close", () => finish({ kind: "read-failed" }));
139466
- });
139467
-
139468
- // src/deliveryHttp.ts
139469
- var LOOPBACK = "127.0.0.1";
139470
- var MAX_BODY_BYTES = 1 * 1024 * 1024;
139471
- var PATH = "/v1/delivery/messages";
139472
- var statusFor = (code2) => {
139473
- switch (code2) {
139474
- case "bad-request":
139475
- return 400;
139476
- case "unauthorized":
139477
- return 401;
139478
- case "unknown-bot":
139479
- case "unknown-target":
139480
- return 404;
139481
- case "bot-not-connected":
139482
- return 503;
139483
- case "delivery-failed":
139484
- return 502;
139485
- case "method-not-allowed":
139486
- return 405;
139487
- case "unsupported-media-type":
139488
- return 415;
139489
- case "payload-too-large":
139490
- return 413;
139491
- }
139492
- };
139493
- var writeJson = (res, status, body) => {
139494
- const payload = JSON.stringify(body);
139495
- res.writeHead(status, {
139496
- "content-type": "application/json; charset=utf-8",
139497
- "content-length": Buffer.byteLength(payload),
139498
- "cache-control": "no-store"
139499
- });
139500
- res.end(payload);
139501
- };
139502
- var writeError = (res, code2, message) => {
139503
- writeJson(res, statusFor(code2), { error: { code: code2, message } });
139504
- };
139505
- var isLoopbackRemote = (remote) => {
139506
- if (remote === void 0 || remote === "") return false;
139507
- return remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
139508
- };
139509
- var timingSafeEqualString = (a, b) => {
139510
- const left = Buffer.from(a);
139511
- const right = Buffer.from(b);
139512
- if (left.length !== right.length) return false;
139513
- return NodeCrypto.timingSafeEqual(left, right);
139514
- };
139515
- var parseSendBody = (raw) => {
139516
- let parsed;
139517
- try {
139518
- parsed = JSON.parse(raw.toString("utf8"));
139519
- } catch {
139520
- return "bad-request";
139521
- }
139522
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return "bad-request";
139523
- const record2 = parsed;
139524
- const botId = record2["botId"];
139525
- const targetId = record2["targetId"];
139526
- const text3 = record2["text"];
139527
- const format2 = record2["format"];
139528
- if (typeof botId !== "string" || botId.trim() === "") return "bad-request";
139529
- if (typeof targetId !== "string" || targetId.trim() === "") return "bad-request";
139530
- if (typeof text3 !== "string" || text3.trim() === "") return "bad-request";
139531
- if (format2 !== void 0 && format2 !== "plain" && format2 !== "markdown") return "bad-request";
139532
- for (const key of Object.keys(record2)) {
139533
- if (key !== "botId" && key !== "targetId" && key !== "text" && key !== "format") {
139534
- return "bad-request";
139535
- }
139536
- }
139537
- return {
139538
- botId: botId.trim(),
139539
- targetId: targetId.trim(),
139540
- text: text3,
139541
- format: format2 === "markdown" ? "markdown" : format2 === "plain" ? "plain" : void 0
139542
- };
139543
- };
139544
- var startDeliveryHttpServer = async (options) => {
139545
- const server = NodeHttp.createServer((req, res) => {
139546
- void (async () => {
139547
- if (!isLoopbackRemote(req.socket.remoteAddress)) {
139548
- writeError(res, "unauthorized", "Only loopback clients are accepted.");
139549
- return;
139550
- }
139551
- const url = new URL(req.url ?? "/", `http://${LOOPBACK}`);
139552
- if (url.pathname !== PATH) {
139553
- writeError(res, "bad-request", `Unknown path '${url.pathname}'.`);
139554
- return;
139555
- }
139556
- if (req.method !== "POST") {
139557
- res.setHeader("allow", "POST");
139558
- writeError(res, "method-not-allowed", "Only POST is accepted.");
139559
- return;
139560
- }
139561
- const contentType = (req.headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase();
139562
- if (contentType !== "application/json") {
139563
- writeError(res, "unsupported-media-type", "Content-Type must be application/json.");
139564
- return;
139565
- }
139566
- if (options.token !== "") {
139567
- const header = req.headers.authorization ?? "";
139568
- const prefix = "Bearer ";
139569
- if (!header.startsWith(prefix) || !timingSafeEqualString(header.slice(prefix.length), options.token)) {
139570
- writeError(res, "unauthorized", "Bearer token required.");
139571
- return;
139572
- }
139573
- }
139574
- const read = await readBoundedBody(req, MAX_BODY_BYTES);
139575
- if (read.kind !== "ok") {
139576
- if (read.kind === "too-large") {
139577
- writeError(res, "payload-too-large", "JSON body exceeds 1 MiB.");
139578
- } else {
139579
- writeError(res, "bad-request", "Failed to read body.");
139580
- }
139581
- return;
139582
- }
139583
- const parsed = parseSendBody(read.bytes);
139584
- if (typeof parsed === "string") {
139585
- writeError(res, parsed, "Invalid JSON body.");
139586
- return;
139587
- }
139588
- try {
139589
- const result = await options.send(parsed);
139590
- writeJson(res, 200, { sent: result.sent });
139591
- } catch (error) {
139592
- const rawCode = error instanceof Error ? error.code : void 0;
139593
- const code2 = isDeliverySendErrorCode(rawCode) ? rawCode : "delivery-failed";
139594
- const message = error instanceof Error ? error.message : String(error);
139595
- options.log?.warn("delivery http send failed", { code: code2, message });
139596
- writeError(res, code2, message);
139597
- }
139598
- })().catch((error) => {
139599
- options.log?.warn("delivery http handler crashed", {
139600
- error: error instanceof Error ? error.message : String(error)
139601
- });
139602
- if (!res.headersSent) writeError(res, "delivery-failed", "Internal error.");
139603
- });
139604
- });
139605
- await new Promise((resolve, reject) => {
139606
- server.once("error", reject);
139607
- server.listen(options.port, LOOPBACK, () => resolve());
139608
- });
139609
- const address = server.address();
139610
- const port = address.port;
139611
- options.log?.info("delivery http listening", { host: LOOPBACK, port, path: PATH });
139612
- return {
139613
- port,
139614
- origin: `http://${LOOPBACK}:${String(port)}`,
139615
- close: () => new Promise((resolve, reject) => {
139616
- server.close((error) => error ? reject(error) : resolve());
139617
- })
139618
- };
139619
- };
139620
-
139621
140048
  // src/sendFile.ts
139622
140049
  var failure = (code2, text3) => ({
139623
140050
  text: text3,
@@ -139673,7 +140100,7 @@ var makeSendFileTool = (deps) => ({
139673
140100
  handle: async (input, invocation) => {
139674
140101
  const path = typeof input["path"] === "string" ? input["path"].trim() : "";
139675
140102
  if (path === "") return failure("unreadable", "`path` is required.");
139676
- const binding = invocation.threadId === null ? void 0 : deps.boundChat(invocation.threadId);
140103
+ const binding = invocation.threadId === null ? void 0 : await deps.boundChat(invocation.threadId);
139677
140104
  if (binding === void 0) {
139678
140105
  return failure(
139679
140106
  "not-in-chat",
@@ -139779,17 +140206,16 @@ var makeSessions = (core) => {
139779
140206
  };
139780
140207
  };
139781
140208
  const webShellsForProject = async (projectId) => {
139782
- const listThreads = host.projections.listThreads;
139783
- if (listThreads === void 0 || projectId === "") return [];
140209
+ if (projectId === "") return [];
139784
140210
  const shells = [];
139785
- let cursor = void 0;
140211
+ let cursor = null;
139786
140212
  for (let page = 0; page < 5; page += 1) {
139787
- const result = await listThreads({
140213
+ const result = await host.threads.list({
139788
140214
  limit: WEB_LIST_PAGE,
139789
- includeArchived: false,
139790
- ...cursor === void 0 || cursor === null ? {} : { cursor }
140215
+ archived: "exclude",
140216
+ cursor
139791
140217
  });
139792
- for (const shell of result.threads) {
140218
+ for (const shell of result.items) {
139793
140219
  if (shell.projectId !== projectId) continue;
139794
140220
  if (shell.archivedAt != null) continue;
139795
140221
  shells.push({
@@ -139992,6 +140418,7 @@ var chatIdOf = (message) => {
139992
140418
  const thread = message.message_thread_id;
139993
140419
  return message.is_topic_message === true && thread !== void 0 && thread > 0 ? `${String(message.chat.id)}:${String(thread)}` : String(message.chat.id);
139994
140420
  };
140421
+ var parentChatIdOf = (message) => chatIdOf(message) === String(message.chat.id) ? void 0 : String(message.chat.id);
139995
140422
  var splitChatId = (chatId) => {
139996
140423
  const match = /^(.+):([1-9]\d*)$/u.exec(chatId);
139997
140424
  return match === null ? { chat: chatId, thread: null } : { chat: match[1] ?? chatId, thread: Number(match[2]) };
@@ -140515,12 +140942,17 @@ var makeTelegramAdapter = (deps) => {
140515
140942
  arg: action.arg ?? null
140516
140943
  }) ?? null;
140517
140944
  };
140518
- const apiFor = async (binding) => {
140945
+ const apiFor = (binding) => {
140946
+ const token = tokenOf(binding);
140947
+ const builtFor = JSON.stringify([token, binding.proxy === void 0 ? [] : [binding.proxy]]);
140519
140948
  const existing = apis.get(binding.rowId);
140520
- if (existing !== void 0) return existing;
140521
- const http = await deps.fetchVia(binding.proxy);
140522
- const api = makeTelegramApi({ http, token: tokenOf(binding) });
140523
- apis.set(binding.rowId, api);
140949
+ if (existing?.builtFor === builtFor) return existing.api;
140950
+ const api = deps.fetchVia(binding.proxy).then((http) => makeTelegramApi({ http, token }));
140951
+ const entry = { builtFor, api };
140952
+ apis.set(binding.rowId, entry);
140953
+ api.catch(() => {
140954
+ if (apis.get(binding.rowId) === entry) apis.delete(binding.rowId);
140955
+ });
140524
140956
  return api;
140525
140957
  };
140526
140958
  const makeWriter = (api, chatId, resume) => makeTurnWriter({
@@ -140618,9 +141050,11 @@ var makeTelegramAdapter = (deps) => {
140618
141050
  });
140619
141051
  continue;
140620
141052
  }
141053
+ const parentChatId = parentChatIdOf(message);
140621
141054
  handlers.onMessage({
140622
141055
  messageId: String(message.message_id),
140623
141056
  chatId: chatIdOf(message),
141057
+ ...parentChatId === void 0 ? {} : { parentChatId },
140624
141058
  senderId: message.from === void 0 ? null : String(message.from.id),
140625
141059
  text: text3,
140626
141060
  chatKind: message.chat.type === "private" ? "private" : "group",
@@ -140846,6 +141280,10 @@ var makeTurns = (core, deps) => {
140846
141280
  return true;
140847
141281
  }
140848
141282
  };
141283
+ const waitingFor = (chatKey, threadId) => {
141284
+ const waiting = pendingByChat.get(chatKey);
141285
+ return waiting !== void 0 && waiting.threadId === threadId ? waiting : void 0;
141286
+ };
140849
141287
  const interactionModeOf = (chatKey) => chatOf(core.state, chatKey).plan ? "plan" : "default";
140850
141288
  const ensureThread = async (binding, titleSeed) => {
140851
141289
  const chatKey = chatKeyOf(binding);
@@ -140905,9 +141343,8 @@ var makeTurns = (core, deps) => {
140905
141343
  const text3 = message.text;
140906
141344
  const threadId = await ensureThread(binding, text3 || message.resources[0]?.name || "Chat");
140907
141345
  if (threadId === null) return null;
140908
- const waiting = pendingByChat.get(chatKey);
141346
+ const carried = waitingFor(chatKey, threadId);
140909
141347
  pendingByChat.delete(chatKey);
140910
- const carried = waiting?.threadId === threadId ? waiting : void 0;
140911
141348
  const own = await collectResources(
140912
141349
  binding,
140913
141350
  threadId,
@@ -140918,6 +141355,7 @@ var makeTurns = (core, deps) => {
140918
141355
  const attachments = [...carried?.attachments ?? [], ...own.attachments];
140919
141356
  const notes = [...carried?.notes ?? [], ...own.notes];
140920
141357
  const userMessageId = crypto.randomUUID();
141358
+ deps.anchors.note(threadId, userMessageId, binding);
140921
141359
  let intentAction;
140922
141360
  if (binding.queueMode === "steer") {
140923
141361
  try {
@@ -140965,7 +141403,8 @@ var makeTurns = (core, deps) => {
140965
141403
  chatKey,
140966
141404
  threadId,
140967
141405
  userMessageId,
140968
- text3.trim() || attachments[0]?.name || message.resources[0]?.name || ""
141406
+ text3.trim() || attachments[0]?.name || message.resources[0]?.name || "",
141407
+ binding.reply
140969
141408
  );
140970
141409
  return threadId;
140971
141410
  };
@@ -140991,7 +141430,14 @@ var makeTurns = (core, deps) => {
140991
141430
  })
140992
141431
  );
140993
141432
  };
140994
- return { threadStillUsable, ensureThread, startTurn, receiveAttachmentsOnly };
141433
+ const hasWaitingAttachments = (chatKey) => (waitingFor(chatKey, chatOf(core.state, chatKey).threadId)?.attachments.length ?? 0) > 0;
141434
+ return {
141435
+ threadStillUsable,
141436
+ ensureThread,
141437
+ startTurn,
141438
+ receiveAttachmentsOnly,
141439
+ hasWaitingAttachments
141440
+ };
140995
141441
  };
140996
141442
 
140997
141443
  // src/webSync.ts
@@ -141814,6 +142260,7 @@ ${kept}`;
141814
142260
  icon: "wecom",
141815
142261
  label: "WeCom",
141816
142262
  columns: COLUMNS4,
142263
+ unlistedValues: ["wecomEndpoint"],
141817
142264
  isConfigured: (binding) => {
141818
142265
  const { botId, botSecret } = credentials2(binding);
141819
142266
  return botId !== "" && botSecret !== "";
@@ -142918,9 +143365,11 @@ var makeWecomAppAdapter = (deps) => {
142918
143365
  // src/index.ts
142919
143366
  var imPlugin = definePlugin({
142920
143367
  id: "im",
142921
- apiVersion: 3,
143368
+ apiVersion: 5,
142922
143369
  activate: async (ctx) => {
142923
- const t = makeTranslate(parseReplyLanguage(ctx.config[REPLY_LANGUAGE_FIELD]));
143370
+ const { t, setLanguage } = makeLiveTranslate(
143371
+ parseReplyLanguage(ctx.config[REPLY_LANGUAGE_FIELD])
143372
+ );
142924
143373
  const adapters = [
142925
143374
  makeLarkAdapter({
142926
143375
  http: ctx.http,
@@ -142980,6 +143429,8 @@ var imPlugin = definePlugin({
142980
143429
  host: {
142981
143430
  log: ctx.log,
142982
143431
  projections: ctx.projections,
143432
+ threads: ctx.threads,
143433
+ approvals: ctx.approvals,
142983
143434
  commands: ctx.commands,
142984
143435
  plugins: ctx.plugins,
142985
143436
  attachments: ctx.attachments,
@@ -142989,29 +143440,36 @@ var imPlugin = definePlugin({
142989
143440
  onDispose: ctx.onDispose
142990
143441
  },
142991
143442
  pluginId: ctx.id,
142992
- config: ctx.config,
143443
+ config: () => ctx.config,
142993
143444
  derivedState: ctx.derivedState,
142994
143445
  t,
142995
143446
  adapters
142996
143447
  });
142997
143448
  const acknowledgements = makeAcknowledgements(core);
142998
143449
  const failures = makeFailureNotices(core);
142999
- const replyCards = makeReplyCards(core, failures);
143450
+ const replyAnchors = makeReplyAnchors(core.host);
143451
+ const replyCards = makeReplyCards(core, failures, replyAnchors);
143000
143452
  const teams = makeTeamRuns(core, { clearWorking: acknowledgements.clearWorking });
143001
143453
  const notifications = makeNotifications(core, {
143002
143454
  clearWorking: acknowledgements.clearWorking,
143003
143455
  replyCards,
143004
- failures
143456
+ failures,
143457
+ anchors: replyAnchors
143005
143458
  });
143006
143459
  const webSync = makeWebSync(core);
143007
- const approvals = makeApprovals(core);
143008
- const interactions = makeInteractions(core, approvals);
143460
+ const approvals = makeApprovals(core, replyAnchors);
143461
+ const interactions = makeInteractions(core, {
143462
+ ...approvals,
143463
+ anchors: replyAnchors,
143464
+ rotateTurnCard: replyCards.turnCards.rotate
143465
+ });
143009
143466
  const inboundAttachments = makeInboundAttachments(core);
143010
143467
  const turns = makeTurns(core, {
143011
143468
  attachments: inboundAttachments,
143012
143469
  queueFlow: interactions.queueFlow,
143013
143470
  rememberThreadProject: notifications.rememberThreadProject,
143014
- noteInboundUserMessage: webSync.noteInboundUserMessage
143471
+ noteInboundUserMessage: webSync.noteInboundUserMessage,
143472
+ anchors: replyAnchors
143015
143473
  });
143016
143474
  const sessions = makeSessions(core);
143017
143475
  const modelPlan = makeModelPlan(core, turns);
@@ -143027,7 +143485,7 @@ var imPlugin = definePlugin({
143027
143485
  });
143028
143486
  const chatSetup = makeChatSetup(core, {
143029
143487
  rows: projectRows,
143030
- openPicker: (chatKey, rows) => panelWiring.panel.openProjects(chatKey, rows)
143488
+ openPicker: (chatKey, rows, reply) => panelWiring.panel.openProjects(chatKey, rows, reply)
143031
143489
  });
143032
143490
  const commands = makeCommands(core, {
143033
143491
  clearWorking: acknowledgements.clearWorking,
@@ -143035,7 +143493,7 @@ var imPlugin = definePlugin({
143035
143493
  sessions,
143036
143494
  modelPlan,
143037
143495
  chatSetup,
143038
- openPanel: (chatKey) => panelWiring.panel.open(chatKey)
143496
+ openPanel: (chatKey, reply) => panelWiring.panel.open(chatKey, reply)
143039
143497
  });
143040
143498
  const inbound = makeInbound(core, {
143041
143499
  enqueueForChat: chatQueue.enqueueForChat,
@@ -143053,7 +143511,10 @@ var imPlugin = definePlugin({
143053
143511
  name: "im",
143054
143512
  tools: [
143055
143513
  makeSendFileTool({
143056
- boundChat: (threadId) => core.bindingOfThread(threadId),
143514
+ boundChat: async (threadId) => {
143515
+ const binding = core.bindingOfThread(threadId);
143516
+ return binding === void 0 ? void 0 : replyAnchors.anchored(binding, threadId, null);
143517
+ },
143057
143518
  adapterFor: (binding) => core.adapterFor(binding.platform),
143058
143519
  translate: t,
143059
143520
  log: {
@@ -143063,34 +143524,21 @@ var imPlugin = definePlugin({
143063
143524
  })
143064
143525
  ]
143065
143526
  });
143066
- const { bots, connecting } = startConnections(core, inbound.inboundHandlersFor);
143067
- if (readDeliveryHttpEnabled(ctx.config)) {
143068
- const port = readDeliveryHttpPort(ctx.config);
143069
- const token = readDeliveryHttpToken(ctx.config);
143070
- const send = makeDeliverySender(core, ctx.config);
143071
- try {
143072
- const server = await startDeliveryHttpServer({
143073
- port,
143074
- token,
143075
- send,
143076
- log: {
143077
- info: (message, fields) => ctx.log.info(message, fields),
143078
- warn: (message, fields) => ctx.log.warn(message, fields)
143079
- }
143080
- });
143081
- ctx.onDispose(() => {
143082
- void server.close().catch((error) => {
143083
- ctx.log.warn("delivery http could not close", {
143084
- error: error instanceof Error ? error.message : String(error)
143085
- });
143086
- });
143087
- });
143088
- } catch (error) {
143089
- ctx.log.warn("delivery http failed to listen", {
143090
- port,
143091
- error: error instanceof Error ? error.message : String(error)
143092
- });
143093
- }
143527
+ const connections = startConnections(core, inbound.inboundHandlersFor);
143528
+ const { bots, connecting } = connections.reconcile();
143529
+ const deliveryHttp = makeDeliveryHttp({
143530
+ // 每次请求按此刻的设置找目标:投递目标表改了不用重开监听。
143531
+ send: (input) => makeDeliverySender(core, ctx.config)(input),
143532
+ log: ctx.log,
143533
+ onDispose: ctx.onDispose
143534
+ });
143535
+ await deliveryHttp.apply(ctx.config);
143536
+ if (typeof ctx.settings.onChange === "function") {
143537
+ ctx.settings.onChange(async (config2) => {
143538
+ setLanguage(parseReplyLanguage(config2[REPLY_LANGUAGE_FIELD]));
143539
+ connections.reconcile();
143540
+ await deliveryHttp.apply(config2);
143541
+ });
143094
143542
  }
143095
143543
  ctx.log.info("chat plugin active", { bots, connecting });
143096
143544
  }