@ycodium-ai/plugin-im 0.2.3202 → 0.2.3208

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
@@ -137,7 +137,7 @@ var require_punycode = __commonJS({
137
137
  }
138
138
  return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
139
139
  };
140
- var decode2 = function(input) {
140
+ var decode3 = function(input) {
141
141
  const output = [];
142
142
  const inputLength = input.length;
143
143
  let i = 0;
@@ -249,7 +249,7 @@ var require_punycode = __commonJS({
249
249
  };
250
250
  var toUnicode = function(input) {
251
251
  return mapDomain(input, function(string) {
252
- return regexPunycode.test(string) ? decode2(string.slice(4).toLowerCase()) : string;
252
+ return regexPunycode.test(string) ? decode3(string.slice(4).toLowerCase()) : string;
253
253
  });
254
254
  };
255
255
  var toASCII = function(input) {
@@ -275,7 +275,7 @@ var require_punycode = __commonJS({
275
275
  "decode": ucs2decode,
276
276
  "encode": ucs2encode
277
277
  },
278
- "decode": decode2,
278
+ "decode": decode3,
279
279
  "encode": encode2,
280
280
  "toASCII": toASCII,
281
281
  "toUnicode": toUnicode
@@ -18475,7 +18475,7 @@ var require_utils = __commonJS({
18475
18475
  return acc;
18476
18476
  }, target2);
18477
18477
  };
18478
- var decode2 = function(str2, defaultDecoder, charset) {
18478
+ var decode3 = function(str2, defaultDecoder, charset) {
18479
18479
  var strWithoutPlus = str2.replace(/\+/g, " ");
18480
18480
  if (charset === "iso-8859-1") {
18481
18481
  return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);
@@ -18601,7 +18601,7 @@ var require_utils = __commonJS({
18601
18601
  assign,
18602
18602
  combine,
18603
18603
  compact,
18604
- decode: decode2,
18604
+ decode: decode3,
18605
18605
  encode: encode2,
18606
18606
  isBuffer,
18607
18607
  isOverflow,
@@ -23360,7 +23360,7 @@ var require_websocket = __commonJS({
23360
23360
  var http = __require("http");
23361
23361
  var net = __require("net");
23362
23362
  var tls = __require("tls");
23363
- var { randomBytes, createHash: createHash6 } = __require("crypto");
23363
+ var { randomBytes, createHash: createHash7 } = __require("crypto");
23364
23364
  var { Duplex, Readable } = __require("stream");
23365
23365
  var { URL: URL2 } = __require("url");
23366
23366
  var PerMessageDeflate = require_permessage_deflate();
@@ -24028,7 +24028,7 @@ var require_websocket = __commonJS({
24028
24028
  abortHandshake(websocket, socket, "Invalid Upgrade header");
24029
24029
  return;
24030
24030
  }
24031
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
24031
+ const digest = createHash7("sha1").update(key + GUID).digest("base64");
24032
24032
  if (res.headers["sec-websocket-accept"] !== digest) {
24033
24033
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
24034
24034
  return;
@@ -24397,7 +24397,7 @@ var require_websocket_server = __commonJS({
24397
24397
  var EventEmitter = __require("events");
24398
24398
  var http = __require("http");
24399
24399
  var { Duplex } = __require("stream");
24400
- var { createHash: createHash6 } = __require("crypto");
24400
+ var { createHash: createHash7 } = __require("crypto");
24401
24401
  var extension = require_extension();
24402
24402
  var PerMessageDeflate = require_permessage_deflate();
24403
24403
  var subprotocol = require_subprotocol();
@@ -24704,7 +24704,7 @@ var require_websocket_server = __commonJS({
24704
24704
  );
24705
24705
  }
24706
24706
  if (this._state > RUNNING) return abortHandshake(socket, 503);
24707
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
24707
+ const digest = createHash7("sha1").update(key + GUID).digest("base64");
24708
24708
  const headers = [
24709
24709
  "HTTP/1.1 101 Switching Protocols",
24710
24710
  "Upgrade: websocket",
@@ -24912,7 +24912,7 @@ var require_base64 = __commonJS({
24912
24912
  return String.fromCharCode.apply(String, chunk.slice(0, i2));
24913
24913
  };
24914
24914
  var invalidEncoding = "invalid encoding";
24915
- base64.decode = function decode2(string, buffer, offset) {
24915
+ base64.decode = function decode3(string, buffer, offset) {
24916
24916
  var start = offset;
24917
24917
  var j = 0, t;
24918
24918
  for (var i2 = 0; i2 < string.length; ) {
@@ -121305,7 +121305,7 @@ var require_lib2 = __commonJS({
121305
121305
  Header.encodeDelimited = function encodeDelimited(message, writer) {
121306
121306
  return this.encode(message, writer).ldelim();
121307
121307
  };
121308
- Header.decode = function decode3(reader, length) {
121308
+ Header.decode = function decode4(reader, length) {
121309
121309
  if (!(reader instanceof $Reader))
121310
121310
  reader = $Reader.create(reader);
121311
121311
  let end = length === void 0 ? reader.len : reader.pos + length, message = new $root.pbbp2.Header();
@@ -121451,7 +121451,7 @@ var require_lib2 = __commonJS({
121451
121451
  Frame.encodeDelimited = function encodeDelimited(message, writer) {
121452
121452
  return this.encode(message, writer).ldelim();
121453
121453
  };
121454
- Frame.decode = function decode3(reader, length) {
121454
+ Frame.decode = function decode4(reader, length) {
121455
121455
  if (!(reader instanceof $Reader))
121456
121456
  reader = $Reader.create(reader);
121457
121457
  let end = length === void 0 ? reader.len : reader.pos + length, message = new $root.pbbp2.Frame();
@@ -121678,7 +121678,7 @@ var require_lib2 = __commonJS({
121678
121678
  })();
121679
121679
  return pbbp22;
121680
121680
  })();
121681
- var decode2 = (buffer) => {
121681
+ var decode3 = (buffer) => {
121682
121682
  return pbbp2.Frame.decode(buffer);
121683
121683
  };
121684
121684
  var WSConfig = class {
@@ -122177,7 +122177,7 @@ var require_lib2 = __commonJS({
122177
122177
  const wsInstance = this.wsConfig.getWSInstance();
122178
122178
  wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.on("message", (buffer) => __awaiter(this, void 0, void 0, function* () {
122179
122179
  this.clearLiveness();
122180
- const data = decode2(buffer);
122180
+ const data = decode3(buffer);
122181
122181
  const { method } = data;
122182
122182
  if (method === FrameType.control) {
122183
122183
  yield this.handleControlData(data);
@@ -127416,6 +127416,31 @@ var makeChatQueue = (core) => {
127416
127416
  // src/chatSetup.ts
127417
127417
  import { threadOrNull } from "@ycodium-ai/plugin-api";
127418
127418
 
127419
+ // src/cooldown.ts
127420
+ var makeCooldown = (windowMs, now, maxKeys = Number.POSITIVE_INFINITY) => {
127421
+ const lastAt = /* @__PURE__ */ new Map();
127422
+ return {
127423
+ tryAcquire: (key) => {
127424
+ const current = now();
127425
+ const previous = lastAt.get(key);
127426
+ if (previous !== void 0 && current - previous < windowMs) return false;
127427
+ lastAt.delete(key);
127428
+ lastAt.set(key, current);
127429
+ if (lastAt.size > maxKeys) {
127430
+ for (const [candidate, at] of lastAt) {
127431
+ if (current - at >= windowMs) lastAt.delete(candidate);
127432
+ }
127433
+ while (lastAt.size > maxKeys) {
127434
+ const oldest = lastAt.keys().next().value;
127435
+ if (oldest === void 0) break;
127436
+ lastAt.delete(oldest);
127437
+ }
127438
+ }
127439
+ return true;
127440
+ }
127441
+ };
127442
+ };
127443
+
127419
127444
  // src/projectList.ts
127420
127445
  var PROJECT_LIST_MAX = 20;
127421
127446
  var projectLine = (row, position) => `${String(position)}. ${row.title.replace(/\s+/g, " ").trim()}${row.current ? " \u2605" : ""}`;
@@ -127504,16 +127529,21 @@ var pickSession = (arg, listed, rows) => {
127504
127529
  var makeChatSetup = (core, deps) => {
127505
127530
  const { host, t, adapterFor, commit, deliver, bindingOf: bindingOf2, hasPanel } = core;
127506
127531
  const { projectRowsFor, listedProjectsByChat } = deps.rows;
127507
- const promptProject = async (binding) => {
127508
- const chatKey = chatKeyOf(binding);
127509
- let rows;
127532
+ const pendingMessages = makeBoundedMap(100);
127533
+ const projectPromptCooldown = makeCooldown(3e4, () => Date.now());
127534
+ const loadProjectRows = async (binding) => {
127510
127535
  try {
127511
- rows = await projectRowsFor(chatKey);
127536
+ return await projectRowsFor(chatKeyOf(binding));
127512
127537
  } catch (error) {
127513
127538
  host.log.warn("chat could not list the projects", { error: describeError(error) });
127514
127539
  await deliver(binding, t("panel.loadFailed"));
127515
- return;
127540
+ return null;
127516
127541
  }
127542
+ };
127543
+ const promptProject = async (binding, available) => {
127544
+ const chatKey = chatKeyOf(binding);
127545
+ const rows = available ?? await loadProjectRows(binding);
127546
+ if (rows === null) return;
127517
127547
  listedProjectsByChat.set(chatKey, listedProjectIds(rows));
127518
127548
  if (rows.length > 0 && hasPanel(binding)) {
127519
127549
  try {
@@ -127533,14 +127563,8 @@ var makeChatSetup = (core, deps) => {
127533
127563
  return;
127534
127564
  }
127535
127565
  const chatKey = chatKeyOf(binding);
127536
- let rows;
127537
- try {
127538
- rows = await projectRowsFor(chatKey);
127539
- } catch (error) {
127540
- host.log.warn("chat could not list the projects", { error: describeError(error) });
127541
- await deliver(binding, t("panel.loadFailed"));
127542
- return;
127543
- }
127566
+ const rows = await loadProjectRows(binding);
127567
+ if (rows === null) return;
127544
127568
  const picked = pickProject(arg, listedProjectsByChat.get(chatKey), rows);
127545
127569
  if (picked === null) {
127546
127570
  await deliver(binding, t("repo.notFound", { query: arg }));
@@ -127572,6 +127596,9 @@ var makeChatSetup = (core, deps) => {
127572
127596
  listedProjectsByChat.delete(chatKey);
127573
127597
  host.log.info("chat picked a project", { bot: binding.displayName, projectId: picked });
127574
127598
  await deliver(binding, t("repo.done", { title: target2.title }));
127599
+ const pending = pendingMessages.get(chatKey);
127600
+ pendingMessages.forget((key) => key === chatKey);
127601
+ if (pending !== void 0) await deps.resumeMessage(binding, pending);
127575
127602
  };
127576
127603
  const handleGroup = async (binding, name, senderId) => {
127577
127604
  const adapter = adapterFor(binding.platform);
@@ -127599,7 +127626,27 @@ var makeChatSetup = (core, deps) => {
127599
127626
  await deliver(binding, t("group.created", { name }));
127600
127627
  await promptProject(bindingOf2(binding, chatId));
127601
127628
  };
127602
- return { promptProject, handleRepo, handleGroup };
127629
+ const prepareMessage = async (binding, message) => {
127630
+ const chatKey = chatKeyOf(binding);
127631
+ const asked = message.text.trim();
127632
+ if (/^[1-9]\d{0,2}$/u.test(asked) && listedProjectsByChat.has(chatKey)) {
127633
+ await handleRepo(binding, asked);
127634
+ return false;
127635
+ }
127636
+ const rows = await loadProjectRows(binding);
127637
+ if (rows === null) return false;
127638
+ const only = rows.length === 1 ? rows[0] : void 0;
127639
+ if (only !== void 0) {
127640
+ commit("chat-updated", { chatKey, projectId: only.projectId });
127641
+ pendingMessages.forget((key) => key === chatKey);
127642
+ await deliver(binding, t("repo.autoSelected", { title: only.title }));
127643
+ return true;
127644
+ }
127645
+ pendingMessages.set(chatKey, message);
127646
+ if (projectPromptCooldown.tryAcquire(chatKey)) await promptProject(binding, rows);
127647
+ return false;
127648
+ };
127649
+ return { promptProject, handleRepo, handleGroup, prepareMessage };
127603
127650
  };
127604
127651
 
127605
127652
  // src/commands.ts
@@ -127692,7 +127739,6 @@ var parseChatCommand = (input) => {
127692
127739
  return query === "" ? { kind: "selectTeam" } : { kind: "help" };
127693
127740
  }
127694
127741
  if (isThreadCommand(input.text, input.chinese)) return { kind: "selectThread" };
127695
- if (/^\/[a-z][a-z0-9_-]*/i.test(trimmed)) return { kind: "help" };
127696
127742
  if (input.chinese) {
127697
127743
  const alias = ZH_ALIASES[trimmed];
127698
127744
  if (alias !== void 0) return { kind: alias };
@@ -128396,7 +128442,7 @@ var makeImCore = (input) => {
128396
128442
  init: emptyState,
128397
128443
  apply: foldPluginEvent(input.pluginId)
128398
128444
  });
128399
- const ready = stateHandle.get().then(
128445
+ const chatReady = stateHandle.get().then(
128400
128446
  (restored) => {
128401
128447
  state = restored;
128402
128448
  },
@@ -128404,6 +128450,7 @@ var makeImCore = (input) => {
128404
128450
  host.log.warn("chat could not restore its saved state", { error: describeError(error) });
128405
128451
  }
128406
128452
  );
128453
+ const ready = Promise.all([chatReady, input.inboundReady]).then(() => void 0);
128407
128454
  let persisting = Promise.resolve();
128408
128455
  const commit = (name, data) => {
128409
128456
  state = foldImEvent(state, name, data);
@@ -128935,63 +128982,15 @@ var makeInboundAttachments = (core) => {
128935
128982
 
128936
128983
  // src/inbound.ts
128937
128984
  import { projectOrNull } from "@ycodium-ai/plugin-api";
128938
-
128939
- // src/boundedSeen.ts
128940
- var makeBoundedSeen = (limit) => {
128941
- const seen = /* @__PURE__ */ new Set();
128942
- return {
128943
- has: (key) => seen.has(key),
128944
- /** 记下这个键:第一次见返回 true;已经记着返回 false(不重排,最早记的先被忘)。 */
128945
- claim: (key) => {
128946
- if (seen.has(key)) return false;
128947
- seen.add(key);
128948
- if (seen.size > limit) {
128949
- const oldest = seen.values().next().value;
128950
- if (oldest !== void 0) seen.delete(oldest);
128951
- }
128952
- return true;
128953
- }
128954
- };
128955
- };
128956
-
128957
- // src/cooldown.ts
128958
- var makeCooldown = (windowMs, now, maxKeys = Number.POSITIVE_INFINITY) => {
128959
- const lastAt = /* @__PURE__ */ new Map();
128960
- return {
128961
- tryAcquire: (key) => {
128962
- const current = now();
128963
- const previous = lastAt.get(key);
128964
- if (previous !== void 0 && current - previous < windowMs) return false;
128965
- lastAt.delete(key);
128966
- lastAt.set(key, current);
128967
- if (lastAt.size > maxKeys) {
128968
- for (const [candidate, at] of lastAt) {
128969
- if (current - at >= windowMs) lastAt.delete(candidate);
128970
- }
128971
- while (lastAt.size > maxKeys) {
128972
- const oldest = lastAt.keys().next().value;
128973
- if (oldest === void 0) break;
128974
- lastAt.delete(oldest);
128975
- }
128976
- }
128977
- return true;
128978
- }
128979
- };
128980
- };
128981
-
128982
- // src/inbound.ts
128983
- var SEEN_MESSAGE_LIMIT = 256;
128984
128985
  var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
128985
128986
  var UNSUPPORTED_REPLY_COOLDOWN_MS = 6e4;
128986
128987
  var NOT_ALLOWED_REPLY_COOLDOWN_MS = 10 * 6e4;
128987
128988
  var NOT_ALLOWED_REPLY_KEY_LIMIT = 1e3;
128988
- var PROJECT_PROMPT_COOLDOWN_MS = 3e4;
128989
128989
  var makeInbound = (core, deps) => {
128990
128990
  const { host, t, adapterFor, commit, deliver, bindingOf: bindingOf2, bots } = core;
128991
128991
  const { enqueueForChat } = deps;
128992
128992
  const { handleCommand, targetOf } = deps.commands;
128993
- const { promptProject, handleRepo } = deps.chatSetup;
128994
- const { listedProjectsByChat } = deps.rows;
128993
+ const { prepareMessage } = deps.chatSetup;
128995
128994
  const { questionFlow, handleButtonClick } = deps.interactions;
128996
128995
  const { answerByToken } = deps.approvals;
128997
128996
  const { handlePanelClick } = deps.panelWiring;
@@ -129005,10 +129004,9 @@ var makeInbound = (core, deps) => {
129005
129004
  () => Date.now(),
129006
129005
  NOT_ALLOWED_REPLY_KEY_LIMIT
129007
129006
  );
129008
- const projectPromptCooldown = makeCooldown(PROJECT_PROMPT_COOLDOWN_MS, () => Date.now());
129009
- const handledMessages = makeBoundedSeen(SEEN_MESSAGE_LIMIT);
129010
- const alreadyHandled = (key) => !handledMessages.claim(key);
129011
- const claimCallback = (rowId, callbackId) => callbackId === void 0 || callbackId === "" || !alreadyHandled(`c:${rowId}:${callbackId}`);
129007
+ const { inboundState } = deps;
129008
+ const alreadyHandled = (rowId, key) => !inboundState.claim(rowId, key);
129009
+ const claimCallback = (rowId, callbackId) => callbackId === void 0 || callbackId === "" || !alreadyHandled(rowId, `c:${callbackId}`);
129012
129010
  const turnAwayStranger = (target2, sender) => {
129013
129011
  host.log.warn("chat ignored a message from someone not on the allow list", {
129014
129012
  bot: target2.displayName,
@@ -129048,6 +129046,61 @@ var makeInbound = (core, deps) => {
129048
129046
  const threadId = await startTurn(speaker, message);
129049
129047
  if (threadId !== null) await markWorking(speaker, threadId, message.messageId);
129050
129048
  };
129049
+ const resumeMessage = async (current, message) => {
129050
+ inheritFromParent(current, message.chatId, message.parentChatId);
129051
+ let target2 = {
129052
+ ...bindingOf2(current, message.chatId),
129053
+ reply: anchorOfMessage(adapterFor(current.platform), message)
129054
+ };
129055
+ const chatKey = chatKeyOf(target2);
129056
+ if (await handleCommand(target2, message.text, message.senderId)) return;
129057
+ if (target2.projectId === "") {
129058
+ if (!await prepareMessage(target2, message)) return;
129059
+ target2 = { ...bindingOf2(current, message.chatId), reply: target2.reply };
129060
+ }
129061
+ const boundThread = chatOf(core.state, chatKey).threadId;
129062
+ if (boundThread !== null && message.text !== "" && await questionFlow.answerByText(chatKey, boundThread, message.text)) {
129063
+ return;
129064
+ }
129065
+ const project = await projectOrNull(host.projects, target2.projectId);
129066
+ if (project === null || project.archivedAt != null) {
129067
+ if (project === null) {
129068
+ host.log.warn("chat cannot start work: the chat's project is gone", {
129069
+ bot: target2.displayName,
129070
+ projectId: target2.projectId
129071
+ });
129072
+ }
129073
+ if (archivedReplyCooldown.tryAcquire(chatKey)) {
129074
+ await deliver(
129075
+ target2,
129076
+ project === null ? t("reply.projectMissing") : t("reply.projectArchived", { title: project.title })
129077
+ );
129078
+ }
129079
+ return;
129080
+ }
129081
+ const routed = targetOf(target2);
129082
+ const attachmentsOnly = message.text.trim() === "" && message.resources.length > 0;
129083
+ if (routed.kind === "team") {
129084
+ if (message.resources.length > 0) await deliver(target2, t("reply.attachmentsNoTeam"));
129085
+ if (attachmentsOnly) return;
129086
+ const leadThreadId = await startTeamGoal(target2, message);
129087
+ if (leadThreadId !== null) await markWorking(target2, leadThreadId, message.messageId);
129088
+ return;
129089
+ }
129090
+ if (attachmentsOnly) {
129091
+ await receiveAttachmentsOnly(target2, message);
129092
+ return;
129093
+ }
129094
+ const branchChatId = hasWaitingAttachments(chatKey) ? void 0 : message.branchChatId;
129095
+ if (branchChatId === void 0) {
129096
+ await openTurn(target2, message);
129097
+ return;
129098
+ }
129099
+ enqueueForChat(chatKeyOf({ rowId: current.rowId, chatId: branchChatId }), async () => {
129100
+ inheritFromParent(current, branchChatId, message.chatId);
129101
+ await openTurn({ ...bindingOf2(current, branchChatId), reply: target2.reply }, message);
129102
+ });
129103
+ };
129051
129104
  const inboundHandlersFor = (bot) => ({
129052
129105
  onError: (message) => host.log.warn("chat inbound connection failed", {
129053
129106
  platform: bot.platform,
@@ -129070,7 +129123,7 @@ var makeInbound = (core, deps) => {
129070
129123
  if (current === void 0) return;
129071
129124
  const message = settleChat(current, received);
129072
129125
  const arrived = bindingOf2(current, message.chatId);
129073
- if (alreadyHandled(`m:${chatKeyOf(arrived)}:${message.messageId}`)) return;
129126
+ if (alreadyHandled(current.rowId, `m:${message.chatId}:${message.messageId}`)) return;
129074
129127
  if (message.chatKind !== void 0 && chatOf(core.state, chatKeyOf(arrived)).chatKind !== message.chatKind) {
129075
129128
  commit("chat-updated", { chatKey: chatKeyOf(arrived), chatKind: message.chatKind });
129076
129129
  }
@@ -129097,69 +129150,129 @@ var makeInbound = (core, deps) => {
129097
129150
  });
129098
129151
  return;
129099
129152
  }
129100
- enqueueForChat(chatKeyOf(arrived), async () => {
129101
- inheritFromParent(current, message.chatId, message.parentChatId);
129102
- const target2 = {
129103
- ...bindingOf2(current, message.chatId),
129104
- reply: anchorOfMessage(adapterFor(current.platform), message)
129105
- };
129106
- const chatKey = chatKeyOf(target2);
129107
- if (await handleCommand(target2, message.text, message.senderId)) return;
129108
- if (target2.projectId === "") {
129109
- const asked = message.text.trim();
129110
- if (/^[1-9]\d{0,2}$/u.test(asked) && listedProjectsByChat.has(chatKey)) {
129111
- await handleRepo(target2, asked);
129112
- } else if (projectPromptCooldown.tryAcquire(chatKey)) {
129113
- await promptProject(target2);
129114
- }
129115
- return;
129116
- }
129117
- const boundThread = chatOf(core.state, chatKey).threadId;
129118
- if (boundThread !== null && message.text !== "" && await questionFlow.answerByText(chatKey, boundThread, message.text)) {
129119
- return;
129153
+ enqueueForChat(chatKeyOf(arrived), () => resumeMessage(current, message));
129154
+ }
129155
+ });
129156
+ return { inboundHandlersFor, resumeMessage };
129157
+ };
129158
+
129159
+ // src/inboundStore.ts
129160
+ import * as NodeFSP from "node:fs/promises";
129161
+ import * as NodePath from "node:path";
129162
+ var SEEN_LIMIT = 1e3;
129163
+ var fileIo = {
129164
+ readFile: (path) => NodeFSP.readFile(path, "utf8"),
129165
+ mkdir: async (path) => {
129166
+ await NodeFSP.mkdir(path, { recursive: true });
129167
+ },
129168
+ writeFile: (path, contents) => NodeFSP.writeFile(path, contents, "utf8"),
129169
+ rename: (from, to) => NodeFSP.rename(from, to)
129170
+ };
129171
+ var isRecord2 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
129172
+ var decode = (contents) => {
129173
+ const value2 = JSON.parse(contents);
129174
+ if (!isRecord2(value2) || value2["version"] !== 1 || !isRecord2(value2["bots"]))
129175
+ throw new Error("invalid inbound store");
129176
+ const bots = /* @__PURE__ */ new Map();
129177
+ for (const [rowId, row] of Object.entries(value2["bots"])) {
129178
+ if (!isRecord2(row) || !Array.isArray(row["seenIds"]) || !row["seenIds"].every((id) => typeof id === "string"))
129179
+ throw new Error("invalid inbound bot row");
129180
+ const telegram = row["telegram"];
129181
+ if (telegram !== void 0 && (!isRecord2(telegram) || typeof telegram["tokenHash"] !== "string" || telegram["tokenHash"] === "" || typeof telegram["offset"] !== "number" || !Number.isSafeInteger(telegram["offset"]) || telegram["offset"] < 0))
129182
+ throw new Error("invalid telegram cursor");
129183
+ bots.set(rowId, {
129184
+ seenIds: row["seenIds"].slice(-SEEN_LIMIT),
129185
+ ...telegram === void 0 ? {} : {
129186
+ telegram: {
129187
+ tokenHash: telegram["tokenHash"],
129188
+ offset: telegram["offset"]
129120
129189
  }
129121
- const project = await projectOrNull(host.projects, target2.projectId);
129122
- if (project === null || project.archivedAt != null) {
129123
- if (project === null) {
129124
- host.log.warn("chat cannot start work: the chat's project is gone", {
129125
- bot: target2.displayName,
129126
- projectId: target2.projectId
129127
- });
129128
- }
129129
- if (archivedReplyCooldown.tryAcquire(chatKey)) {
129130
- await deliver(
129131
- target2,
129132
- project === null ? t("reply.projectMissing") : t("reply.projectArchived", { title: project.title })
129133
- );
129190
+ }
129191
+ });
129192
+ }
129193
+ return bots;
129194
+ };
129195
+ var makeInboundStore = (input) => {
129196
+ const io = input.io ?? fileIo;
129197
+ const path = NodePath.join(input.dataDir, "inbound.json");
129198
+ const temporary = `${path}.tmp`;
129199
+ let bots = /* @__PURE__ */ new Map();
129200
+ let dirty = false;
129201
+ let writing;
129202
+ const warn = (message, error) => input.log.warn(message, { error: describeError(error) });
129203
+ const ready = io.readFile(path).then(
129204
+ (contents) => {
129205
+ try {
129206
+ bots = decode(contents);
129207
+ } catch (error) {
129208
+ warn("chat could not restore inbound state", error);
129209
+ }
129210
+ },
129211
+ (error) => {
129212
+ if (!isRecord2(error) || error["code"] !== "ENOENT")
129213
+ warn("chat could not restore inbound state", error);
129214
+ }
129215
+ );
129216
+ const write = () => {
129217
+ writing ??= Promise.resolve().then(async () => {
129218
+ try {
129219
+ while (dirty) {
129220
+ dirty = false;
129221
+ const contents = JSON.stringify({ version: 1, bots: Object.fromEntries(bots) });
129222
+ try {
129223
+ await io.mkdir(input.dataDir);
129224
+ await io.writeFile(temporary, contents);
129225
+ await io.rename(temporary, path);
129226
+ } catch (error) {
129227
+ dirty = true;
129228
+ warn("chat could not save inbound state", error);
129229
+ break;
129134
129230
  }
129135
- return;
129136
- }
129137
- const routed = targetOf(target2);
129138
- const attachmentsOnly = message.text.trim() === "" && message.resources.length > 0;
129139
- if (routed.kind === "team") {
129140
- if (message.resources.length > 0) await deliver(target2, t("reply.attachmentsNoTeam"));
129141
- if (attachmentsOnly) return;
129142
- const leadThreadId = await startTeamGoal(target2, message);
129143
- if (leadThreadId !== null) await markWorking(target2, leadThreadId, message.messageId);
129144
- return;
129145
129231
  }
129146
- if (attachmentsOnly) {
129147
- await receiveAttachmentsOnly(target2, message);
129148
- return;
129149
- }
129150
- const branchChatId = hasWaitingAttachments(chatKey) ? void 0 : message.branchChatId;
129151
- if (branchChatId === void 0) {
129152
- await openTurn(target2, message);
129153
- return;
129154
- }
129155
- enqueueForChat(chatKeyOf({ rowId: current.rowId, chatId: branchChatId }), async () => {
129156
- inheritFromParent(current, branchChatId, message.chatId);
129157
- await openTurn({ ...bindingOf2(current, branchChatId), reply: target2.reply }, message);
129158
- });
129159
- });
129232
+ } finally {
129233
+ writing = void 0;
129234
+ }
129235
+ });
129236
+ return writing;
129237
+ };
129238
+ const changed = () => {
129239
+ dirty = true;
129240
+ void write();
129241
+ };
129242
+ const rowOf = (rowId) => {
129243
+ let row = bots.get(rowId);
129244
+ if (row === void 0) {
129245
+ row = { seenIds: [] };
129246
+ bots.set(rowId, row);
129160
129247
  }
129161
- });
129162
- return { inboundHandlersFor };
129248
+ return row;
129249
+ };
129250
+ return {
129251
+ ready,
129252
+ claim: (rowId, id) => {
129253
+ const row = rowOf(rowId);
129254
+ if (row.seenIds.includes(id)) return false;
129255
+ row.seenIds.push(id);
129256
+ if (row.seenIds.length > SEEN_LIMIT) row.seenIds.shift();
129257
+ changed();
129258
+ return true;
129259
+ },
129260
+ readCursor: (rowId, tokenHash) => {
129261
+ const cursor = bots.get(rowId)?.telegram;
129262
+ return cursor?.tokenHash === tokenHash ? cursor.offset : void 0;
129263
+ },
129264
+ saveCursor: (rowId, tokenHash, offset) => {
129265
+ const row = rowOf(rowId);
129266
+ if (row.telegram?.tokenHash === tokenHash && row.telegram.offset === offset) return;
129267
+ if (row.telegram?.tokenHash !== tokenHash) row.seenIds = [];
129268
+ row.telegram = { tokenHash, offset };
129269
+ changed();
129270
+ },
129271
+ flush: async () => {
129272
+ await ready;
129273
+ if (dirty || writing) await write();
129274
+ }
129275
+ };
129163
129276
  };
129164
129277
 
129165
129278
  // src/interactions.ts
@@ -132203,9 +132316,9 @@ function getDecodeCache(exclude) {
132203
132316
  }
132204
132317
  return cache;
132205
132318
  }
132206
- function decode(string, exclude) {
132319
+ function decode2(string, exclude) {
132207
132320
  if (typeof exclude !== "string") {
132208
- exclude = decode.defaultChars;
132321
+ exclude = decode2.defaultChars;
132209
132322
  }
132210
132323
  const cache = getDecodeCache(exclude);
132211
132324
  return string.replace(/(%[a-f0-9]{2})+/gi, function(seq) {
@@ -132264,9 +132377,9 @@ function decode(string, exclude) {
132264
132377
  return result;
132265
132378
  });
132266
132379
  }
132267
- decode.defaultChars = ";/?:@&=+$,#";
132268
- decode.componentChars = "";
132269
- var decode_default = decode;
132380
+ decode2.defaultChars = ";/?:@&=+$,#";
132381
+ decode2.componentChars = "";
132382
+ var decode_default = decode2;
132270
132383
 
132271
132384
  // ../../node_modules/.pnpm/mdurl@2.1.0/node_modules/mdurl/lib/encode.mjs
132272
132385
  var encodeCache = {};
@@ -133287,12 +133400,12 @@ var LinkifyIt = class {
133287
133400
  *
133288
133401
  * See [twitter mentions example](https://github.com/markdown-it/linkify-it/blob/master/examples/twitter.mjs).
133289
133402
  */
133290
- add(schema, definition = null) {
133291
- if (!definition) delete this.__schemas__[schema];
133403
+ add(schema, definition2 = null) {
133404
+ if (!definition2) delete this.__schemas__[schema];
133292
133405
  else {
133293
133406
  const def = {
133294
133407
  normalize: (match, self2) => self2.normalize(match),
133295
- ...definition
133408
+ ...definition2
133296
133409
  };
133297
133410
  this.__schemas__[schema] = def;
133298
133411
  }
@@ -137801,6 +137914,24 @@ var startLarkInbound = (input) => {
137801
137914
  };
137802
137915
  };
137803
137916
 
137917
+ // src/boundedSeen.ts
137918
+ var makeBoundedSeen = (limit) => {
137919
+ const seen = /* @__PURE__ */ new Set();
137920
+ return {
137921
+ has: (key) => seen.has(key),
137922
+ /** 记下这个键:第一次见返回 true;已经记着返回 false(不重排,最早记的先被忘)。 */
137923
+ claim: (key) => {
137924
+ if (seen.has(key)) return false;
137925
+ seen.add(key);
137926
+ if (seen.size > limit) {
137927
+ const oldest = seen.values().next().value;
137928
+ if (oldest !== void 0) seen.delete(oldest);
137929
+ }
137930
+ return true;
137931
+ }
137932
+ };
137933
+ };
137934
+
137804
137935
  // src/deadline.ts
137805
137936
  var withDeadline2 = async (timeoutMs, load) => {
137806
137937
  const signal = AbortSignal.timeout(timeoutMs);
@@ -138928,6 +139059,7 @@ var en_default = {
138928
139059
  }
138929
139060
  },
138930
139061
  repo: {
139062
+ autoSelected: "Using project {{title}}.",
138931
139063
  needProject: "This chat has no project yet, so I won't start any work. Pick one and I will.",
138932
139064
  currentLine: 'This chat works on project "{{title}}".',
138933
139065
  noProjects: "There are no projects on this machine yet. Add one in Ycodium first.",
@@ -139630,6 +139762,7 @@ var zh_CN_default = {
139630
139762
  }
139631
139763
  },
139632
139764
  repo: {
139765
+ autoSelected: "\u4F7F\u7528\u9879\u76EE\u300C{{title}}\u300D\u3002",
139633
139766
  needProject: "\u8FD9\u4E2A\u804A\u5929\u8FD8\u6CA1\u9009\u9879\u76EE\uFF0C\u9009\u5B9A\u4E4B\u524D\u6211\u4E0D\u5F00\u5DE5\u3002\u9009\u4E00\u4E2A\u5C31\u5F00\u59CB\u3002",
139634
139767
  currentLine: "\u8FD9\u4E2A\u804A\u5929\u73B0\u5728\u5728\u9879\u76EE\u300C{{title}}\u300D\u4E0A\u3002",
139635
139768
  noProjects: "\u8FD9\u53F0\u673A\u5668\u4E0A\u8FD8\u6CA1\u6709\u9879\u76EE\uFF0C\u5148\u5230 Ycodium \u91CC\u6DFB\u52A0\u4E00\u4E2A\u3002",
@@ -140069,7 +140202,10 @@ var makeNotifications = (core, deps) => {
140069
140202
  const said = threadsWithSomethingToReport.delete(threadId);
140070
140203
  await ready;
140071
140204
  const targets = targetsForProject(threadId, await projectOfThread(threadId));
140072
- const { debt, handled, execution } = await conclusions.forTurn(threadId);
140205
+ const owes = Object.values(conclusionDebtsOf(core.state)).some(
140206
+ (debt2) => debt2.threadId === threadId
140207
+ );
140208
+ const { debt, handled, execution } = said || owes || targets.length > 0 ? await conclusions.forTurn(threadId) : { debt: void 0, handled: false, execution: null };
140073
140209
  if (debt !== void 0) {
140074
140210
  const binding = bindingForChatKey(debt.chatKey);
140075
140211
  if (binding !== void 0 && !streamsTo(binding, threadId) && execution?.session?.activeTurnId == null && execution?.latestTurn?.state !== "running" && execution?.latestTurn?.state !== "error" && payload.session?.status !== "error") {
@@ -141473,7 +141609,7 @@ var makeSessions = (core) => {
141473
141609
 
141474
141610
  // src/teamGoals.ts
141475
141611
  import * as NodeCrypto4 from "node:crypto";
141476
- var isRecord2 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
141612
+ var isRecord3 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
141477
141613
  var teamGoalRequestId = (binding, messageId) => {
141478
141614
  const identity = JSON.stringify([
141479
141615
  "im",
@@ -141515,7 +141651,7 @@ var makeTeamGoals = (core) => {
141515
141651
  requestId
141516
141652
  }
141517
141653
  });
141518
- if (isRecord2(result) && result._tag === "TeamGoalRejection") {
141654
+ if (isRecord3(result) && result._tag === "TeamGoalRejection") {
141519
141655
  await deliver(
141520
141656
  binding,
141521
141657
  t(
@@ -141524,7 +141660,7 @@ var makeTeamGoals = (core) => {
141524
141660
  );
141525
141661
  return null;
141526
141662
  }
141527
- if (!isRecord2(result) || typeof result.leadThreadId !== "string" || result.leadThreadId === "" || result.leadName !== null && typeof result.leadName !== "string") {
141663
+ if (!isRecord3(result) || typeof result.leadThreadId !== "string" || result.leadThreadId === "" || result.leadName !== null && typeof result.leadName !== "string") {
141528
141664
  throw new Error("team startGoal returned an invalid result");
141529
141665
  }
141530
141666
  topicRoot?.confirmConsumed();
@@ -141547,6 +141683,9 @@ var makeTeamGoals = (core) => {
141547
141683
  return { hasRosterFor, startTeamGoal };
141548
141684
  };
141549
141685
 
141686
+ // src/telegramAdapter.ts
141687
+ import * as NodeCrypto5 from "node:crypto";
141688
+
141550
141689
  // src/telegramApi.ts
141551
141690
  var API_BASE = "https://api.telegram.org";
141552
141691
  var POLL_TIMEOUT_SECONDS = 45;
@@ -141571,6 +141710,10 @@ var target = (chatId) => {
141571
141710
  const { chat, thread } = splitChatId(chatId);
141572
141711
  return { chat_id: chat, ...thread === null ? {} : { message_thread_id: thread } };
141573
141712
  };
141713
+ var replyParameters = (messageId) => {
141714
+ const id = Number(messageId);
141715
+ return messageId === void 0 || !Number.isSafeInteger(id) ? {} : { reply_parameters: { message_id: id, allow_sending_without_reply: true } };
141716
+ };
141574
141717
  var chatOnly = (chatId) => ({ chat_id: splitChatId(chatId).chat });
141575
141718
  var TelegramApiError = class extends Error {
141576
141719
  code;
@@ -141702,13 +141845,15 @@ var makeTelegramApi = (input) => {
141702
141845
  }
141703
141846
  return envelope.result;
141704
141847
  };
141705
- const upload = async (method, field, chatId, file) => {
141848
+ const upload = async (method, field, chatId, file, replyMessageId) => {
141706
141849
  const form = new FormData();
141707
141850
  const where = target(chatId);
141708
141851
  form.set("chat_id", where.chat_id);
141709
141852
  if (where.message_thread_id !== void 0) {
141710
141853
  form.set("message_thread_id", String(where.message_thread_id));
141711
141854
  }
141855
+ const reply = replyParameters(replyMessageId).reply_parameters;
141856
+ if (reply !== void 0) form.set("reply_parameters", JSON.stringify(reply));
141712
141857
  form.set(field, new Blob([new Uint8Array(file.bytes)], { type: file.mimeType }), file.name);
141713
141858
  const response = await send(
141714
141859
  method,
@@ -141731,9 +141876,10 @@ var makeTelegramApi = (input) => {
141731
141876
  (row) => row.map((button) => ({ text: button.text, callback_data: button.data }))
141732
141877
  )
141733
141878
  });
141734
- const sendSingle = async (method, chatId, body, rows) => {
141879
+ const sendSingle = async (method, chatId, body, rows, replyMessageId) => {
141735
141880
  const sent = await call(method, {
141736
141881
  ...target(chatId),
141882
+ ...replyParameters(replyMessageId),
141737
141883
  ...body,
141738
141884
  ...rows === void 0 ? {} : { reply_markup: keyboard(rows) }
141739
141885
  });
@@ -141793,10 +141939,11 @@ var makeTelegramApi = (input) => {
141793
141939
  },
141794
141940
  POLL_CALL_TIMEOUT_MS
141795
141941
  ),
141796
- sendMessage: async (chatId, text4, retry) => {
141797
- for (const chunk of splitForTelegram(text4)) {
141942
+ sendMessage: async (chatId, text4, retry, replyMessageId) => {
141943
+ for (const [index, chunk] of splitForTelegram(text4).entries()) {
141944
+ const reply = index === 0 ? replyMessageId : void 0;
141798
141945
  await sendWithRetry(
141799
- () => call("sendMessage", { ...target(chatId), text: chunk }),
141946
+ () => sendSingle("sendMessage", chatId, { text: chunk }, void 0, reply),
141800
141947
  telegramSendVerdict,
141801
141948
  retry
141802
141949
  );
@@ -141806,12 +141953,12 @@ var makeTelegramApi = (input) => {
141806
141953
  * 发一条消息,回它的 id。调用方保证 `text` 不超过单条上限(边写边更新的回复要自己
141807
141954
  * 在上限处翻篇,这里不替它切)。带 `rows` 就挂一块内联键盘。
141808
141955
  */
141809
- sendOne: (chatId, text4, rows) => sendSingle("sendMessage", chatId, { text: text4 }, rows),
141956
+ sendOne: (chatId, text4, rows, replyMessageId) => sendSingle("sendMessage", chatId, { text: text4 }, rows, replyMessageId),
141810
141957
  /**
141811
141958
  * 发一条富文本,回它的 id。`markdown` 已是平台要的样子。带 `rows` 就挂一块内联键盘。
141812
141959
  * 调用方保证不超富文本单条上限(这里不替它切)。
141813
141960
  */
141814
- sendRichOne: (chatId, markdown, rows) => sendSingle("sendRichMessage", chatId, { rich_message: { markdown } }, rows),
141961
+ sendRichOne: (chatId, markdown, rows, replyMessageId) => sendSingle("sendRichMessage", chatId, { rich_message: { markdown } }, rows, replyMessageId),
141815
141962
  /**
141816
141963
  * 改一条已发出的消息。内容没变(`message is not modified`)当成功。`rows` 不给就是
141817
141964
  * 去掉按钮:改了文字又要保留按钮的调用方得自己再传一次。
@@ -141823,20 +141970,21 @@ var makeTelegramApi = (input) => {
141823
141970
  */
141824
141971
  editRich: (chatId, messageId, markdown, rows = []) => editSingle(chatId, messageId, { rich_message: { markdown } }, rows),
141825
141972
  /** 带按钮的审批消息,一行一个按钮;回消息 id,之后才改得了它(平台没给就是 null)。 */
141826
- sendButtons: (chatId, text4, buttons) => sendSingle(
141973
+ sendButtons: (chatId, text4, buttons, replyMessageId) => sendSingle(
141827
141974
  "sendMessage",
141828
141975
  chatId,
141829
141976
  { text: text4 },
141830
- buttons.map((button) => [{ text: button.label, data: button.token }])
141977
+ buttons.map((button) => [{ text: button.label, data: button.token }]),
141978
+ replyMessageId
141831
141979
  ),
141832
141980
  /** 答完点击,不然客户端上那个按钮一直转圈。带 `text` 时点击者会看到一句提示。 */
141833
141981
  answerCallback: (callbackQueryId, text4) => call("answerCallbackQuery", {
141834
141982
  callback_query_id: callbackQueryId,
141835
141983
  ...text4 === void 0 ? {} : { text: text4 }
141836
141984
  }),
141837
- sendPhoto: (chatId, photo) => upload("sendPhoto", "photo", chatId, photo),
141985
+ sendPhoto: (chatId, photo, replyMessageId) => upload("sendPhoto", "photo", chatId, photo, replyMessageId),
141838
141986
  /** 以文件形式发(不压缩、不限尺寸比例):什么类型都行,音频与视频也走这条。 */
141839
- sendDocument: (chatId, file) => upload("sendDocument", "document", chatId, file),
141987
+ sendDocument: (chatId, file, replyMessageId) => upload("sendDocument", "document", chatId, file, replyMessageId),
141840
141988
  /** 取一个文件的字节。要两步:先换路径,再按路径下载。 */
141841
141989
  fetchFile: async (fileId, maxBytes) => {
141842
141990
  const file = await call("getFile", {
@@ -142048,7 +142196,7 @@ var makeTurnWriter = (deps) => {
142048
142196
  const put = async (text4) => {
142049
142197
  const bounded = text4.length <= deps.limit ? text4 : text4.slice(0, deps.limit);
142050
142198
  if (live === null) {
142051
- const id = await deps.messages.send(bounded);
142199
+ const id = await deps.messages.send(bounded, first === null);
142052
142200
  if (id === null) throw new Error("telegram sent a message without an id");
142053
142201
  live = id;
142054
142202
  first ??= id;
@@ -142188,19 +142336,18 @@ var makeTelegramAdapter = (deps) => {
142188
142336
  limit: TELEGRAM_TEXT_MAX_CHARS,
142189
142337
  ...resume === void 0 ? {} : { resume },
142190
142338
  messages: {
142191
- send: (text4) => withRichFallback(
142192
- toTelegramRichMarkdown(text4),
142193
- async (markdown) => (await apiFor(slot.binding)).sendRichOne(chatId, markdown),
142194
- async () => (await apiFor(slot.binding)).sendOne(chatId, text4)
142195
- ),
142339
+ send: (text4, first) => {
142340
+ const reply = first ? slot.binding.reply?.messageId : void 0;
142341
+ return withRichFallback(
142342
+ toTelegramRichMarkdown(text4),
142343
+ async (markdown) => (await apiFor(slot.binding)).sendRichOne(chatId, markdown, void 0, reply),
142344
+ async () => (await apiFor(slot.binding)).sendOne(chatId, text4, void 0, reply)
142345
+ );
142346
+ },
142196
142347
  edit: (messageId, text4) => withRichFallback(
142197
142348
  toTelegramRichMarkdown(text4),
142198
- async (markdown) => {
142199
- await (await apiFor(slot.binding)).editRich(chatId, messageId, markdown);
142200
- },
142201
- async () => {
142202
- await (await apiFor(slot.binding)).editMessage(chatId, messageId, text4);
142203
- }
142349
+ async (markdown) => (await apiFor(slot.binding)).editRich(chatId, messageId, markdown),
142350
+ async () => (await apiFor(slot.binding)).editMessage(chatId, messageId, text4)
142204
142351
  )
142205
142352
  }
142206
142353
  });
@@ -142208,6 +142355,7 @@ var makeTelegramAdapter = (deps) => {
142208
142355
  platform: "telegram",
142209
142356
  icon: "telegram",
142210
142357
  label: "Telegram",
142358
+ quotesReplies: true,
142211
142359
  columns: COLUMNS2,
142212
142360
  validate: telegramConfigIssues,
142213
142361
  isConfigured: (binding) => telegramConfigIssues(binding).length === 0,
@@ -142216,7 +142364,12 @@ var makeTelegramAdapter = (deps) => {
142216
142364
  };
142217
142365
  const stopped = new Promise((resolve) => stop = resolve);
142218
142366
  let running = true;
142219
- let offset = 0;
142367
+ const tokenHash = NodeCrypto5.createHash("sha256").update(tokenOf(binding)).digest("hex");
142368
+ let offset = deps.readCursor(binding.rowId, tokenHash);
142369
+ const advanceOffset = (update) => {
142370
+ offset = nextOffset([update], offset ?? 0);
142371
+ deps.saveCursor(binding.rowId, tokenHash, offset);
142372
+ };
142220
142373
  let acknowledged = 0;
142221
142374
  let backoff = BACKOFF_START_MS;
142222
142375
  let botUsername;
@@ -142229,7 +142382,7 @@ var makeTelegramAdapter = (deps) => {
142229
142382
  });
142230
142383
  const publishAck = () => {
142231
142384
  const api = liveApi;
142232
- if (api === void 0 || offset <= acknowledged) return;
142385
+ if (api === void 0 || offset === void 0 || offset <= acknowledged) return;
142233
142386
  const next = offset;
142234
142387
  acknowledged = next;
142235
142388
  void api.getUpdates(next, 0).catch(() => void 0);
@@ -142258,6 +142411,12 @@ var makeTelegramAdapter = (deps) => {
142258
142411
  });
142259
142412
  });
142260
142413
  }
142414
+ if (offset === void 0) {
142415
+ const backlog = await api.getUpdates(-1, 0);
142416
+ if (!running) return;
142417
+ offset = nextOffset(backlog, 0);
142418
+ deps.saveCursor(binding.rowId, tokenHash, offset);
142419
+ }
142261
142420
  acknowledged = offset;
142262
142421
  const updates = await api.getUpdates(offset);
142263
142422
  if (!running) return;
@@ -142270,13 +142429,13 @@ var makeTelegramAdapter = (deps) => {
142270
142429
  if (callback?.data !== void 0) {
142271
142430
  const operatorId = callback.from === void 0 ? null : String(callback.from.id);
142272
142431
  const notice = callback.data.startsWith(PANEL_DATA_PREFIX) ? panelClick(binding.rowId, callback, operatorId, handlers) : handlers.onButton(callback.data, operatorId, callback.id);
142273
- offset = nextOffset([update], offset);
142432
+ advanceOffset(update);
142274
142433
  await api.answerCallback(callback.id, notice ?? void 0).catch(() => void 0);
142275
142434
  continue;
142276
142435
  }
142277
142436
  const message = update.message;
142278
142437
  if (message === void 0) {
142279
- offset = nextOffset([update], offset);
142438
+ advanceOffset(update);
142280
142439
  continue;
142281
142440
  }
142282
142441
  const resource = resourceOf2(message);
@@ -142285,7 +142444,7 @@ var makeTelegramAdapter = (deps) => {
142285
142444
  botUsername
142286
142445
  );
142287
142446
  if (text4 === null) {
142288
- offset = nextOffset([update], offset);
142447
+ advanceOffset(update);
142289
142448
  continue;
142290
142449
  }
142291
142450
  const gated = !passesGroupGate2({
@@ -142299,12 +142458,12 @@ var makeTelegramAdapter = (deps) => {
142299
142458
  const solo = binding.groupMention === "always" && await isSoloGroup(String(message.chat.id));
142300
142459
  if (!running) break;
142301
142460
  if (!solo) {
142302
- offset = nextOffset([update], offset);
142461
+ advanceOffset(update);
142303
142462
  continue;
142304
142463
  }
142305
142464
  }
142306
142465
  if (!running) break;
142307
- offset = nextOffset([update], offset);
142466
+ advanceOffset(update);
142308
142467
  const senderId = message.from === void 0 ? null : String(message.from.id);
142309
142468
  const senderName = telegramDisplayName(message.from);
142310
142469
  const chatKind = message.chat.type === "private" ? "private" : "group";
@@ -142367,7 +142526,7 @@ var makeTelegramAdapter = (deps) => {
142367
142526
  },
142368
142527
  sendText: async (binding, text4, options) => {
142369
142528
  const api = await apiFor(binding);
142370
- await api.sendMessage(binding.chatId, text4, options?.retry);
142529
+ await api.sendMessage(binding.chatId, text4, options?.retry, binding.reply?.messageId);
142371
142530
  },
142372
142531
  checkConnection: async (bot) => {
142373
142532
  const token = tokenOf(bot);
@@ -142436,25 +142595,30 @@ var makeTelegramAdapter = (deps) => {
142436
142595
  sendImage: async (binding, image2) => {
142437
142596
  const api = await apiFor(binding);
142438
142597
  try {
142439
- await api.sendPhoto(binding.chatId, image2);
142598
+ await api.sendPhoto(binding.chatId, image2, binding.reply?.messageId);
142440
142599
  } catch (error) {
142441
142600
  if (!(error instanceof TelegramApiError && error.code === 400)) throw error;
142442
142601
  deps.log.info("telegram could not send the picture as a photo, sending it as a file", {
142443
142602
  name: image2.name,
142444
142603
  reason: error.message
142445
142604
  });
142446
- await api.sendDocument(binding.chatId, image2);
142605
+ await api.sendDocument(binding.chatId, image2, binding.reply?.messageId);
142447
142606
  }
142448
142607
  },
142449
142608
  sendFile: async (binding, file) => {
142450
142609
  const api = await apiFor(binding);
142451
- await api.sendDocument(binding.chatId, file);
142610
+ await api.sendDocument(binding.chatId, file, binding.reply?.messageId);
142452
142611
  },
142453
142612
  // 单张照片 10 MB、单个文件 50 MB。这两个数来自 Bot API 文档的记忆,本仓库里没有核对过。
142454
142613
  outboundLimits: { imageBytes: 10 * 1024 * 1024, fileBytes: 50 * 1024 * 1024 },
142455
142614
  askApproval: async (binding, request) => {
142456
142615
  const api = await apiFor(binding);
142457
- return api.sendButtons(binding.chatId, request.body, request.options);
142616
+ return api.sendButtons(
142617
+ binding.chatId,
142618
+ request.body,
142619
+ request.options,
142620
+ binding.reply?.messageId
142621
+ );
142458
142622
  },
142459
142623
  settleApproval: async (binding, cardId, settlement) => {
142460
142624
  const api = await apiFor(binding);
@@ -142506,7 +142670,7 @@ ${line}`
142506
142670
  const api = await apiFor(binding);
142507
142671
  const rendered = renderPanel(card, 1);
142508
142672
  const cardId = await withFloodWait(
142509
- () => api.sendOne(binding.chatId, rendered.text, rendered.rows)
142673
+ () => api.sendOne(binding.chatId, rendered.text, rendered.rows, binding.reply?.messageId)
142510
142674
  );
142511
142675
  if (cardId !== null) {
142512
142676
  panelKeyboards.set(`${binding.rowId}:${binding.chatId}:${cardId}`, {
@@ -142531,7 +142695,9 @@ ${line}`
142531
142695
  open: async (binding, card) => {
142532
142696
  const api = await apiFor(binding);
142533
142697
  const rendered = renderChoiceCard(card);
142534
- return withFloodWait(() => api.sendOne(binding.chatId, rendered.text, rendered.rows));
142698
+ return withFloodWait(
142699
+ () => api.sendOne(binding.chatId, rendered.text, rendered.rows, binding.reply?.messageId)
142700
+ );
142535
142701
  },
142536
142702
  update: async (binding, cardId, card) => {
142537
142703
  const api = await apiFor(binding);
@@ -142971,7 +143137,7 @@ var registerSettingsRowActions = (ctx, adapters) => {
142971
143137
  };
142972
143138
 
142973
143139
  // src/settingsView.ts
142974
- var isRecord3 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
143140
+ var isRecord4 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
142975
143141
  var hasStoredSecret = (row, key) => {
142976
143142
  const value2 = row[key];
142977
143143
  return typeof value2 === "string" && value2 !== "";
@@ -142989,10 +143155,10 @@ var targetTable = () => ({
142989
143155
  });
142990
143156
  var targetViews = (config2) => {
142991
143157
  const table2 = config2[DELIVERY_TARGETS_FIELD];
142992
- if (!isRecord3(table2)) return [];
143158
+ if (!isRecord4(table2)) return [];
142993
143159
  const columns = deliveryTargetColumns();
142994
143160
  return Object.entries(table2).flatMap(([rowId, raw]) => {
142995
- if (!isRecord3(raw)) return [];
143161
+ if (!isRecord4(raw)) return [];
142996
143162
  const issues = targetIssues(raw);
142997
143163
  const values = Object.fromEntries(
142998
143164
  columns.map((column) => [column.key, readString(raw, column.key)])
@@ -143004,9 +143170,9 @@ var buildSettingsView = (config2, adapters) => {
143004
143170
  const columns = buildBotColumns(adapters);
143005
143171
  const table2 = config2[BOTS_FIELD];
143006
143172
  const bots = [];
143007
- if (isRecord3(table2)) {
143173
+ if (isRecord4(table2)) {
143008
143174
  for (const [rowId, raw] of Object.entries(table2)) {
143009
- if (!isRecord3(raw)) continue;
143175
+ if (!isRecord4(raw)) continue;
143010
143176
  const platform = platformOf(raw);
143011
143177
  const values = {};
143012
143178
  const secrets = {};
@@ -143069,10 +143235,10 @@ var applyRowEdit = (config2, { field, columns }, edit) => {
143069
143235
  for (const key of clearSecrets) {
143070
143236
  if (!secretColumns.has(key)) return columnError("not-a-secret", key);
143071
143237
  }
143072
- const table2 = isRecord3(config2[field]) ? config2[field] : {};
143238
+ const table2 = isRecord4(config2[field]) ? config2[field] : {};
143073
143239
  const requested = edit.rowId;
143074
143240
  const current = requested === void 0 ? {} : Object.hasOwn(table2, requested) ? table2[requested] : void 0;
143075
- if (!isRecord3(current)) return { ok: false, code: "unknown-row" };
143241
+ if (!isRecord4(current)) return { ok: false, code: "unknown-row" };
143076
143242
  const rowId = requested ?? nextRowId(table2);
143077
143243
  const nextRow = { ...current, ...edit.values };
143078
143244
  const clearing = clearSecrets.filter((key) => !Object.hasOwn(secrets, key));
@@ -143090,7 +143256,7 @@ var applyBotEdit = (config2, adapters, edit) => applyRowEdit(config2, { field: B
143090
143256
  var applyTargetEdit = (config2, edit) => applyRowEdit(config2, targetTable(), edit);
143091
143257
  var removeRow = (config2, field, rowId) => {
143092
143258
  const table2 = config2[field];
143093
- if (!isRecord3(table2) || !Object.hasOwn(table2, rowId)) return null;
143259
+ if (!isRecord4(table2) || !Object.hasOwn(table2, rowId)) return null;
143094
143260
  const nextTable = { ...table2 };
143095
143261
  delete nextTable[rowId];
143096
143262
  return { ...config2, [field]: nextTable };
@@ -143329,7 +143495,7 @@ var makeWechatAdapter = (deps) => {
143329
143495
  };
143330
143496
 
143331
143497
  // src/wecomUpload.ts
143332
- import * as NodeCrypto5 from "node:crypto";
143498
+ import * as NodeCrypto6 from "node:crypto";
143333
143499
  var WECOM_CHUNK_BYTES = 512 * 1024;
143334
143500
  var WECOM_MAX_CHUNKS = 100;
143335
143501
  var WECOM_MIN_UPLOAD_BYTES = 5;
@@ -143362,7 +143528,7 @@ var uploadWecomMedia = async (input) => {
143362
143528
  filename: clampName(input.filename),
143363
143529
  total_size: bytes.byteLength,
143364
143530
  total_chunks: totalChunks,
143365
- md5: NodeCrypto5.createHash("md5").update(bytes).digest("hex")
143531
+ md5: NodeCrypto6.createHash("md5").update(bytes).digest("hex")
143366
143532
  });
143367
143533
  const uploadId = init.upload_id;
143368
143534
  if (typeof uploadId !== "string" || uploadId === "") {
@@ -143777,7 +143943,7 @@ var runWecomClient = (input) => {
143777
143943
  };
143778
143944
 
143779
143945
  // src/wecomMedia.ts
143780
- import * as NodeCrypto6 from "node:crypto";
143946
+ import * as NodeCrypto7 from "node:crypto";
143781
143947
 
143782
143948
  // src/wecomPadding.ts
143783
143949
  var PADDING_BLOCK = 32;
@@ -143808,7 +143974,7 @@ var decryptWecomMedia = (cipher, aesKey) => {
143808
143974
  if (cipher.byteLength === 0 || cipher.byteLength % AES_BLOCK !== 0) {
143809
143975
  throw new WecomDecryptError("the ciphertext is not a whole number of blocks");
143810
143976
  }
143811
- const decipher = NodeCrypto6.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
143977
+ const decipher = NodeCrypto7.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
143812
143978
  decipher.setAutoPadding(false);
143813
143979
  const plain = Buffer.concat([decipher.update(cipher), decipher.final()]);
143814
143980
  const unpadded = stripPadding(plain);
@@ -144406,7 +144572,7 @@ import * as NodeHttp2 from "node:http";
144406
144572
  import * as NodeNet from "node:net";
144407
144573
 
144408
144574
  // src/wecomAppCrypto.ts
144409
- import * as NodeCrypto7 from "node:crypto";
144575
+ import * as NodeCrypto8 from "node:crypto";
144410
144576
  var AES_KEY_BYTES = 32;
144411
144577
  var RANDOM_BYTES = 16;
144412
144578
  var WecomAppCryptoError = class extends Error {
@@ -144429,13 +144595,13 @@ var timingSafeEqualString2 = (a, b) => {
144429
144595
  const left = Buffer.from(a);
144430
144596
  const right = Buffer.from(b);
144431
144597
  if (left.length === 0 || left.length !== right.length) return false;
144432
- return NodeCrypto7.timingSafeEqual(left, right);
144598
+ return NodeCrypto8.timingSafeEqual(left, right);
144433
144599
  };
144434
- var computeSignature = (token, timestamp, nonce, encrypt) => NodeCrypto7.createHash("sha1").update([token, timestamp, nonce, encrypt].sort().join("")).digest("hex");
144600
+ var computeSignature = (token, timestamp, nonce, encrypt) => NodeCrypto8.createHash("sha1").update([token, timestamp, nonce, encrypt].sort().join("")).digest("hex");
144435
144601
  var verifySignature = (token, timestamp, nonce, encrypt, signature) => timingSafeEqualString2(computeSignature(token, timestamp, nonce, encrypt), signature);
144436
144602
  var decrypt = (encodingAESKey, encryptText, expectReceiveId) => {
144437
144603
  const { key, iv } = parseEncodingAESKey(encodingAESKey);
144438
- const decipher = NodeCrypto7.createDecipheriv("aes-256-cbc", key, iv);
144604
+ const decipher = NodeCrypto8.createDecipheriv("aes-256-cbc", key, iv);
144439
144605
  decipher.setAutoPadding(false);
144440
144606
  let deciphered;
144441
144607
  try {
@@ -144494,10 +144660,10 @@ var createWecomAppCrypto = (input) => {
144494
144660
  decrypt: (encryptText) => decrypt(encodingAESKey, encryptText, corpId).message
144495
144661
  };
144496
144662
  };
144497
- var deriveCallbackPathSecret = (token, encodingAESKey) => NodeCrypto7.createHash("sha256").update(`${token.trim()}:${encodingAESKey.trim()}`).digest("hex").slice(0, 24);
144663
+ var deriveCallbackPathSecret = (token, encodingAESKey) => NodeCrypto8.createHash("sha256").update(`${token.trim()}:${encodingAESKey.trim()}`).digest("hex").slice(0, 24);
144498
144664
 
144499
144665
  // src/wecomAppParse.ts
144500
- var isRecord4 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
144666
+ var isRecord5 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
144501
144667
  var isXmlFormat = (raw) => {
144502
144668
  const trimmed = raw.trim();
144503
144669
  return trimmed.startsWith("<") && trimmed.endsWith(">");
@@ -144543,7 +144709,7 @@ var parseWecomAppPlainMessage = (raw) => {
144543
144709
  } catch {
144544
144710
  return null;
144545
144711
  }
144546
- if (!isRecord4(data)) return null;
144712
+ if (!isRecord5(data)) return null;
144547
144713
  const fromRaw = data["from"];
144548
144714
  const textRaw = data["text"];
144549
144715
  const imageRaw = data["image"];
@@ -144552,10 +144718,10 @@ var parseWecomAppPlainMessage = (raw) => {
144552
144718
  msgtype: String(data["msgtype"] ?? data["MsgType"] ?? "").toLowerCase(),
144553
144719
  createTime: data["createTime"] !== void 0 ? String(data["createTime"]) : data["CreateTime"] !== void 0 ? String(data["CreateTime"]) : void 0,
144554
144720
  agentId: data["agentid"] !== void 0 ? String(data["agentid"]) : data["AgentID"] !== void 0 ? String(data["AgentID"]) : void 0,
144555
- from: isRecord4(fromRaw) ? { userid: typeof fromRaw["userid"] === "string" ? fromRaw["userid"] : void 0 } : typeof data["FromUserName"] === "string" ? { userid: data["FromUserName"] } : void 0,
144721
+ from: isRecord5(fromRaw) ? { userid: typeof fromRaw["userid"] === "string" ? fromRaw["userid"] : void 0 } : typeof data["FromUserName"] === "string" ? { userid: data["FromUserName"] } : void 0,
144556
144722
  to: typeof data["to"] === "string" ? data["to"] : void 0,
144557
- text: isRecord4(textRaw) ? { content: typeof textRaw["content"] === "string" ? textRaw["content"] : void 0 } : void 0,
144558
- image: isRecord4(imageRaw) ? { url: typeof imageRaw["url"] === "string" ? imageRaw["url"] : void 0 } : void 0,
144723
+ text: isRecord5(textRaw) ? { content: typeof textRaw["content"] === "string" ? textRaw["content"] : void 0 } : void 0,
144724
+ image: isRecord5(imageRaw) ? { url: typeof imageRaw["url"] === "string" ? imageRaw["url"] : void 0 } : void 0,
144559
144725
  mediaId: typeof data["mediaid"] === "string" ? data["mediaid"] : void 0,
144560
144726
  recognition: typeof data["recognition"] === "string" ? data["recognition"] : void 0,
144561
144727
  event: String(data["event"] ?? data["eventtype"] ?? "").toLowerCase() || void 0,
@@ -145221,10 +145387,16 @@ var makeWecomAppAdapter = (deps) => {
145221
145387
  };
145222
145388
 
145223
145389
  // src/index.ts
145224
- var imPlugin = definePlugin({
145390
+ var definition = {
145225
145391
  id: "im",
145226
- apiVersion: 18,
145227
- activate: async (ctx) => {
145392
+ apiVersion: 19,
145393
+ activate: async (ctx, inboundIo) => {
145394
+ const inboundState = makeInboundStore({
145395
+ dataDir: ctx.paths.dataDir,
145396
+ log: ctx.log,
145397
+ io: inboundIo
145398
+ });
145399
+ ctx.onDispose(inboundState.flush);
145228
145400
  const { t, setLanguage } = makeLiveTranslate(
145229
145401
  parseReplyLanguage(ctx.config[REPLY_LANGUAGE_FIELD])
145230
145402
  );
@@ -145246,6 +145418,8 @@ var imPlugin = definePlugin({
145246
145418
  // Telegram 在不少网络里要绕代理,而绕不绕、绕哪个是按行配的 —— 这是插件面上
145247
145419
  // `ctx.proxies` 的第一个真实消费者。
145248
145420
  fetchVia: (proxy) => ctx.proxies.fetch(proxy),
145421
+ readCursor: inboundState.readCursor,
145422
+ saveCursor: inboundState.saveCursor,
145249
145423
  translate: t,
145250
145424
  log: {
145251
145425
  info: (message, fields) => ctx.log.info(message, fields),
@@ -145302,7 +145476,8 @@ var imPlugin = definePlugin({
145302
145476
  config: () => ctx.config,
145303
145477
  derivedState: ctx.derivedState,
145304
145478
  t,
145305
- adapters
145479
+ adapters,
145480
+ inboundReady: inboundState.ready
145306
145481
  });
145307
145482
  const acknowledgements = makeAcknowledgements(core);
145308
145483
  const failures = makeFailureNotices(core);
@@ -145353,6 +145528,7 @@ var imPlugin = definePlugin({
145353
145528
  });
145354
145529
  const chatSetup = makeChatSetup(core, {
145355
145530
  rows: projectRows,
145531
+ resumeMessage: (bot, message) => inbound.resumeMessage(bot, message),
145356
145532
  openPicker: (chatKey, rows, reply) => panelWiring.panel.openProjects(chatKey, rows, reply)
145357
145533
  });
145358
145534
  const commands = makeCommands(core, {
@@ -145365,10 +145541,10 @@ var imPlugin = definePlugin({
145365
145541
  openPanel: (chatKey, reply) => panelWiring.panel.open(chatKey, reply)
145366
145542
  });
145367
145543
  const inbound = makeInbound(core, {
145544
+ inboundState,
145368
145545
  enqueueForChat: chatQueue.enqueueForChat,
145369
145546
  commands,
145370
145547
  chatSetup,
145371
- rows: projectRows,
145372
145548
  interactions,
145373
145549
  approvals,
145374
145550
  panelWiring,
@@ -145412,8 +145588,11 @@ var imPlugin = definePlugin({
145412
145588
  }
145413
145589
  ctx.log.info("chat plugin active", { bots, connecting });
145414
145590
  }
145415
- });
145591
+ };
145592
+ var activateImPlugin = definition.activate;
145593
+ var imPlugin = definePlugin(definition);
145416
145594
  var index_default = imPlugin;
145417
145595
  export {
145596
+ activateImPlugin,
145418
145597
  index_default as default
145419
145598
  };