@ycodium-ai/plugin-im 0.2.2968 → 0.2.2974

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
@@ -24,9 +24,9 @@ var __commonJS = (cb, mod) => function __require2() {
24
24
  throw mod = 0, e;
25
25
  }
26
26
  };
27
- var __export = (target, all) => {
27
+ var __export = (target2, all) => {
28
28
  for (var name in all)
29
- __defProp(target, name, { get: all[name], enumerable: true });
29
+ __defProp(target2, name, { get: all[name], enumerable: true });
30
30
  };
31
31
  var __copyProps = (to, from, except, desc) => {
32
32
  if (from && typeof from === "object" || typeof from === "function") {
@@ -36,12 +36,12 @@ var __copyProps = (to, from, except, desc) => {
36
36
  }
37
37
  return to;
38
38
  };
39
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
39
+ var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
40
40
  // If the importer is in node compatibility mode or this is not an ESM
41
41
  // file that has been converted to a CommonJS file using a Babel-
42
42
  // compatible transform (i.e. "__esModule" has not been set), then set
43
43
  // "default" to the CommonJS "module.exports" for node compatibility.
44
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
44
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target2, "default", { value: mod, enumerable: true }) : target2,
45
45
  mod
46
46
  ));
47
47
 
@@ -9683,15 +9683,15 @@ var require_implementation = __commonJS({
9683
9683
  return str2;
9684
9684
  };
9685
9685
  module.exports = function bind(that) {
9686
- var target = this;
9687
- if (typeof target !== "function" || toStr.apply(target) !== funcType) {
9688
- throw new TypeError(ERROR_MESSAGE + target);
9686
+ var target2 = this;
9687
+ if (typeof target2 !== "function" || toStr.apply(target2) !== funcType) {
9688
+ throw new TypeError(ERROR_MESSAGE + target2);
9689
9689
  }
9690
9690
  var args = slicy(arguments, 1);
9691
9691
  var bound;
9692
9692
  var binder = function() {
9693
9693
  if (this instanceof bound) {
9694
- var result = target.apply(
9694
+ var result = target2.apply(
9695
9695
  this,
9696
9696
  concatty(args, arguments)
9697
9697
  );
@@ -9700,21 +9700,21 @@ var require_implementation = __commonJS({
9700
9700
  }
9701
9701
  return this;
9702
9702
  }
9703
- return target.apply(
9703
+ return target2.apply(
9704
9704
  that,
9705
9705
  concatty(args, arguments)
9706
9706
  );
9707
9707
  };
9708
- var boundLength = max(0, target.length - args.length);
9708
+ var boundLength = max(0, target2.length - args.length);
9709
9709
  var boundArgs = [];
9710
9710
  for (var i = 0; i < boundLength; i++) {
9711
9711
  boundArgs[i] = "$" + i;
9712
9712
  }
9713
9713
  bound = Function("binder", "return function (" + joiny(boundArgs, ",") + "){ return binder.apply(this,arguments); }")(binder);
9714
- if (target.prototype) {
9714
+ if (target2.prototype) {
9715
9715
  var Empty = function Empty2() {
9716
9716
  };
9717
- Empty.prototype = target.prototype;
9717
+ Empty.prototype = target2.prototype;
9718
9718
  bound.prototype = new Empty();
9719
9719
  Empty.prototype = null;
9720
9720
  }
@@ -12219,8 +12219,8 @@ var require_follow_redirects = __commonJS({
12219
12219
  }
12220
12220
  return input;
12221
12221
  }
12222
- function spreadUrlObject(urlObject, target) {
12223
- var spread = target || {};
12222
+ function spreadUrlObject(urlObject, target2) {
12223
+ var spread = target2 || {};
12224
12224
  for (var key of preservedUrlFields) {
12225
12225
  spread[key] = urlObject[key];
12226
12226
  }
@@ -12768,22 +12768,22 @@ var require_axios = __commonJS({
12768
12768
  }
12769
12769
  if (!("toJSON" in source)) {
12770
12770
  visited.add(source);
12771
- let target;
12771
+ let target2;
12772
12772
  if (isSet(source)) {
12773
- target = [];
12773
+ target2 = [];
12774
12774
  for (const value2 of source) {
12775
12775
  const reducedValue = visit(value2);
12776
- !isUndefined(reducedValue) && target.push(reducedValue);
12776
+ !isUndefined(reducedValue) && target2.push(reducedValue);
12777
12777
  }
12778
12778
  } else {
12779
- target = isArray(source) ? [] : {};
12779
+ target2 = isArray(source) ? [] : {};
12780
12780
  forEach(source, (value2, key) => {
12781
12781
  const reducedValue = visit(value2);
12782
- !isUndefined(reducedValue) && (target[key] = reducedValue);
12782
+ !isUndefined(reducedValue) && (target2[key] = reducedValue);
12783
12783
  });
12784
12784
  }
12785
12785
  visited.delete(source);
12786
- return target;
12786
+ return target2;
12787
12787
  }
12788
12788
  }
12789
12789
  return source;
@@ -13234,7 +13234,7 @@ var require_axios = __commonJS({
13234
13234
  }
13235
13235
  static concat(first, ...targets) {
13236
13236
  const computed = new this(first);
13237
- targets.forEach((target) => computed.set(target));
13237
+ targets.forEach((target2) => computed.set(target2));
13238
13238
  return computed;
13239
13239
  }
13240
13240
  static accessor(header) {
@@ -13862,27 +13862,27 @@ var require_axios = __commonJS({
13862
13862
  return obj;
13863
13863
  }
13864
13864
  function formDataToJSON(formData) {
13865
- function buildPath(path2, value2, target, index) {
13865
+ function buildPath(path2, value2, target2, index) {
13866
13866
  throwIfDepthExceeded(index);
13867
13867
  let name = path2[index++];
13868
13868
  if (name === "__proto__") return true;
13869
13869
  const isNumericKey = Number.isFinite(+name);
13870
13870
  const isLast = index >= path2.length;
13871
- name = !name && utils$1.isArray(target) ? target.length : name;
13871
+ name = !name && utils$1.isArray(target2) ? target2.length : name;
13872
13872
  if (isLast) {
13873
- if (utils$1.hasOwnProp(target, name)) {
13874
- target[name] = utils$1.isArray(target[name]) ? target[name].concat(value2) : [target[name], value2];
13873
+ if (utils$1.hasOwnProp(target2, name)) {
13874
+ target2[name] = utils$1.isArray(target2[name]) ? target2[name].concat(value2) : [target2[name], value2];
13875
13875
  } else {
13876
- target[name] = value2;
13876
+ target2[name] = value2;
13877
13877
  }
13878
13878
  return !isNumericKey;
13879
13879
  }
13880
- if (!utils$1.hasOwnProp(target, name) || !utils$1.isObject(target[name])) {
13881
- target[name] = [];
13880
+ if (!utils$1.hasOwnProp(target2, name) || !utils$1.isObject(target2[name])) {
13881
+ target2[name] = [];
13882
13882
  }
13883
- const result = buildPath(path2, value2, target[name], index);
13884
- if (result && utils$1.isArray(target[name])) {
13885
- target[name] = arrayToObject(target[name]);
13883
+ const result = buildPath(path2, value2, target2[name], index);
13884
+ if (result && utils$1.isArray(target2[name])) {
13885
+ target2[name] = arrayToObject(target2[name]);
13886
13886
  }
13887
13887
  return !isNumericKey;
13888
13888
  }
@@ -16043,11 +16043,11 @@ var require_axios = __commonJS({
16043
16043
  writable: true,
16044
16044
  configurable: true
16045
16045
  });
16046
- function getMergedValue(target, source, prop, caseless) {
16047
- if (utils$1.isPlainObject(target) && utils$1.isPlainObject(source)) {
16046
+ function getMergedValue(target2, source, prop, caseless) {
16047
+ if (utils$1.isPlainObject(target2) && utils$1.isPlainObject(source)) {
16048
16048
  return utils$1.merge.call({
16049
16049
  caseless
16050
- }, target, source);
16050
+ }, target2, source);
16051
16051
  } else if (utils$1.isPlainObject(source)) {
16052
16052
  return utils$1.merge({}, source);
16053
16053
  } else if (utils$1.isArray(source)) {
@@ -16862,14 +16862,14 @@ var require_axios = __commonJS({
16862
16862
  Response
16863
16863
  } = env;
16864
16864
  const seeds = [Request, Response, fetch2];
16865
- let len = seeds.length, i = len, seed, target, map = seedCache;
16865
+ let len = seeds.length, i = len, seed, target2, map = seedCache;
16866
16866
  while (i--) {
16867
16867
  seed = seeds[i];
16868
- target = map.get(seed);
16869
- target === void 0 && map.set(seed, target = i ? /* @__PURE__ */ new Map() : factory(env));
16870
- map = target;
16868
+ target2 = map.get(seed);
16869
+ target2 === void 0 && map.set(seed, target2 = i ? /* @__PURE__ */ new Map() : factory(env));
16870
+ map = target2;
16871
16871
  }
16872
- return target;
16872
+ return target2;
16873
16873
  };
16874
16874
  getFetch();
16875
16875
  var knownAdapters = {
@@ -18377,46 +18377,46 @@ var require_utils = __commonJS({
18377
18377
  obj[key] = value2;
18378
18378
  }
18379
18379
  };
18380
- var merge = function merge2(target, source, options) {
18380
+ var merge = function merge2(target2, source, options) {
18381
18381
  if (!source) {
18382
- return target;
18382
+ return target2;
18383
18383
  }
18384
18384
  if (typeof source !== "object" && typeof source !== "function") {
18385
- if (isArray(target)) {
18386
- var nextIndex = target.length;
18385
+ if (isArray(target2)) {
18386
+ var nextIndex = target2.length;
18387
18387
  if (options && typeof options.arrayLimit === "number" && nextIndex >= options.arrayLimit) {
18388
18388
  if (options.throwOnLimitExceeded) {
18389
18389
  throw new RangeError("Array limit exceeded. Only " + options.arrayLimit + " element" + (options.arrayLimit === 1 ? "" : "s") + " allowed in an array.");
18390
18390
  }
18391
- return markOverflow(arrayToObject(target.concat(source), options), nextIndex);
18392
- }
18393
- target[nextIndex] = source;
18394
- } else if (target && typeof target === "object") {
18395
- if (isOverflow(target)) {
18396
- var newIndex = getMaxIndex(target) + 1;
18397
- target[newIndex] = source;
18398
- setMaxIndex(target, newIndex);
18391
+ return markOverflow(arrayToObject(target2.concat(source), options), nextIndex);
18392
+ }
18393
+ target2[nextIndex] = source;
18394
+ } else if (target2 && typeof target2 === "object") {
18395
+ if (isOverflow(target2)) {
18396
+ var newIndex = getMaxIndex(target2) + 1;
18397
+ target2[newIndex] = source;
18398
+ setMaxIndex(target2, newIndex);
18399
18399
  } else if (options && options.strictMerge) {
18400
- return [target, source];
18400
+ return [target2, source];
18401
18401
  } else if (options && (options.plainObjects || options.allowPrototypes) || !has.call(Object.prototype, source)) {
18402
- target[source] = true;
18402
+ target2[source] = true;
18403
18403
  }
18404
18404
  } else {
18405
- return [target, source];
18405
+ return [target2, source];
18406
18406
  }
18407
- return target;
18407
+ return target2;
18408
18408
  }
18409
- if (!target || typeof target !== "object") {
18409
+ if (!target2 || typeof target2 !== "object") {
18410
18410
  if (isOverflow(source)) {
18411
18411
  var sourceKeys = Object.keys(source);
18412
- var result = options && options.plainObjects ? { __proto__: null, 0: target } : { 0: target };
18412
+ var result = options && options.plainObjects ? { __proto__: null, 0: target2 } : { 0: target2 };
18413
18413
  for (var m = 0; m < sourceKeys.length; m++) {
18414
18414
  var oldKey = parseInt(sourceKeys[m], 10);
18415
18415
  result[oldKey + 1] = source[sourceKeys[m]];
18416
18416
  }
18417
18417
  return markOverflow(result, getMaxIndex(source) + 1);
18418
18418
  }
18419
- var combined = [target].concat(source);
18419
+ var combined = [target2].concat(source);
18420
18420
  if (options && typeof options.arrayLimit === "number" && combined.length > options.arrayLimit) {
18421
18421
  if (options.throwOnLimitExceeded) {
18422
18422
  throw new RangeError("Array limit exceeded. Only " + options.arrayLimit + " element" + (options.arrayLimit === 1 ? "" : "s") + " allowed in an array.");
@@ -18425,30 +18425,30 @@ var require_utils = __commonJS({
18425
18425
  }
18426
18426
  return combined;
18427
18427
  }
18428
- var mergeTarget = target;
18429
- if (isArray(target) && !isArray(source)) {
18430
- mergeTarget = arrayToObject(target, options);
18428
+ var mergeTarget = target2;
18429
+ if (isArray(target2) && !isArray(source)) {
18430
+ mergeTarget = arrayToObject(target2, options);
18431
18431
  }
18432
- if (isArray(target) && isArray(source)) {
18432
+ if (isArray(target2) && isArray(source)) {
18433
18433
  source.forEach(function(item, i) {
18434
- if (has.call(target, i)) {
18435
- var targetItem = target[i];
18434
+ if (has.call(target2, i)) {
18435
+ var targetItem = target2[i];
18436
18436
  if (targetItem && typeof targetItem === "object" && item && typeof item === "object") {
18437
- target[i] = merge2(targetItem, item, options);
18437
+ target2[i] = merge2(targetItem, item, options);
18438
18438
  } else {
18439
- target[target.length] = item;
18439
+ target2[target2.length] = item;
18440
18440
  }
18441
18441
  } else {
18442
- target[i] = item;
18442
+ target2[i] = item;
18443
18443
  }
18444
18444
  });
18445
- if (options && typeof options.arrayLimit === "number" && target.length > options.arrayLimit) {
18445
+ if (options && typeof options.arrayLimit === "number" && target2.length > options.arrayLimit) {
18446
18446
  if (options.throwOnLimitExceeded) {
18447
18447
  throw new RangeError("Array limit exceeded. Only " + options.arrayLimit + " element" + (options.arrayLimit === 1 ? "" : "s") + " allowed in an array.");
18448
18448
  }
18449
- return markOverflow(arrayToObject(target, options), target.length - 1);
18449
+ return markOverflow(arrayToObject(target2, options), target2.length - 1);
18450
18450
  }
18451
- return target;
18451
+ return target2;
18452
18452
  }
18453
18453
  return Object.keys(source).reduce(function(acc, key) {
18454
18454
  var value2 = source[key];
@@ -18469,11 +18469,11 @@ var require_utils = __commonJS({
18469
18469
  return acc;
18470
18470
  }, mergeTarget);
18471
18471
  };
18472
- var assign = function assignSingleSource(target, source) {
18472
+ var assign = function assignSingleSource(target2, source) {
18473
18473
  return Object.keys(source).reduce(function(acc, key) {
18474
18474
  setProperty(acc, key, source[key]);
18475
18475
  return acc;
18476
- }, target);
18476
+ }, target2);
18477
18477
  };
18478
18478
  var decode2 = function(str2, defaultDecoder, charset) {
18479
18479
  var strWithoutPlus = str2.replace(/\+/g, " ");
@@ -20911,7 +20911,7 @@ var require_node_gyp_build = __commonJS({
20911
20911
  if (prebuild) return prebuild;
20912
20912
  var nearby = resolve(path.dirname(process.execPath));
20913
20913
  if (nearby) return nearby;
20914
- var target = [
20914
+ var target2 = [
20915
20915
  "platform=" + platform,
20916
20916
  "arch=" + arch,
20917
20917
  "runtime=" + runtime,
@@ -20924,7 +20924,7 @@ var require_node_gyp_build = __commonJS({
20924
20924
  typeof __webpack_require__ === "function" ? "webpack=true" : ""
20925
20925
  // eslint-disable-line
20926
20926
  ].filter(Boolean).join(" ");
20927
- throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
20927
+ throw new Error("No native build was found for " + target2 + "\n loaded from: " + dir + "\n");
20928
20928
  function resolve(dir2) {
20929
20929
  var tuples = readdirSync(path.join(dir2, "prebuilds")).map(parseTuple);
20930
20930
  var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
@@ -21095,17 +21095,17 @@ var require_buffer_util = __commonJS({
21095
21095
  function concat(list2, totalLength) {
21096
21096
  if (list2.length === 0) return EMPTY_BUFFER;
21097
21097
  if (list2.length === 1) return list2[0];
21098
- const target = Buffer.allocUnsafe(totalLength);
21098
+ const target2 = Buffer.allocUnsafe(totalLength);
21099
21099
  let offset = 0;
21100
21100
  for (let i = 0; i < list2.length; i++) {
21101
21101
  const buf = list2[i];
21102
- target.set(buf, offset);
21102
+ target2.set(buf, offset);
21103
21103
  offset += buf.length;
21104
21104
  }
21105
21105
  if (offset < totalLength) {
21106
- return new FastBuffer(target.buffer, target.byteOffset, offset);
21106
+ return new FastBuffer(target2.buffer, target2.byteOffset, offset);
21107
21107
  }
21108
- return target;
21108
+ return target2;
21109
21109
  }
21110
21110
  function _mask(source, mask, output, offset, length) {
21111
21111
  for (let i = 0; i < length; i++) {
@@ -22587,29 +22587,29 @@ var require_sender = __commonJS({
22587
22587
  offset += 2;
22588
22588
  payloadLength = 126;
22589
22589
  }
22590
- const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
22591
- target[0] = options.fin ? options.opcode | 128 : options.opcode;
22592
- if (options.rsv1) target[0] |= 64;
22593
- target[1] = payloadLength;
22590
+ const target2 = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
22591
+ target2[0] = options.fin ? options.opcode | 128 : options.opcode;
22592
+ if (options.rsv1) target2[0] |= 64;
22593
+ target2[1] = payloadLength;
22594
22594
  if (payloadLength === 126) {
22595
- target.writeUInt16BE(dataLength, 2);
22595
+ target2.writeUInt16BE(dataLength, 2);
22596
22596
  } else if (payloadLength === 127) {
22597
- target[2] = target[3] = 0;
22598
- target.writeUIntBE(dataLength, 4, 6);
22599
- }
22600
- if (!options.mask) return [target, data];
22601
- target[1] |= 128;
22602
- target[offset - 4] = mask[0];
22603
- target[offset - 3] = mask[1];
22604
- target[offset - 2] = mask[2];
22605
- target[offset - 1] = mask[3];
22606
- if (skipMasking) return [target, data];
22597
+ target2[2] = target2[3] = 0;
22598
+ target2.writeUIntBE(dataLength, 4, 6);
22599
+ }
22600
+ if (!options.mask) return [target2, data];
22601
+ target2[1] |= 128;
22602
+ target2[offset - 4] = mask[0];
22603
+ target2[offset - 3] = mask[1];
22604
+ target2[offset - 2] = mask[2];
22605
+ target2[offset - 1] = mask[3];
22606
+ if (skipMasking) return [target2, data];
22607
22607
  if (merge) {
22608
- applyMask(data, mask, target, offset, dataLength);
22609
- return [target];
22608
+ applyMask(data, mask, target2, offset, dataLength);
22609
+ return [target2];
22610
22610
  }
22611
22611
  applyMask(data, mask, data, 0, dataLength);
22612
- return [target, data];
22612
+ return [target2, data];
22613
22613
  }
22614
22614
  /**
22615
22615
  * Sends a close message to the other peer.
@@ -126168,6 +126168,11 @@ var makeAcknowledgements = (core) => {
126168
126168
  const acknowledgements = /* @__PURE__ */ new Map();
126169
126169
  const markWorking = async (binding, threadId, messageId) => {
126170
126170
  const adapter = adapterFor(binding.platform);
126171
+ if (adapter?.sendTyping !== void 0) {
126172
+ void adapter.sendTyping(binding).catch((error) => {
126173
+ host.log.info("chat could not show typing", { error: describeError(error) });
126174
+ });
126175
+ }
126171
126176
  if (adapter?.acknowledge === void 0) return;
126172
126177
  try {
126173
126178
  const ackId = await adapter.acknowledge(binding, messageId);
@@ -126180,14 +126185,14 @@ var makeAcknowledgements = (core) => {
126180
126185
  host.log.warn("chat could not acknowledge the message", { error: describeError(error) });
126181
126186
  }
126182
126187
  };
126183
- const clearWorking = async (binding, threadId) => {
126188
+ const clearWorking = async (binding, threadId, outcome) => {
126184
126189
  const adapter = adapterFor(binding.platform);
126185
126190
  const pending = acknowledgements.get(threadId);
126186
126191
  if (pending === void 0 || adapter?.clearAcknowledge === void 0) return;
126187
126192
  acknowledgements.delete(threadId);
126188
126193
  for (const entry of pending) {
126189
126194
  try {
126190
- await adapter.clearAcknowledge(binding, entry.messageId, entry.ackId);
126195
+ await adapter.clearAcknowledge(binding, entry.messageId, entry.ackId, outcome);
126191
126196
  } catch (error) {
126192
126197
  host.log.warn("chat could not clear the acknowledgement", {
126193
126198
  error: describeError(error)
@@ -126220,6 +126225,26 @@ var readAttached = (data) => {
126220
126225
  return chatKey === null || threadId === null || projectId === null || attachedAt === null ? null : { chatKey, threadId, projectId, attachedAt };
126221
126226
  };
126222
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
+
126223
126248
  // src/chatState.ts
126224
126249
  var IM_STATE_KEY = "chats";
126225
126250
  var IM_STATE_VERSION = 1;
@@ -126231,7 +126256,8 @@ var emptyState = () => ({
126231
126256
  approvals: {},
126232
126257
  turnCards: {},
126233
126258
  questions: {},
126234
- queue: {}
126259
+ queue: {},
126260
+ aliases: {}
126235
126261
  });
126236
126262
  var questionsOf = (state) => state.questions ?? {};
126237
126263
  var queueOf = (state) => state.queue ?? {};
@@ -126289,11 +126315,11 @@ var openQueueOfThread = (state, threadId) => Object.values(queueOf(state)).filte
126289
126315
  (record2) => record2.threadId === threadId && record2.settled === void 0
126290
126316
  );
126291
126317
  var isRecord = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
126292
- var nonEmpty2 = (value2) => typeof value2 === "string" && value2 !== "" ? value2 : null;
126318
+ var nonEmpty3 = (value2) => typeof value2 === "string" && value2 !== "" ? value2 : null;
126293
126319
  var readTarget = (value2) => {
126294
126320
  if (!isRecord(value2)) return null;
126295
126321
  if (value2["kind"] === "thread") return { kind: "thread" };
126296
- const teamId = nonEmpty2(value2["teamId"]);
126322
+ const teamId = nonEmpty3(value2["teamId"]);
126297
126323
  return value2["kind"] === "team" && teamId !== null ? { kind: "team", teamId } : null;
126298
126324
  };
126299
126325
  var readButtons = (value2) => {
@@ -126301,8 +126327,8 @@ var readButtons = (value2) => {
126301
126327
  const buttons = [];
126302
126328
  for (const entry of value2) {
126303
126329
  if (!isRecord(entry)) return null;
126304
- const token = nonEmpty2(entry["token"]);
126305
- const decision = nonEmpty2(entry["decision"]);
126330
+ const token = nonEmpty3(entry["token"]);
126331
+ const decision = nonEmpty3(entry["decision"]);
126306
126332
  const label = typeof entry["label"] === "string" ? entry["label"] : null;
126307
126333
  if (token === null || decision === null || label === null) return null;
126308
126334
  buttons.push({ token, label, decision });
@@ -126314,8 +126340,8 @@ var readQuestionOptions = (value2) => {
126314
126340
  const options = [];
126315
126341
  for (const entry of value2) {
126316
126342
  if (!isRecord(entry)) return null;
126317
- const token = nonEmpty2(entry["token"]);
126318
- const optionValue = nonEmpty2(entry["value"]);
126343
+ const token = nonEmpty3(entry["token"]);
126344
+ const optionValue = nonEmpty3(entry["value"]);
126319
126345
  if (token === null || optionValue === null || typeof entry["label"] !== "string") return null;
126320
126346
  const planAction = entry["planAction"];
126321
126347
  options.push({
@@ -126333,10 +126359,10 @@ var readQuestionItems = (value2) => {
126333
126359
  const items = [];
126334
126360
  for (const entry of value2) {
126335
126361
  if (!isRecord(entry)) return null;
126336
- const id = nonEmpty2(entry["id"]);
126362
+ const id = nonEmpty3(entry["id"]);
126337
126363
  const options = readQuestionOptions(entry["options"]);
126338
126364
  if (id === null || options === null || typeof entry["question"] !== "string") return null;
126339
- const submitToken = nonEmpty2(entry["submitToken"]);
126365
+ const submitToken = nonEmpty3(entry["submitToken"]);
126340
126366
  items.push({
126341
126367
  id,
126342
126368
  header: typeof entry["header"] === "string" ? entry["header"] : "",
@@ -126379,10 +126405,6 @@ var withQueueRecord = (state, intentId, patch) => {
126379
126405
  if (intentId === null || record2 === void 0) return state;
126380
126406
  return { ...state, queue: { ...queueOf(state), [intentId]: patch(record2) } };
126381
126407
  };
126382
- var keepNewest = (record2, limit) => {
126383
- const entries = Object.entries(record2);
126384
- return entries.length <= limit ? record2 : Object.fromEntries(entries.slice(entries.length - limit));
126385
- };
126386
126408
  var withChat = (state, chatKey, patch) => ({
126387
126409
  ...state,
126388
126410
  chats: { ...state.chats, [chatKey]: { ...chatOf(state, chatKey), ...patch } }
@@ -126391,19 +126413,19 @@ var foldImEvent = (state, name, data) => {
126391
126413
  if (!isRecord(data)) return state;
126392
126414
  switch (name) {
126393
126415
  case "chat-updated": {
126394
- const chatKey = nonEmpty2(data["chatKey"]);
126416
+ const chatKey = nonEmpty3(data["chatKey"]);
126395
126417
  if (chatKey === null) return state;
126396
126418
  const patch = {};
126397
126419
  if (data["projectId"] === null) patch.projectId = null;
126398
- else if (nonEmpty2(data["projectId"]) !== null) patch.projectId = nonEmpty2(data["projectId"]);
126420
+ else if (nonEmpty3(data["projectId"]) !== null) patch.projectId = nonEmpty3(data["projectId"]);
126399
126421
  if (data["threadId"] === null) patch.threadId = null;
126400
- else if (nonEmpty2(data["threadId"]) !== null) patch.threadId = nonEmpty2(data["threadId"]);
126401
- const target = readTarget(data["target"]);
126402
- if (target !== null) patch.target = target;
126422
+ else if (nonEmpty3(data["threadId"]) !== null) patch.threadId = nonEmpty3(data["threadId"]);
126423
+ const target2 = readTarget(data["target"]);
126424
+ if (target2 !== null) patch.target = target2;
126403
126425
  if (data["model"] === null) patch.model = null;
126404
126426
  else if (isRecord(data["model"])) {
126405
- const instanceId = nonEmpty2(data["model"]["instanceId"]);
126406
- const model = nonEmpty2(data["model"]["model"]);
126427
+ const instanceId = nonEmpty3(data["model"]["instanceId"]);
126428
+ const model = nonEmpty3(data["model"]["model"]);
126407
126429
  if (instanceId !== null && model !== null) patch.model = { instanceId, model };
126408
126430
  }
126409
126431
  if (typeof data["plan"] === "boolean") patch.plan = data["plan"];
@@ -126414,10 +126436,10 @@ var foldImEvent = (state, name, data) => {
126414
126436
  return withChat(state, chatKey, patch);
126415
126437
  }
126416
126438
  case "thread-opened": {
126417
- const chatKey = nonEmpty2(data["chatKey"]);
126418
- const threadId = nonEmpty2(data["threadId"]);
126419
- const projectId = nonEmpty2(data["projectId"]);
126420
- 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"]);
126421
126443
  if (chatKey === null || threadId === null || projectId === null || openedAt === null) {
126422
126444
  return state;
126423
126445
  }
@@ -126438,9 +126460,9 @@ var foldImEvent = (state, name, data) => {
126438
126460
  return { ...state, attached: keepNewest(attached, ATTACHED_THREAD_LIMIT) };
126439
126461
  }
126440
126462
  case "approval-offered": {
126441
- const chatKey = nonEmpty2(data["chatKey"]);
126442
- const threadId = nonEmpty2(data["threadId"]);
126443
- const requestId = nonEmpty2(data["requestId"]);
126463
+ const chatKey = nonEmpty3(data["chatKey"]);
126464
+ const threadId = nonEmpty3(data["threadId"]);
126465
+ const requestId = nonEmpty3(data["requestId"]);
126444
126466
  const options = readButtons(data["options"]);
126445
126467
  if (chatKey === null || threadId === null || requestId === null || options === null) {
126446
126468
  return state;
@@ -126457,7 +126479,7 @@ var foldImEvent = (state, name, data) => {
126457
126479
  };
126458
126480
  }
126459
126481
  case "approval-resolved": {
126460
- const requestId = nonEmpty2(data["requestId"]);
126482
+ const requestId = nonEmpty3(data["requestId"]);
126461
126483
  const existing = requestId === null ? void 0 : state.approvals[requestId];
126462
126484
  if (requestId === null || existing === void 0) return state;
126463
126485
  return {
@@ -126466,8 +126488,8 @@ var foldImEvent = (state, name, data) => {
126466
126488
  };
126467
126489
  }
126468
126490
  case "approval-carded": {
126469
- const requestId = nonEmpty2(data["requestId"]);
126470
- const cardId = nonEmpty2(data["cardId"]);
126491
+ const requestId = nonEmpty3(data["requestId"]);
126492
+ const cardId = nonEmpty3(data["cardId"]);
126471
126493
  const existing = requestId === null ? void 0 : state.approvals[requestId];
126472
126494
  if (requestId === null || cardId === null || existing === void 0) return state;
126473
126495
  return {
@@ -126476,9 +126498,9 @@ var foldImEvent = (state, name, data) => {
126476
126498
  };
126477
126499
  }
126478
126500
  case "question-offered": {
126479
- const chatKey = nonEmpty2(data["chatKey"]);
126480
- const threadId = nonEmpty2(data["threadId"]);
126481
- const requestId = nonEmpty2(data["requestId"]);
126501
+ const chatKey = nonEmpty3(data["chatKey"]);
126502
+ const threadId = nonEmpty3(data["threadId"]);
126503
+ const requestId = nonEmpty3(data["requestId"]);
126482
126504
  const items = readQuestionItems(data["items"]);
126483
126505
  if (chatKey === null || threadId === null || requestId === null || items === null) {
126484
126506
  return state;
@@ -126495,12 +126517,12 @@ var foldImEvent = (state, name, data) => {
126495
126517
  };
126496
126518
  }
126497
126519
  case "question-carded": {
126498
- const cardId = nonEmpty2(data["cardId"]);
126520
+ const cardId = nonEmpty3(data["cardId"]);
126499
126521
  if (cardId === null) return state;
126500
126522
  return withQuestionItem(
126501
126523
  state,
126502
- nonEmpty2(data["requestId"]),
126503
- nonEmpty2(data["questionId"]),
126524
+ nonEmpty3(data["requestId"]),
126525
+ nonEmpty3(data["questionId"]),
126504
126526
  (item) => ({ ...item, cardId })
126505
126527
  );
126506
126528
  }
@@ -126509,8 +126531,8 @@ var foldImEvent = (state, name, data) => {
126509
126531
  if (!Array.isArray(selected)) return state;
126510
126532
  return withQuestionItem(
126511
126533
  state,
126512
- nonEmpty2(data["requestId"]),
126513
- nonEmpty2(data["questionId"]),
126534
+ nonEmpty3(data["requestId"]),
126535
+ nonEmpty3(data["questionId"]),
126514
126536
  (item) => ({ ...item, selected })
126515
126537
  );
126516
126538
  }
@@ -126520,13 +126542,13 @@ var foldImEvent = (state, name, data) => {
126520
126542
  const label = typeof data["label"] === "string" ? data["label"] : "";
126521
126543
  return withQuestionItem(
126522
126544
  state,
126523
- nonEmpty2(data["requestId"]),
126524
- nonEmpty2(data["questionId"]),
126545
+ nonEmpty3(data["requestId"]),
126546
+ nonEmpty3(data["questionId"]),
126525
126547
  (item) => ({ ...item, answer, answerLabel: label })
126526
126548
  );
126527
126549
  }
126528
126550
  case "question-resolved": {
126529
- const requestId = nonEmpty2(data["requestId"]);
126551
+ const requestId = nonEmpty3(data["requestId"]);
126530
126552
  const existing = requestId === null ? void 0 : questionsOf(state)[requestId];
126531
126553
  if (requestId === null || existing === void 0) return state;
126532
126554
  const ending = data["ending"];
@@ -126543,12 +126565,12 @@ var foldImEvent = (state, name, data) => {
126543
126565
  };
126544
126566
  }
126545
126567
  case "queue-offered": {
126546
- const chatKey = nonEmpty2(data["chatKey"]);
126547
- const threadId = nonEmpty2(data["threadId"]);
126548
- const intentId = nonEmpty2(data["intentId"]);
126549
- const steerToken = nonEmpty2(data["steerToken"]);
126550
- const withdrawToken = nonEmpty2(data["withdrawToken"]);
126551
- 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"]);
126552
126574
  if (chatKey === null || threadId === null || intentId === null || steerToken === null || withdrawToken === null || resumeToken === null) {
126553
126575
  return state;
126554
126576
  }
@@ -126573,30 +126595,30 @@ var foldImEvent = (state, name, data) => {
126573
126595
  };
126574
126596
  }
126575
126597
  case "queue-carded": {
126576
- const cardId = nonEmpty2(data["cardId"]);
126598
+ const cardId = nonEmpty3(data["cardId"]);
126577
126599
  if (cardId === null) return state;
126578
- return withQueueRecord(state, nonEmpty2(data["intentId"]), (record2) => ({
126600
+ return withQueueRecord(state, nonEmpty3(data["intentId"]), (record2) => ({
126579
126601
  ...record2,
126580
126602
  cardId
126581
126603
  }));
126582
126604
  }
126583
126605
  case "queue-steering":
126584
- return withQueueRecord(state, nonEmpty2(data["intentId"]), (record2) => ({
126606
+ return withQueueRecord(state, nonEmpty3(data["intentId"]), (record2) => ({
126585
126607
  ...record2,
126586
126608
  steering: data["steering"] === true
126587
126609
  }));
126588
126610
  case "queue-settled": {
126589
126611
  const outcome = data["outcome"];
126590
126612
  if (outcome !== "steered" && outcome !== "sent" && outcome !== "removed") return state;
126591
- return withQueueRecord(state, nonEmpty2(data["intentId"]), (record2) => ({
126613
+ return withQueueRecord(state, nonEmpty3(data["intentId"]), (record2) => ({
126592
126614
  ...record2,
126593
126615
  settled: outcome
126594
126616
  }));
126595
126617
  }
126596
126618
  case "turn-card-opened": {
126597
- const cardId = nonEmpty2(data["cardId"]);
126598
- const chatKey = nonEmpty2(data["chatKey"]);
126599
- const threadId = nonEmpty2(data["threadId"]);
126619
+ const cardId = nonEmpty3(data["cardId"]);
126620
+ const chatKey = nonEmpty3(data["chatKey"]);
126621
+ const threadId = nonEmpty3(data["threadId"]);
126600
126622
  if (cardId === null || chatKey === null || threadId === null) return state;
126601
126623
  const title = typeof data["title"] === "string" ? data["title"] : "";
126602
126624
  return {
@@ -126608,11 +126630,13 @@ var foldImEvent = (state, name, data) => {
126608
126630
  };
126609
126631
  }
126610
126632
  case "turn-card-closed": {
126611
- const cardId = nonEmpty2(data["cardId"]);
126633
+ const cardId = nonEmpty3(data["cardId"]);
126612
126634
  if (cardId === null) return state;
126613
126635
  const { [cardId]: _closed, ...rest } = openTurnCardsOf(state);
126614
126636
  return { ...state, turnCards: rest };
126615
126637
  }
126638
+ case "chat-aliased":
126639
+ return foldAliased(state, data);
126616
126640
  default:
126617
126641
  return state;
126618
126642
  }
@@ -126985,6 +127009,7 @@ var readBots = (config2, adapterFor) => {
126985
127009
  };
126986
127010
  var bindingOf = (bot, chatId, state) => ({
126987
127011
  ...bot,
127012
+ reply: void 0,
126988
127013
  chatId,
126989
127014
  projectId: chatOf(state, chatKeyOf({ rowId: bot.rowId, chatId })).projectId ?? ""
126990
127015
  });
@@ -127039,7 +127064,7 @@ var numberedApprovalText = (request) => [
127039
127064
  ].join("\n");
127040
127065
 
127041
127066
  // src/approvals.ts
127042
- var makeApprovals = (core) => {
127067
+ var makeApprovals = (core, anchors) => {
127043
127068
  const { host, t, adapterFor, commit, outbound, bindingForChatKey, bindingOfThread, ready } = core;
127044
127069
  const settleApprovalCard = async (approval, settlement) => {
127045
127070
  if (approval.cardId === void 0) return;
@@ -127075,7 +127100,7 @@ var makeApprovals = (core) => {
127075
127100
  };
127076
127101
  const sendApprovalCard = async (binding, adapter, threadId, offer) => {
127077
127102
  try {
127078
- const cardId = await adapter.askApproval(binding, {
127103
+ const cardId = await adapter.askApproval(await anchors.anchored(binding, threadId, null), {
127079
127104
  title: offer.title,
127080
127105
  body: offer.body,
127081
127106
  options: offer.buttons,
@@ -127136,10 +127161,10 @@ var makeApprovals = (core) => {
127136
127161
  const found = findApprovalByToken(core.state, token);
127137
127162
  if (found === void 0) return null;
127138
127163
  if (found.approval.resolved) return t("approval.alreadyHandled");
127139
- const target = bindingForChatKey(found.approval.chatKey);
127140
- if (target === void 0 || !isAllowedSender(target, operatorId)) {
127164
+ const target2 = bindingForChatKey(found.approval.chatKey);
127165
+ if (target2 === void 0 || !isAllowedSender(target2, operatorId)) {
127141
127166
  host.log.warn("chat ignored an approval click from someone not on the allow list", {
127142
- bot: target?.displayName ?? found.approval.chatKey,
127167
+ bot: target2?.displayName ?? found.approval.chatKey,
127143
127168
  operator: operatorId
127144
127169
  });
127145
127170
  return t("reply.notAllowed");
@@ -127219,7 +127244,7 @@ var makeChatSetup = (core, deps) => {
127219
127244
  listedProjectsByChat.set(chatKey, listedProjectIds(rows));
127220
127245
  if (rows.length > 0 && hasPanel(binding)) {
127221
127246
  try {
127222
- await deps.openPicker(chatKey, rows);
127247
+ await deps.openPicker(chatKey, rows, binding.reply);
127223
127248
  return;
127224
127249
  } catch (error) {
127225
127250
  host.log.warn("chat could not open the project picker card", {
@@ -127248,13 +127273,13 @@ var makeChatSetup = (core, deps) => {
127248
127273
  await deliver(binding, t("repo.notFound", { query: arg }));
127249
127274
  return;
127250
127275
  }
127251
- const target = rows.find((row) => row.projectId === picked);
127252
- if (target === void 0) {
127276
+ const target2 = rows.find((row) => row.projectId === picked);
127277
+ if (target2 === void 0) {
127253
127278
  await deliver(binding, t("repo.gone"));
127254
127279
  return;
127255
127280
  }
127256
- if (target.current) {
127257
- await deliver(binding, t("repo.already", { title: target.title }));
127281
+ if (target2.current) {
127282
+ await deliver(binding, t("repo.already", { title: target2.title }));
127258
127283
  return;
127259
127284
  }
127260
127285
  const threadId = chatOf(core.state, chatKey).threadId;
@@ -127273,7 +127298,7 @@ var makeChatSetup = (core, deps) => {
127273
127298
  });
127274
127299
  listedProjectsByChat.delete(chatKey);
127275
127300
  host.log.info("chat picked a project", { bot: binding.displayName, projectId: picked });
127276
- await deliver(binding, t("repo.done", { title: target.title }));
127301
+ await deliver(binding, t("repo.done", { title: target2.title }));
127277
127302
  };
127278
127303
  const handleGroup = async (binding, name, senderId) => {
127279
127304
  const adapter = adapterFor(binding.platform);
@@ -127428,9 +127453,9 @@ var helpText = (t, options = {}) => [
127428
127453
  t("help.aliases"),
127429
127454
  ...options.panel === true ? ["", t("help.panel")] : []
127430
127455
  ].join("\n");
127431
- var describeTargets = (target, teams, t) => {
127432
- const current = target.kind === "thread" ? t("targets.currentThread") : t("targets.currentTeam", {
127433
- title: teams.find((team) => team.teamId === target.teamId)?.title ?? target.teamId
127456
+ var describeTargets = (target2, teams, t) => {
127457
+ const current = target2.kind === "thread" ? t("targets.currentThread") : t("targets.currentTeam", {
127458
+ title: teams.find((team) => team.teamId === target2.teamId)?.title ?? target2.teamId
127434
127459
  });
127435
127460
  const list2 = teams.length === 0 ? t("targets.noTeams") : [t("targets.teamList"), ...teams.map((team) => `\xB7 ${team.title}`)].join("\n");
127436
127461
  return [current, "", list2, "", t("targets.hint")].join("\n");
@@ -127569,7 +127594,7 @@ var makeCommands = (core, deps) => {
127569
127594
  case "panel":
127570
127595
  if (hasPanel(binding)) {
127571
127596
  try {
127572
- await openPanel(chatKey);
127597
+ await openPanel(chatKey, binding.reply);
127573
127598
  return true;
127574
127599
  } catch (error) {
127575
127600
  host.log.warn("chat could not open the command panel", {
@@ -127680,7 +127705,7 @@ var makeCommands = (core, deps) => {
127680
127705
  turnId: activeTurnId,
127681
127706
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
127682
127707
  });
127683
- await clearWorking(binding, threadId);
127708
+ await clearWorking(binding, threadId, "stopped");
127684
127709
  await deliver(binding, t("command.stopped"));
127685
127710
  return true;
127686
127711
  }
@@ -127689,51 +127714,339 @@ var makeCommands = (core, deps) => {
127689
127714
  return { handleCommand, targetOf };
127690
127715
  };
127691
127716
 
127717
+ // src/keyedQueue.ts
127718
+ var makeKeyedQueue = (onError) => {
127719
+ const tails = /* @__PURE__ */ new Map();
127720
+ return (key, work) => {
127721
+ const run = (tails.get(key) ?? Promise.resolve()).then(work).catch(onError).then(() => {
127722
+ if (tails.get(key) === run) tails.delete(key);
127723
+ });
127724
+ tails.set(key, run);
127725
+ };
127726
+ };
127727
+
127692
127728
  // src/connections.ts
127729
+ var connectionKey = (adapter, bot) => JSON.stringify([
127730
+ adapter.platform,
127731
+ bot.displayName,
127732
+ // 「跟机器默认」与「不加代理」是两种选择,分开记。
127733
+ bot.proxy === void 0 ? [] : [bot.proxy],
127734
+ bot.groupMention,
127735
+ [...adapter.columns.map((column) => column.key), ...adapter.unlistedValues ?? []].map(
127736
+ (key) => bot.values[key] ?? ""
127737
+ )
127738
+ ]);
127693
127739
  var startConnections = (core, inboundHandlersFor) => {
127694
127740
  const { host, adapterFor, ready } = core;
127695
- const bots = core.bots();
127696
- const connections = [];
127697
- const started = /* @__PURE__ */ new Set();
127741
+ const live = /* @__PURE__ */ new Map();
127742
+ const enqueueForRow = makeKeyedQueue((error) => {
127743
+ host.log.warn("chat could not start an inbound connection", { error: describeError(error) });
127744
+ });
127698
127745
  let disposed = false;
127699
- for (const bot of bots) {
127746
+ const open = (bot, key) => {
127700
127747
  const adapter = adapterFor(bot.platform);
127701
- if (adapter === void 0) continue;
127702
- const fingerprint = `${bot.platform}:${adapter.columns.map((column) => bot.values[column.key] ?? "").join(" ")}`;
127703
- if (started.has(fingerprint)) {
127704
- host.log.warn("chat did not connect a bot that repeats another bot's credentials", {
127705
- bot: bot.displayName,
127706
- platform: bot.platform
127707
- });
127708
- continue;
127709
- }
127710
- started.add(fingerprint);
127711
- void ready.then(() => adapter.connect(bot, inboundHandlersFor(bot))).then((connection) => {
127712
- if (disposed) connection.close();
127713
- else connections.push(connection);
127714
- }).catch((error) => {
127715
- host.log.warn("chat could not start an inbound connection", {
127716
- bot: bot.displayName,
127717
- platform: bot.platform,
127718
- error: describeError(error)
127719
- });
127748
+ let closed = false;
127749
+ let closeConnection;
127750
+ enqueueForRow(bot.rowId, async () => {
127751
+ try {
127752
+ await ready;
127753
+ if (closed || adapter === void 0) return;
127754
+ const connection = await adapter.connect(bot, inboundHandlersFor(bot));
127755
+ if (closed) connection.close();
127756
+ else closeConnection = () => connection.close();
127757
+ } catch (error) {
127758
+ host.log.warn("chat could not start an inbound connection", {
127759
+ bot: bot.displayName,
127760
+ platform: bot.platform,
127761
+ error: describeError(error)
127762
+ });
127763
+ }
127720
127764
  });
127721
- }
127765
+ return {
127766
+ key,
127767
+ close: () => {
127768
+ closed = true;
127769
+ closeConnection?.();
127770
+ }
127771
+ };
127772
+ };
127773
+ const reconcile = () => {
127774
+ const bots = disposed ? [] : core.bots();
127775
+ const wanted = /* @__PURE__ */ new Map();
127776
+ const credentials3 = /* @__PURE__ */ new Set();
127777
+ for (const bot of bots) {
127778
+ const adapter = adapterFor(bot.platform);
127779
+ if (adapter === void 0) continue;
127780
+ const fingerprint = `${bot.platform}:${adapter.columns.map((column) => bot.values[column.key] ?? "").join(" ")}`;
127781
+ if (credentials3.has(fingerprint)) {
127782
+ host.log.warn("chat did not connect a bot that repeats another bot's credentials", {
127783
+ bot: bot.displayName,
127784
+ platform: bot.platform
127785
+ });
127786
+ continue;
127787
+ }
127788
+ credentials3.add(fingerprint);
127789
+ wanted.set(bot.rowId, { bot, key: connectionKey(adapter, bot) });
127790
+ }
127791
+ for (const [rowId, connection] of live) {
127792
+ if (wanted.get(rowId)?.key === connection.key) continue;
127793
+ connection.close();
127794
+ live.delete(rowId);
127795
+ }
127796
+ for (const [rowId, { bot, key }] of wanted) {
127797
+ if (!live.has(rowId)) live.set(rowId, open(bot, key));
127798
+ }
127799
+ return { bots: bots.length, connecting: wanted.size };
127800
+ };
127722
127801
  host.onDispose(() => {
127723
127802
  disposed = true;
127724
- for (const connection of connections) connection.close();
127803
+ reconcile();
127725
127804
  });
127726
- return { bots: bots.length, connecting: started.size };
127805
+ return { reconcile };
127727
127806
  };
127728
127807
 
127729
- // src/keyedQueue.ts
127730
- var makeKeyedQueue = (onError) => {
127731
- const tails = /* @__PURE__ */ new Map();
127732
- return (key, work) => {
127733
- const run = (tails.get(key) ?? Promise.resolve()).then(work).catch(onError).then(() => {
127734
- if (tails.get(key) === run) tails.delete(key);
127808
+ // src/deliveryHttp.ts
127809
+ import * as NodeCrypto from "node:crypto";
127810
+ import * as NodeHttp from "node:http";
127811
+
127812
+ // src/boundedBody.ts
127813
+ var readBoundedBody = (req, limit) => new Promise((resolve) => {
127814
+ const chunks = [];
127815
+ let size = 0;
127816
+ let settled = false;
127817
+ const finish = (result) => {
127818
+ if (settled) return;
127819
+ settled = true;
127820
+ resolve(result);
127821
+ };
127822
+ req.on("data", (chunk) => {
127823
+ if (settled) return;
127824
+ size += chunk.length;
127825
+ if (size > limit) {
127826
+ finish({ kind: "too-large" });
127827
+ req.destroy();
127828
+ return;
127829
+ }
127830
+ chunks.push(chunk);
127831
+ });
127832
+ req.on("end", () => finish({ kind: "ok", bytes: Buffer.concat(chunks) }));
127833
+ req.on("error", () => finish({ kind: "read-failed" }));
127834
+ req.on("close", () => finish({ kind: "read-failed" }));
127835
+ });
127836
+
127837
+ // src/deliveryContract.ts
127838
+ var DELIVERY_SEND_ERROR_CODES = [
127839
+ "bad-request",
127840
+ "unknown-bot",
127841
+ "unknown-target",
127842
+ "bot-not-connected",
127843
+ "delivery-failed"
127844
+ ];
127845
+ var isDeliverySendErrorCode = (value2) => typeof value2 === "string" && DELIVERY_SEND_ERROR_CODES.includes(value2);
127846
+ var DeliverySendError = class extends Error {
127847
+ code;
127848
+ constructor(code2, message) {
127849
+ super(message);
127850
+ this.name = "DeliverySendError";
127851
+ this.code = code2;
127852
+ }
127853
+ };
127854
+
127855
+ // src/deliveryHttp.ts
127856
+ var LOOPBACK = "127.0.0.1";
127857
+ var MAX_BODY_BYTES = 1 * 1024 * 1024;
127858
+ var PATH = "/v1/delivery/messages";
127859
+ var statusFor = (code2) => {
127860
+ switch (code2) {
127861
+ case "bad-request":
127862
+ return 400;
127863
+ case "unauthorized":
127864
+ return 401;
127865
+ case "unknown-bot":
127866
+ case "unknown-target":
127867
+ return 404;
127868
+ case "bot-not-connected":
127869
+ return 503;
127870
+ case "delivery-failed":
127871
+ return 502;
127872
+ case "method-not-allowed":
127873
+ return 405;
127874
+ case "unsupported-media-type":
127875
+ return 415;
127876
+ case "payload-too-large":
127877
+ return 413;
127878
+ }
127879
+ };
127880
+ var writeJson = (res, status, body) => {
127881
+ const payload = JSON.stringify(body);
127882
+ res.writeHead(status, {
127883
+ "content-type": "application/json; charset=utf-8",
127884
+ "content-length": Buffer.byteLength(payload),
127885
+ "cache-control": "no-store"
127886
+ });
127887
+ res.end(payload);
127888
+ };
127889
+ var writeError = (res, code2, message) => {
127890
+ writeJson(res, statusFor(code2), { error: { code: code2, message } });
127891
+ };
127892
+ var isLoopbackRemote = (remote) => {
127893
+ if (remote === void 0 || remote === "") return false;
127894
+ return remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
127895
+ };
127896
+ var timingSafeEqualString = (a, b) => {
127897
+ const left = Buffer.from(a);
127898
+ const right = Buffer.from(b);
127899
+ if (left.length !== right.length) return false;
127900
+ return NodeCrypto.timingSafeEqual(left, right);
127901
+ };
127902
+ var parseSendBody = (raw) => {
127903
+ let parsed;
127904
+ try {
127905
+ parsed = JSON.parse(raw.toString("utf8"));
127906
+ } catch {
127907
+ return "bad-request";
127908
+ }
127909
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return "bad-request";
127910
+ const record2 = parsed;
127911
+ const botId = record2["botId"];
127912
+ const targetId = record2["targetId"];
127913
+ const text3 = record2["text"];
127914
+ const format2 = record2["format"];
127915
+ if (typeof botId !== "string" || botId.trim() === "") return "bad-request";
127916
+ if (typeof targetId !== "string" || targetId.trim() === "") return "bad-request";
127917
+ if (typeof text3 !== "string" || text3.trim() === "") return "bad-request";
127918
+ if (format2 !== void 0 && format2 !== "plain" && format2 !== "markdown") return "bad-request";
127919
+ for (const key of Object.keys(record2)) {
127920
+ if (key !== "botId" && key !== "targetId" && key !== "text" && key !== "format") {
127921
+ return "bad-request";
127922
+ }
127923
+ }
127924
+ return {
127925
+ botId: botId.trim(),
127926
+ targetId: targetId.trim(),
127927
+ text: text3,
127928
+ format: format2 === "markdown" ? "markdown" : format2 === "plain" ? "plain" : void 0
127929
+ };
127930
+ };
127931
+ var startDeliveryHttpServer = async (options) => {
127932
+ const server = NodeHttp.createServer((req, res) => {
127933
+ void (async () => {
127934
+ if (!isLoopbackRemote(req.socket.remoteAddress)) {
127935
+ writeError(res, "unauthorized", "Only loopback clients are accepted.");
127936
+ return;
127937
+ }
127938
+ const url = new URL(req.url ?? "/", `http://${LOOPBACK}`);
127939
+ if (url.pathname !== PATH) {
127940
+ writeError(res, "bad-request", `Unknown path '${url.pathname}'.`);
127941
+ return;
127942
+ }
127943
+ if (req.method !== "POST") {
127944
+ res.setHeader("allow", "POST");
127945
+ writeError(res, "method-not-allowed", "Only POST is accepted.");
127946
+ return;
127947
+ }
127948
+ const contentType = (req.headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase();
127949
+ if (contentType !== "application/json") {
127950
+ writeError(res, "unsupported-media-type", "Content-Type must be application/json.");
127951
+ return;
127952
+ }
127953
+ if (options.token !== "") {
127954
+ const header = req.headers.authorization ?? "";
127955
+ const prefix = "Bearer ";
127956
+ if (!header.startsWith(prefix) || !timingSafeEqualString(header.slice(prefix.length), options.token)) {
127957
+ writeError(res, "unauthorized", "Bearer token required.");
127958
+ return;
127959
+ }
127960
+ }
127961
+ const read = await readBoundedBody(req, MAX_BODY_BYTES);
127962
+ if (read.kind !== "ok") {
127963
+ if (read.kind === "too-large") {
127964
+ writeError(res, "payload-too-large", "JSON body exceeds 1 MiB.");
127965
+ } else {
127966
+ writeError(res, "bad-request", "Failed to read body.");
127967
+ }
127968
+ return;
127969
+ }
127970
+ const parsed = parseSendBody(read.bytes);
127971
+ if (typeof parsed === "string") {
127972
+ writeError(res, parsed, "Invalid JSON body.");
127973
+ return;
127974
+ }
127975
+ try {
127976
+ const result = await options.send(parsed);
127977
+ writeJson(res, 200, { sent: result.sent });
127978
+ } catch (error) {
127979
+ const rawCode = error instanceof Error ? error.code : void 0;
127980
+ const code2 = isDeliverySendErrorCode(rawCode) ? rawCode : "delivery-failed";
127981
+ const message = error instanceof Error ? error.message : String(error);
127982
+ options.log?.warn("delivery http send failed", { code: code2, message });
127983
+ writeError(res, code2, message);
127984
+ }
127985
+ })().catch((error) => {
127986
+ options.log?.warn("delivery http handler crashed", {
127987
+ error: error instanceof Error ? error.message : String(error)
127988
+ });
127989
+ if (!res.headersSent) writeError(res, "delivery-failed", "Internal error.");
127735
127990
  });
127736
- tails.set(key, run);
127991
+ });
127992
+ await new Promise((resolve, reject) => {
127993
+ server.once("error", reject);
127994
+ server.listen(options.port, LOOPBACK, () => resolve());
127995
+ });
127996
+ const address = server.address();
127997
+ const port = address.port;
127998
+ options.log?.info("delivery http listening", { host: LOOPBACK, port, path: PATH });
127999
+ return {
128000
+ port,
128001
+ origin: `http://${LOOPBACK}:${String(port)}`,
128002
+ close: () => new Promise((resolve, reject) => {
128003
+ server.close((error) => error ? reject(error) : resolve());
128004
+ })
128005
+ };
128006
+ };
128007
+
128008
+ // src/deliveryHttpLifecycle.ts
128009
+ var makeDeliveryHttp = (input) => {
128010
+ const start = input.start ?? startDeliveryHttpServer;
128011
+ let running;
128012
+ let disposed = false;
128013
+ const stop = async () => {
128014
+ const current = running;
128015
+ running = void 0;
128016
+ if (current === void 0) return;
128017
+ await current.server.close().catch((error) => {
128018
+ input.log.warn("delivery http could not close", { error: describeError(error) });
128019
+ });
128020
+ };
128021
+ input.onDispose(() => {
128022
+ disposed = true;
128023
+ void stop();
128024
+ });
128025
+ return {
128026
+ apply: async (config2) => {
128027
+ const port = readDeliveryHttpPort(config2);
128028
+ const token = readDeliveryHttpToken(config2);
128029
+ const wanted = readDeliveryHttpEnabled(config2) ? `${String(port)}
128030
+ ${token}` : void 0;
128031
+ if (wanted === running?.listening) return;
128032
+ await stop();
128033
+ if (wanted === void 0 || disposed) return;
128034
+ try {
128035
+ const server = await start({
128036
+ port,
128037
+ token,
128038
+ send: input.send,
128039
+ log: {
128040
+ info: (message, fields) => input.log.info(message, fields),
128041
+ warn: (message, fields) => input.log.warn(message, fields)
128042
+ }
128043
+ });
128044
+ running = { listening: wanted, server };
128045
+ if (disposed) await stop();
128046
+ } catch (error) {
128047
+ input.log.warn("delivery http failed to listen", { port, error: describeError(error) });
128048
+ }
128049
+ }
127737
128050
  };
127738
128051
  };
127739
128052
 
@@ -127804,7 +128117,7 @@ var makeImCore = (input) => {
127804
128117
  host.log.warn("chat could not deliver", { error: describeError(error) });
127805
128118
  }
127806
128119
  };
127807
- const bots = () => readBots(input.config, adapterFor);
128120
+ const bots = () => readBots(input.config(), adapterFor);
127808
128121
  const bindingForChatKey = (chatKey) => findBindingByChatKey(bots(), state, chatKey);
127809
128122
  return {
127810
128123
  host,
@@ -128118,6 +128431,64 @@ var makeCooldown = (windowMs, now, maxKeys = Number.POSITIVE_INFINITY) => {
128118
128431
  };
128119
128432
  };
128120
128433
 
128434
+ // src/boundedMap.ts
128435
+ var makeBoundedMap = (limit) => {
128436
+ const entries = /* @__PURE__ */ new Map();
128437
+ return {
128438
+ get: (key) => entries.get(key),
128439
+ set: (key, value2) => {
128440
+ entries.delete(key);
128441
+ entries.set(key, value2);
128442
+ if (entries.size > limit) {
128443
+ const oldest = entries.keys().next().value;
128444
+ if (oldest !== void 0) entries.delete(oldest);
128445
+ }
128446
+ }
128447
+ };
128448
+ };
128449
+
128450
+ // src/replyAnchors.ts
128451
+ var THREAD_LIMIT = 256;
128452
+ var TURNS_PER_THREAD = 16;
128453
+ var anchorOfMessage = (adapter, message) => adapter?.quotesReplies === true ? { messageId: message.messageId, inThread: message.replyInThread === true } : void 0;
128454
+ var makeReplyAnchors = (host) => {
128455
+ const byThread = makeBoundedMap(THREAD_LIMIT);
128456
+ const userMessageOfTurn = async (threadId, turnId) => {
128457
+ const execution = await host.projections.getThreadExecution(threadId).catch(() => null);
128458
+ if (execution === null) return void 0;
128459
+ const latest = execution.latestTurn;
128460
+ const wanted = turnId ?? execution.session?.activeTurnId ?? latest?.turnId;
128461
+ if (wanted == null) return void 0;
128462
+ const intent = execution.turnIntents.find((candidate) => candidate.turn?.turnId === wanted);
128463
+ return intent?.messageId ?? (latest?.turnId === wanted ? latest.userMessageId : void 0);
128464
+ };
128465
+ return {
128466
+ /**
128467
+ * 开轮时记一笔:`userMessageId` 是派给宿主的那条用户消息,`binding` 是收到聊天消息的那个
128468
+ * 聊天(带着它的锚点)。没有锚点(平台不引用)就不记。
128469
+ */
128470
+ note: (threadId, userMessageId, binding) => {
128471
+ if (binding.reply === void 0) return;
128472
+ const turns = byThread.get(threadId) ?? makeBoundedMap(TURNS_PER_THREAD);
128473
+ byThread.set(threadId, turns);
128474
+ turns.set(userMessageId, { chatKey: chatKeyOf(binding), anchor: binding.reply });
128475
+ },
128476
+ /**
128477
+ * 给发往这条会话的聊天挂上 `turnId` 那一轮的锚点(null 的含义见上);取不到就原样返回。
128478
+ *
128479
+ * 锚点只配给它那条消息所在的聊天:会话可以被另一个聊天接走(`/resume`),而回复接口只按被
128480
+ * 回复的消息寻址,挂错了答复就发回原来的聊天。
128481
+ */
128482
+ anchored: async (binding, threadId, turnId) => {
128483
+ const turns = byThread.get(threadId);
128484
+ if (turns === void 0) return binding;
128485
+ const asked = await userMessageOfTurn(threadId, turnId);
128486
+ const noted = asked === void 0 ? void 0 : turns.get(asked);
128487
+ return noted?.chatKey === chatKeyOf(binding) ? { ...binding, reply: noted.anchor } : binding;
128488
+ }
128489
+ };
128490
+ };
128491
+
128121
128492
  // src/inbound.ts
128122
128493
  var SEEN_MESSAGE_LIMIT = 256;
128123
128494
  var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
@@ -128134,7 +128505,7 @@ var makeInbound = (core, deps) => {
128134
128505
  const { questionFlow, handleButtonClick } = deps.interactions;
128135
128506
  const { answerByToken } = deps.approvals;
128136
128507
  const { handlePanelClick } = deps.panelWiring;
128137
- const { startTurn, receiveAttachmentsOnly } = deps.turns;
128508
+ const { startTurn, receiveAttachmentsOnly, hasWaitingAttachments } = deps.turns;
128138
128509
  const { fetchTeams, startTeamRun } = deps.teams;
128139
128510
  const { markWorking } = deps.acknowledgements;
128140
128511
  const archivedReplyCooldown = makeCooldown(ARCHIVED_REPLY_COOLDOWN_MS, () => Date.now());
@@ -128146,21 +128517,49 @@ var makeInbound = (core, deps) => {
128146
128517
  );
128147
128518
  const projectPromptCooldown = makeCooldown(PROJECT_PROMPT_COOLDOWN_MS, () => Date.now());
128148
128519
  const handledMessages = makeBoundedSeen(SEEN_MESSAGE_LIMIT);
128149
- const alreadyHandled = (target, messageId) => {
128150
- return !handledMessages.claim(`${chatKeyOf(target)}:${messageId}`);
128520
+ const alreadyHandled = (target2, messageId) => {
128521
+ return !handledMessages.claim(`${chatKeyOf(target2)}:${messageId}`);
128151
128522
  };
128152
- const turnAwayStranger = (target, sender) => {
128523
+ const turnAwayStranger = (target2, sender) => {
128153
128524
  host.log.warn("chat ignored a message from someone not on the allow list", {
128154
- bot: target.displayName,
128525
+ bot: target2.displayName,
128155
128526
  sender
128156
128527
  });
128157
128528
  if (sender === null) return;
128158
- if (!notAllowedReplyCooldown.tryAcquire(`${target.rowId}:${sender}`)) return;
128529
+ if (!notAllowedReplyCooldown.tryAcquire(`${target2.rowId}:${sender}`)) return;
128159
128530
  enqueueForChat(
128160
- chatKeyOf(target),
128161
- () => deliver(target, t("reply.notAllowedYet", { id: sender }))
128531
+ chatKeyOf(target2),
128532
+ () => deliver(target2, t("reply.notAllowedYet", { id: sender }))
128162
128533
  );
128163
128534
  };
128535
+ const currentBot = (rowId) => bots().find((candidate) => candidate.rowId === rowId);
128536
+ const inheritFromParent = (bot, chatId, parentChatId) => {
128537
+ if (parentChatId === void 0) return;
128538
+ const chatKey = chatKeyOf({ rowId: bot.rowId, chatId });
128539
+ if (chatOf(core.state, chatKey).projectId) return;
128540
+ const parent = chatOf(core.state, chatKeyOf({ rowId: bot.rowId, chatId: parentChatId }));
128541
+ if (!parent.projectId) return;
128542
+ commit("chat-updated", {
128543
+ chatKey,
128544
+ projectId: parent.projectId,
128545
+ model: parent.model,
128546
+ plan: parent.plan
128547
+ });
128548
+ };
128549
+ const noteChatAlias = (bot, alias, chatId) => {
128550
+ if (chatAliasOf(core.state, bot.rowId, alias) !== void 0) return;
128551
+ commit("chat-aliased", { rowId: bot.rowId, alias, chatId });
128552
+ };
128553
+ const settleChat = (bot, message) => {
128554
+ if (message.chatAlias === void 0) return message;
128555
+ const known = chatAliasOf(core.state, bot.rowId, message.chatAlias);
128556
+ if (known === void 0) noteChatAlias(bot, message.chatAlias, message.chatId);
128557
+ return known === void 0 || known === message.chatId ? message : { ...message, chatId: known };
128558
+ };
128559
+ const openTurn = async (speaker, message) => {
128560
+ const threadId = await startTurn(speaker, message);
128561
+ if (threadId !== null) await markWorking(speaker, threadId, message.messageId);
128562
+ };
128164
128563
  const inboundHandlersFor = (bot) => ({
128165
128564
  onError: (message) => host.log.warn("chat inbound connection failed", {
128166
128565
  platform: bot.platform,
@@ -128168,10 +128567,16 @@ var makeInbound = (core, deps) => {
128168
128567
  message
128169
128568
  }),
128170
128569
  onButton: handleButtonClick,
128171
- onPanelClick: (click) => handlePanelClick(bot, click),
128172
- onMessage: (message) => {
128173
- const current = bots().find((candidate) => candidate.rowId === bot.rowId);
128570
+ onPanelClick: (click) => {
128571
+ const current = currentBot(bot.rowId);
128572
+ return current === void 0 ? null : handlePanelClick(current, click);
128573
+ },
128574
+ noteChatAlias: (alias, chatId) => noteChatAlias(bot, alias, chatId),
128575
+ knowsChatAlias: (alias) => chatAliasOf(core.state, bot.rowId, alias) !== void 0,
128576
+ onMessage: (received) => {
128577
+ const current = currentBot(bot.rowId);
128174
128578
  if (current === void 0) return;
128579
+ const message = settleChat(current, received);
128175
128580
  const arrived = bindingOf2(current, message.chatId);
128176
128581
  if (alreadyHandled(arrived, message.messageId)) return;
128177
128582
  if (message.chatKind !== void 0 && chatOf(core.state, chatKeyOf(arrived)).chatKind !== message.chatKind) {
@@ -128201,16 +128606,20 @@ var makeInbound = (core, deps) => {
128201
128606
  return;
128202
128607
  }
128203
128608
  enqueueForChat(chatKeyOf(arrived), async () => {
128204
- const target = bindingOf2(current, message.chatId);
128205
- const chatKey = chatKeyOf(target);
128206
- const teams = await fetchTeams(target);
128207
- if (await handleCommand(target, message.text, teams ?? [], message.senderId)) return;
128208
- if (target.projectId === "") {
128609
+ inheritFromParent(current, message.chatId, message.parentChatId);
128610
+ const target2 = {
128611
+ ...bindingOf2(current, message.chatId),
128612
+ reply: anchorOfMessage(adapterFor(current.platform), message)
128613
+ };
128614
+ const chatKey = chatKeyOf(target2);
128615
+ const teams = await fetchTeams(target2);
128616
+ if (await handleCommand(target2, message.text, teams ?? [], message.senderId)) return;
128617
+ if (target2.projectId === "") {
128209
128618
  const asked = message.text.trim();
128210
128619
  if (/^[1-9]\d{0,2}$/u.test(asked) && listedProjectsByChat.has(chatKey)) {
128211
- await handleRepo(target, asked);
128620
+ await handleRepo(target2, asked);
128212
128621
  } else if (projectPromptCooldown.tryAcquire(chatKey)) {
128213
- await promptProject(target);
128622
+ await promptProject(target2);
128214
128623
  }
128215
128624
  return;
128216
128625
  }
@@ -128218,23 +128627,23 @@ var makeInbound = (core, deps) => {
128218
128627
  if (boundThread !== null && message.text !== "" && await questionFlow.answerByText(chatKey, boundThread, message.text)) {
128219
128628
  return;
128220
128629
  }
128221
- const project = await host.projections.getProject(target.projectId);
128630
+ const project = await host.projections.getProject(target2.projectId);
128222
128631
  if (project === null || project.archivedAt != null) {
128223
128632
  if (project === null) {
128224
128633
  host.log.warn("chat cannot start work: the chat's project is gone", {
128225
- bot: target.displayName,
128226
- projectId: target.projectId
128634
+ bot: target2.displayName,
128635
+ projectId: target2.projectId
128227
128636
  });
128228
128637
  }
128229
128638
  if (archivedReplyCooldown.tryAcquire(chatKey)) {
128230
128639
  await deliver(
128231
- target,
128640
+ target2,
128232
128641
  project === null ? t("reply.projectMissing") : t("reply.projectArchived", { title: project.title })
128233
128642
  );
128234
128643
  }
128235
128644
  return;
128236
128645
  }
128237
- const routed = targetOf(target);
128646
+ const routed = targetOf(target2);
128238
128647
  const attachmentsOnly = message.text.trim() === "" && message.resources.length > 0;
128239
128648
  if (routed.kind === "team") {
128240
128649
  if (teams !== null && !teams.some((team) => team.teamId === routed.teamId)) {
@@ -128243,21 +128652,28 @@ var makeInbound = (core, deps) => {
128243
128652
  target: { kind: "thread" },
128244
128653
  threadId: null
128245
128654
  });
128246
- await deliver(target, t("reply.teamGone"));
128655
+ await deliver(target2, t("reply.teamGone"));
128247
128656
  } else {
128248
- if (message.resources.length > 0) await deliver(target, t("reply.attachmentsNoTeam"));
128657
+ if (message.resources.length > 0) await deliver(target2, t("reply.attachmentsNoTeam"));
128249
128658
  if (attachmentsOnly) return;
128250
- await startTeamRun(target, routed.teamId, message.text);
128251
- await markWorking(target, chatKey, message.messageId);
128659
+ await startTeamRun(target2, routed.teamId, message.text);
128660
+ await markWorking(target2, chatKey, message.messageId);
128252
128661
  return;
128253
128662
  }
128254
128663
  }
128255
128664
  if (attachmentsOnly) {
128256
- await receiveAttachmentsOnly(target, message);
128665
+ await receiveAttachmentsOnly(target2, message);
128257
128666
  return;
128258
128667
  }
128259
- const threadId = await startTurn(target, message);
128260
- if (threadId !== null) await markWorking(target, threadId, message.messageId);
128668
+ const branchChatId = hasWaitingAttachments(chatKey) ? void 0 : message.branchChatId;
128669
+ if (branchChatId === void 0) {
128670
+ await openTurn(target2, message);
128671
+ return;
128672
+ }
128673
+ enqueueForChat(chatKeyOf({ rowId: current.rowId, chatId: branchChatId }), async () => {
128674
+ inheritFromParent(current, branchChatId, message.chatId);
128675
+ await openTurn({ ...bindingOf2(current, branchChatId), reply: target2.reply }, message);
128676
+ });
128261
128677
  });
128262
128678
  }
128263
128679
  });
@@ -128376,12 +128792,12 @@ var makeQuestionFlow = (host) => {
128376
128792
  const redraw = async (requestId, questionId) => {
128377
128793
  const record2 = recordOf(requestId);
128378
128794
  const item = itemOf(requestId, questionId);
128379
- const target = record2 === void 0 ? void 0 : host.target(record2.chatKey);
128380
- if (record2 === void 0 || item?.cardId === void 0 || target === void 0) return;
128795
+ const target2 = record2 === void 0 ? void 0 : host.target(record2.chatKey);
128796
+ if (record2 === void 0 || item?.cardId === void 0 || target2 === void 0) return;
128381
128797
  if (record2.resolved || item.answer !== void 0) return;
128382
128798
  try {
128383
- await target.adapter.choiceCards?.update(
128384
- target.binding,
128799
+ await target2.adapter.choiceCards?.update(
128800
+ target2.binding,
128385
128801
  item.cardId,
128386
128802
  questionCardOf(item, record2.items.indexOf(item), record2.items.length, t)
128387
128803
  );
@@ -128392,11 +128808,11 @@ var makeQuestionFlow = (host) => {
128392
128808
  const freeze = async (requestId, questionId) => {
128393
128809
  const record2 = recordOf(requestId);
128394
128810
  const item = itemOf(requestId, questionId);
128395
- const target = record2 === void 0 ? void 0 : host.target(record2.chatKey);
128396
- if (record2 === void 0 || item?.cardId === void 0 || target === void 0) return;
128811
+ const target2 = record2 === void 0 ? void 0 : host.target(record2.chatKey);
128812
+ if (record2 === void 0 || item?.cardId === void 0 || target2 === void 0) return;
128397
128813
  try {
128398
- await target.adapter.choiceCards?.update(
128399
- target.binding,
128814
+ await target2.adapter.choiceCards?.update(
128815
+ target2.binding,
128400
128816
  item.cardId,
128401
128817
  questionSettledCardOf(
128402
128818
  item,
@@ -128422,9 +128838,9 @@ var makeQuestionFlow = (host) => {
128422
128838
  };
128423
128839
  const post = async (requestId) => {
128424
128840
  const first = recordOf(requestId);
128425
- const target = first === void 0 ? void 0 : host.target(first.chatKey);
128426
- if (first === void 0 || target === void 0) return;
128427
- const cards = target.adapter.choiceCards;
128841
+ const target2 = first === void 0 ? void 0 : host.target(first.chatKey);
128842
+ if (first === void 0 || target2 === void 0) return;
128843
+ const cards = target2.adapter.choiceCards;
128428
128844
  if (cards === void 0) {
128429
128845
  await sayNext(requestId);
128430
128846
  return;
@@ -128435,7 +128851,7 @@ var makeQuestionFlow = (host) => {
128435
128851
  if (record2 === void 0 || record2.resolved || item === void 0) return;
128436
128852
  try {
128437
128853
  const cardId = await cards.open(
128438
- target.binding,
128854
+ await host.anchored(target2.binding, record2.threadId),
128439
128855
  questionCardOf(item, record2.items.indexOf(item), record2.items.length, t)
128440
128856
  );
128441
128857
  if (cardId === null) continue;
@@ -128450,10 +128866,10 @@ var makeQuestionFlow = (host) => {
128450
128866
  }
128451
128867
  };
128452
128868
  const offer = (chatKey, threadId, pending) => {
128453
- const target = host.target(chatKey);
128454
- if (target === void 0) return;
128455
- const buttons = target.adapter.choiceCards !== void 0;
128456
- if (!buttons && target.adapter.answerByText === void 0) {
128869
+ const target2 = host.target(chatKey);
128870
+ if (target2 === void 0) return;
128871
+ const buttons = target2.adapter.choiceCards !== void 0;
128872
+ if (!buttons && target2.adapter.answerByText === void 0) {
128457
128873
  host.warn("chat cannot show questions on this platform");
128458
128874
  return;
128459
128875
  }
@@ -128673,9 +129089,9 @@ var makeQueueFlow = (host, deps) => {
128673
129089
  queuePaused: execution.queuePaused,
128674
129090
  intent: execution.intents.find((intent) => intent.intentId === intentId)
128675
129091
  });
128676
- const noteQueued = async (chatKey, threadId, messageId, text3) => {
128677
- const target = host.target(chatKey);
128678
- if (target === void 0) return;
129092
+ const noteQueued = async (chatKey, threadId, messageId, text3, reply) => {
129093
+ const target2 = host.target(chatKey);
129094
+ if (target2 === void 0) return;
128679
129095
  let execution;
128680
129096
  try {
128681
129097
  execution = await deps.readExecution(threadId);
@@ -128689,7 +129105,7 @@ var makeQueueFlow = (host, deps) => {
128689
129105
  (other) => other.intentId !== intent.intentId && LIVE_STATUSES.has(other.status) && other.sequence < intent.sequence
128690
129106
  );
128691
129107
  if (!waiting) return;
128692
- const cards = target.adapter.choiceCards;
129108
+ const cards = target2.adapter.choiceCards;
128693
129109
  if (cards === void 0) {
128694
129110
  await host.say(chatKey, t("queue.plainNotice"));
128695
129111
  return;
@@ -128718,7 +129134,10 @@ var makeQueueFlow = (host, deps) => {
128718
129134
  host.commit("queue-settled", { intentId, outcome: view.outcome });
128719
129135
  return;
128720
129136
  }
128721
- const cardId = await cards.open(target.binding, queueCardOf(record2, view, null, t));
129137
+ const cardId = await cards.open(
129138
+ { ...target2.binding, reply },
129139
+ queueCardOf(record2, view, null, t)
129140
+ );
128722
129141
  if (cardId === null) return;
128723
129142
  host.commit("queue-carded", { intentId, cardId });
128724
129143
  shown.set(intentId, queueViewKey(view, null));
@@ -128729,8 +129148,8 @@ var makeQueueFlow = (host, deps) => {
128729
129148
  };
128730
129149
  const redraw = async (intentId, released) => {
128731
129150
  const record2 = recordOf(intentId);
128732
- const target = record2 === void 0 ? void 0 : host.target(record2.chatKey);
128733
- if (record2 === void 0 || record2.settled !== void 0 || target === void 0) return;
129151
+ const target2 = record2 === void 0 ? void 0 : host.target(record2.chatKey);
129152
+ if (record2 === void 0 || record2.settled !== void 0 || target2 === void 0) return;
128734
129153
  if (record2.cardId === void 0) return;
128735
129154
  let execution;
128736
129155
  try {
@@ -128754,14 +129173,14 @@ var makeQueueFlow = (host, deps) => {
128754
129173
  }
128755
129174
  };
128756
129175
  const paint = async (record2, view) => {
128757
- const target = host.target(record2.chatKey);
128758
- const cards = target?.adapter.choiceCards;
128759
- if (target === void 0 || cards === void 0 || record2.cardId === void 0) return;
129176
+ const target2 = host.target(record2.chatKey);
129177
+ const cards = target2?.adapter.choiceCards;
129178
+ if (target2 === void 0 || cards === void 0 || record2.cardId === void 0) return;
128760
129179
  const note = notes.get(record2.intentId) ?? null;
128761
129180
  const key = queueViewKey(view, note);
128762
129181
  if (shown.get(record2.intentId) === key) return;
128763
129182
  try {
128764
- await cards.update(target.binding, record2.cardId, queueCardOf(record2, view, note, t));
129183
+ await cards.update(target2.binding, record2.cardId, queueCardOf(record2, view, note, t));
128765
129184
  shown.set(record2.intentId, key);
128766
129185
  } catch (error) {
128767
129186
  host.warn("chat could not update the queued message card", error);
@@ -128878,6 +129297,8 @@ var makeInteractions = (core, deps) => {
128878
129297
  const adapter = binding === void 0 ? void 0 : adapterFor(binding.platform);
128879
129298
  return binding === void 0 || adapter === void 0 ? void 0 : { binding, adapter };
128880
129299
  },
129300
+ // 提问是在跑的那一轮问的。
129301
+ anchored: (binding, threadId) => deps.anchors.anchored(binding, threadId, null),
128881
129302
  allowed: (chatKey, operatorId) => {
128882
129303
  const binding = bindingForChatKey(chatKey);
128883
129304
  if (binding !== void 0 && isAllowedSender(binding, operatorId)) return true;
@@ -128956,6 +129377,9 @@ var makeInteractions = (core, deps) => {
128956
129377
  const resolved = readRequestResolved(event);
128957
129378
  if (resolved === null) return;
128958
129379
  await ready;
129380
+ if (resolved.outcome !== "cancelled" && event.threadId !== null) {
129381
+ deps.rotateTurnCard(event.threadId);
129382
+ }
128959
129383
  if (resolved.requestKind === "user-input") {
128960
129384
  questionFlow.resolved(
128961
129385
  resolved.requestId,
@@ -130774,24 +131198,50 @@ var fetchTenantToken = async (input) => {
130774
131198
  }
130775
131199
  return { token: body.tenant_access_token, expiresInSeconds: body.expire ?? 0 };
130776
131200
  };
130777
- var sendChatText = async (input) => {
131201
+ var postMessage = async (input) => {
131202
+ const headers = {
131203
+ authorization: `Bearer ${input.token}`,
131204
+ "content-type": "application/json; charset=utf-8"
131205
+ };
131206
+ const message = { msg_type: input.msgType, content: JSON.stringify(input.content) };
131207
+ const sentOf = async (response2) => (await readEnvelope(input.operation, response2)).data;
131208
+ if (input.reply !== void 0) {
131209
+ try {
131210
+ const response2 = await input.http(
131211
+ `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.reply.messageId)}/reply`,
131212
+ {
131213
+ method: "POST",
131214
+ headers,
131215
+ body: JSON.stringify({
131216
+ ...message,
131217
+ ...input.reply.inThread ? { reply_in_thread: true } : {}
131218
+ })
131219
+ }
131220
+ );
131221
+ const sent = await sentOf(response2);
131222
+ if (input.reply.inThread) input.onThreadReply?.(sent?.thread_id || void 0);
131223
+ return sent?.message_id ?? null;
131224
+ } catch (error) {
131225
+ if (!(error instanceof LarkApiError)) throw error;
131226
+ }
131227
+ }
130778
131228
  const idType = input.receiveIdType ?? "chat_id";
130779
131229
  const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=${idType}`, {
130780
131230
  method: "POST",
130781
- headers: {
130782
- authorization: `Bearer ${input.token}`,
130783
- "content-type": "application/json; charset=utf-8"
130784
- },
130785
- body: JSON.stringify({
130786
- receive_id: input.chatId,
130787
- msg_type: "text",
130788
- // content 是一个 JSON **字符串**,不是对象——飞书这条接口就是这么定的。
130789
- content: JSON.stringify({ text: input.text })
130790
- })
131231
+ headers,
131232
+ body: JSON.stringify({ receive_id: input.chatId, ...message })
130791
131233
  });
130792
- const body = await readEnvelope("send", response);
130793
- return body.data?.message_id ?? null;
131234
+ return (await sentOf(response))?.message_id ?? null;
131235
+ };
131236
+ var fetchMessageThreadId = async (input) => {
131237
+ const response = await input.http(
131238
+ `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}`,
131239
+ { headers: { authorization: `Bearer ${input.token}` } }
131240
+ );
131241
+ const body = await readEnvelope("message.get", response);
131242
+ return body.data?.items?.[0]?.thread_id || void 0;
130794
131243
  };
131244
+ var sendChatText = async (input) => postMessage({ ...input, operation: "send", msgType: "text", content: { text: input.text } });
130795
131245
  var LARK_WORKING_REACTION = "OnIt";
130796
131246
  var addReaction = async (input) => {
130797
131247
  const response = await input.http(
@@ -130851,54 +131301,19 @@ var uploadFile = async (input) => {
130851
131301
  }
130852
131302
  return body.data.file_key;
130853
131303
  };
130854
- var sendChatFile = async (input) => {
130855
- const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=chat_id`, {
130856
- method: "POST",
130857
- headers: {
130858
- authorization: `Bearer ${input.token}`,
130859
- "content-type": "application/json; charset=utf-8"
130860
- },
130861
- body: JSON.stringify({
130862
- receive_id: input.chatId,
130863
- msg_type: "file",
130864
- content: JSON.stringify({ file_key: input.fileKey })
130865
- })
130866
- });
130867
- const body = await readEnvelope("send.file", response);
130868
- return body.data?.message_id ?? null;
130869
- };
130870
- var sendChatImage = async (input) => {
130871
- const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=chat_id`, {
130872
- method: "POST",
130873
- headers: {
130874
- authorization: `Bearer ${input.token}`,
130875
- "content-type": "application/json; charset=utf-8"
130876
- },
130877
- body: JSON.stringify({
130878
- receive_id: input.chatId,
130879
- msg_type: "image",
130880
- content: JSON.stringify({ image_key: input.imageKey })
130881
- })
130882
- });
130883
- const body = await readEnvelope("send.image", response);
130884
- return body.data?.message_id ?? null;
130885
- };
130886
- var sendCard = async (input) => {
130887
- const response = await input.http(`${LARK_BASE}/im/v1/messages?receive_id_type=chat_id`, {
130888
- method: "POST",
130889
- headers: {
130890
- authorization: `Bearer ${input.token}`,
130891
- "content-type": "application/json; charset=utf-8"
130892
- },
130893
- body: JSON.stringify({
130894
- receive_id: input.chatId,
130895
- msg_type: "interactive",
130896
- content: JSON.stringify(input.card)
130897
- })
130898
- });
130899
- const body = await readEnvelope("send.card", response);
130900
- return body.data?.message_id ?? null;
130901
- };
131304
+ var sendChatFile = async (input) => postMessage({
131305
+ ...input,
131306
+ operation: "send.file",
131307
+ msgType: "file",
131308
+ content: { file_key: input.fileKey }
131309
+ });
131310
+ var sendChatImage = async (input) => postMessage({
131311
+ ...input,
131312
+ operation: "send.image",
131313
+ msgType: "image",
131314
+ content: { image_key: input.imageKey }
131315
+ });
131316
+ var sendCard = async (input) => postMessage({ ...input, operation: "send.card", msgType: "interactive", content: input.card });
130902
131317
  var patchCard = async (input) => {
130903
131318
  const response = await input.http(
130904
131319
  `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}`,
@@ -132374,13 +132789,13 @@ var LinkifyIt = class {
132374
132789
  var import_punycode = __toESM(require_punycode(), 1);
132375
132790
  var __defProp2 = Object.defineProperty;
132376
132791
  var __exportAll = (all, no_symbols) => {
132377
- let target = {};
132378
- for (var name in all) __defProp2(target, name, {
132792
+ let target2 = {};
132793
+ for (var name in all) __defProp2(target2, name, {
132379
132794
  get: all[name],
132380
132795
  enumerable: true
132381
132796
  });
132382
- if (!no_symbols) __defProp2(target, Symbol.toStringTag, { value: "Module" });
132383
- return target;
132797
+ if (!no_symbols) __defProp2(target2, Symbol.toStringTag, { value: "Module" });
132798
+ return target2;
132384
132799
  };
132385
132800
  var utils_exports = /* @__PURE__ */ __exportAll({
132386
132801
  arrayReplaceAt: () => arrayReplaceAt,
@@ -136199,7 +136614,8 @@ var STATUS_TEMPLATE = {
136199
136614
  working: "wathet",
136200
136615
  done: "green",
136201
136616
  error: "red",
136202
- interrupted: "orange"
136617
+ interrupted: "orange",
136618
+ continued: "grey"
136203
136619
  };
136204
136620
  var encoder = new TextEncoder();
136205
136621
  var stripAngleBrackets = (text3) => text3.replaceAll("<", "").replaceAll(">", "");
@@ -136383,9 +136799,9 @@ var styledText = (text3, style) => {
136383
136799
  if (style?.includes("underline")) out = `<u>${out}</u>`;
136384
136800
  return out;
136385
136801
  };
136386
- var nonEmpty3 = (value2) => typeof value2 === "string" && value2 !== "";
136802
+ var nonEmpty4 = (value2) => typeof value2 === "string" && value2 !== "";
136387
136803
  var resourceOf = (element, index) => {
136388
- if (element.tag === "img" && nonEmpty3(element.image_key)) {
136804
+ if (element.tag === "img" && nonEmpty4(element.image_key)) {
136389
136805
  return {
136390
136806
  fileKey: element.image_key,
136391
136807
  kind: "image",
@@ -136393,14 +136809,14 @@ var resourceOf = (element, index) => {
136393
136809
  mimeType: "image/png"
136394
136810
  };
136395
136811
  }
136396
- if ((element.tag === "media" || element.tag === "file") && nonEmpty3(element.file_key)) {
136812
+ if ((element.tag === "media" || element.tag === "file") && nonEmpty4(element.file_key)) {
136397
136813
  const video = element.tag === "media";
136398
- const name = nonEmpty3(element.file_name) ? element.file_name : video ? `video-${String(index)}.mp4` : `file-${String(index)}`;
136814
+ const name = nonEmpty4(element.file_name) ? element.file_name : video ? `video-${String(index)}.mp4` : `file-${String(index)}`;
136399
136815
  return {
136400
136816
  fileKey: element.file_key,
136401
136817
  kind: "file",
136402
136818
  name,
136403
- mimeType: video && !nonEmpty3(element.file_name) ? "video/mp4" : mimeFromName(name)
136819
+ mimeType: video && !nonEmpty4(element.file_name) ? "video/mp4" : mimeFromName(name)
136404
136820
  };
136405
136821
  }
136406
136822
  return null;
@@ -136412,11 +136828,11 @@ var serializeElement = (element, index, botIds) => {
136412
136828
  return styledText(element.text ?? "", element.style);
136413
136829
  case "a": {
136414
136830
  const label = element.text ?? element.href ?? "";
136415
- return nonEmpty3(element.href) ? `[${label}](${element.href})` : label;
136831
+ return nonEmpty4(element.href) ? `[${label}](${element.href})` : label;
136416
136832
  }
136417
136833
  case "at": {
136418
136834
  if (element.user_id !== void 0 && botIds.has(element.user_id)) return "";
136419
- return nonEmpty3(element.user_name) ? `@${element.user_name}` : "";
136835
+ return nonEmpty4(element.user_name) ? `@${element.user_name}` : "";
136420
136836
  }
136421
136837
  case "code_block":
136422
136838
  return `\`\`\`${element.language ?? ""}
@@ -136456,7 +136872,7 @@ var serializePost = (content, botIds = /* @__PURE__ */ new Set()) => {
136456
136872
  for (const element of elements) {
136457
136873
  const index = indexOf.get(element);
136458
136874
  const key = element.tag === "img" ? element.image_key : element.file_key;
136459
- if (index !== void 0 && nonEmpty3(key)) {
136875
+ if (index !== void 0 && nonEmpty4(key)) {
136460
136876
  placeholderByKey.set(key, placeholderOf(element, index));
136461
136877
  }
136462
136878
  }
@@ -136547,7 +136963,7 @@ var readText = (content) => {
136547
136963
  const text3 = readJson(content)?.["text"];
136548
136964
  return typeof text3 === "string" ? text3 : null;
136549
136965
  };
136550
- var parseInboundMessage = (event, appId, groupMention = "always") => {
136966
+ var parseInboundMessage = (event, appId, groupMention = "always", topicOnMention = true) => {
136551
136967
  if (event.sender?.sender_type === "app") return null;
136552
136968
  const message = event.message;
136553
136969
  if (message === void 0) return null;
@@ -136557,6 +136973,11 @@ var parseInboundMessage = (event, appId, groupMention = "always") => {
136557
136973
  const messageType = message.message_type ?? "";
136558
136974
  if (!passesGroupGate(event, appId, groupMention, messageType)) return null;
136559
136975
  const senderOpenId = event.sender?.sender_id?.open_id ?? null;
136976
+ const threadId = typeof message.thread_id === "string" && message.thread_id !== "" ? message.thread_id : void 0;
136977
+ const inThread = threadId !== void 0 || topicOnMention && mentionsApp(event, appId);
136978
+ const rootGiven = typeof message.root_id === "string" && message.root_id !== "";
136979
+ const rootId = rootGiven && message.root_id !== void 0 ? message.root_id : messageId;
136980
+ const topic = chatType === "p2p" || !inThread ? {} : threadId === void 0 ? { topic: { rootMessageId: messageId } } : { topic: { rootMessageId: rootId, threadId, rootGiven } };
136560
136981
  if (UNSUPPORTED_MESSAGE_TYPES.has(messageType)) {
136561
136982
  return {
136562
136983
  messageId,
@@ -136565,6 +136986,8 @@ var parseInboundMessage = (event, appId, groupMention = "always") => {
136565
136986
  senderOpenId,
136566
136987
  text: "",
136567
136988
  resources: [],
136989
+ inThread,
136990
+ ...topic,
136568
136991
  unsupportedType: messageType
136569
136992
  };
136570
136993
  }
@@ -136575,7 +136998,7 @@ var parseInboundMessage = (event, appId, groupMention = "always") => {
136575
136998
  const raw = post !== void 0 ? post.text : messageType === "text" ? readText(message.content) : null;
136576
136999
  const text3 = mentions.reduce((value2, mention) => value2.replaceAll(mention.key, ""), raw ?? "").trim();
136577
137000
  if (text3.length === 0 && resources.length === 0) return null;
136578
- return { messageId, chatId, chatType, senderOpenId, text: text3, resources };
137001
+ return { messageId, chatId, chatType, senderOpenId, text: text3, resources, inThread, ...topic };
136579
137002
  };
136580
137003
  var fetchMessageResource = async (input) => {
136581
137004
  const url = `${LARK_BASE}/im/v1/messages/${encodeURIComponent(
@@ -136651,7 +137074,13 @@ var startLarkInbound = (input) => {
136651
137074
  const dispatcher = new import_node_sdk.EventDispatcher({}).register({
136652
137075
  "im.message.receive_v1": (event) => {
136653
137076
  const mode = input.groupMention ?? "always";
136654
- const parsed = parseInboundMessage(event, input.appId, mode);
137077
+ const parse = (groupMention) => parseInboundMessage(
137078
+ event,
137079
+ input.appId,
137080
+ groupMention,
137081
+ input.topicOnMention
137082
+ );
137083
+ const parsed = parse(mode);
136655
137084
  if (parsed !== null) {
136656
137085
  input.onMessage(parsed);
136657
137086
  return {};
@@ -136661,7 +137090,7 @@ var startLarkInbound = (input) => {
136661
137090
  const chatId = chat?.chat_id;
136662
137091
  if (typeof chatId === "string") {
136663
137092
  void input.isSoloGroup(chatId).then((solo) => {
136664
- const relaxed = solo ? parseInboundMessage(event, input.appId, "never") : null;
137093
+ const relaxed = solo ? parse("never") : null;
136665
137094
  if (relaxed !== null) input.onMessage(relaxed);
136666
137095
  });
136667
137096
  }
@@ -136728,8 +137157,27 @@ var COLUMNS = [
136728
137157
  labelKey: "column.larkAppId.label",
136729
137158
  placeholderKey: "column.larkAppId.placeholder"
136730
137159
  },
136731
- { key: "larkAppSecret", kind: "password", labelKey: "column.larkAppSecret.label" }
137160
+ { key: "larkAppSecret", kind: "password", labelKey: "column.larkAppSecret.label" },
137161
+ // 被 @ 时答复要不要开话题。只有这个适配器读它,没填过就是开(见 `topicOnMentionOf`)。
137162
+ {
137163
+ key: "larkTopicOnMention",
137164
+ kind: "select",
137165
+ labelKey: "column.larkTopicOnMention.label",
137166
+ descriptionKey: "column.larkTopicOnMention.description",
137167
+ options: [
137168
+ { value: "on", labelKey: "settings.onOff.on" },
137169
+ { value: "off", labelKey: "settings.onOff.off" }
137170
+ ]
137171
+ }
136732
137172
  ];
137173
+ var topicOnMentionOf = (binding) => binding.values["larkTopicOnMention"] !== "off";
137174
+ var TOPIC_THREAD_CACHE_LIMIT = 2e3;
137175
+ var topicChatIdOf = (chatId, rootMessageId) => `${chatId}:${rootMessageId}`;
137176
+ var splitTopicChatId = (chatId) => {
137177
+ const at = chatId.indexOf(":");
137178
+ return at < 0 ? { chatId } : { chatId: chatId.slice(0, at), rootMessageId: chatId.slice(at + 1) };
137179
+ };
137180
+ var topicAliasOf = (chatId, threadId) => `${chatId}:${threadId}`;
136733
137181
  var credentials = (binding) => ({
136734
137182
  appId: binding.values["larkAppId"] ?? "",
136735
137183
  appSecret: binding.values["larkAppSecret"] ?? ""
@@ -136744,6 +137192,60 @@ var makeLarkAdapter = (deps) => {
136744
137192
  await deps.cacheSet(key, { token: minted.token });
136745
137193
  return minted.token;
136746
137194
  };
137195
+ const aliasPorts = /* @__PURE__ */ new Map();
137196
+ const topicThreadIds = makeBoundedMap(TOPIC_THREAD_CACHE_LIMIT);
137197
+ const learning = /* @__PURE__ */ new Map();
137198
+ const learnTopic = (binding, target2, threadId) => {
137199
+ const key = chatKeyOf(binding);
137200
+ const report = (id) => {
137201
+ topicThreadIds.set(key, id);
137202
+ target2.port?.noteChatAlias?.(topicAliasOf(target2.chatId, id), binding.chatId);
137203
+ };
137204
+ const known = threadId ?? topicThreadIds.get(key);
137205
+ if (known !== void 0) {
137206
+ report(known);
137207
+ return;
137208
+ }
137209
+ const group = chatKeyOf({ rowId: binding.rowId, chatId: target2.chatId });
137210
+ const inflight = learning.get(group) ?? /* @__PURE__ */ new Map();
137211
+ if (inflight.has(key)) return;
137212
+ learning.set(group, inflight);
137213
+ const reading = fetchMessageThreadId({
137214
+ http: deps.http,
137215
+ token: target2.token,
137216
+ messageId: target2.rootMessageId
137217
+ }).then((id) => {
137218
+ if (id !== void 0) report(id);
137219
+ }).catch((error) => {
137220
+ deps.log.warn("lark could not learn the topic of a chat", { error: describeError(error) });
137221
+ }).finally(() => {
137222
+ inflight.delete(key);
137223
+ if (inflight.size === 0) learning.delete(group);
137224
+ });
137225
+ inflight.set(key, reading);
137226
+ };
137227
+ const topicUnsettled = (rowId, groupId, topic) => {
137228
+ if (topic.rootGiven === true) return null;
137229
+ const inflight = learning.get(chatKeyOf({ rowId, chatId: groupId }));
137230
+ if (inflight === void 0) return null;
137231
+ const known = aliasPorts.get(rowId)?.knowsChatAlias?.(topicAliasOf(groupId, topic.threadId));
137232
+ return known === true ? null : Promise.all(inflight.values());
137233
+ };
137234
+ const sendTarget = async (binding) => {
137235
+ const port = aliasPorts.get(binding.rowId);
137236
+ const token = await tenantToken(binding);
137237
+ const { chatId, rootMessageId } = splitTopicChatId(binding.chatId);
137238
+ if (rootMessageId === void 0) {
137239
+ return { http: deps.http, token, chatId, reply: binding.reply };
137240
+ }
137241
+ return {
137242
+ http: deps.http,
137243
+ token,
137244
+ chatId,
137245
+ reply: binding.reply ?? { messageId: rootMessageId, inThread: true },
137246
+ onThreadReply: (threadId) => learnTopic(binding, { token, chatId, rootMessageId, port }, threadId)
137247
+ };
137248
+ };
136747
137249
  const soloGroupFor = (bot) => makeSoloGroupCheck(async (chatId) => {
136748
137250
  const stats = await fetchChatStats({
136749
137251
  http: deps.http,
@@ -136757,16 +137259,28 @@ var makeLarkAdapter = (deps) => {
136757
137259
  icon: "lark",
136758
137260
  label: "Lark",
136759
137261
  columns: COLUMNS,
137262
+ quotesReplies: true,
137263
+ parentChatIdOf: (chatId) => {
137264
+ const { chatId: groupId, rootMessageId } = splitTopicChatId(chatId);
137265
+ return rootMessageId === void 0 ? void 0 : groupId;
137266
+ },
136760
137267
  isConfigured: (binding) => {
136761
137268
  const { appId, appSecret } = credentials(binding);
136762
137269
  return appId !== "" && appSecret !== "";
136763
137270
  },
136764
137271
  connect: (binding, handlers) => {
136765
137272
  const { appId, appSecret } = credentials(binding);
136766
- return startLarkInbound({
137273
+ const port = {
137274
+ noteChatAlias: handlers.noteChatAlias,
137275
+ knowsChatAlias: handlers.knowsChatAlias
137276
+ };
137277
+ aliasPorts.set(binding.rowId, port);
137278
+ let open = true;
137279
+ const inbound = startLarkInbound({
136767
137280
  appId,
136768
137281
  appSecret,
136769
137282
  groupMention: binding.groupMention,
137283
+ topicOnMention: topicOnMentionOf(binding),
136770
137284
  isSoloGroup: soloGroupFor(binding),
136771
137285
  onError: handlers.onError,
136772
137286
  onCardAction: handlers.onButton,
@@ -136778,12 +137292,23 @@ var makeLarkAdapter = (deps) => {
136778
137292
  arg: click.arg
136779
137293
  }) ?? null,
136780
137294
  onMessage: (message) => {
136781
- handlers.onMessage({
137295
+ const { topic } = message;
137296
+ const deliver = () => handlers.onMessage({
136782
137297
  messageId: message.messageId,
136783
- chatId: message.chatId,
137298
+ // 已经开出来的话题就是它自己那个聊天;将要开出来的(主窗口里 @ 机器人)先算群的,
137299
+ // 由核心决定要不要分出去。
137300
+ ...topic === void 0 ? { chatId: message.chatId } : topic.threadId === void 0 ? {
137301
+ chatId: message.chatId,
137302
+ branchChatId: topicChatIdOf(message.chatId, topic.rootMessageId)
137303
+ } : {
137304
+ chatId: topicChatIdOf(message.chatId, topic.rootMessageId),
137305
+ parentChatId: message.chatId,
137306
+ chatAlias: topicAliasOf(message.chatId, topic.threadId)
137307
+ },
136784
137308
  senderId: message.senderOpenId,
136785
137309
  text: message.text,
136786
137310
  chatKind: message.chatType === "p2p" ? "private" : "group",
137311
+ replyInThread: message.inThread,
136787
137312
  resources: message.resources.map((resource) => ({
136788
137313
  key: resource.fileKey,
136789
137314
  name: resource.name,
@@ -136792,8 +137317,21 @@ var makeLarkAdapter = (deps) => {
136792
137317
  })),
136793
137318
  ...message.unsupportedType === void 0 ? {} : { unsupportedType: message.unsupportedType }
136794
137319
  });
137320
+ const pending = topic?.threadId === void 0 ? null : topicUnsettled(binding.rowId, message.chatId, {
137321
+ threadId: topic.threadId,
137322
+ rootGiven: topic.rootGiven
137323
+ });
137324
+ if (pending === null) deliver();
137325
+ else void pending.then(() => open ? deliver() : void 0);
136795
137326
  }
136796
137327
  });
137328
+ return {
137329
+ close: () => {
137330
+ open = false;
137331
+ if (aliasPorts.get(binding.rowId) === port) aliasPorts.delete(binding.rowId);
137332
+ inbound.close();
137333
+ }
137334
+ };
136797
137335
  },
136798
137336
  createGroup: async (binding, input) => createChat({
136799
137337
  http: deps.http,
@@ -136802,13 +137340,11 @@ var makeLarkAdapter = (deps) => {
136802
137340
  memberOpenIds: input.memberIds
136803
137341
  }),
136804
137342
  sendText: async (binding, text3, options) => {
136805
- const token = await tenantToken(binding);
137343
+ const target2 = await sendTarget(binding);
136806
137344
  const receiveIdType = options?.targetKind === "user" ? "open_id" : "chat_id";
136807
137345
  for (const chunk of splitForLark(text3)) {
136808
137346
  const messageId = await sendChatText({
136809
- http: deps.http,
136810
- token,
136811
- chatId: binding.chatId,
137347
+ ...target2,
136812
137348
  receiveIdType,
136813
137349
  text: chunk
136814
137350
  });
@@ -136836,35 +137372,39 @@ var makeLarkAdapter = (deps) => {
136836
137372
  }
136837
137373
  },
136838
137374
  sendImage: async (binding, image2) => {
136839
- const token = await tenantToken(binding);
137375
+ const target2 = await sendTarget(binding);
136840
137376
  const imageKey = await uploadImage({
136841
137377
  http: deps.http,
136842
- token,
137378
+ token: target2.token,
136843
137379
  bytes: image2.bytes,
136844
137380
  name: image2.name,
136845
137381
  mimeType: image2.mimeType
136846
137382
  });
136847
- await sendChatImage({ http: deps.http, token, chatId: binding.chatId, imageKey });
137383
+ await sendChatImage({
137384
+ ...target2,
137385
+ imageKey
137386
+ });
136848
137387
  deps.log.info("lark delivered an image", { name: image2.name });
136849
137388
  },
136850
137389
  sendFile: async (binding, file) => {
136851
- const token = await tenantToken(binding);
137390
+ const target2 = await sendTarget(binding);
136852
137391
  const fileKey = await uploadFile({
136853
137392
  http: deps.http,
136854
- token,
137393
+ token: target2.token,
136855
137394
  bytes: file.bytes,
136856
137395
  name: file.name,
136857
137396
  mimeType: file.mimeType
136858
137397
  });
136859
- await sendChatFile({ http: deps.http, token, chatId: binding.chatId, fileKey });
137398
+ await sendChatFile({
137399
+ ...target2,
137400
+ fileKey
137401
+ });
136860
137402
  deps.log.info("lark delivered a file", { name: file.name });
136861
137403
  },
136862
137404
  // 图 10 MB、文件 30 MB(飞书开放平台文档:上传图片接口、上传文件接口;空文件会被拒)。
136863
137405
  outboundLimits: { imageBytes: 10 * 1024 * 1024, fileBytes: 30 * 1024 * 1024 },
136864
137406
  askApproval: async (binding, request) => sendCard({
136865
- http: deps.http,
136866
- token: await tenantToken(binding),
136867
- chatId: binding.chatId,
137407
+ ...await sendTarget(binding),
136868
137408
  card: buildApprovalCard({
136869
137409
  title: request.title,
136870
137410
  body: request.body,
@@ -136884,10 +137424,9 @@ var makeLarkAdapter = (deps) => {
136884
137424
  },
136885
137425
  turnCards: {
136886
137426
  throttleMs: LARK_CARD_THROTTLE_MS,
137427
+ rotatesOnInteraction: true,
136887
137428
  open: async (binding, view) => sendCard({
136888
- http: deps.http,
136889
- token: await tenantToken(binding),
136890
- chatId: binding.chatId,
137429
+ ...await sendTarget(binding),
136891
137430
  card: buildTurnCard(view, deps.translate)
136892
137431
  }),
136893
137432
  update: async (binding, cardId, view) => {
@@ -136901,9 +137440,7 @@ var makeLarkAdapter = (deps) => {
136901
137440
  },
136902
137441
  panel: {
136903
137442
  open: async (binding, card) => sendCard({
136904
- http: deps.http,
136905
- token: await tenantToken(binding),
136906
- chatId: binding.chatId,
137443
+ ...await sendTarget(binding),
136907
137444
  card: buildPanelCard(card)
136908
137445
  }),
136909
137446
  update: async (binding, cardId, card) => {
@@ -136917,9 +137454,7 @@ var makeLarkAdapter = (deps) => {
136917
137454
  },
136918
137455
  choiceCards: {
136919
137456
  open: async (binding, card) => sendCard({
136920
- http: deps.http,
136921
- token: await tenantToken(binding),
136922
- chatId: binding.chatId,
137457
+ ...await sendTarget(binding),
136923
137458
  card: buildChoiceCard(card)
136924
137459
  }),
136925
137460
  update: async (binding, cardId, card) => {
@@ -136971,7 +137506,7 @@ var en_default = {
136971
137506
  bots: "Bots",
136972
137507
  language: {
136973
137508
  label: "Reply language",
136974
- 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.",
137509
+ 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.",
136975
137510
  zhCN: "\u4E2D\u6587",
136976
137511
  en: "English"
136977
137512
  },
@@ -137092,7 +137627,7 @@ var en_default = {
137092
137627
  },
137093
137628
  deliveryPlatformChatId: {
137094
137629
  label: "Platform chat ID",
137095
- description: "Lark open_id/chat_id, Telegram chat id, WeCom user/group id, etc.",
137630
+ description: "Lark open_id/chat_id, Telegram chat id (a forum topic: chat id:topic id), WeCom user/group id, etc.",
137096
137631
  placeholder: "oc_\u2026 / ou_\u2026 / -100\u2026"
137097
137632
  },
137098
137633
  queueMode: {
@@ -137103,6 +137638,10 @@ var en_default = {
137103
137638
  label: "Command panel",
137104
137639
  description: "Which commands /panel (or typing /) lists. Minimal hides low-frequency system items; typing the same command still works."
137105
137640
  },
137641
+ larkTopicOnMention: {
137642
+ label: "Open a topic when mentioned",
137643
+ 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."
137644
+ },
137106
137645
  wecomNumberedHints: {
137107
137646
  label: "Numbered-reply hints",
137108
137647
  description: "WeCom has no buttons; approvals and questions use reply numbers. When on, include fuller usage hints."
@@ -137332,7 +137871,8 @@ var en_default = {
137332
137871
  working: "Working on it\u2026",
137333
137872
  done: "Done",
137334
137873
  error: "Something went wrong",
137335
- interrupted: "Interrupted"
137874
+ interrupted: "Interrupted",
137875
+ continued: "Continues in the card below"
137336
137876
  },
137337
137877
  step: {
137338
137878
  think: "think",
@@ -137518,7 +138058,13 @@ var en_default = {
137518
138058
  running: "Checking {{rowId}}\u2026",
137519
138059
  done: "Connection OK",
137520
138060
  failed: "Connection failed (ref {{ref}})",
137521
- notSaved: "Save this row first (credentials are read from saved config only)."
138061
+ notSaved: "Save this row first (credentials are read from saved config only).",
138062
+ hint: {
138063
+ badToken: "This does not look like a bot token. Copy it again from BotFather: digits, a colon, then the key (ref {{ref}})",
138064
+ unauthorized: "Telegram does not accept this token. It may have been revoked; get the current one from BotFather (ref {{ref}})",
138065
+ notBot: "This token does not belong to a bot. Use the token BotFather gave you (ref {{ref}})",
138066
+ webhookActive: "This bot has a webhook set by another service, so it cannot receive messages here. Remove the webhook there, then check again (ref {{ref}})"
138067
+ }
137522
138068
  },
137523
138069
  copyDiagnostics: {
137524
138070
  label: "Copy diagnostics",
@@ -137545,6 +138091,21 @@ var en_default = {
137545
138091
  failed: "Could not verify the Lark app (ref {{ref}})",
137546
138092
  notLark: "This row is not a saved Lark bot."
137547
138093
  }
138094
+ },
138095
+ menu: {
138096
+ new: "Start a new session with the next message",
138097
+ stop: "Stop the turn that is running",
138098
+ continue: "Resume sending after a stop or a failure",
138099
+ status: "What is going on right now",
138100
+ sessions: "List the sessions for this chat's project",
138101
+ resume: "Switch to a session (number from /sessions)",
138102
+ model: "See or switch the model",
138103
+ plan: "Toggle plan mode",
138104
+ repo: "Pick the project this chat works on",
138105
+ team: "Send the next messages to a team",
138106
+ thread: "Go back to a normal session",
138107
+ panel: "Open the command panel",
138108
+ help: "Show all commands"
137548
138109
  }
137549
138110
  };
137550
138111
 
@@ -137556,7 +138117,7 @@ var zh_CN_default = {
137556
138117
  bots: "\u673A\u5668\u4EBA",
137557
138118
  language: {
137558
138119
  label: "\u56DE\u8BDD\u8BED\u8A00",
137559
- 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",
138120
+ 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",
137560
138121
  zhCN: "\u4E2D\u6587",
137561
138122
  en: "English"
137562
138123
  },
@@ -137677,7 +138238,7 @@ var zh_CN_default = {
137677
138238
  },
137678
138239
  deliveryPlatformChatId: {
137679
138240
  label: "\u5E73\u53F0\u804A\u5929 ID",
137680
- description: "\u98DE\u4E66 open_id\uFF0Fchat_id\u3001Telegram chat id\u3001\u4F01\u5FAE\u7528\u6237\uFF0F\u7FA4 id \u7B49\u3002",
138241
+ description: "\u98DE\u4E66 open_id\uFF0Fchat_id\u3001Telegram chat id\uFF08\u8BBA\u575B\u8BDD\u9898\u586B \u7FA4\u53F7:\u8BDD\u9898\u53F7\uFF09\u3001\u4F01\u5FAE\u7528\u6237\uFF0F\u7FA4 id \u7B49\u3002",
137681
138242
  placeholder: "oc_\u2026 / ou_\u2026 / -100\u2026"
137682
138243
  },
137683
138244
  queueMode: {
@@ -137688,6 +138249,10 @@ var zh_CN_default = {
137688
138249
  label: "\u6307\u4EE4\u9762\u677F",
137689
138250
  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"
137690
138251
  },
138252
+ larkTopicOnMention: {
138253
+ label: "\u88AB @ \u65F6\u5F00\u8BDD\u9898",
138254
+ 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"
138255
+ },
137691
138256
  wecomNumberedHints: {
137692
138257
  label: "\u7F16\u53F7\u5E94\u7B54\u63D0\u793A",
137693
138258
  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"
@@ -137917,7 +138482,8 @@ var zh_CN_default = {
137917
138482
  working: "\u5904\u7406\u4E2D\u2026",
137918
138483
  done: "\u5DF2\u5B8C\u6210",
137919
138484
  error: "\u51FA\u9519\u4E86",
137920
- interrupted: "\u5DF2\u4E2D\u65AD"
138485
+ interrupted: "\u5DF2\u4E2D\u65AD",
138486
+ continued: "\u540E\u7EED\u89C1\u4E0B\u65B9\u5361\u7247"
137921
138487
  },
137922
138488
  step: {
137923
138489
  think: "\u601D\u8003",
@@ -138103,7 +138669,13 @@ var zh_CN_default = {
138103
138669
  running: "\u6B63\u5728\u68C0\u67E5 {{rowId}} \u2026",
138104
138670
  done: "\u8FDE\u63A5\u6B63\u5E38",
138105
138671
  failed: "\u8FDE\u63A5\u5931\u8D25\uFF08\u53C2\u8003\u53F7 {{ref}}\uFF09",
138106
- notSaved: "\u8BF7\u5148\u4FDD\u5B58\u8FD9\u4E00\u884C\u518D\u68C0\u67E5\u8FDE\u63A5\uFF08\u51ED\u636E\u53EA\u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u8BFB\u53D6\uFF09\u3002"
138672
+ notSaved: "\u8BF7\u5148\u4FDD\u5B58\u8FD9\u4E00\u884C\u518D\u68C0\u67E5\u8FDE\u63A5\uFF08\u51ED\u636E\u53EA\u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u8BFB\u53D6\uFF09\u3002",
138673
+ hint: {
138674
+ badToken: "\u8FD9\u4E0D\u50CF\u673A\u5668\u4EBA\u7684 token\u3002\u5230 BotFather \u91CD\u65B0\u590D\u5236\u4E00\u4EFD\uFF1A\u6570\u5B57\u3001\u5192\u53F7\uFF0C\u540E\u9762\u662F\u5BC6\u94A5\uFF08\u53C2\u8003\u53F7 {{ref}}\uFF09",
138675
+ unauthorized: "Telegram \u4E0D\u8BA4\u8FD9\u4E2A token\uFF0C\u53EF\u80FD\u5DF2\u7ECF\u4F5C\u5E9F\u3002\u5230 BotFather \u53D6\u73B0\u5728\u7684\u90A3\u4E00\u4EFD\uFF08\u53C2\u8003\u53F7 {{ref}}\uFF09",
138676
+ notBot: "\u8FD9\u4E2A token \u4E0D\u662F\u673A\u5668\u4EBA\u7684\u3002\u8BF7\u7528 BotFather \u7ED9\u7684\u90A3\u4E00\u4EFD\uFF08\u53C2\u8003\u53F7 {{ref}}\uFF09",
138677
+ webhookActive: "\u8FD9\u4E2A\u673A\u5668\u4EBA\u88AB\u522B\u7684\u670D\u52A1\u8BBE\u4E86 webhook\uFF0C\u5728\u8FD9\u91CC\u6536\u4E0D\u5230\u6D88\u606F\u3002\u5148\u5230\u90A3\u8FB9\u64A4\u6389 webhook\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\uFF08\u53C2\u8003\u53F7 {{ref}}\uFF09"
138678
+ }
138107
138679
  },
138108
138680
  copyDiagnostics: {
138109
138681
  label: "\u590D\u5236\u8BCA\u65AD",
@@ -138130,6 +138702,21 @@ var zh_CN_default = {
138130
138702
  failed: "\u65E0\u6CD5\u9A8C\u8BC1\u98DE\u4E66\u5E94\u7528\uFF08\u53C2\u8003\u53F7 {{ref}}\uFF09",
138131
138703
  notLark: "\u8FD9\u4E00\u884C\u4E0D\u662F\u5DF2\u4FDD\u5B58\u7684\u98DE\u4E66\u673A\u5668\u4EBA\u3002"
138132
138704
  }
138705
+ },
138706
+ menu: {
138707
+ new: "\u4E0B\u4E00\u53E5\u5F00\u4E00\u4E2A\u65B0\u4F1A\u8BDD",
138708
+ stop: "\u505C\u6389\u6B63\u5728\u8DD1\u7684\u8FD9\u4E00\u8F6E",
138709
+ continue: "\u505C\u4E0B\u6216\u5931\u8D25\u4E4B\u540E\u63A5\u7740\u53D1",
138710
+ status: "\u770B\u73B0\u5728\u5728\u5E72\u4EC0\u4E48",
138711
+ sessions: "\u5217\u51FA\u8FD9\u4E2A\u804A\u5929\u6240\u5728\u9879\u76EE\u7684\u4F1A\u8BDD",
138712
+ resume: "\u5207\u5230\u67D0\u4E2A\u4F1A\u8BDD\uFF08/sessions \u91CC\u7684\u7F16\u53F7\uFF09",
138713
+ model: "\u770B\u6216\u6362\u6A21\u578B",
138714
+ plan: "\u5F00\u5173\u8BA1\u5212\u6A21\u5F0F",
138715
+ repo: "\u9009\u8FD9\u4E2A\u804A\u5929\u5728\u54EA\u4E2A\u9879\u76EE\u4E0A\u5E72\u6D3B",
138716
+ team: "\u628A\u63A5\u4E0B\u6765\u7684\u8BDD\u53D1\u7ED9\u4E00\u652F\u56E2\u961F",
138717
+ thread: "\u5207\u56DE\u666E\u901A\u4F1A\u8BDD",
138718
+ panel: "\u6253\u5F00\u547D\u4EE4\u9762\u677F",
138719
+ help: "\u5217\u51FA\u5168\u90E8\u547D\u4EE4"
138133
138720
  }
138134
138721
  };
138135
138722
 
@@ -138160,6 +138747,24 @@ var makeTranslate = (language) => {
138160
138747
  { optional, language }
138161
138748
  );
138162
138749
  };
138750
+ var makeLiveTranslate = (initial) => {
138751
+ let current = makeTranslate(initial);
138752
+ const t = Object.defineProperties(
138753
+ (key, params) => current(key, params),
138754
+ {
138755
+ optional: {
138756
+ value: (key, params) => current.optional(key, params)
138757
+ },
138758
+ language: { get: () => current.language }
138759
+ }
138760
+ );
138761
+ return {
138762
+ t,
138763
+ setLanguage: (language) => {
138764
+ if (language !== current.language) current = makeTranslate(language);
138765
+ }
138766
+ };
138767
+ };
138163
138768
 
138164
138769
  // src/modelPlan.ts
138165
138770
  var makeModelPlan = (core, deps) => {
@@ -138237,13 +138842,28 @@ var makeModelPlan = (core, deps) => {
138237
138842
  return { currentModelOf, handleModel, handlePlan };
138238
138843
  };
138239
138844
 
138845
+ // src/notificationTargets.ts
138846
+ var notificationChatKeys = (state, threadId, projectId) => {
138847
+ const owner = chatOfThread(state, threadId);
138848
+ if (owner !== void 0) return [owner];
138849
+ return projectId === null ? [] : chatsOnProject(state, projectId);
138850
+ };
138851
+
138852
+ // src/turnOutcome.ts
138853
+ var outcomeOfSession = (status) => {
138854
+ if (status === "error") return "failed";
138855
+ if (status === "interrupted" || status === "stopped") return "stopped";
138856
+ return "done";
138857
+ };
138858
+ var outcomeOfTeamRun = (state) => state === "settled" ? "done" : state === "failed" ? "failed" : "stopped";
138859
+
138240
138860
  // src/notifications.ts
138241
138861
  var THREAD_PROJECT_CACHE_LIMIT = 256;
138242
138862
  var makeNotifications = (core, deps) => {
138243
138863
  const { host, adapterFor, deliver, bindingForChatKey, ready } = core;
138244
138864
  const { clearWorking } = deps;
138245
138865
  const { turnCards } = deps.replyCards;
138246
- const { failures } = deps;
138866
+ const { failures, anchors } = deps;
138247
138867
  const projectByThread = /* @__PURE__ */ new Map();
138248
138868
  const projectOfThread = async (threadId) => {
138249
138869
  const known = projectByThread.get(threadId);
@@ -138257,7 +138877,7 @@ var makeNotifications = (core, deps) => {
138257
138877
  projectByThread.set(threadId, thread.projectId);
138258
138878
  return thread.projectId;
138259
138879
  };
138260
- const targetsForProject = (projectId) => projectId === null ? [] : chatsOnProject(core.state, projectId).flatMap((chatKey) => {
138880
+ const targetsForProject = (threadId, projectId) => notificationChatKeys(core.state, threadId, projectId).flatMap((chatKey) => {
138261
138881
  const binding = bindingForChatKey(chatKey);
138262
138882
  return binding === void 0 ? [] : [binding];
138263
138883
  });
@@ -138283,7 +138903,7 @@ var makeNotifications = (core, deps) => {
138283
138903
  if (payload.session?.activeTurnId != null) return;
138284
138904
  const said = threadsWithSomethingToReport.delete(threadId);
138285
138905
  await ready;
138286
- const targets = targetsForProject(await projectOfThread(threadId));
138906
+ const targets = targetsForProject(threadId, await projectOfThread(threadId));
138287
138907
  const reporters = said ? targets : [];
138288
138908
  if (reporters.length > 0) {
138289
138909
  const thread = await host.projections.getThread(threadId);
@@ -138293,11 +138913,17 @@ var makeNotifications = (core, deps) => {
138293
138913
  const text3 = streamsTo(binding, threadId) ? "" : `${thread.title}
138294
138914
 
138295
138915
  ${final.text}`;
138296
- await deliver(binding, text3, final.attachments);
138916
+ const owns = chatOf(core.state, chatKeyOf(binding)).threadId === threadId;
138917
+ await deliver(
138918
+ owns ? await anchors.anchored(binding, threadId, final.turnId) : binding,
138919
+ text3,
138920
+ final.attachments
138921
+ );
138297
138922
  }
138298
138923
  }
138299
138924
  }
138300
- for (const binding of targets) await clearWorking(binding, threadId);
138925
+ const outcome = outcomeOfSession(payload.session?.status);
138926
+ for (const binding of targets) await clearWorking(binding, threadId, outcome);
138301
138927
  }
138302
138928
  });
138303
138929
  host.events.on("thread.failed", async (event) => {
@@ -138305,7 +138931,7 @@ ${final.text}`;
138305
138931
  if (threadId === null) return;
138306
138932
  threadsWithSomethingToReport.delete(threadId);
138307
138933
  await ready;
138308
- const targets = targetsForProject(await projectOfThread(threadId));
138934
+ const targets = targetsForProject(threadId, await projectOfThread(threadId));
138309
138935
  const textTargets = targets.filter(
138310
138936
  (binding) => !(streamsTo(binding, threadId) && turnCards.covers(threadId))
138311
138937
  );
@@ -138317,7 +138943,7 @@ ${final.text}`;
138317
138943
  if (notice !== null && drawingTurn !== null)
138318
138944
  failures.holdForCard(threadId, drawingTurn, notice);
138319
138945
  for (const binding of targets) {
138320
- await clearWorking(binding, threadId);
138946
+ await clearWorking(binding, threadId, "failed");
138321
138947
  if (notice !== null && textTargets.includes(binding)) await deliver(binding, notice);
138322
138948
  }
138323
138949
  });
@@ -138753,8 +139379,8 @@ ${t("panel.projects.hint")}`;
138753
139379
  };
138754
139380
  return {
138755
139381
  /** 开一张新面板卡(`/help`)。发不出就抛,调用方退回文字版。 */
138756
- open: async (chatKey) => {
138757
- const surface = deps.surface(chatKey);
139382
+ open: async (chatKey, reply) => {
139383
+ const surface = deps.surface(chatKey, reply);
138758
139384
  if (surface === null) throw new Error("this chat has no panel");
138759
139385
  const entry = { cardId: "", chatKey, stack: [{ kind: "menu" }], busy: false };
138760
139386
  const cardId = await surface.open(menuCard(chatKey));
@@ -138766,8 +139392,8 @@ ${t("panel.projects.hint")}`;
138766
139392
  * 单独发一张选项目卡(聊天还没选项目时的第一句话、`/repo`)。发不出就抛,调用方退回文字
138767
139393
  * 列表。选完不回面板菜单。
138768
139394
  */
138769
- openProjects: async (chatKey, rows) => {
138770
- const surface = deps.surface(chatKey);
139395
+ openProjects: async (chatKey, rows, reply) => {
139396
+ const surface = deps.surface(chatKey, reply);
138771
139397
  if (surface === null) throw new Error("this chat has no panel");
138772
139398
  const entry = {
138773
139399
  cardId: "",
@@ -138781,6 +139407,11 @@ ${t("panel.projects.hint")}`;
138781
139407
  entry.cardId = cardId;
138782
139408
  remember(entry);
138783
139409
  },
139410
+ /**
139411
+ * 这张卡开在哪个聊天上。没记过,或已经被淘汰,就是 undefined。
139412
+ * 只读:不改 `cards`,也不改淘汰顺序。
139413
+ */
139414
+ chatKeyOfCard: (cardId) => cards.get(cardId)?.chatKey,
138784
139415
  /**
138785
139416
  * 处理一次点击,同步回给点击者的一句提示(null 就是没话说)。取数据、跑命令、改卡都
138786
139417
  * 在回调应答之后做。
@@ -138852,12 +139483,12 @@ var makePanelWiring = (core, deps) => {
138852
139483
  const { projectRowsFor, listedProjectsByChat } = deps.rows;
138853
139484
  const panel = makePanelController({
138854
139485
  t,
138855
- surface: (chatKey) => {
139486
+ surface: (chatKey, reply) => {
138856
139487
  const binding = bindingForChatKey(chatKey);
138857
139488
  const surface = binding === void 0 ? void 0 : adapterFor(binding.platform)?.panel;
138858
139489
  if (binding === void 0 || surface === void 0) return null;
138859
139490
  return {
138860
- open: (card) => surface.open(binding, card),
139491
+ open: (card) => surface.open({ ...binding, reply }, card),
138861
139492
  update: (cardId, card) => surface.update(binding, cardId, card)
138862
139493
  };
138863
139494
  },
@@ -138909,16 +139540,19 @@ var makePanelWiring = (core, deps) => {
138909
139540
  }
138910
139541
  });
138911
139542
  const handlePanelClick = (bot, click) => {
138912
- const target = bindingOf2(bot, click.chatId);
138913
- if (!isAllowedSender(target, click.operatorId)) {
139543
+ const remembered = panel.chatKeyOfCard(click.cardId);
139544
+ const opener = remembered === void 0 ? void 0 : bindingForChatKey(remembered);
139545
+ const chatId = opener !== void 0 && opener.rowId === bot.rowId && adapterFor(opener.platform)?.parentChatIdOf?.(opener.chatId) === click.chatId ? opener.chatId : click.chatId;
139546
+ const target2 = bindingOf2(bot, chatId);
139547
+ if (!isAllowedSender(target2, click.operatorId)) {
138914
139548
  host.log.warn("chat ignored a panel click from someone not on the allow list", {
138915
- bot: target.displayName,
139549
+ bot: target2.displayName,
138916
139550
  operator: click.operatorId
138917
139551
  });
138918
139552
  return t("reply.notAllowed");
138919
139553
  }
138920
139554
  return panel.click({
138921
- chatKey: chatKeyOf(target),
139555
+ chatKey: chatKeyOf(target2),
138922
139556
  cardId: click.cardId,
138923
139557
  op: click.op,
138924
139558
  arg: click.arg
@@ -139055,6 +139689,8 @@ var trailingReply = (messages) => {
139055
139689
  };
139056
139690
 
139057
139691
  // src/turnCards.ts
139692
+ var ROTATE = "rotate";
139693
+ var isDrawing = (live) => live.card !== null || live.reopen;
139058
139694
  var RECENT_ENDED_LIMIT = 256;
139059
139695
  var makeTurnCards = (deps) => {
139060
139696
  const slots = /* @__PURE__ */ new Map();
@@ -139091,12 +139727,37 @@ var makeTurnCards = (deps) => {
139091
139727
  status: end.status,
139092
139728
  errorText: end.errorText
139093
139729
  });
139730
+ const openCard = (target2, view) => makeCardStream({
139731
+ open: target2.open,
139732
+ update: target2.update,
139733
+ schedule: deps.schedule,
139734
+ throttleMs: target2.throttleMs,
139735
+ warn: deps.warn
139736
+ }).open(view);
139737
+ const seal = async (slot) => {
139738
+ await deps.ready;
139739
+ const live = slot.live;
139740
+ if (disposed || live === null || live.card === null || !live.target.rotates) return;
139741
+ const card = live.card;
139742
+ await card.finish(
139743
+ buildTurnView({
139744
+ title: live.title,
139745
+ progress: live.lastProgress,
139746
+ texts: await readTexts(live, live.lastProgress),
139747
+ status: "continued"
139748
+ })
139749
+ );
139750
+ deps.onClosed(card.cardId);
139751
+ live.card = null;
139752
+ live.reopen = true;
139753
+ };
139094
139754
  const finalize = async (slot, threadId, progress, end) => {
139095
139755
  const live = slot.live;
139096
139756
  slot.live = null;
139097
- const target = live?.target ?? deps.resolveTarget(threadId);
139098
- if (target === null) return;
139099
- rememberEnded(threadId, progress.turnId ?? live?.turnId ?? null);
139757
+ const turnId = progress.turnId ?? live?.turnId ?? null;
139758
+ const target2 = live?.target ?? deps.resolveTarget(threadId, turnId);
139759
+ if (target2 === null) return;
139760
+ rememberEnded(threadId, turnId);
139100
139761
  if (end.status === "error") erroredThreads.add(threadId);
139101
139762
  const title = live?.title ?? await deps.titleOf(threadId);
139102
139763
  const view = await finalView(title, progress, live, end);
@@ -139105,15 +139766,9 @@ var makeTurnCards = (deps) => {
139105
139766
  deps.onClosed(live.card.cardId);
139106
139767
  return;
139107
139768
  }
139108
- const card = await makeCardStream({
139109
- open: target.open,
139110
- update: target.update,
139111
- schedule: deps.schedule,
139112
- throttleMs: target.throttleMs,
139113
- warn: deps.warn
139114
- }).open(view);
139769
+ const card = await openCard(target2, view);
139115
139770
  if (card === null) {
139116
- await deps.fallback(threadId, target.chatKey, view);
139771
+ await deps.fallback(threadId, turnId, target2.chatKey, view);
139117
139772
  return;
139118
139773
  }
139119
139774
  };
@@ -139132,56 +139787,63 @@ var makeTurnCards = (deps) => {
139132
139787
  }
139133
139788
  }
139134
139789
  if (item.phase === "ended") {
139135
- if (live === null && deps.resolveTarget(threadId) === null) return;
139790
+ if (live === null && deps.resolveTarget(threadId, item.turnId) === null) return;
139136
139791
  await finalize(slot, threadId, item, await deps.endOf(threadId, item.turnId));
139137
139792
  return;
139138
139793
  }
139139
139794
  erroredThreads.delete(threadId);
139140
139795
  if (live === null) {
139141
- const target = deps.resolveTarget(threadId);
139142
- if (target === null) return;
139796
+ const target2 = deps.resolveTarget(threadId, item.turnId);
139797
+ if (target2 === null) return;
139143
139798
  const title = await deps.titleOf(threadId);
139144
139799
  const fresh = {
139145
- chatKey: target.chatKey,
139800
+ chatKey: target2.chatKey,
139146
139801
  title,
139147
139802
  card: null,
139148
- target,
139803
+ reopen: false,
139804
+ target: target2,
139149
139805
  turnId: item.turnId,
139150
139806
  lastSeq: item.seq,
139151
139807
  lastProgress: item,
139152
139808
  settledTexts: /* @__PURE__ */ new Map()
139153
139809
  };
139154
- const view = buildTurnView({
139810
+ const view2 = buildTurnView({
139155
139811
  title,
139156
139812
  progress: item,
139157
139813
  texts: await readTexts(fresh, item),
139158
139814
  status: "working"
139159
139815
  });
139160
- const card = await makeCardStream({
139161
- open: target.open,
139162
- update: target.update,
139163
- schedule: deps.schedule,
139164
- throttleMs: target.throttleMs,
139165
- warn: deps.warn
139166
- }).open(view);
139816
+ const card = await openCard(target2, view2);
139167
139817
  slot.live = { ...fresh, card };
139168
139818
  if (card !== null) {
139169
- deps.onOpened({ cardId: card.cardId, chatKey: target.chatKey, threadId, title });
139819
+ deps.onOpened({ cardId: card.cardId, chatKey: target2.chatKey, threadId, title });
139170
139820
  }
139171
139821
  return;
139172
139822
  }
139173
139823
  live.turnId ??= item.turnId;
139174
139824
  live.lastSeq = item.seq;
139175
139825
  live.lastProgress = item;
139176
- if (live.card === null) return;
139177
- live.card.push(
139178
- buildTurnView({
139179
- title: live.title,
139180
- progress: item,
139181
- texts: await readTexts(live, item),
139182
- status: "working"
139183
- })
139184
- );
139826
+ if (live.card === null && !live.reopen) return;
139827
+ const view = buildTurnView({
139828
+ title: live.title,
139829
+ progress: item,
139830
+ texts: await readTexts(live, item),
139831
+ status: "working"
139832
+ });
139833
+ if (live.card !== null) {
139834
+ live.card.push(view);
139835
+ return;
139836
+ }
139837
+ live.reopen = false;
139838
+ live.card = await openCard(live.target, view);
139839
+ if (live.card !== null) {
139840
+ deps.onOpened({
139841
+ cardId: live.card.cardId,
139842
+ chatKey: live.chatKey,
139843
+ threadId,
139844
+ title: live.title
139845
+ });
139846
+ }
139185
139847
  };
139186
139848
  const pump = async (threadId, slot) => {
139187
139849
  try {
@@ -139189,7 +139851,8 @@ var makeTurnCards = (deps) => {
139189
139851
  const item = slot.queue.shift();
139190
139852
  if (item === void 0) break;
139191
139853
  try {
139192
- await handle(slot, threadId, item);
139854
+ if (item === ROTATE) await seal(slot);
139855
+ else await handle(slot, threadId, item);
139193
139856
  } catch (error) {
139194
139857
  deps.warn("could not draw a turn card", error);
139195
139858
  }
@@ -139209,7 +139872,7 @@ var makeTurnCards = (deps) => {
139209
139872
  slots.set(threadId, slot);
139210
139873
  }
139211
139874
  const last = slot.queue.at(-1);
139212
- if (last !== void 0 && last.phase === "running" && last.turnId === progress.turnId) {
139875
+ if (last !== void 0 && last !== ROTATE && last.phase === "running" && last.turnId === progress.turnId) {
139213
139876
  slot.queue[slot.queue.length - 1] = progress;
139214
139877
  } else {
139215
139878
  slot.queue.push(progress);
@@ -139219,13 +139882,29 @@ var makeTurnCards = (deps) => {
139219
139882
  void pump(threadId, slot);
139220
139883
  }
139221
139884
  },
139222
- /** 这条会话正在画卡的那一轮;没有在画的卡是 null。 */
139885
+ /**
139886
+ * 审批或提问答完了:封存这条会话开着的卡,下一拍另开一张。立刻返回;没有在画的卡、或
139887
+ * 平台不换卡,就什么都不做。
139888
+ */
139889
+ rotate: (threadId) => {
139890
+ const slot = slots.get(threadId);
139891
+ if (disposed || slot === void 0) return;
139892
+ slot.queue.push(ROTATE);
139893
+ if (!slot.running) {
139894
+ slot.running = true;
139895
+ void pump(threadId, slot);
139896
+ }
139897
+ },
139898
+ /** 这条会话正在画卡的那一轮(封存后等着另开的也算);没有在画的卡是 null。 */
139223
139899
  drawingTurn: (threadId) => {
139224
139900
  const live = slots.get(threadId)?.live;
139225
- return live?.card != null ? live.turnId : null;
139901
+ return live != null && isDrawing(live) ? live.turnId : null;
139226
139902
  },
139227
139903
  /** 这条会话现在有一张在画的卡,或者上一轮刚以失败收了卡:失败原因已经在卡上了。 */
139228
- covers: (threadId) => slots.get(threadId)?.live?.card != null || erroredThreads.has(threadId),
139904
+ covers: (threadId) => {
139905
+ const live = slots.get(threadId)?.live;
139906
+ return live != null && isDrawing(live) || erroredThreads.has(threadId);
139907
+ },
139229
139908
  /** 插件停用:放下所有在画的卡,不再发任何更新。卡会停在最后一拍,重启时被标「已中断」。 */
139230
139909
  dispose: () => {
139231
139910
  disposed = true;
@@ -139236,19 +139915,21 @@ var makeTurnCards = (deps) => {
139236
139915
  };
139237
139916
 
139238
139917
  // src/replyCards.ts
139239
- var makeReplyCards = (core, failures) => {
139918
+ var makeReplyCards = (core, failures, anchors) => {
139240
139919
  const { host, adapterFor, commit, deliver, bindingForChatKey, bindingOfThread, ready } = core;
139241
139920
  const stopRequested = /* @__PURE__ */ new Set();
139242
139921
  const turnCards = makeTurnCards({
139243
139922
  ready,
139244
- resolveTarget: (threadId) => {
139923
+ resolveTarget: (threadId, turnId) => {
139245
139924
  const binding = bindingOfThread(threadId);
139246
139925
  const cards = binding === void 0 ? void 0 : adapterFor(binding.platform)?.turnCards;
139247
139926
  if (binding === void 0 || cards === void 0) return null;
139927
+ let anchored;
139248
139928
  return {
139249
139929
  chatKey: chatKeyOf(binding),
139250
139930
  throttleMs: cards.throttleMs,
139251
- open: (view) => cards.open(binding, view),
139931
+ rotates: cards.rotatesOnInteraction === true,
139932
+ open: async (view) => cards.open(await (anchored ??= anchors.anchored(binding, threadId, turnId)), view),
139252
139933
  update: (cardId, view) => cards.update(binding, cardId, view)
139253
139934
  };
139254
139935
  },
@@ -139264,25 +139945,26 @@ var makeReplyCards = (core, failures) => {
139264
139945
  const stopped = stopRequested.delete(threadId);
139265
139946
  const thread = await host.projections.getThread(threadId).catch(() => null);
139266
139947
  const session = thread?.session;
139948
+ const outcome = outcomeOfSession(session?.status);
139267
139949
  const errorText2 = failures.forCard(
139268
139950
  threadId,
139269
139951
  turnId,
139270
139952
  session?.lastError,
139271
- session?.status === "error"
139953
+ outcome === "failed"
139272
139954
  );
139273
139955
  if (errorText2 !== null) return { status: "error", errorText: errorText2 };
139274
- if (stopped || session?.status === "interrupted" || session?.status === "stopped") {
139956
+ if (stopped || outcome === "stopped") {
139275
139957
  return { status: "interrupted", errorText: null };
139276
139958
  }
139277
139959
  return { status: "done", errorText: null };
139278
139960
  },
139279
139961
  onOpened: (card) => commit("turn-card-opened", card),
139280
139962
  onClosed: (cardId) => commit("turn-card-closed", { cardId }),
139281
- fallback: async (_threadId, chatKey, view) => {
139963
+ fallback: async (threadId, turnId, chatKey, view) => {
139282
139964
  const binding = bindingForChatKey(chatKey);
139283
139965
  if (binding === void 0) return;
139284
139966
  const text3 = [view.body, view.errorText ?? ""].filter((part) => part !== "").join("\n\n");
139285
- await deliver(binding, text3);
139967
+ await deliver(await anchors.anchored(binding, threadId, turnId), text3);
139286
139968
  },
139287
139969
  schedule: (run, delayMs) => {
139288
139970
  const timer = setTimeout(run, delayMs);
@@ -139333,24 +140015,6 @@ var ImNotConnectedError = class extends Error {
139333
140015
  }
139334
140016
  };
139335
140017
 
139336
- // src/deliveryContract.ts
139337
- var DELIVERY_SEND_ERROR_CODES = [
139338
- "bad-request",
139339
- "unknown-bot",
139340
- "unknown-target",
139341
- "bot-not-connected",
139342
- "delivery-failed"
139343
- ];
139344
- var isDeliverySendErrorCode = (value2) => typeof value2 === "string" && DELIVERY_SEND_ERROR_CODES.includes(value2);
139345
- var DeliverySendError = class extends Error {
139346
- code;
139347
- constructor(code2, message) {
139348
- super(message);
139349
- this.name = "DeliverySendError";
139350
- this.code = code2;
139351
- }
139352
- };
139353
-
139354
140018
  // src/delivery.ts
139355
140019
  var makeDeliverySender = (core, config2) => {
139356
140020
  return async (input) => {
@@ -139359,16 +140023,16 @@ var makeDeliverySender = (core, config2) => {
139359
140023
  if (bot === void 0) {
139360
140024
  throw new DeliverySendError("unknown-bot", `Unknown bot '${input.botId}'.`);
139361
140025
  }
139362
- const target = findDeliveryTarget(readDeliveryTargets(config2), input.botId, input.targetId);
139363
- if (target === void 0) {
140026
+ const target2 = findDeliveryTarget(readDeliveryTargets(config2), input.botId, input.targetId);
140027
+ if (target2 === void 0) {
139364
140028
  throw new DeliverySendError(
139365
140029
  "unknown-target",
139366
140030
  `Unknown target '${input.targetId}' for bot '${input.botId}'.`
139367
140031
  );
139368
140032
  }
139369
- const binding = core.bindingOf(bot, target.platformChatId);
140033
+ const binding = core.bindingOf(bot, target2.platformChatId);
139370
140034
  try {
139371
- await core.adapterFor(bot.platform)?.sendText(binding, input.text, { targetKind: target.kind });
140035
+ await core.adapterFor(bot.platform)?.sendText(binding, input.text, { targetKind: target2.kind });
139372
140036
  } catch (error) {
139373
140037
  throw new DeliverySendError(
139374
140038
  error instanceof ImNotConnectedError ? "bot-not-connected" : "delivery-failed",
@@ -139379,188 +140043,6 @@ var makeDeliverySender = (core, config2) => {
139379
140043
  };
139380
140044
  };
139381
140045
 
139382
- // src/deliveryHttp.ts
139383
- import * as NodeCrypto from "node:crypto";
139384
- import * as NodeHttp from "node:http";
139385
-
139386
- // src/boundedBody.ts
139387
- var readBoundedBody = (req, limit) => new Promise((resolve) => {
139388
- const chunks = [];
139389
- let size = 0;
139390
- let settled = false;
139391
- const finish = (result) => {
139392
- if (settled) return;
139393
- settled = true;
139394
- resolve(result);
139395
- };
139396
- req.on("data", (chunk) => {
139397
- if (settled) return;
139398
- size += chunk.length;
139399
- if (size > limit) {
139400
- finish({ kind: "too-large" });
139401
- req.destroy();
139402
- return;
139403
- }
139404
- chunks.push(chunk);
139405
- });
139406
- req.on("end", () => finish({ kind: "ok", bytes: Buffer.concat(chunks) }));
139407
- req.on("error", () => finish({ kind: "read-failed" }));
139408
- req.on("close", () => finish({ kind: "read-failed" }));
139409
- });
139410
-
139411
- // src/deliveryHttp.ts
139412
- var LOOPBACK = "127.0.0.1";
139413
- var MAX_BODY_BYTES = 1 * 1024 * 1024;
139414
- var PATH = "/v1/delivery/messages";
139415
- var statusFor = (code2) => {
139416
- switch (code2) {
139417
- case "bad-request":
139418
- return 400;
139419
- case "unauthorized":
139420
- return 401;
139421
- case "unknown-bot":
139422
- case "unknown-target":
139423
- return 404;
139424
- case "bot-not-connected":
139425
- return 503;
139426
- case "delivery-failed":
139427
- return 502;
139428
- case "method-not-allowed":
139429
- return 405;
139430
- case "unsupported-media-type":
139431
- return 415;
139432
- case "payload-too-large":
139433
- return 413;
139434
- }
139435
- };
139436
- var writeJson = (res, status, body) => {
139437
- const payload = JSON.stringify(body);
139438
- res.writeHead(status, {
139439
- "content-type": "application/json; charset=utf-8",
139440
- "content-length": Buffer.byteLength(payload),
139441
- "cache-control": "no-store"
139442
- });
139443
- res.end(payload);
139444
- };
139445
- var writeError = (res, code2, message) => {
139446
- writeJson(res, statusFor(code2), { error: { code: code2, message } });
139447
- };
139448
- var isLoopbackRemote = (remote) => {
139449
- if (remote === void 0 || remote === "") return false;
139450
- return remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
139451
- };
139452
- var timingSafeEqualString = (a, b) => {
139453
- const left = Buffer.from(a);
139454
- const right = Buffer.from(b);
139455
- if (left.length !== right.length) return false;
139456
- return NodeCrypto.timingSafeEqual(left, right);
139457
- };
139458
- var parseSendBody = (raw) => {
139459
- let parsed;
139460
- try {
139461
- parsed = JSON.parse(raw.toString("utf8"));
139462
- } catch {
139463
- return "bad-request";
139464
- }
139465
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return "bad-request";
139466
- const record2 = parsed;
139467
- const botId = record2["botId"];
139468
- const targetId = record2["targetId"];
139469
- const text3 = record2["text"];
139470
- const format2 = record2["format"];
139471
- if (typeof botId !== "string" || botId.trim() === "") return "bad-request";
139472
- if (typeof targetId !== "string" || targetId.trim() === "") return "bad-request";
139473
- if (typeof text3 !== "string" || text3.trim() === "") return "bad-request";
139474
- if (format2 !== void 0 && format2 !== "plain" && format2 !== "markdown") return "bad-request";
139475
- for (const key of Object.keys(record2)) {
139476
- if (key !== "botId" && key !== "targetId" && key !== "text" && key !== "format") {
139477
- return "bad-request";
139478
- }
139479
- }
139480
- return {
139481
- botId: botId.trim(),
139482
- targetId: targetId.trim(),
139483
- text: text3,
139484
- format: format2 === "markdown" ? "markdown" : format2 === "plain" ? "plain" : void 0
139485
- };
139486
- };
139487
- var startDeliveryHttpServer = async (options) => {
139488
- const server = NodeHttp.createServer((req, res) => {
139489
- void (async () => {
139490
- if (!isLoopbackRemote(req.socket.remoteAddress)) {
139491
- writeError(res, "unauthorized", "Only loopback clients are accepted.");
139492
- return;
139493
- }
139494
- const url = new URL(req.url ?? "/", `http://${LOOPBACK}`);
139495
- if (url.pathname !== PATH) {
139496
- writeError(res, "bad-request", `Unknown path '${url.pathname}'.`);
139497
- return;
139498
- }
139499
- if (req.method !== "POST") {
139500
- res.setHeader("allow", "POST");
139501
- writeError(res, "method-not-allowed", "Only POST is accepted.");
139502
- return;
139503
- }
139504
- const contentType = (req.headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase();
139505
- if (contentType !== "application/json") {
139506
- writeError(res, "unsupported-media-type", "Content-Type must be application/json.");
139507
- return;
139508
- }
139509
- if (options.token !== "") {
139510
- const header = req.headers.authorization ?? "";
139511
- const prefix = "Bearer ";
139512
- if (!header.startsWith(prefix) || !timingSafeEqualString(header.slice(prefix.length), options.token)) {
139513
- writeError(res, "unauthorized", "Bearer token required.");
139514
- return;
139515
- }
139516
- }
139517
- const read = await readBoundedBody(req, MAX_BODY_BYTES);
139518
- if (read.kind !== "ok") {
139519
- if (read.kind === "too-large") {
139520
- writeError(res, "payload-too-large", "JSON body exceeds 1 MiB.");
139521
- } else {
139522
- writeError(res, "bad-request", "Failed to read body.");
139523
- }
139524
- return;
139525
- }
139526
- const parsed = parseSendBody(read.bytes);
139527
- if (typeof parsed === "string") {
139528
- writeError(res, parsed, "Invalid JSON body.");
139529
- return;
139530
- }
139531
- try {
139532
- const result = await options.send(parsed);
139533
- writeJson(res, 200, { sent: result.sent });
139534
- } catch (error) {
139535
- const rawCode = error instanceof Error ? error.code : void 0;
139536
- const code2 = isDeliverySendErrorCode(rawCode) ? rawCode : "delivery-failed";
139537
- const message = error instanceof Error ? error.message : String(error);
139538
- options.log?.warn("delivery http send failed", { code: code2, message });
139539
- writeError(res, code2, message);
139540
- }
139541
- })().catch((error) => {
139542
- options.log?.warn("delivery http handler crashed", {
139543
- error: error instanceof Error ? error.message : String(error)
139544
- });
139545
- if (!res.headersSent) writeError(res, "delivery-failed", "Internal error.");
139546
- });
139547
- });
139548
- await new Promise((resolve, reject) => {
139549
- server.once("error", reject);
139550
- server.listen(options.port, LOOPBACK, () => resolve());
139551
- });
139552
- const address = server.address();
139553
- const port = address.port;
139554
- options.log?.info("delivery http listening", { host: LOOPBACK, port, path: PATH });
139555
- return {
139556
- port,
139557
- origin: `http://${LOOPBACK}:${String(port)}`,
139558
- close: () => new Promise((resolve, reject) => {
139559
- server.close((error) => error ? reject(error) : resolve());
139560
- })
139561
- };
139562
- };
139563
-
139564
140046
  // src/sendFile.ts
139565
140047
  var failure = (code2, text3) => ({
139566
140048
  text: text3,
@@ -139616,7 +140098,7 @@ var makeSendFileTool = (deps) => ({
139616
140098
  handle: async (input, invocation) => {
139617
140099
  const path = typeof input["path"] === "string" ? input["path"].trim() : "";
139618
140100
  if (path === "") return failure("unreadable", "`path` is required.");
139619
- const binding = invocation.threadId === null ? void 0 : deps.boundChat(invocation.threadId);
140101
+ const binding = invocation.threadId === null ? void 0 : await deps.boundChat(invocation.threadId);
139620
140102
  if (binding === void 0) {
139621
140103
  return failure(
139622
140104
  "not-in-chat",
@@ -139795,22 +140277,22 @@ var makeSessions = (core) => {
139795
140277
  listedByChat.set(chatKeyOf(binding), listedThreadIds(rows));
139796
140278
  await deliver(binding, formatSessions(rows, Date.now(), t));
139797
140279
  };
139798
- const bindToSession = async (binding, chatKey, target) => {
140280
+ const bindToSession = async (binding, chatKey, target2) => {
139799
140281
  for (const [otherKey, record2] of Object.entries(core.state.chats)) {
139800
- if (otherKey !== chatKey && record2.threadId === target.threadId) bindChat(otherKey, null);
140282
+ if (otherKey !== chatKey && record2.threadId === target2.threadId) bindChat(otherKey, null);
139801
140283
  }
139802
- if (target.origin === "web" && !hasAttached(core.state, chatKey, target.threadId)) {
140284
+ if (target2.origin === "web" && !hasAttached(core.state, chatKey, target2.threadId)) {
139803
140285
  commit("thread-attached", {
139804
140286
  chatKey,
139805
- threadId: target.threadId,
140287
+ threadId: target2.threadId,
139806
140288
  projectId: binding.projectId,
139807
140289
  attachedAt: (/* @__PURE__ */ new Date()).toISOString()
139808
140290
  });
139809
140291
  }
139810
- commit("chat-updated", { chatKey, threadId: target.threadId, target: { kind: "thread" } });
140292
+ commit("chat-updated", { chatKey, threadId: target2.threadId, target: { kind: "thread" } });
139811
140293
  pendingAttachByChat.delete(chatKey);
139812
140294
  listedByChat.delete(chatKey);
139813
- await deliver(binding, t("resume.done", { title: target.title }));
140295
+ await deliver(binding, t("resume.done", { title: target2.title }));
139814
140296
  };
139815
140297
  const resumeSession = async (binding, arg) => {
139816
140298
  const chatKey = chatKeyOf(binding);
@@ -139845,8 +140327,8 @@ var makeSessions = (core) => {
139845
140327
  return;
139846
140328
  }
139847
140329
  }
139848
- const target = rows.find((row) => row.threadId === targetId);
139849
- if (target === void 0) {
140330
+ const target2 = rows.find((row) => row.threadId === targetId);
140331
+ if (target2 === void 0) {
139850
140332
  if (confirming) pendingAttachByChat.delete(chatKey);
139851
140333
  await deliver(binding, t("resume.gone"));
139852
140334
  return;
@@ -139855,21 +140337,21 @@ var makeSessions = (core) => {
139855
140337
  await deliver(binding, t("resume.busy"));
139856
140338
  return;
139857
140339
  }
139858
- if (target.current) {
140340
+ if (target2.current) {
139859
140341
  if (confirming) pendingAttachByChat.delete(chatKey);
139860
140342
  await deliver(binding, t("resume.already"));
139861
140343
  return;
139862
140344
  }
139863
- if (target.running && target.elsewhere) {
140345
+ if (target2.running && target2.elsewhere) {
139864
140346
  await deliver(binding, t("resume.runningElsewhere"));
139865
140347
  return;
139866
140348
  }
139867
- if (target.origin === "web" && !confirming && pending !== target.threadId && !hasAttached(core.state, chatKey, target.threadId)) {
139868
- pendingAttachByChat.set(chatKey, target.threadId);
139869
- await deliver(binding, t("resume.confirmWeb", { title: target.title }));
140349
+ if (target2.origin === "web" && !confirming && pending !== target2.threadId && !hasAttached(core.state, chatKey, target2.threadId)) {
140350
+ pendingAttachByChat.set(chatKey, target2.threadId);
140351
+ await deliver(binding, t("resume.confirmWeb", { title: target2.title }));
139870
140352
  return;
139871
140353
  }
139872
- await bindToSession(binding, chatKey, target);
140354
+ await bindToSession(binding, chatKey, target2);
139873
140355
  };
139874
140356
  return { listedByChat, sessionRowsFor, showSessions, resumeSession };
139875
140357
  };
@@ -139915,7 +140397,7 @@ var makeTeamRuns = (core, deps) => {
139915
140397
  const binding = chatByRun.get(runId);
139916
140398
  if (binding === void 0) return;
139917
140399
  chatByRun.delete(runId);
139918
- await clearWorking(binding, chatKeyOf(binding));
140400
+ await clearWorking(binding, chatKeyOf(binding), outcomeOfTeamRun(event.data.state));
139919
140401
  await deliver(binding, event.summary);
139920
140402
  });
139921
140403
  return { fetchTeams, startTeamRun };
@@ -139924,7 +140406,27 @@ var makeTeamRuns = (core, deps) => {
139924
140406
  // src/telegramApi.ts
139925
140407
  var API_BASE = "https://api.telegram.org";
139926
140408
  var POLL_TIMEOUT_SECONDS = 45;
140409
+ var CALL_TIMEOUT_MS = 15e3;
140410
+ var POLL_CALL_TIMEOUT_MS = (POLL_TIMEOUT_SECONDS + 10) * 1e3;
140411
+ var UPLOAD_TIMEOUT_MS = 12e4;
140412
+ var DOWNLOAD_TIMEOUT_MS = 6e4;
140413
+ var HINT_TIMEOUT_MS = 3e3;
140414
+ var isTelegramToken = (token) => /^\d{5,20}:[A-Za-z0-9_-]{20,}$/u.test(token);
139927
140415
  var TELEGRAM_TEXT_MAX_CHARS = 4e3;
140416
+ var chatIdOf = (message) => {
140417
+ const thread = message.message_thread_id;
140418
+ return message.is_topic_message === true && thread !== void 0 && thread > 0 ? `${String(message.chat.id)}:${String(thread)}` : String(message.chat.id);
140419
+ };
140420
+ var parentChatIdOf = (message) => chatIdOf(message) === String(message.chat.id) ? void 0 : String(message.chat.id);
140421
+ var splitChatId = (chatId) => {
140422
+ const match = /^(.+):([1-9]\d*)$/u.exec(chatId);
140423
+ return match === null ? { chat: chatId, thread: null } : { chat: match[1] ?? chatId, thread: Number(match[2]) };
140424
+ };
140425
+ var target = (chatId) => {
140426
+ const { chat, thread } = splitChatId(chatId);
140427
+ return { chat_id: chat, ...thread === null ? {} : { message_thread_id: thread } };
140428
+ };
140429
+ var chatOnly = (chatId) => ({ chat_id: splitChatId(chatId).chat });
139928
140430
  var TelegramApiError = class extends Error {
139929
140431
  code;
139930
140432
  /** 限流(429)时平台给的等待秒数;别的错误没有。 */
@@ -140019,12 +140521,27 @@ var passesGroupGate2 = (input) => {
140019
140521
  return input.groupMention === "ambient" && names.length === 0;
140020
140522
  };
140021
140523
  var makeTelegramApi = (input) => {
140022
- const call = async (method, body) => {
140023
- const response = await input.http(`${API_BASE}/bot${input.token}/${method}`, {
140024
- method: "POST",
140025
- headers: { "content-type": "application/json" },
140026
- body: JSON.stringify(body)
140027
- });
140524
+ const send = async (method, url, init, timeoutMs) => {
140525
+ try {
140526
+ return await input.http(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
140527
+ } catch (error) {
140528
+ if (error instanceof Error && error.name === "TimeoutError") {
140529
+ throw new TelegramApiError(method, void 0, `timed out after ${String(timeoutMs)} ms`);
140530
+ }
140531
+ throw error;
140532
+ }
140533
+ };
140534
+ const call = async (method, body, timeoutMs = CALL_TIMEOUT_MS) => {
140535
+ const response = await send(
140536
+ method,
140537
+ `${API_BASE}/bot${input.token}/${method}`,
140538
+ {
140539
+ method: "POST",
140540
+ headers: { "content-type": "application/json" },
140541
+ body: JSON.stringify(body)
140542
+ },
140543
+ timeoutMs
140544
+ );
140028
140545
  const envelope = await response.json();
140029
140546
  if (envelope.ok !== true) {
140030
140547
  throw new TelegramApiError(
@@ -140038,12 +140555,18 @@ var makeTelegramApi = (input) => {
140038
140555
  };
140039
140556
  const upload = async (method, field, chatId, file) => {
140040
140557
  const form = new FormData();
140041
- form.set("chat_id", chatId);
140558
+ const where = target(chatId);
140559
+ form.set("chat_id", where.chat_id);
140560
+ if (where.message_thread_id !== void 0) {
140561
+ form.set("message_thread_id", String(where.message_thread_id));
140562
+ }
140042
140563
  form.set(field, new Blob([new Uint8Array(file.bytes)], { type: file.mimeType }), file.name);
140043
- const response = await input.http(`${API_BASE}/bot${input.token}/${method}`, {
140044
- method: "POST",
140045
- body: form
140046
- });
140564
+ const response = await send(
140565
+ method,
140566
+ `${API_BASE}/bot${input.token}/${method}`,
140567
+ { method: "POST", body: form },
140568
+ UPLOAD_TIMEOUT_MS
140569
+ );
140047
140570
  const envelope = await response.json();
140048
140571
  if (envelope.ok !== true) {
140049
140572
  throw new TelegramApiError(
@@ -140061,7 +140584,7 @@ var makeTelegramApi = (input) => {
140061
140584
  });
140062
140585
  const sendSingle = async (method, chatId, body, rows) => {
140063
140586
  const sent = await call(method, {
140064
- chat_id: chatId,
140587
+ ...target(chatId),
140065
140588
  ...body,
140066
140589
  ...rows === void 0 ? {} : { reply_markup: keyboard(rows) }
140067
140590
  });
@@ -140070,7 +140593,7 @@ var makeTelegramApi = (input) => {
140070
140593
  const editSingle = async (chatId, messageId, body, rows) => {
140071
140594
  try {
140072
140595
  await call("editMessageText", {
140073
- chat_id: chatId,
140596
+ ...chatOnly(chatId),
140074
140597
  message_id: Number(messageId),
140075
140598
  ...body,
140076
140599
  reply_markup: keyboard(rows)
@@ -140081,23 +140604,49 @@ var makeTelegramApi = (input) => {
140081
140604
  };
140082
140605
  return {
140083
140606
  /** 机器人自己是谁;群里的命令靠它的 `username` 认出是不是发给自己的。 */
140084
- getMe: () => call("getMe", {}),
140607
+ getMe: () => call(
140608
+ "getMe",
140609
+ {}
140610
+ ),
140611
+ /**
140612
+ * 这个机器人是不是还挂着别处设的 webhook。挂着的话 `getUpdates` 一直回 409,表现就是
140613
+ * 「没反应」。只查不删:那多半是另一个服务正在用这个机器人。
140614
+ */
140615
+ getWebhookInfo: () => call("getWebhookInfo", {}),
140616
+ /** 整表替换输入 `/` 时弹出的命令菜单(缺省范围、缺省语言)。 */
140617
+ setMyCommands: (commands) => call("setMyCommands", { commands }),
140618
+ /** 「正在输入」:客户端显示几秒,或机器人发出下一条消息时消失。 */
140619
+ sendTyping: (chatId) => call("sendChatAction", { ...target(chatId), action: "typing" }, HINT_TIMEOUT_MS),
140620
+ /** 给一条消息贴一个表情(整体替换);`emoji` 为 null 就撤掉。 */
140621
+ setReaction: (chatId, messageId, emoji) => call(
140622
+ "setMessageReaction",
140623
+ {
140624
+ ...chatOnly(chatId),
140625
+ message_id: Number(messageId),
140626
+ reaction: emoji === null ? [] : [{ type: "emoji", emoji }]
140627
+ },
140628
+ HINT_TIMEOUT_MS
140629
+ ),
140085
140630
  /** 群里一共多少成员(含机器人自己)。单人群放宽靠它:两个成员就是发命令的人加机器人。 */
140086
- getChatMemberCount: (chatId) => call("getChatMemberCount", { chat_id: chatId }),
140631
+ getChatMemberCount: (chatId) => call("getChatMemberCount", chatOnly(chatId)),
140087
140632
  /**
140088
140633
  * 拉一批更新。挂在服务端最多 `POLL_TIMEOUT_SECONDS` 秒。
140089
140634
  *
140090
140635
  * `allowed_updates` 必须列全:它是服务端白名单,不写 `callback_query` 的话按钮点击
140091
140636
  * 根本不会投递过来,而且不会有任何报错。
140092
140637
  */
140093
- getUpdates: (offset) => call("getUpdates", {
140094
- offset,
140095
- timeout: POLL_TIMEOUT_SECONDS,
140096
- allowed_updates: ["message", "callback_query"]
140097
- }),
140638
+ getUpdates: (offset) => call(
140639
+ "getUpdates",
140640
+ {
140641
+ offset,
140642
+ timeout: POLL_TIMEOUT_SECONDS,
140643
+ allowed_updates: ["message", "callback_query"]
140644
+ },
140645
+ POLL_CALL_TIMEOUT_MS
140646
+ ),
140098
140647
  sendMessage: async (chatId, text3) => {
140099
140648
  for (const chunk of splitForTelegram(text3)) {
140100
- await call("sendMessage", { chat_id: chatId, text: chunk });
140649
+ await call("sendMessage", { ...target(chatId), text: chunk });
140101
140650
  }
140102
140651
  },
140103
140652
  /**
@@ -140147,7 +140696,12 @@ var makeTelegramApi = (input) => {
140147
140696
  });
140148
140697
  const path = file.file_path;
140149
140698
  if (path === void 0) throw new TelegramApiError("getFile", void 0, "no file_path");
140150
- const response = await input.http(`${API_BASE}/file/bot${input.token}/${path}`);
140699
+ const response = await send(
140700
+ "file",
140701
+ `${API_BASE}/file/bot${input.token}/${path}`,
140702
+ {},
140703
+ DOWNLOAD_TIMEOUT_MS
140704
+ );
140151
140705
  if (!response.ok) {
140152
140706
  throw new TelegramApiError("file", response.status, "download failed");
140153
140707
  }
@@ -140156,6 +140710,29 @@ var makeTelegramApi = (input) => {
140156
140710
  };
140157
140711
  };
140158
140712
 
140713
+ // src/telegramCommandMenu.ts
140714
+ var DESCRIPTIONS = {
140715
+ new: "menu.new",
140716
+ stop: "menu.stop",
140717
+ continue: "menu.continue",
140718
+ repo: "menu.repo",
140719
+ sessions: "menu.sessions",
140720
+ team: "menu.team",
140721
+ thread: "menu.thread",
140722
+ model: "menu.model",
140723
+ plan: "menu.plan",
140724
+ status: "menu.status",
140725
+ help: "menu.help",
140726
+ resume: "menu.resume",
140727
+ panel: "menu.panel"
140728
+ };
140729
+ var TELEGRAM_MENU = [
140730
+ ...PANEL_COMMANDS.map((spec) => ({ command: spec.line.slice(1), key: DESCRIPTIONS[spec.id] })),
140731
+ { command: "resume", key: DESCRIPTIONS.resume },
140732
+ { command: "panel", key: DESCRIPTIONS.panel }
140733
+ ];
140734
+ var telegramCommandMenu = (t) => TELEGRAM_MENU.map((item) => ({ command: item.command, description: t(item.key) }));
140735
+
140159
140736
  // src/telegramChoice.ts
140160
140737
  var renderChoiceCard = (card) => ({
140161
140738
  text: [card.title, card.heading ?? "", card.body, card.note ?? ""].filter((part) => part !== "").join("\n\n"),
@@ -140294,8 +140871,18 @@ var BACKOFF_MAX_MS = 6e4;
140294
140871
  var TELEGRAM_CARD_THROTTLE_MS = 1200;
140295
140872
  var FLOOD_WAIT_MAX_SECONDS = 30;
140296
140873
  var REMEMBERED_CARD_LIMIT = 200;
140297
- var tokenOf = (binding) => binding.values["telegramToken"] ?? "";
140874
+ var tokenOf = (binding) => (binding.values["telegramToken"] ?? "").trim();
140875
+ var REACTION_WORKING = "\u{1F440}";
140876
+ var REACTION_DONE = "\u{1F44D}";
140877
+ var REACTION_FAILED = "\u{1F44E}";
140298
140878
  var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
140879
+ var webhookHost = (url) => {
140880
+ try {
140881
+ return new URL(url).host;
140882
+ } catch {
140883
+ return "(unreadable)";
140884
+ }
140885
+ };
140299
140886
  var makeTelegramAdapter = (deps) => {
140300
140887
  const apis = /* @__PURE__ */ new Map();
140301
140888
  const sleep3 = deps.sleep ?? defaultSleep;
@@ -140340,26 +140927,31 @@ var makeTelegramAdapter = (deps) => {
140340
140927
  const panelKeyboards = remembering();
140341
140928
  const panelClick = (rowId, callback, operatorId, handlers) => {
140342
140929
  const messageId = callback.message?.message_id;
140343
- const chatId = callback.message?.chat.id;
140930
+ const chatId = callback.message === void 0 ? void 0 : chatIdOf(callback.message);
140344
140931
  if (messageId === void 0 || chatId === void 0) return t("panel.expired");
140345
- const keyboard = panelKeyboards.get(`${rowId}:${String(chatId)}:${String(messageId)}`);
140932
+ const keyboard = panelKeyboards.get(`${rowId}:${chatId}:${String(messageId)}`);
140346
140933
  const parsed = parsePanelData(callback.data ?? "");
140347
140934
  const action = keyboard === void 0 || parsed === null || parsed.version !== keyboard.version ? void 0 : keyboard.actions[parsed.index];
140348
140935
  if (action === void 0) return t("panel.expired");
140349
140936
  return handlers.onPanelClick?.({
140350
- chatId: String(chatId),
140937
+ chatId,
140351
140938
  cardId: String(messageId),
140352
140939
  operatorId,
140353
140940
  op: action.op,
140354
140941
  arg: action.arg ?? null
140355
140942
  }) ?? null;
140356
140943
  };
140357
- const apiFor = async (binding) => {
140944
+ const apiFor = (binding) => {
140945
+ const token = tokenOf(binding);
140946
+ const builtFor = JSON.stringify([token, binding.proxy === void 0 ? [] : [binding.proxy]]);
140358
140947
  const existing = apis.get(binding.rowId);
140359
- if (existing !== void 0) return existing;
140360
- const http = await deps.fetchVia(binding.proxy);
140361
- const api = makeTelegramApi({ http, token: tokenOf(binding) });
140362
- apis.set(binding.rowId, api);
140948
+ if (existing?.builtFor === builtFor) return existing.api;
140949
+ const api = deps.fetchVia(binding.proxy).then((http) => makeTelegramApi({ http, token }));
140950
+ const entry = { builtFor, api };
140951
+ apis.set(binding.rowId, entry);
140952
+ api.catch(() => {
140953
+ if (apis.get(binding.rowId) === entry) apis.delete(binding.rowId);
140954
+ });
140363
140955
  return api;
140364
140956
  };
140365
140957
  const makeWriter = (api, chatId, resume) => makeTurnWriter({
@@ -140393,6 +140985,7 @@ var makeTelegramAdapter = (deps) => {
140393
140985
  let offset = 0;
140394
140986
  let backoff = BACKOFF_START_MS;
140395
140987
  let botUsername;
140988
+ let menuSent = false;
140396
140989
  const isSoloGroup = makeSoloGroupCheck(async (chatId) => {
140397
140990
  const count = await (await apiFor(binding)).getChatMemberCount(chatId);
140398
140991
  return Number.isFinite(count) ? count <= 2 : void 0;
@@ -140403,6 +140996,15 @@ var makeTelegramAdapter = (deps) => {
140403
140996
  try {
140404
140997
  const api = await apiFor(binding);
140405
140998
  botUsername ??= (await api.getMe()).username ?? null;
140999
+ if (!menuSent) {
141000
+ menuSent = true;
141001
+ void api.setMyCommands(telegramCommandMenu(t)).catch((error) => {
141002
+ deps.log.warn("telegram could not set the command menu", {
141003
+ bot: binding.displayName,
141004
+ error: describeError(error)
141005
+ });
141006
+ });
141007
+ }
140406
141008
  const updates = await api.getUpdates(offset);
140407
141009
  if (!running) return;
140408
141010
  backoff = BACKOFF_START_MS;
@@ -140438,7 +141040,7 @@ var makeTelegramAdapter = (deps) => {
140438
141040
  if (unsupported === null) continue;
140439
141041
  handlers.onMessage({
140440
141042
  messageId: String(message.message_id),
140441
- chatId: String(message.chat.id),
141043
+ chatId: chatIdOf(message),
140442
141044
  senderId: message.from === void 0 ? null : String(message.from.id),
140443
141045
  text: "",
140444
141046
  chatKind: message.chat.type === "private" ? "private" : "group",
@@ -140447,9 +141049,11 @@ var makeTelegramAdapter = (deps) => {
140447
141049
  });
140448
141050
  continue;
140449
141051
  }
141052
+ const parentChatId = parentChatIdOf(message);
140450
141053
  handlers.onMessage({
140451
141054
  messageId: String(message.message_id),
140452
- chatId: String(message.chat.id),
141055
+ chatId: chatIdOf(message),
141056
+ ...parentChatId === void 0 ? {} : { parentChatId },
140453
141057
  senderId: message.from === void 0 ? null : String(message.from.id),
140454
141058
  text: text3,
140455
141059
  chatKind: message.chat.type === "private" ? "private" : "group",
@@ -140487,16 +141091,68 @@ var makeTelegramAdapter = (deps) => {
140487
141091
  await api.sendMessage(binding.chatId, text3);
140488
141092
  },
140489
141093
  checkConnection: async (bot) => {
140490
- if (tokenOf(bot) === "") {
141094
+ const token = tokenOf(bot);
141095
+ if (token === "") {
140491
141096
  return { stage: "credentials", ok: false, detail: "missing bot token in saved config" };
140492
141097
  }
141098
+ if (!isTelegramToken(token)) {
141099
+ return {
141100
+ stage: "token-format",
141101
+ ok: false,
141102
+ detail: "saved token is not in the BotFather format",
141103
+ hint: "badToken"
141104
+ };
141105
+ }
141106
+ const api = await apiFor(bot);
141107
+ let username;
141108
+ try {
141109
+ const me = await api.getMe();
141110
+ if (me.is_bot !== true) {
141111
+ return { stage: "getMe", ok: false, detail: "token is not a bot's", hint: "notBot" };
141112
+ }
141113
+ username = me.username !== void 0 && me.username !== "" ? `@${me.username}` : "(no username)";
141114
+ } catch (error) {
141115
+ const rejected = error instanceof TelegramApiError && (error.code === 401 || error.code === 404);
141116
+ return {
141117
+ stage: "getMe",
141118
+ ok: false,
141119
+ detail: describeError(error),
141120
+ ...rejected ? { hint: "unauthorized" } : {}
141121
+ };
141122
+ }
140493
141123
  try {
140494
- const me = await (await apiFor(bot)).getMe();
140495
- const username = me.username !== void 0 && me.username !== "" ? `@${me.username}` : "(no username)";
140496
- return { stage: "getMe", ok: true, detail: `username=${username}` };
141124
+ const webhook = (await api.getWebhookInfo()).url ?? "";
141125
+ if (webhook !== "") {
141126
+ return {
141127
+ stage: "getWebhookInfo",
141128
+ ok: false,
141129
+ detail: `webhook is set (host ${webhookHost(webhook)})`,
141130
+ hint: "webhookActive"
141131
+ };
141132
+ }
140497
141133
  } catch (error) {
140498
- return { stage: "getMe", ok: false, detail: describeError(error) };
141134
+ return { stage: "getWebhookInfo", ok: false, detail: describeError(error) };
140499
141135
  }
141136
+ return { stage: "getMe", ok: true, detail: `username=${username} webhook=none` };
141137
+ },
141138
+ sendTyping: async (binding) => {
141139
+ await (await apiFor(binding)).sendTyping(binding.chatId);
141140
+ },
141141
+ acknowledge: async (binding, messageId) => {
141142
+ try {
141143
+ await (await apiFor(binding)).setReaction(binding.chatId, messageId, REACTION_WORKING);
141144
+ return REACTION_WORKING;
141145
+ } catch (error) {
141146
+ if (error instanceof TelegramApiError && error.code === 400) {
141147
+ deps.log.info("telegram could not react to the message", { reason: error.message });
141148
+ return null;
141149
+ }
141150
+ throw error;
141151
+ }
141152
+ },
141153
+ clearAcknowledge: async (binding, messageId, _ackId, outcome) => {
141154
+ const emoji = outcome === "done" ? REACTION_DONE : outcome === "failed" ? REACTION_FAILED : null;
141155
+ await (await apiFor(binding)).setReaction(binding.chatId, messageId, emoji);
140500
141156
  },
140501
141157
  sendImage: async (binding, image2) => {
140502
141158
  const api = await apiFor(binding);
@@ -140623,6 +141279,10 @@ var makeTurns = (core, deps) => {
140623
141279
  return true;
140624
141280
  }
140625
141281
  };
141282
+ const waitingFor = (chatKey, threadId) => {
141283
+ const waiting = pendingByChat.get(chatKey);
141284
+ return waiting !== void 0 && waiting.threadId === threadId ? waiting : void 0;
141285
+ };
140626
141286
  const interactionModeOf = (chatKey) => chatOf(core.state, chatKey).plan ? "plan" : "default";
140627
141287
  const ensureThread = async (binding, titleSeed) => {
140628
141288
  const chatKey = chatKeyOf(binding);
@@ -140682,9 +141342,8 @@ var makeTurns = (core, deps) => {
140682
141342
  const text3 = message.text;
140683
141343
  const threadId = await ensureThread(binding, text3 || message.resources[0]?.name || "Chat");
140684
141344
  if (threadId === null) return null;
140685
- const waiting = pendingByChat.get(chatKey);
141345
+ const carried = waitingFor(chatKey, threadId);
140686
141346
  pendingByChat.delete(chatKey);
140687
- const carried = waiting?.threadId === threadId ? waiting : void 0;
140688
141347
  const own = await collectResources(
140689
141348
  binding,
140690
141349
  threadId,
@@ -140695,6 +141354,7 @@ var makeTurns = (core, deps) => {
140695
141354
  const attachments = [...carried?.attachments ?? [], ...own.attachments];
140696
141355
  const notes = [...carried?.notes ?? [], ...own.notes];
140697
141356
  const userMessageId = crypto.randomUUID();
141357
+ deps.anchors.note(threadId, userMessageId, binding);
140698
141358
  let intentAction;
140699
141359
  if (binding.queueMode === "steer") {
140700
141360
  try {
@@ -140742,7 +141402,8 @@ var makeTurns = (core, deps) => {
140742
141402
  chatKey,
140743
141403
  threadId,
140744
141404
  userMessageId,
140745
- text3.trim() || attachments[0]?.name || message.resources[0]?.name || ""
141405
+ text3.trim() || attachments[0]?.name || message.resources[0]?.name || "",
141406
+ binding.reply
140746
141407
  );
140747
141408
  return threadId;
140748
141409
  };
@@ -140768,7 +141429,14 @@ var makeTurns = (core, deps) => {
140768
141429
  })
140769
141430
  );
140770
141431
  };
140771
- return { threadStillUsable, ensureThread, startTurn, receiveAttachmentsOnly };
141432
+ const hasWaitingAttachments = (chatKey) => (waitingFor(chatKey, chatOf(core.state, chatKey).threadId)?.attachments.length ?? 0) > 0;
141433
+ return {
141434
+ threadStillUsable,
141435
+ ensureThread,
141436
+ startTurn,
141437
+ receiveAttachmentsOnly,
141438
+ hasWaitingAttachments
141439
+ };
140772
141440
  };
140773
141441
 
140774
141442
  // src/webSync.ts
@@ -140842,7 +141510,7 @@ var botOf = (config2, adapters, rowId) => {
140842
141510
  const adapterFor = (platform) => adapters.find((adapter) => adapter.platform === platform);
140843
141511
  return readBots(config2, adapterFor).find((bot) => bot.rowId === rowId);
140844
141512
  };
140845
- var reportOf = (bot, stage, ok, detail) => ({
141513
+ var reportOf = (bot, stage, ok, detail, hint) => ({
140846
141514
  ref: diagnosticRef("conn"),
140847
141515
  platform: bot.platform,
140848
141516
  botName: bot.displayName,
@@ -140850,7 +141518,8 @@ var reportOf = (bot, stage, ok, detail) => ({
140850
141518
  stage,
140851
141519
  ok,
140852
141520
  detail,
140853
- at: (/* @__PURE__ */ new Date()).toISOString()
141521
+ at: (/* @__PURE__ */ new Date()).toISOString(),
141522
+ ...hint === void 0 ? {} : { hint }
140854
141523
  });
140855
141524
  var probe = async (adapters, bot) => {
140856
141525
  const adapter = adapters.find((candidate) => candidate.platform === bot.platform);
@@ -140867,7 +141536,8 @@ var probe = async (adapters, bot) => {
140867
141536
  bot,
140868
141537
  result.stage,
140869
141538
  result.ok,
140870
- result.ok ? result.detail : redactor.redact(result.detail)
141539
+ result.ok ? result.detail : redactor.redact(result.detail),
141540
+ result.hint
140871
141541
  );
140872
141542
  } catch (error) {
140873
141543
  return reportOf(bot, "check", false, redactor.redact(describeError(error)));
@@ -140886,7 +141556,7 @@ var emitReport = (emit, report, action) => {
140886
141556
  }
140887
141557
  emit({
140888
141558
  kind: "failed",
140889
- reasonKey: `${action.keyPrefix}.failed`,
141559
+ reasonKey: report.hint !== void 0 && action.keyPrefix === "rowAction.checkConnection" ? `rowAction.checkConnection.hint.${report.hint}` : `${action.keyPrefix}.failed`,
140890
141560
  params: { ref: report.ref },
140891
141561
  detail
140892
141562
  });
@@ -141589,6 +142259,7 @@ ${kept}`;
141589
142259
  icon: "wecom",
141590
142260
  label: "WeCom",
141591
142261
  columns: COLUMNS4,
142262
+ unlistedValues: ["wecomEndpoint"],
141592
142263
  isConfigured: (binding) => {
141593
142264
  const { botId, botSecret } = credentials2(binding);
141594
142265
  return botId !== "" && botSecret !== "";
@@ -142695,7 +143366,9 @@ var imPlugin = definePlugin({
142695
143366
  id: "im",
142696
143367
  apiVersion: 3,
142697
143368
  activate: async (ctx) => {
142698
- const t = makeTranslate(parseReplyLanguage(ctx.config[REPLY_LANGUAGE_FIELD]));
143369
+ const { t, setLanguage } = makeLiveTranslate(
143370
+ parseReplyLanguage(ctx.config[REPLY_LANGUAGE_FIELD])
143371
+ );
142699
143372
  const adapters = [
142700
143373
  makeLarkAdapter({
142701
143374
  http: ctx.http,
@@ -142764,29 +143437,36 @@ var imPlugin = definePlugin({
142764
143437
  onDispose: ctx.onDispose
142765
143438
  },
142766
143439
  pluginId: ctx.id,
142767
- config: ctx.config,
143440
+ config: () => ctx.config,
142768
143441
  derivedState: ctx.derivedState,
142769
143442
  t,
142770
143443
  adapters
142771
143444
  });
142772
143445
  const acknowledgements = makeAcknowledgements(core);
142773
143446
  const failures = makeFailureNotices(core);
142774
- const replyCards = makeReplyCards(core, failures);
143447
+ const replyAnchors = makeReplyAnchors(core.host);
143448
+ const replyCards = makeReplyCards(core, failures, replyAnchors);
142775
143449
  const teams = makeTeamRuns(core, { clearWorking: acknowledgements.clearWorking });
142776
143450
  const notifications = makeNotifications(core, {
142777
143451
  clearWorking: acknowledgements.clearWorking,
142778
143452
  replyCards,
142779
- failures
143453
+ failures,
143454
+ anchors: replyAnchors
142780
143455
  });
142781
143456
  const webSync = makeWebSync(core);
142782
- const approvals = makeApprovals(core);
142783
- const interactions = makeInteractions(core, approvals);
143457
+ const approvals = makeApprovals(core, replyAnchors);
143458
+ const interactions = makeInteractions(core, {
143459
+ ...approvals,
143460
+ anchors: replyAnchors,
143461
+ rotateTurnCard: replyCards.turnCards.rotate
143462
+ });
142784
143463
  const inboundAttachments = makeInboundAttachments(core);
142785
143464
  const turns = makeTurns(core, {
142786
143465
  attachments: inboundAttachments,
142787
143466
  queueFlow: interactions.queueFlow,
142788
143467
  rememberThreadProject: notifications.rememberThreadProject,
142789
- noteInboundUserMessage: webSync.noteInboundUserMessage
143468
+ noteInboundUserMessage: webSync.noteInboundUserMessage,
143469
+ anchors: replyAnchors
142790
143470
  });
142791
143471
  const sessions = makeSessions(core);
142792
143472
  const modelPlan = makeModelPlan(core, turns);
@@ -142802,7 +143482,7 @@ var imPlugin = definePlugin({
142802
143482
  });
142803
143483
  const chatSetup = makeChatSetup(core, {
142804
143484
  rows: projectRows,
142805
- openPicker: (chatKey, rows) => panelWiring.panel.openProjects(chatKey, rows)
143485
+ openPicker: (chatKey, rows, reply) => panelWiring.panel.openProjects(chatKey, rows, reply)
142806
143486
  });
142807
143487
  const commands = makeCommands(core, {
142808
143488
  clearWorking: acknowledgements.clearWorking,
@@ -142810,7 +143490,7 @@ var imPlugin = definePlugin({
142810
143490
  sessions,
142811
143491
  modelPlan,
142812
143492
  chatSetup,
142813
- openPanel: (chatKey) => panelWiring.panel.open(chatKey)
143493
+ openPanel: (chatKey, reply) => panelWiring.panel.open(chatKey, reply)
142814
143494
  });
142815
143495
  const inbound = makeInbound(core, {
142816
143496
  enqueueForChat: chatQueue.enqueueForChat,
@@ -142828,7 +143508,10 @@ var imPlugin = definePlugin({
142828
143508
  name: "im",
142829
143509
  tools: [
142830
143510
  makeSendFileTool({
142831
- boundChat: (threadId) => core.bindingOfThread(threadId),
143511
+ boundChat: async (threadId) => {
143512
+ const binding = core.bindingOfThread(threadId);
143513
+ return binding === void 0 ? void 0 : replyAnchors.anchored(binding, threadId, null);
143514
+ },
142832
143515
  adapterFor: (binding) => core.adapterFor(binding.platform),
142833
143516
  translate: t,
142834
143517
  log: {
@@ -142838,34 +143521,21 @@ var imPlugin = definePlugin({
142838
143521
  })
142839
143522
  ]
142840
143523
  });
142841
- const { bots, connecting } = startConnections(core, inbound.inboundHandlersFor);
142842
- if (readDeliveryHttpEnabled(ctx.config)) {
142843
- const port = readDeliveryHttpPort(ctx.config);
142844
- const token = readDeliveryHttpToken(ctx.config);
142845
- const send = makeDeliverySender(core, ctx.config);
142846
- try {
142847
- const server = await startDeliveryHttpServer({
142848
- port,
142849
- token,
142850
- send,
142851
- log: {
142852
- info: (message, fields) => ctx.log.info(message, fields),
142853
- warn: (message, fields) => ctx.log.warn(message, fields)
142854
- }
142855
- });
142856
- ctx.onDispose(() => {
142857
- void server.close().catch((error) => {
142858
- ctx.log.warn("delivery http could not close", {
142859
- error: error instanceof Error ? error.message : String(error)
142860
- });
142861
- });
142862
- });
142863
- } catch (error) {
142864
- ctx.log.warn("delivery http failed to listen", {
142865
- port,
142866
- error: error instanceof Error ? error.message : String(error)
142867
- });
142868
- }
143524
+ const connections = startConnections(core, inbound.inboundHandlersFor);
143525
+ const { bots, connecting } = connections.reconcile();
143526
+ const deliveryHttp = makeDeliveryHttp({
143527
+ // 每次请求按此刻的设置找目标:投递目标表改了不用重开监听。
143528
+ send: (input) => makeDeliverySender(core, ctx.config)(input),
143529
+ log: ctx.log,
143530
+ onDispose: ctx.onDispose
143531
+ });
143532
+ await deliveryHttp.apply(ctx.config);
143533
+ if (typeof ctx.settings.onChange === "function") {
143534
+ ctx.settings.onChange(async (config2) => {
143535
+ setLanguage(parseReplyLanguage(config2[REPLY_LANGUAGE_FIELD]));
143536
+ connections.reconcile();
143537
+ await deliveryHttp.apply(config2);
143538
+ });
142869
143539
  }
142870
143540
  ctx.log.info("chat plugin active", { bots, connecting });
142871
143541
  }