opencode-feishu-plugin 0.1.1 → 0.1.2

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.
Files changed (4) hide show
  1. package/README.en.md +74 -5
  2. package/README.md +135 -241
  3. package/dist/index.js +1958 -473
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8870,11 +8870,11 @@ var require_mime_types = __commonJS({
8870
8870
  }
8871
8871
  return false;
8872
8872
  }
8873
- function contentType(str5) {
8874
- if (!str5 || typeof str5 !== "string") {
8873
+ function contentType(str6) {
8874
+ if (!str6 || typeof str6 !== "string") {
8875
8875
  return false;
8876
8876
  }
8877
- var mime = str5.indexOf("/") === -1 ? exports.lookup(str5) : str5;
8877
+ var mime = str6.indexOf("/") === -1 ? exports.lookup(str6) : str6;
8878
8878
  if (!mime) {
8879
8879
  return false;
8880
8880
  }
@@ -9413,14 +9413,14 @@ var require_implementation = __commonJS({
9413
9413
  return arr;
9414
9414
  };
9415
9415
  var joiny = function(arr, joiner) {
9416
- var str5 = "";
9416
+ var str6 = "";
9417
9417
  for (var i = 0; i < arr.length; i += 1) {
9418
- str5 += arr[i];
9418
+ str6 += arr[i];
9419
9419
  if (i + 1 < arr.length) {
9420
- str5 += joiner;
9420
+ str6 += joiner;
9421
9421
  }
9422
9422
  }
9423
- return str5;
9423
+ return str6;
9424
9424
  };
9425
9425
  module.exports = function bind6(that) {
9426
9426
  var target = this;
@@ -9978,8 +9978,8 @@ var require_form_data = __commonJS({
9978
9978
  var setToStringTag = require_es_set_tostringtag();
9979
9979
  var hasOwn = require_hasown();
9980
9980
  var populate = require_populate();
9981
- function escapeHeaderParam(str5) {
9982
- return String(str5).replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/"/g, "%22");
9981
+ function escapeHeaderParam(str6) {
9982
+ return String(str6).replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/"/g, "%22");
9983
9983
  }
9984
9984
  function FormData4(options2) {
9985
9985
  if (!(this instanceof FormData4)) {
@@ -10303,13 +10303,13 @@ var require_ms = __commonJS({
10303
10303
  "val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
10304
10304
  );
10305
10305
  };
10306
- function parse(str5) {
10307
- str5 = String(str5);
10308
- if (str5.length > 100) {
10306
+ function parse(str6) {
10307
+ str6 = String(str6);
10308
+ if (str6.length > 100) {
10309
10309
  return;
10310
10310
  }
10311
10311
  var match8 = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
10312
- str5
10312
+ str6
10313
10313
  );
10314
10314
  if (!match8) {
10315
10315
  return;
@@ -10910,7 +10910,7 @@ var require_node = __commonJS({
10910
10910
  var { formatters } = module.exports;
10911
10911
  formatters.o = function(v) {
10912
10912
  this.inspectOpts.colors = this.useColors;
10913
- return util4.inspect(v, this.inspectOpts).split("\n").map((str5) => str5.trim()).join(" ");
10913
+ return util4.inspect(v, this.inspectOpts).split("\n").map((str6) => str6.trim()).join(" ");
10914
10914
  };
10915
10915
  formatters.O = function(v) {
10916
10916
  this.inspectOpts.colors = this.useColors;
@@ -11952,20 +11952,20 @@ var require_object_inspect = __commonJS({
11952
11952
  var gPO = (typeof Reflect === "function" ? Reflect.getPrototypeOf : Object.getPrototypeOf) || ([].__proto__ === Array.prototype ? function(O) {
11953
11953
  return O.__proto__;
11954
11954
  } : null);
11955
- function addNumericSeparator(num, str5) {
11956
- if (num === Infinity || num === -Infinity || num !== num || num && num > -1e3 && num < 1e3 || $test.call(/e/, str5)) {
11957
- return str5;
11955
+ function addNumericSeparator(num, str6) {
11956
+ if (num === Infinity || num === -Infinity || num !== num || num && num > -1e3 && num < 1e3 || $test.call(/e/, str6)) {
11957
+ return str6;
11958
11958
  }
11959
11959
  var sepRegex = /[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;
11960
11960
  if (typeof num === "number") {
11961
11961
  var int = num < 0 ? -$floor(-num) : $floor(num);
11962
11962
  if (int !== num) {
11963
11963
  var intStr = String(int);
11964
- var dec = $slice.call(str5, intStr.length + 1);
11964
+ var dec = $slice.call(str6, intStr.length + 1);
11965
11965
  return $replace.call(intStr, sepRegex, "$&_") + "." + $replace.call($replace.call(dec, /([0-9]{3})/g, "$&_"), /_$/, "");
11966
11966
  }
11967
11967
  }
11968
- return $replace.call(str5, sepRegex, "$&_");
11968
+ return $replace.call(str6, sepRegex, "$&_");
11969
11969
  }
11970
11970
  var utilInspect = require_util_inspect();
11971
11971
  var inspectCustom = utilInspect.custom;
@@ -12015,8 +12015,8 @@ var require_object_inspect = __commonJS({
12015
12015
  if (obj === 0) {
12016
12016
  return Infinity / obj > 0 ? "0" : "-0";
12017
12017
  }
12018
- var str5 = String(obj);
12019
- return numericSeparator ? addNumericSeparator(obj, str5) : str5;
12018
+ var str6 = String(obj);
12019
+ return numericSeparator ? addNumericSeparator(obj, str6) : str6;
12020
12020
  }
12021
12021
  if (typeof obj === "bigint") {
12022
12022
  var bigIntStr = String(obj) + "n";
@@ -12336,15 +12336,15 @@ var require_object_inspect = __commonJS({
12336
12336
  }
12337
12337
  return typeof x.nodeName === "string" && typeof x.getAttribute === "function";
12338
12338
  }
12339
- function inspectString(str5, opts) {
12340
- if (str5.length > opts.maxStringLength) {
12341
- var remaining = str5.length - opts.maxStringLength;
12339
+ function inspectString(str6, opts) {
12340
+ if (str6.length > opts.maxStringLength) {
12341
+ var remaining = str6.length - opts.maxStringLength;
12342
12342
  var trailer = "... " + remaining + " more character" + (remaining > 1 ? "s" : "");
12343
- return inspectString($slice.call(str5, 0, opts.maxStringLength), opts) + trailer;
12343
+ return inspectString($slice.call(str6, 0, opts.maxStringLength), opts) + trailer;
12344
12344
  }
12345
12345
  var quoteRE = quoteREs[opts.quoteStyle || "single"];
12346
12346
  quoteRE.lastIndex = 0;
12347
- var s = $replace.call($replace.call(str5, quoteRE, "\\$1"), /[\x00-\x1f]/g, lowbyte);
12347
+ var s = $replace.call($replace.call(str6, quoteRE, "\\$1"), /[\x00-\x1f]/g, lowbyte);
12348
12348
  return wrapQuotes(s, "single", opts);
12349
12349
  }
12350
12350
  function lowbyte(c) {
@@ -12361,8 +12361,8 @@ var require_object_inspect = __commonJS({
12361
12361
  }
12362
12362
  return "\\x" + (n < 16 ? "0" : "") + $toUpperCase.call(n.toString(16));
12363
12363
  }
12364
- function markBoxed(str5) {
12365
- return "Object(" + str5 + ")";
12364
+ function markBoxed(str6) {
12365
+ return "Object(" + str6 + ")";
12366
12366
  }
12367
12367
  function weakCollectionOf(type) {
12368
12368
  return type + " { ? }";
@@ -12912,8 +12912,8 @@ var require_utils = __commonJS({
12912
12912
  return acc;
12913
12913
  }, target);
12914
12914
  };
12915
- var decode3 = function(str5, defaultDecoder, charset) {
12916
- var strWithoutPlus = str5.replace(/\+/g, " ");
12915
+ var decode3 = function(str6, defaultDecoder, charset) {
12916
+ var strWithoutPlus = str6.replace(/\+/g, " ");
12917
12917
  if (charset === "iso-8859-1") {
12918
12918
  return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);
12919
12919
  }
@@ -12924,15 +12924,15 @@ var require_utils = __commonJS({
12924
12924
  }
12925
12925
  };
12926
12926
  var limit = 1024;
12927
- var encode4 = function encode5(str5, defaultEncoder, charset, kind, format5) {
12928
- if (str5.length === 0) {
12929
- return str5;
12927
+ var encode4 = function encode5(str6, defaultEncoder, charset, kind, format5) {
12928
+ if (str6.length === 0) {
12929
+ return str6;
12930
12930
  }
12931
- var string3 = str5;
12932
- if (typeof str5 === "symbol") {
12933
- string3 = Symbol.prototype.toString.call(str5);
12934
- } else if (typeof str5 !== "string") {
12935
- string3 = String(str5);
12931
+ var string3 = str6;
12932
+ if (typeof str6 === "symbol") {
12933
+ string3 = Symbol.prototype.toString.call(str6);
12934
+ } else if (typeof str6 !== "string") {
12935
+ string3 = String(str6);
12936
12936
  }
12937
12937
  if (charset === "iso-8859-1") {
12938
12938
  return escape(string3).replace(/%u[0-9a-f]{4}/gi, function($0) {
@@ -13380,8 +13380,8 @@ var require_parse = __commonJS({
13380
13380
  strictNullHandling: false,
13381
13381
  throwOnLimitExceeded: false
13382
13382
  };
13383
- var interpretNumericEntities = function(str5) {
13384
- return str5.replace(/&#(\d+);/g, function($0, numberStr) {
13383
+ var interpretNumericEntities = function(str6) {
13384
+ return str6.replace(/&#(\d+);/g, function($0, numberStr) {
13385
13385
  return String.fromCharCode(parseInt(numberStr, 10));
13386
13386
  });
13387
13387
  };
@@ -13407,9 +13407,9 @@ var require_parse = __commonJS({
13407
13407
  };
13408
13408
  var isoSentinel = "utf8=%26%2310003%3B";
13409
13409
  var charsetSentinel = "utf8=%E2%9C%93";
13410
- var parseValues = function parseQueryStringValues(str5, options2) {
13410
+ var parseValues = function parseQueryStringValues(str6, options2) {
13411
13411
  var obj = { __proto__: null };
13412
- var cleanStr = options2.ignoreQueryPrefix ? str5.replace(/^\?/, "") : str5;
13412
+ var cleanStr = options2.ignoreQueryPrefix ? str6.replace(/^\?/, "") : str6;
13413
13413
  cleanStr = cleanStr.replace(/%5B/gi, "[").replace(/%5D/gi, "]");
13414
13414
  var limit = options2.parameterLimit === Infinity ? void 0 : options2.parameterLimit;
13415
13415
  var parts = cleanStr.split(
@@ -13656,17 +13656,17 @@ var require_parse = __commonJS({
13656
13656
  throwOnLimitExceeded: typeof opts.throwOnLimitExceeded === "boolean" ? opts.throwOnLimitExceeded : false
13657
13657
  };
13658
13658
  };
13659
- module.exports = function(str5, opts) {
13659
+ module.exports = function(str6, opts) {
13660
13660
  var options2 = normalizeParseOptions(opts);
13661
- if (str5 === "" || str5 === null || typeof str5 === "undefined") {
13661
+ if (str6 === "" || str6 === null || typeof str6 === "undefined") {
13662
13662
  return options2.plainObjects ? { __proto__: null } : {};
13663
13663
  }
13664
- var tempObj = typeof str5 === "string" ? parseValues(str5, options2) : str5;
13664
+ var tempObj = typeof str6 === "string" ? parseValues(str6, options2) : str6;
13665
13665
  var obj = options2.plainObjects ? { __proto__: null } : {};
13666
13666
  var keys4 = Object.keys(tempObj);
13667
13667
  for (var i = 0; i < keys4.length; ++i) {
13668
13668
  var key = keys4[i];
13669
- var newObj = parseKeys(key, tempObj[key], options2, typeof str5 === "string");
13669
+ var newObj = parseKeys(key, tempObj[key], options2, typeof str6 === "string");
13670
13670
  obj = utils.merge(obj, newObj, options2);
13671
13671
  }
13672
13672
  if (options2.allowSparse === true) {
@@ -19964,27 +19964,27 @@ var require_umd = __commonJS({
19964
19964
  }
19965
19965
  Long.fromBits = fromBits;
19966
19966
  var pow_dbl = Math.pow;
19967
- function fromString2(str5, unsigned, radix) {
19968
- if (str5.length === 0) throw Error("empty string");
19967
+ function fromString2(str6, unsigned, radix) {
19968
+ if (str6.length === 0) throw Error("empty string");
19969
19969
  if (typeof unsigned === "number") {
19970
19970
  radix = unsigned;
19971
19971
  unsigned = false;
19972
19972
  } else {
19973
19973
  unsigned = !!unsigned;
19974
19974
  }
19975
- if (str5 === "NaN" || str5 === "Infinity" || str5 === "+Infinity" || str5 === "-Infinity")
19975
+ if (str6 === "NaN" || str6 === "Infinity" || str6 === "+Infinity" || str6 === "-Infinity")
19976
19976
  return unsigned ? UZERO : ZERO;
19977
19977
  radix = radix || 10;
19978
19978
  if (radix < 2 || 36 < radix) throw RangeError("radix");
19979
19979
  var p;
19980
- if ((p = str5.indexOf("-")) > 0) throw Error("interior hyphen");
19980
+ if ((p = str6.indexOf("-")) > 0) throw Error("interior hyphen");
19981
19981
  else if (p === 0) {
19982
- return fromString2(str5.substring(1), unsigned, radix).neg();
19982
+ return fromString2(str6.substring(1), unsigned, radix).neg();
19983
19983
  }
19984
19984
  var radixToPower = fromNumber(pow_dbl(radix, 8));
19985
19985
  var result3 = ZERO;
19986
- for (var i = 0; i < str5.length; i += 8) {
19987
- var size6 = Math.min(8, str5.length - i), value3 = parseInt(str5.substring(i, i + size6), radix);
19986
+ for (var i = 0; i < str6.length; i += 8) {
19987
+ var size6 = Math.min(8, str6.length - i), value3 = parseInt(str6.substring(i, i + size6), radix);
19988
19988
  if (size6 < 8) {
19989
19989
  var power = fromNumber(pow_dbl(radix, size6));
19990
19990
  result3 = result3.mul(power).add(fromNumber(value3));
@@ -20632,8 +20632,8 @@ var require_minimal = __commonJS({
20632
20632
  writable: true
20633
20633
  });
20634
20634
  };
20635
- util4.lcFirst = function lcFirst(str5) {
20636
- return str5.charAt(0).toLowerCase() + str5.substring(1);
20635
+ util4.lcFirst = function lcFirst(str6) {
20636
+ return str6.charAt(0).toLowerCase() + str6.substring(1);
20637
20637
  };
20638
20638
  function newError(name) {
20639
20639
  function CustomError(message, properties) {
@@ -21436,8 +21436,8 @@ var toSafeFlatObject = (thing) => {
21436
21436
  return result3;
21437
21437
  };
21438
21438
  var kindOf = /* @__PURE__ */ ((cache) => (thing) => {
21439
- const str5 = toString.call(thing);
21440
- return cache[str5] || (cache[str5] = str5.slice(8, -1).toLowerCase());
21439
+ const str6 = toString.call(thing);
21440
+ return cache[str6] || (cache[str6] = str6.slice(8, -1).toLowerCase());
21441
21441
  })(/* @__PURE__ */ Object.create(null));
21442
21442
  var kindOfTest = (type) => {
21443
21443
  type = type.toLowerCase();
@@ -21520,8 +21520,8 @@ var [isReadableStream, isRequest, isResponse, isHeaders] = [
21520
21520
  "Response",
21521
21521
  "Headers"
21522
21522
  ].map(kindOfTest);
21523
- var trim = (str5) => {
21524
- return str5.trim ? str5.trim() : str5.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
21523
+ var trim = (str6) => {
21524
+ return str6.trim ? str6.trim() : str6.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
21525
21525
  };
21526
21526
  function forEach(obj, fn3, { allOwnKeys = false } = {}) {
21527
21527
  if (obj === null || typeof obj === "undefined") {
@@ -21678,13 +21678,13 @@ var toFlatObject = (sourceObj, destObj, filter9, propFilter) => {
21678
21678
  } while (sourceObj && (!filter9 || filter9(sourceObj, destObj)) && sourceObj !== Object.prototype);
21679
21679
  return destObj;
21680
21680
  };
21681
- var endsWith = (str5, searchString, position) => {
21682
- str5 = String(str5);
21683
- if (position === void 0 || position > str5.length) {
21684
- position = str5.length;
21681
+ var endsWith = (str6, searchString, position) => {
21682
+ str6 = String(str6);
21683
+ if (position === void 0 || position > str6.length) {
21684
+ position = str6.length;
21685
21685
  }
21686
21686
  position -= searchString.length;
21687
- const lastIndex = str5.indexOf(searchString, position);
21687
+ const lastIndex = str6.indexOf(searchString, position);
21688
21688
  return lastIndex !== -1 && lastIndex === position;
21689
21689
  };
21690
21690
  var toArray = (thing) => {
@@ -21712,17 +21712,17 @@ var forEachEntry = (obj, fn3) => {
21712
21712
  fn3.call(obj, pair[0], pair[1]);
21713
21713
  }
21714
21714
  };
21715
- var matchAll = (regExp, str5) => {
21715
+ var matchAll = (regExp, str6) => {
21716
21716
  let matches;
21717
21717
  const arr = [];
21718
- while ((matches = regExp.exec(str5)) !== null) {
21718
+ while ((matches = regExp.exec(str6)) !== null) {
21719
21719
  arr.push(matches);
21720
21720
  }
21721
21721
  return arr;
21722
21722
  };
21723
21723
  var isHTMLForm = kindOfTest("HTMLFormElement");
21724
- var toCamelCase = (str5) => {
21725
- return str5.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g, function replacer(m, p1, p2) {
21724
+ var toCamelCase = (str6) => {
21725
+ return str6.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g, function replacer(m, p1, p2) {
21726
21726
  return p1.toUpperCase() + p2;
21727
21727
  });
21728
21728
  };
@@ -21949,24 +21949,24 @@ var parseHeaders_default = (rawHeaders) => {
21949
21949
  };
21950
21950
 
21951
21951
  // node_modules/axios/lib/helpers/sanitizeHeaderValue.js
21952
- function trimSPorHTAB(str5) {
21952
+ function trimSPorHTAB(str6) {
21953
21953
  let start2 = 0;
21954
- let end = str5.length;
21954
+ let end = str6.length;
21955
21955
  while (start2 < end) {
21956
- const code = str5.charCodeAt(start2);
21956
+ const code = str6.charCodeAt(start2);
21957
21957
  if (code !== 9 && code !== 32) {
21958
21958
  break;
21959
21959
  }
21960
21960
  start2 += 1;
21961
21961
  }
21962
21962
  while (end > start2) {
21963
- const code = str5.charCodeAt(end - 1);
21963
+ const code = str6.charCodeAt(end - 1);
21964
21964
  if (code !== 9 && code !== 32) {
21965
21965
  break;
21966
21966
  }
21967
21967
  end -= 1;
21968
21968
  }
21969
- return start2 === 0 && end === str5.length ? str5 : str5.slice(start2, end);
21969
+ return start2 === 0 && end === str6.length ? str6 : str6.slice(start2, end);
21970
21970
  }
21971
21971
  var INVALID_UNICODE_HEADER_VALUE_CHARS = new RegExp("[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+", "g");
21972
21972
  var INVALID_BYTE_STRING_HEADER_VALUE_CHARS = new RegExp("[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+", "g");
@@ -21997,11 +21997,11 @@ function normalizeValue(value3) {
21997
21997
  }
21998
21998
  return utils_default.isArray(value3) ? value3.map(normalizeValue) : sanitizeHeaderValue(String(value3));
21999
21999
  }
22000
- function parseTokens(str5) {
22000
+ function parseTokens(str6) {
22001
22001
  const tokens = /* @__PURE__ */ Object.create(null);
22002
22002
  const tokensRE = /([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;
22003
22003
  let match8;
22004
- while (match8 = tokensRE.exec(str5)) {
22004
+ while (match8 = tokensRE.exec(str6)) {
22005
22005
  tokens[match8[1]] = match8[2];
22006
22006
  }
22007
22007
  return tokens;
@@ -22049,12 +22049,12 @@ function decodeQuotedString(value3) {
22049
22049
  }
22050
22050
  function parseParameters(value3) {
22051
22051
  const parameters = /* @__PURE__ */ Object.create(null);
22052
- const str5 = String(value3);
22052
+ const str6 = String(value3);
22053
22053
  let start2 = 0;
22054
22054
  let quoted = false;
22055
22055
  let escaped = false;
22056
22056
  function parseParameter2(end) {
22057
- const part = trimOWS(str5.slice(start2, end));
22057
+ const part = trimOWS(str6.slice(start2, end));
22058
22058
  const equals4 = part.indexOf("=");
22059
22059
  if (equals4 < 1) {
22060
22060
  return;
@@ -22070,8 +22070,8 @@ function parseParameters(value3) {
22070
22070
  const parameterValue = trimOWS(part.slice(equals4 + 1));
22071
22071
  parameters[normalizedName] = decodeQuotedString(parameterValue);
22072
22072
  }
22073
- for (let i = 0; i < str5.length; i++) {
22074
- const code = str5.charCodeAt(i);
22073
+ for (let i = 0; i < str6.length; i++) {
22074
+ const code = str6.charCodeAt(i);
22075
22075
  if (quoted) {
22076
22076
  if (escaped) {
22077
22077
  escaped = false;
@@ -22087,10 +22087,10 @@ function parseParameters(value3) {
22087
22087
  start2 = i + 1;
22088
22088
  }
22089
22089
  }
22090
- parseParameter2(str5.length);
22090
+ parseParameter2(str6.length);
22091
22091
  return parameters;
22092
22092
  }
22093
- var isValidHeaderName = (str5) => /^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(str5.trim());
22093
+ var isValidHeaderName = (str6) => /^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(str6.trim());
22094
22094
  function matchHeaderValue(context3, value3, header, filter9, isHeaderNameFilter) {
22095
22095
  if (utils_default.isFunction(filter9)) {
22096
22096
  return filter9.call(this, value3, header);
@@ -22107,8 +22107,8 @@ function matchHeaderValue(context3, value3, header, filter9, isHeaderNameFilter)
22107
22107
  }
22108
22108
  }
22109
22109
  function formatHeader(header) {
22110
- return header.trim().toLowerCase().replace(/([a-z\d])(\w*)/g, (w, char, str5) => {
22111
- return char.toUpperCase() + str5;
22110
+ return header.trim().toLowerCase().replace(/([a-z\d])(\w*)/g, (w, char, str6) => {
22111
+ return char.toUpperCase() + str6;
22112
22112
  });
22113
22113
  }
22114
22114
  function buildAccessors(obj, header) {
@@ -22643,7 +22643,7 @@ function toFormData(obj, formData, options2) {
22643
22643
  var toFormData_default = toFormData;
22644
22644
 
22645
22645
  // node_modules/axios/lib/helpers/AxiosURLSearchParams.js
22646
- function encode(str5) {
22646
+ function encode(str6) {
22647
22647
  const charMap = {
22648
22648
  "!": "%21",
22649
22649
  "'": "%27",
@@ -22652,7 +22652,7 @@ function encode(str5) {
22652
22652
  "~": "%7E",
22653
22653
  "%20": "+"
22654
22654
  };
22655
- return encodeURIComponent(str5).replace(/[!'()~]|%20/g, function replacer(match8) {
22655
+ return encodeURIComponent(str6).replace(/[!'()~]|%20/g, function replacer(match8) {
22656
22656
  return charMap[match8];
22657
22657
  });
22658
22658
  }
@@ -22860,14 +22860,14 @@ var ALPHABET = {
22860
22860
  ALPHA_DIGIT: ALPHA + ALPHA.toUpperCase() + DIGIT
22861
22861
  };
22862
22862
  var generateString = (size6 = 16, alphabet = ALPHABET.ALPHA_DIGIT) => {
22863
- let str5 = "";
22863
+ let str6 = "";
22864
22864
  const { length: length2 } = alphabet;
22865
22865
  const randomValues = new Uint32Array(size6);
22866
22866
  crypto3.randomFillSync(randomValues);
22867
22867
  for (let i = 0; i < size6; i++) {
22868
- str5 += alphabet[randomValues[i] % length2];
22868
+ str6 += alphabet[randomValues[i] % length2];
22869
22869
  }
22870
- return str5;
22870
+ return str6;
22871
22871
  };
22872
22872
  var node_default = {
22873
22873
  isNode: true,
@@ -24153,7 +24153,7 @@ var asyncDecorator = (fn3, scheduler = utils_default.asap) => (...args2) => sche
24153
24153
 
24154
24154
  // node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js
24155
24155
  var isHexDigit = (charCode) => charCode >= 48 && charCode <= 57 || charCode >= 65 && charCode <= 70 || charCode >= 97 && charCode <= 102;
24156
- var isPercentEncodedByte = (str5, i, len) => i + 2 < len && isHexDigit(str5.charCodeAt(i + 1)) && isHexDigit(str5.charCodeAt(i + 2));
24156
+ var isPercentEncodedByte = (str6, i, len) => i + 2 < len && isHexDigit(str6.charCodeAt(i + 1)) && isHexDigit(str6.charCodeAt(i + 2));
24157
24157
  var hexValue = (charCode) => charCode <= 57 ? charCode - 48 : (charCode & 223) - 55;
24158
24158
  var isBase64Char = (charCode) => charCode >= 65 && charCode <= 90 || // A-Z
24159
24159
  charCode >= 97 && charCode <= 122 || // a-z
@@ -25469,7 +25469,7 @@ function mergeConfig(config1, config2) {
25469
25469
  }
25470
25470
 
25471
25471
  // node_modules/axios/lib/helpers/resolveConfig.js
25472
- var encodeUTF8 = (str5) => encodeURIComponent(str5).replace(
25472
+ var encodeUTF8 = (str6) => encodeURIComponent(str6).replace(
25473
25473
  /%([0-9A-F]{2})/gi,
25474
25474
  (_, hex) => String.fromCharCode(parseInt(hex, 16))
25475
25475
  );
@@ -25840,7 +25840,7 @@ var DEFAULT_REQUEST_OPTIONS = {
25840
25840
  window: null
25841
25841
  };
25842
25842
  var { isFunction: isFunction2 } = utils_default;
25843
- var encodeUTF82 = (str5) => encodeURIComponent(str5).replace(
25843
+ var encodeUTF82 = (str6) => encodeURIComponent(str6).replace(
25844
25844
  /%([0-9A-F]{2})/gi,
25845
25845
  (_, hex) => String.fromCharCode(parseInt(hex, 16))
25846
25846
  );
@@ -25890,7 +25890,7 @@ var factory = (env) => {
25890
25890
  return false;
25891
25891
  }
25892
25892
  const isReadableStreamSupported = isFetchSupported && isFunction2(ReadableStream2);
25893
- const encodeText = isFetchSupported && (typeof TextEncoder2 === "function" ? /* @__PURE__ */ ((encoder2) => (str5) => encoder2.encode(str5))(new TextEncoder2()) : async (str5) => new Uint8Array(await new Request3(str5).arrayBuffer()));
25893
+ const encodeText = isFetchSupported && (typeof TextEncoder2 === "function" ? /* @__PURE__ */ ((encoder2) => (str6) => encoder2.encode(str6))(new TextEncoder2()) : async (str6) => new Uint8Array(await new Request3(str6).arrayBuffer()));
25894
25894
  const supportsRequestStream = isRequestSupported && isReadableStreamSupported && test(() => {
25895
25895
  let duplexAccessed = false;
25896
25896
  const request3 = new Request3(platform_default.origin, {
@@ -122259,10 +122259,10 @@ var number = (n) => {
122259
122259
  }
122260
122260
  return optimize(h);
122261
122261
  };
122262
- var string = (str5) => {
122263
- let h = 5381, i = str5.length;
122262
+ var string = (str6) => {
122263
+ let h = 5381, i = str6.length;
122264
122264
  while (i) {
122265
- h = h * 33 ^ str5.charCodeAt(--i);
122265
+ h = h * 33 ^ str6.charCodeAt(--i);
122266
122266
  }
122267
122267
  return optimize(h);
122268
122268
  };
@@ -124566,8 +124566,8 @@ var EncodingError = class extends (/* @__PURE__ */ TaggedError2("EncodingError")
124566
124566
  [EncodingErrorTypeId] = EncodingErrorTypeId;
124567
124567
  };
124568
124568
  var encodeBase64 = (input) => typeof input === "string" ? base64EncodeUint8Array(encoder.encode(input)) : base64EncodeUint8Array(input);
124569
- var decodeBase64 = (str5) => {
124570
- const stripped = stripCrlf(str5);
124569
+ var decodeBase64 = (str6) => {
124570
+ const stripped = stripCrlf(str6);
124571
124571
  const length2 = stripped.length;
124572
124572
  if (length2 % 4 !== 0) {
124573
124573
  return fail2(new EncodingError({
@@ -124605,10 +124605,10 @@ var decodeBase64 = (str5) => {
124605
124605
  }));
124606
124606
  }
124607
124607
  };
124608
- var decodeBase64String = (str5) => map2(decodeBase64(str5), (_) => decoder.decode(_));
124608
+ var decodeBase64String = (str6) => map2(decodeBase64(str6), (_) => decoder.decode(_));
124609
124609
  var encodeBase64Url = (input) => typeof input === "string" ? base64UrlEncodeUint8Array(encoder.encode(input)) : base64UrlEncodeUint8Array(input);
124610
- var decodeBase64Url = (str5) => {
124611
- const stripped = stripCrlf(str5);
124610
+ var decodeBase64Url = (str6) => {
124611
+ const stripped = stripCrlf(str6);
124612
124612
  const length2 = stripped.length;
124613
124613
  if (length2 % 4 === 1) {
124614
124614
  return fail2(new EncodingError({
@@ -124630,7 +124630,7 @@ var decodeBase64Url = (str5) => {
124630
124630
  sanitized = sanitized.replace(/-/g, "+").replace(/_/g, "/");
124631
124631
  return decodeBase64(sanitized);
124632
124632
  };
124633
- var decodeBase64UrlString = (str5) => map2(decodeBase64Url(str5), (_) => decoder.decode(_));
124633
+ var decodeBase64UrlString = (str6) => map2(decodeBase64Url(str6), (_) => decoder.decode(_));
124634
124634
  var encodeHex = (input) => typeof input === "string" ? hexEncodeUint8Array(encoder.encode(input)) : hexEncodeUint8Array(input);
124635
124635
  var randomHex = (length2) => {
124636
124636
  let result3 = "";
@@ -124640,13 +124640,13 @@ var randomHex = (length2) => {
124640
124640
  }
124641
124641
  return result3;
124642
124642
  };
124643
- var decodeHex = (str5) => {
124644
- const bytes = new TextEncoder().encode(str5);
124643
+ var decodeHex = (str6) => {
124644
+ const bytes = new TextEncoder().encode(str6);
124645
124645
  if (bytes.length % 2 !== 0) {
124646
124646
  return fail2(new EncodingError({
124647
124647
  module: "Hex",
124648
124648
  kind: "Decode",
124649
- input: str5,
124649
+ input: str6,
124650
124650
  message: `Length must be a multiple of 2, but is ${bytes.length}`
124651
124651
  }));
124652
124652
  }
@@ -124663,15 +124663,15 @@ var decodeHex = (str5) => {
124663
124663
  return fail2(new EncodingError({
124664
124664
  module: "Hex",
124665
124665
  kind: "Decode",
124666
- input: str5,
124666
+ input: str6,
124667
124667
  message: e instanceof Error ? e.message : "Invalid input"
124668
124668
  }));
124669
124669
  }
124670
124670
  };
124671
- var decodeHexString = (str5) => map2(decodeHex(str5), (_) => decoder.decode(_));
124671
+ var decodeHexString = (str6) => map2(decodeHex(str6), (_) => decoder.decode(_));
124672
124672
  var encoder = /* @__PURE__ */ new TextEncoder();
124673
124673
  var decoder = /* @__PURE__ */ new TextDecoder();
124674
- var stripCrlf = (str5) => str5.replace(/[\n\r]/g, "");
124674
+ var stripCrlf = (str6) => str6.replace(/[\n\r]/g, "");
124675
124675
  var base64EncodeUint8Array = (bytes) => {
124676
124676
  const length2 = bytes.length;
124677
124677
  let result3 = "";
@@ -128500,11 +128500,11 @@ var uncapitalize = (self2) => {
128500
128500
  var trim2 = (self2) => self2.trim();
128501
128501
  var snakeToCamel = (self2) => {
128502
128502
  if (self2.length === 0) return self2;
128503
- let str5 = self2[0];
128503
+ let str6 = self2[0];
128504
128504
  for (let i = 1; i < self2.length; i++) {
128505
- str5 += self2[i] === "_" ? self2[++i].toUpperCase() : self2[i];
128505
+ str6 += self2[i] === "_" ? self2[++i].toUpperCase() : self2[i];
128506
128506
  }
128507
- return str5;
128507
+ return str6;
128508
128508
  };
128509
128509
  var camelToSnake = (self2) => self2.replace(/([A-Z])/g, "_$1").toLowerCase();
128510
128510
 
@@ -144016,6 +144016,14 @@ function resolveConfig2(raw, env = process.env, deps = {}) {
144016
144016
  const recentDirsLimit = clamp4(asNumber(merged.recentDirsLimit, 5), 1, 20);
144017
144017
  const recentModelsLimit = clamp4(asNumber(merged.recentModelsLimit, 5), 1, 20);
144018
144018
  const sessionPageSize = clamp4(asNumber(merged.sessionPageSize, 8), 5, 20);
144019
+ const sessionAllowButton = asBoolean(merged.sessionAllowButton, true);
144020
+ const resumeSummary = asBoolean(merged.resumeSummary, true);
144021
+ const resumeSummaryTimeoutMs = clamp4(asNumber(merged.resumeSummaryTimeoutMs, 15e3), 3e3, 6e4);
144022
+ const resumeCompactTimeoutMs = clamp4(asNumber(merged.resumeCompactTimeoutMs, 12e4), 3e4, 3e5);
144023
+ const topicStatus = asBoolean(merged.topicStatus, true);
144024
+ const topicStatusInTitle = asBoolean(merged.topicStatusInTitle, false);
144025
+ const topicStatusThrottleMs = clamp4(asNumber(merged.topicStatusThrottleMs, 1e3), 500, 1e4);
144026
+ const cardMaxTables = clamp4(asNumber(merged.cardMaxTables, 4), 1, 5);
144019
144027
  const domain = merged.domain === "lark" ? "lark" : "feishu";
144020
144028
  const logFile = resolveLogFile(merged.logFile, env, deps);
144021
144029
  const gatewayLocation = asString(merged.gatewayLocation).trim() || void 0;
@@ -144055,7 +144063,15 @@ function resolveConfig2(raw, env = process.env, deps = {}) {
144055
144063
  allowedRoots,
144056
144064
  recentDirsLimit,
144057
144065
  recentModelsLimit,
144058
- sessionPageSize
144066
+ sessionPageSize,
144067
+ sessionAllowButton,
144068
+ resumeSummary,
144069
+ resumeSummaryTimeoutMs,
144070
+ resumeCompactTimeoutMs,
144071
+ topicStatus,
144072
+ topicStatusInTitle,
144073
+ topicStatusThrottleMs,
144074
+ cardMaxTables
144059
144075
  };
144060
144076
  }
144061
144077
  function loadConfigFile(env, deps, warnings) {
@@ -144433,6 +144449,37 @@ function verifyApproval(token, secret, options2 = {}) {
144433
144449
  return { ok: true, claims };
144434
144450
  }
144435
144451
  var STOP_PURPOSE = "stop";
144452
+ var ALLOW_SESSION_PURPOSE = "allow_session";
144453
+ function signAllowSession(input, secret, options2 = {}) {
144454
+ const now3 = input.now ?? Date.now();
144455
+ const claims = {
144456
+ p: ALLOW_SESSION_PURPOSE,
144457
+ r: input.requestID,
144458
+ s: input.sessionID,
144459
+ a: input.action,
144460
+ e: now3 + input.ttlMs,
144461
+ n: options2.nonce ?? randomBytes(9).toString("hex")
144462
+ };
144463
+ const body = b64url(JSON.stringify(claims));
144464
+ return `${body}.${hmac(body, secret)}`;
144465
+ }
144466
+ function verifyAllowSession(token, secret, options2 = {}) {
144467
+ const opened = openSigned(token, secret);
144468
+ if (!opened.ok) return opened;
144469
+ const claims = opened.payload;
144470
+ if (claims?.p !== ALLOW_SESSION_PURPOSE || typeof claims?.r !== "string" || typeof claims?.s !== "string" || typeof claims?.a !== "string" || typeof claims?.e !== "number" || typeof claims?.n !== "string") {
144471
+ return { ok: false, reason: "bad-claims" };
144472
+ }
144473
+ const now3 = options2.now ?? Date.now();
144474
+ if (claims.e <= now3) return { ok: false, reason: "expired" };
144475
+ if (options2.expectSessionID && claims.s !== options2.expectSessionID) {
144476
+ return { ok: false, reason: "session-mismatch" };
144477
+ }
144478
+ if (options2.expectAction && claims.a !== options2.expectAction) {
144479
+ return { ok: false, reason: "action-mismatch" };
144480
+ }
144481
+ return { ok: true, claims };
144482
+ }
144436
144483
  function signStop(input, secret, options2 = {}) {
144437
144484
  const now3 = input.now ?? Date.now();
144438
144485
  const claims = {
@@ -144705,6 +144752,155 @@ function safeArgs(args2) {
144705
144752
  }).join(" ").slice(0, 500);
144706
144753
  }
144707
144754
 
144755
+ // src/feishu/card-limits.ts
144756
+ var DEFAULT_CARD_MAX_TABLES = 4;
144757
+ var CARD_MAX_TABLES_MIN = 1;
144758
+ var CARD_MAX_TABLES_MAX = 5;
144759
+ var DEFAULT_CARD_MAX_ELEMENTS = 200;
144760
+ function clampMaxTables(value3) {
144761
+ const n = typeof value3 === "number" && Number.isFinite(value3) ? Math.floor(value3) : DEFAULT_CARD_MAX_TABLES;
144762
+ return Math.min(CARD_MAX_TABLES_MAX, Math.max(CARD_MAX_TABLES_MIN, n));
144763
+ }
144764
+ function createCardMarkdownBudget(max6 = DEFAULT_CARD_MAX_TABLES) {
144765
+ const clamped = clampMaxTables(max6);
144766
+ return { max: clamped, remaining: clamped, tables: 0, degraded: 0 };
144767
+ }
144768
+ function findMarkdownTables(text) {
144769
+ if (!text || !text.includes("|")) return [];
144770
+ const lines = text.split("\n");
144771
+ const fence = computeFenceMask(lines);
144772
+ const spans = [];
144773
+ let i = 0;
144774
+ while (i < lines.length) {
144775
+ const line = lines[i];
144776
+ if (fence[i] || !isSeparatorRow(line)) {
144777
+ i += 1;
144778
+ continue;
144779
+ }
144780
+ if (i === 0 || fence[i - 1]) {
144781
+ i += 1;
144782
+ continue;
144783
+ }
144784
+ const header = lines[i - 1];
144785
+ if (header.trim() === "" || !header.includes("|") || isSeparatorRow(header)) {
144786
+ i += 1;
144787
+ continue;
144788
+ }
144789
+ let end = i;
144790
+ let j = i + 1;
144791
+ while (j < lines.length && !fence[j]) {
144792
+ const body = lines[j];
144793
+ if (body.trim() === "" || !body.includes("|") || isSeparatorRow(body)) break;
144794
+ end = j;
144795
+ j += 1;
144796
+ }
144797
+ spans.push({ start: i - 1, end });
144798
+ i = end + 1;
144799
+ }
144800
+ return spans;
144801
+ }
144802
+ function degradeExtraTables(text, max6 = DEFAULT_CARD_MAX_TABLES) {
144803
+ const keep = Math.max(0, Math.floor(max6));
144804
+ const spans = findMarkdownTables(text);
144805
+ if (spans.length <= keep) return { text, degraded: 0 };
144806
+ const extras = spans.slice(keep);
144807
+ const starts = new Set(extras.map((s) => s.start));
144808
+ const ends = new Set(extras.map((s) => s.end));
144809
+ const fence = text.includes("```") ? "~~~" : "```";
144810
+ const lines = text.split("\n");
144811
+ const out = [];
144812
+ for (let i = 0; i < lines.length; i += 1) {
144813
+ if (starts.has(i)) out.push(fence);
144814
+ out.push(lines[i]);
144815
+ if (ends.has(i)) out.push(fence);
144816
+ }
144817
+ return { text: out.join("\n"), degraded: extras.length };
144818
+ }
144819
+ function toCardMarkdown(text, budget) {
144820
+ const spans = findMarkdownTables(text);
144821
+ if (spans.length === 0) return text;
144822
+ budget.tables += spans.length;
144823
+ const keep = Math.max(0, Math.min(spans.length, budget.remaining));
144824
+ budget.remaining -= keep;
144825
+ if (keep === spans.length) return text;
144826
+ budget.degraded += spans.length - keep;
144827
+ return degradeExtraTables(text, keep).text;
144828
+ }
144829
+ function enforceCardLimits(card, options2 = {}) {
144830
+ const maxTables = clampMaxTables(options2.maxTables);
144831
+ const maxElements = typeof options2.maxElements === "number" && Number.isFinite(options2.maxElements) ? Math.max(1, Math.floor(options2.maxElements)) : DEFAULT_CARD_MAX_ELEMENTS;
144832
+ const clone2 = deepClone(card);
144833
+ const budget = createCardMarkdownBudget(maxTables);
144834
+ visitMarkdown(clone2, (content) => toCardMarkdown(content, budget));
144835
+ let elements = countElements(clone2);
144836
+ let dropped = 0;
144837
+ if (elements > maxElements) {
144838
+ const body = clone2.body;
144839
+ if (body && Array.isArray(body.elements)) {
144840
+ while (elements > maxElements && body.elements.length > 1) {
144841
+ body.elements.shift();
144842
+ dropped += 1;
144843
+ elements = countElements(clone2);
144844
+ }
144845
+ }
144846
+ }
144847
+ return {
144848
+ card: clone2,
144849
+ report: { tables: budget.tables, degradedTables: budget.degraded, elements, droppedElements: dropped }
144850
+ };
144851
+ }
144852
+ function visitMarkdown(node, fn3) {
144853
+ if (!node || typeof node !== "object") return;
144854
+ if (Array.isArray(node)) {
144855
+ for (const item of node) visitMarkdown(item, fn3);
144856
+ return;
144857
+ }
144858
+ const rec = node;
144859
+ if (rec.tag === "markdown" && typeof rec.content === "string") {
144860
+ rec.content = fn3(rec.content);
144861
+ }
144862
+ for (const value3 of Object.values(rec)) visitMarkdown(value3, fn3);
144863
+ }
144864
+ function countElements(node) {
144865
+ if (!node || typeof node !== "object") return 0;
144866
+ if (Array.isArray(node)) {
144867
+ let total3 = 0;
144868
+ for (const item of node) total3 += countElements(item);
144869
+ return total3;
144870
+ }
144871
+ const rec = node;
144872
+ let total2 = typeof rec.tag === "string" ? 1 : 0;
144873
+ for (const value3 of Object.values(rec)) total2 += countElements(value3);
144874
+ return total2;
144875
+ }
144876
+ function computeFenceMask(lines) {
144877
+ const mask3 = [];
144878
+ let fenceChar;
144879
+ for (const line of lines) {
144880
+ const match8 = /^\s{0,3}(`{3,}|~{3,})/.exec(line);
144881
+ const marker = match8?.[1];
144882
+ const char = marker?.[0];
144883
+ if (char && (!fenceChar || fenceChar === char)) {
144884
+ fenceChar = fenceChar ? void 0 : char;
144885
+ mask3.push(true);
144886
+ continue;
144887
+ }
144888
+ mask3.push(fenceChar !== void 0);
144889
+ }
144890
+ return mask3;
144891
+ }
144892
+ function isSeparatorRow(line) {
144893
+ const trimmed = line.trim();
144894
+ if (!trimmed.includes("|")) return false;
144895
+ const inner = trimmed.replace(/^\|/, "").replace(/\|$/, "");
144896
+ const cells = inner.split("|").map((c) => c.trim());
144897
+ if (cells.length < 1) return false;
144898
+ return cells.every((c) => /^:?-+:?$/.test(c));
144899
+ }
144900
+ function deepClone(value3) {
144901
+ return JSON.parse(JSON.stringify(value3));
144902
+ }
144903
+
144708
144904
  // src/feishu/sender.ts
144709
144905
  function describeLarkError(err2) {
144710
144906
  const base2 = errorMessage(err2);
@@ -144721,7 +144917,20 @@ function describeLarkError(err2) {
144721
144917
  if (typeof data === "string" && data.length > 0) return `${base2} [${data.slice(0, 300)}]`;
144722
144918
  return base2;
144723
144919
  }
144724
- function createFeishuSender(client, log2) {
144920
+ function createFeishuSender(client, log2, options2 = {}) {
144921
+ const guard = (card, tag2) => {
144922
+ const { card: guarded, report } = enforceCardLimits(card, { maxTables: options2.cardMaxTables });
144923
+ if (report.degradedTables > 0 || report.droppedElements > 0) {
144924
+ log2.warn("\u5361\u7247\u5185\u5BB9\u8D85\u9650\uFF0C\u5DF2\u964D\u7EA7\uFF08\u53D1\u9001\u5C42\u515C\u5E95\uFF09", {
144925
+ where: tag2,
144926
+ tables: report.tables,
144927
+ degradedTables: report.degradedTables,
144928
+ elements: report.elements,
144929
+ droppedElements: report.droppedElements
144930
+ });
144931
+ }
144932
+ return guarded;
144933
+ };
144725
144934
  return {
144726
144935
  async sendCard(chatId, card) {
144727
144936
  if (!chatId) return { ok: false, error: "missing chatId" };
@@ -144731,7 +144940,7 @@ function createFeishuSender(client, log2) {
144731
144940
  data: {
144732
144941
  receive_id: chatId,
144733
144942
  msg_type: "interactive",
144734
- content: JSON.stringify(card)
144943
+ content: JSON.stringify(guard(card, "create"))
144735
144944
  }
144736
144945
  });
144737
144946
  if (res?.code && res.code !== 0) {
@@ -144752,7 +144961,7 @@ function createFeishuSender(client, log2) {
144752
144961
  path: { message_id: messageId },
144753
144962
  data: {
144754
144963
  msg_type: "interactive",
144755
- content: JSON.stringify(card),
144964
+ content: JSON.stringify(guard(card, "reply")),
144756
144965
  ...opts?.replyInThread ? { reply_in_thread: true } : {}
144757
144966
  }
144758
144967
  });
@@ -144778,7 +144987,7 @@ function createFeishuSender(client, log2) {
144778
144987
  try {
144779
144988
  const res = await client.im.message.patch({
144780
144989
  path: { message_id: messageId },
144781
- data: { content: JSON.stringify(card) }
144990
+ data: { content: JSON.stringify(guard(card, "patch")) }
144782
144991
  });
144783
144992
  if (res?.code && res.code !== 0) {
144784
144993
  log2.warn("\u66F4\u65B0\u5361\u7247\u5931\u8D25", { messageId, code: res.code, msg: res.msg });
@@ -145070,6 +145279,18 @@ var SessionMap = class {
145070
145279
  async link(chatId, sessionID, openId) {
145071
145280
  await this.addSession(chatId, sessionID, "", openId);
145072
145281
  }
145282
+ /**
145283
+ * 写入 / 清除该会话的**话题根卡基础内容**(工作状态刷新用,见 `types.SessionRootCardBase`)。
145284
+ * 会话不存在返回 false(不凭空造卡)。
145285
+ */
145286
+ async setRootCard(sessionID, base2) {
145287
+ return this.setSessionMeta(sessionID, { rootCard: base2 });
145288
+ }
145289
+ /** 读取该会话的话题根卡基础内容(无则 undefined,状态刷新将跳过)。 */
145290
+ async getRootCard(sessionID) {
145291
+ const link2 = await this.resolveBySession(sessionID);
145292
+ return link2?.rootCard;
145293
+ }
145073
145294
  remember(sessionID, link2) {
145074
145295
  this.sessionToChat.set(sessionID, link2);
145075
145296
  }
@@ -145152,7 +145373,9 @@ function serializeSession(link2) {
145152
145373
  ...link2.perm ? { perm: link2.perm } : {},
145153
145374
  ...link2.gateMode ? { gateMode: link2.gateMode } : {},
145154
145375
  ...link2.dir ? { dir: link2.dir } : {},
145155
- ...link2.model ? { model: link2.model } : {}
145376
+ ...link2.model ? { model: link2.model } : {},
145377
+ ...link2.allowActions && link2.allowActions.length > 0 ? { allowActions: [...link2.allowActions] } : {},
145378
+ ...link2.rootCard ? { rootCard: link2.rootCard } : {}
145156
145379
  };
145157
145380
  }
145158
145381
  function parseSessionLink(value3) {
@@ -145165,6 +145388,8 @@ function parseSessionLink(value3) {
145165
145388
  const gateMode = value3.gateMode === "off" || value3.gateMode === "gate" ? value3.gateMode : void 0;
145166
145389
  const dir = str2(value3.dir);
145167
145390
  const model = parseModelRef(value3.model);
145391
+ const allowActions = parseStringArray(value3.allowActions);
145392
+ const rootCard = parseRootCard(value3.rootCard);
145168
145393
  return {
145169
145394
  chatId,
145170
145395
  openId,
@@ -145172,9 +145397,55 @@ function parseSessionLink(value3) {
145172
145397
  ...perm ? { perm } : {},
145173
145398
  ...gateMode ? { gateMode } : {},
145174
145399
  ...dir ? { dir } : {},
145175
- ...model ? { model } : {}
145400
+ ...model ? { model } : {},
145401
+ ...allowActions.length > 0 ? { allowActions } : {},
145402
+ ...rootCard ? { rootCard } : {}
145403
+ };
145404
+ }
145405
+ function parseRootCard(value3) {
145406
+ if (!isRecord3(value3)) return void 0;
145407
+ const style = value3.style === "created" || value3.style === "resumed" ? value3.style : void 0;
145408
+ const sessionID = str2(value3.sessionID);
145409
+ if (!style || !sessionID) return void 0;
145410
+ const title = str2(value3.title);
145411
+ const dir = str2(value3.dir);
145412
+ const model = str2(value3.model);
145413
+ const perm = str2(value3.perm);
145414
+ const summary = str2(value3.summary);
145415
+ const summaryLabel = str2(value3.summaryLabel);
145416
+ const compactError = str2(value3.compactError);
145417
+ const note2 = str2(value3.note);
145418
+ const updatedAt = typeof value3.updatedAt === "number" && Number.isFinite(value3.updatedAt) ? value3.updatedAt : void 0;
145419
+ return {
145420
+ style,
145421
+ sessionID,
145422
+ title,
145423
+ ...dir ? { dir } : {},
145424
+ ...model ? { model } : {},
145425
+ ...perm ? { perm } : {},
145426
+ ...updatedAt ? { updatedAt } : {},
145427
+ ...summary ? { summary } : {},
145428
+ ...summaryLabel ? { summaryLabel } : {},
145429
+ ...value3.summaryPending === true ? { summaryPending: true } : {},
145430
+ ...value3.compactPending === true ? { compactPending: true } : {},
145431
+ ...compactError ? { compactError } : {},
145432
+ ...value3.compactButton === true ? { compactButton: true } : {},
145433
+ ...note2 ? { note: note2 } : {}
145176
145434
  };
145177
145435
  }
145436
+ function parseStringArray(value3) {
145437
+ if (!Array.isArray(value3)) return [];
145438
+ const out = [];
145439
+ const seen = /* @__PURE__ */ new Set();
145440
+ for (const item of value3) {
145441
+ if (typeof item !== "string") continue;
145442
+ const trimmed = item.trim();
145443
+ if (!trimmed || seen.has(trimmed)) continue;
145444
+ seen.add(trimmed);
145445
+ out.push(trimmed);
145446
+ }
145447
+ return out;
145448
+ }
145178
145449
  function isPreset(value3) {
145179
145450
  return value3 === "readonly" || value3 === "edit" || value3 === "askHigh" || value3 === "trust";
145180
145451
  }
@@ -145501,6 +145772,20 @@ function buildApprovalCard(input) {
145501
145772
  lines.push("\u2139\uFE0F \u672C\u8BF7\u6C42\u672A\u643A\u5E26\u4FDD\u5B58\u9879\uFF0C\u300C\u59CB\u7EC8\u5141\u8BB8\u300D\u7B49\u4EF7\u4E8E\u300C\u5141\u8BB8\u4E00\u6B21\u300D\u3002");
145502
145773
  }
145503
145774
  const alwaysLabel = input.canPersistAlways ? "\u{1F513} \u59CB\u7EC8\u5141\u8BB8" : "\u{1F513} \u59CB\u7EC8\u5141\u8BB8\uFF08\u540C\u4E00\u6B21\uFF09";
145775
+ const buttons = [
145776
+ button("\u2705 \u5141\u8BB8\u4E00\u6B21", "primary", { t: input.token, d: "once" }),
145777
+ button(alwaysLabel, "default", { t: input.token, d: "always" })
145778
+ ];
145779
+ if (input.allowSessionToken) {
145780
+ buttons.push(
145781
+ button("\u2705 \u672C\u4F1A\u8BDD\u5185\u5141\u8BB8\u8BE5\u5DE5\u5177", "default", {
145782
+ cmd: "allow_session",
145783
+ a: input.action,
145784
+ t: input.allowSessionToken
145785
+ })
145786
+ );
145787
+ }
145788
+ buttons.push(button("\u274C \u62D2\u7EDD", "danger", { t: input.token, d: "reject" }));
145504
145789
  return {
145505
145790
  schema: "2.0",
145506
145791
  config: { update_multi: true },
@@ -145511,9 +145796,35 @@ function buildApprovalCard(input) {
145511
145796
  body: {
145512
145797
  elements: [
145513
145798
  { tag: "markdown", content: truncateCardContent(lines.join("\n")) },
145514
- button("\u2705 \u5141\u8BB8\u4E00\u6B21", "primary", { t: input.token, d: "once" }),
145515
- button(alwaysLabel, "default", { t: input.token, d: "always" }),
145516
- button("\u274C \u62D2\u7EDD", "danger", { t: input.token, d: "reject" })
145799
+ ...buttons
145800
+ ]
145801
+ }
145802
+ };
145803
+ }
145804
+ function buildSessionAllowResolvedCard(input, outcome) {
145805
+ const when3 = new Date(outcome.at).toISOString();
145806
+ return {
145807
+ schema: "2.0",
145808
+ config: { update_multi: true },
145809
+ header: {
145810
+ title: { tag: "plain_text", content: `\u2705 \u5DF2\u5141\u8BB8\u672C\u4F1A\u8BDD\u5185 ${outcome.action}` },
145811
+ template: "green"
145812
+ },
145813
+ body: {
145814
+ elements: [
145815
+ {
145816
+ tag: "markdown",
145817
+ content: truncateCardContent(
145818
+ [
145819
+ `**\u64CD\u4F5C**\uFF1A\`${escapeInline(input.action)}\``,
145820
+ "",
145821
+ "\u672C\u4F1A\u8BDD\u5185\u540E\u7EED\u8C03\u7528\u8BE5\u5DE5\u5177\u5C06**\u4E0D\u518D\u8BE2\u95EE**\uFF08\u5176\u5B83\u4F1A\u8BDD\u4E0D\u53D7\u5F71\u54CD\uFF09\u3002",
145822
+ "",
145823
+ `**\u5904\u7406\u4EBA**\uFF1A\`${escapeInline(mask2(outcome.operatorOpenId))}\``,
145824
+ `**\u65F6\u95F4**\uFF1A${when3}`
145825
+ ].join("\n")
145826
+ )
145827
+ }
145517
145828
  ]
145518
145829
  }
145519
145830
  };
@@ -145620,7 +145931,7 @@ var HEADER_SUMMARY_MAX = 80;
145620
145931
  var TOOL_INPUT_MAX = 600;
145621
145932
  var OUTPUT_FIRST_LINE_MAX = 200;
145622
145933
  var TEXT_ELEMENT_MAX = 8 * 1024;
145623
- function renderRunCard(state, stopValue) {
145934
+ function renderRunCard(state, stopValue, options2 = {}) {
145624
145935
  const elements = [];
145625
145936
  for (const group of groupBlocks(state.blocks)) {
145626
145937
  if (group.kind === "text") {
@@ -145655,7 +145966,11 @@ function renderRunCard(state, stopValue) {
145655
145966
  },
145656
145967
  body: { elements }
145657
145968
  };
145658
- return enforceSize(card);
145969
+ const { card: guarded, report } = enforceCardLimits(card, { maxTables: options2.maxTables });
145970
+ if (options2.onLimit && (report.degradedTables > 0 || report.droppedElements > 0)) {
145971
+ options2.onLimit(report);
145972
+ }
145973
+ return enforceSize(guarded);
145659
145974
  }
145660
145975
  function groupBlocks(blocks) {
145661
145976
  const groups = [];
@@ -145858,7 +146173,18 @@ function createRunController(deps) {
145858
146173
  }
145859
146174
  return runs;
145860
146175
  };
145861
- const renderCard = (sessionID, state) => renderRunCard(state, deps.buildStopValue?.(sessionID));
146176
+ const renderCard = (sessionID, state) => renderRunCard(state, deps.buildStopValue?.(sessionID), {
146177
+ maxTables: deps.cardMaxTables,
146178
+ onLimit: (report) => {
146179
+ deps.log.warn("\u8FD0\u884C\u5361\u5185\u5BB9\u8D85\u9650\uFF0C\u5DF2\u964D\u7EA7", {
146180
+ sessionID,
146181
+ tables: report.tables,
146182
+ degradedTables: report.degradedTables,
146183
+ elements: report.elements,
146184
+ droppedElements: report.droppedElements
146185
+ });
146186
+ }
146187
+ });
145862
146188
  const patch = async (card) => {
145863
146189
  if (disposed) return;
145864
146190
  const res = await deps.sender.patchCard(card.messageId, renderCard(card.sessionID, card.state));
@@ -146685,11 +147011,616 @@ function topicTitle(text, max6 = 20) {
146685
147011
 
146686
147012
  // src/feishu/routing.ts
146687
147013
  function decideRoute(facts) {
146688
- if (!facts.hasThread) return { kind: "main-hint" };
146689
147014
  if (facts.threadKnown) return { kind: "use-session", source: "thread" };
146690
147015
  if (facts.rootKnown) return { kind: "use-session", source: "root" };
147016
+ if (!facts.hasThread) return { kind: "main-hint" };
146691
147017
  return { kind: "create-in-thread" };
146692
147018
  }
147019
+
147020
+ // src/feishu/session-list.ts
147021
+ function normalizeSessionList(raw) {
147022
+ const array3 = extractArray(raw);
147023
+ if (!array3) return void 0;
147024
+ const out = [];
147025
+ const seen = /* @__PURE__ */ new Set();
147026
+ for (const item of array3) {
147027
+ const entry = normalizeSessionInfo(item);
147028
+ if (!entry || seen.has(entry.sessionID)) continue;
147029
+ seen.add(entry.sessionID);
147030
+ out.push(entry);
147031
+ }
147032
+ return sortByUpdatedDesc(out);
147033
+ }
147034
+ function normalizeSessionInfo(raw) {
147035
+ if (!isRecord5(raw)) return void 0;
147036
+ const sessionID = str3(raw.id) || str3(raw.sessionID);
147037
+ if (!sessionID) return void 0;
147038
+ const title = str3(raw.title) || str3(raw.slug);
147039
+ const updatedAt = extractUpdatedAt(raw);
147040
+ const directory = extractDirectory(raw);
147041
+ return {
147042
+ sessionID,
147043
+ title,
147044
+ updatedAt,
147045
+ ...directory ? { directory } : {}
147046
+ };
147047
+ }
147048
+ function extractSessionTitle(raw) {
147049
+ const title = normalizeSessionInfo(raw)?.title.trim();
147050
+ return title ? title : void 0;
147051
+ }
147052
+ function extractSessionPermissions(raw) {
147053
+ if (!isRecord5(raw) || !Array.isArray(raw.permissions)) return void 0;
147054
+ const out = [];
147055
+ for (const item of raw.permissions) {
147056
+ if (!isRecord5(item)) continue;
147057
+ const action = str3(item.action);
147058
+ const resource = str3(item.resource);
147059
+ const effect2 = item.effect;
147060
+ if (!action || !resource) continue;
147061
+ if (effect2 !== "allow" && effect2 !== "ask" && effect2 !== "deny") continue;
147062
+ out.push({ action, resource, effect: effect2 });
147063
+ }
147064
+ return out;
147065
+ }
147066
+ function toMillis2(value3) {
147067
+ if (typeof value3 === "number") {
147068
+ return Number.isFinite(value3) && value3 > 0 ? value3 : 0;
147069
+ }
147070
+ if (typeof value3 === "string") {
147071
+ const trimmed = value3.trim();
147072
+ if (!trimmed) return 0;
147073
+ if (/^\d+$/.test(trimmed)) {
147074
+ const parsed2 = Number.parseInt(trimmed, 10);
147075
+ return Number.isFinite(parsed2) && parsed2 > 0 ? parsed2 : 0;
147076
+ }
147077
+ const parsed = Date.parse(trimmed);
147078
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
147079
+ }
147080
+ if (value3 instanceof Date) {
147081
+ const ms = value3.getTime();
147082
+ return Number.isFinite(ms) && ms > 0 ? ms : 0;
147083
+ }
147084
+ if (isRecord5(value3)) {
147085
+ for (const key of ["epochMillis", "epochMilliseconds", "millis", "value"]) {
147086
+ const ms = toMillis2(value3[key]);
147087
+ if (ms > 0) return ms;
147088
+ }
147089
+ }
147090
+ return 0;
147091
+ }
147092
+ function sortByUpdatedDesc(entries3) {
147093
+ return [...entries3].sort(
147094
+ (a, b) => b.updatedAt - a.updatedAt || a.sessionID.localeCompare(b.sessionID)
147095
+ );
147096
+ }
147097
+ function fallbackEntries(entries3) {
147098
+ return sortByUpdatedDesc(
147099
+ entries3.map((entry) => ({
147100
+ sessionID: entry.sessionID,
147101
+ title: entry.title,
147102
+ updatedAt: Number.isFinite(entry.updatedAt) && entry.updatedAt > 0 ? entry.updatedAt : 0
147103
+ }))
147104
+ );
147105
+ }
147106
+ function relativeTime(fromMs, nowMs) {
147107
+ if (!Number.isFinite(fromMs) || fromMs <= 0) return "\u65F6\u95F4\u672A\u77E5";
147108
+ const diff = nowMs - fromMs;
147109
+ if (diff < 6e4) return "\u521A\u521A";
147110
+ const minutes2 = Math.floor(diff / 6e4);
147111
+ if (minutes2 < 60) return `${minutes2} \u5206\u949F\u524D`;
147112
+ const hours2 = Math.floor(minutes2 / 60);
147113
+ if (hours2 < 24) return `${hours2} \u5C0F\u65F6\u524D`;
147114
+ const days2 = Math.floor(hours2 / 24);
147115
+ if (days2 < 30) return `${days2} \u5929\u524D`;
147116
+ const date = new Date(fromMs);
147117
+ return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
147118
+ }
147119
+ function directoryTail(dir) {
147120
+ const trimmed = dir.trim().replace(/\/+$/, "");
147121
+ if (!trimmed) return "";
147122
+ const parts = trimmed.split("/");
147123
+ return parts[parts.length - 1] || trimmed;
147124
+ }
147125
+ function extractArray(raw) {
147126
+ if (Array.isArray(raw)) return raw;
147127
+ if (isRecord5(raw)) {
147128
+ for (const key of ["data", "sessions", "items"]) {
147129
+ if (Array.isArray(raw[key])) return raw[key];
147130
+ }
147131
+ }
147132
+ return void 0;
147133
+ }
147134
+ function extractUpdatedAt(raw) {
147135
+ const time = isRecord5(raw.time) ? raw.time : void 0;
147136
+ const candidates = [
147137
+ time?.updated,
147138
+ raw.updatedAt,
147139
+ raw.updated,
147140
+ time?.created,
147141
+ raw.createdAt,
147142
+ raw.created
147143
+ ];
147144
+ for (const candidate of candidates) {
147145
+ const ms = toMillis2(candidate);
147146
+ if (ms > 0) return ms;
147147
+ }
147148
+ return 0;
147149
+ }
147150
+ function extractDirectory(raw) {
147151
+ if (isRecord5(raw.location)) {
147152
+ const directory = str3(raw.location.directory);
147153
+ if (directory) return directory;
147154
+ }
147155
+ return str3(raw.directory) || (typeof raw.location === "string" ? raw.location : "");
147156
+ }
147157
+ function pad2(value3) {
147158
+ return value3 < 10 ? `0${value3}` : String(value3);
147159
+ }
147160
+ function isRecord5(value3) {
147161
+ return typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
147162
+ }
147163
+ function str3(value3) {
147164
+ return typeof value3 === "string" ? value3 : "";
147165
+ }
147166
+
147167
+ // src/session/topic-status.ts
147168
+ var STATUS_META = {
147169
+ review: { emoji: "\u{1F7E1}", color: "orange", label: "\u5F85\u5BA1\u6838" },
147170
+ running: { emoji: "\u{1F9E0}", color: "blue", label: "\u8FD0\u884C\u4E2D" },
147171
+ pending: { emoji: "\u23F3", color: "grey", label: "\u5F85\u56DE\u590D" },
147172
+ failed: { emoji: "\u{1F534}", color: "red", label: "\u5931\u8D25" },
147173
+ interrupted: { emoji: "\u23F9", color: "grey", label: "\u5DF2\u4E2D\u65AD" },
147174
+ done: { emoji: "\u2705", color: "green", label: "\u5B8C\u6210" }
147175
+ };
147176
+ function topicStatusMeta(kind) {
147177
+ return STATUS_META[kind];
147178
+ }
147179
+ function computeTopicStatus(input) {
147180
+ if (input.permissionPending > 0) {
147181
+ return { kind: "review", ...input.reviewAction ? { reviewAction: input.reviewAction } : {} };
147182
+ }
147183
+ if (input.running) return { kind: "running" };
147184
+ if (input.queued > 0) return { kind: "pending", queued: input.queued };
147185
+ if (input.terminal === "failed") return { kind: "failed" };
147186
+ if (input.terminal === "interrupted") return { kind: "interrupted" };
147187
+ return { kind: "done" };
147188
+ }
147189
+ function formatClock(now3) {
147190
+ const d = new Date(now3);
147191
+ const hh = String(d.getHours()).padStart(2, "0");
147192
+ const mm = String(d.getMinutes()).padStart(2, "0");
147193
+ return `${hh}:${mm}`;
147194
+ }
147195
+ function topicStatusFooter(view, now3) {
147196
+ switch (view.kind) {
147197
+ case "review":
147198
+ return view.reviewAction ? `\u{1F7E1} \u5F85\u5BA1\u6838\uFF1A${view.reviewAction}` : "\u{1F7E1} \u5F85\u5BA1\u6838";
147199
+ case "running":
147200
+ return `\u{1F9E0} \u8FD0\u884C\u4E2D \xB7 ${formatClock(now3)}`;
147201
+ case "pending":
147202
+ return `\u23F3 \u5F85\u56DE\u590D\uFF08\u6392\u961F ${view.queued ?? 0}\uFF09`;
147203
+ case "failed":
147204
+ return "\u{1F534} \u5931\u8D25";
147205
+ case "interrupted":
147206
+ return "\u23F9 \u5DF2\u4E2D\u65AD";
147207
+ case "done":
147208
+ return "\u2705 \u5B8C\u6210";
147209
+ }
147210
+ }
147211
+ function topicStatusTitle(title, view, statusInTitle) {
147212
+ return `${topicStatusMeta(view.kind).emoji} ${title}`;
147213
+ }
147214
+ function newSessionStatusInput() {
147215
+ return { running: false, permissions: /* @__PURE__ */ new Set(), queued: /* @__PURE__ */ new Set() };
147216
+ }
147217
+ var HANDLED_EVENT_TYPES = /* @__PURE__ */ new Set([
147218
+ "session.execution.started",
147219
+ "session.execution.succeeded",
147220
+ "session.execution.failed",
147221
+ "session.execution.interrupted",
147222
+ "session.status",
147223
+ "session.idle",
147224
+ "permission.asked",
147225
+ "permission.replied",
147226
+ "session.inbox.enqueued",
147227
+ "session.inbox.delivered",
147228
+ "session.inbox.cancelled"
147229
+ ]);
147230
+ var TopicStatusMachine = class {
147231
+ sessions = /* @__PURE__ */ new Map();
147232
+ lastKind = /* @__PURE__ */ new Map();
147233
+ permissionSeq = 0;
147234
+ /** 当前档位视图(未跟踪过的会话返回 undefined)。 */
147235
+ view(sessionID) {
147236
+ const input = this.sessions.get(sessionID);
147237
+ if (!input) return void 0;
147238
+ return this.computeView(input);
147239
+ }
147240
+ /** 消费一条事件;未识别/无会话 id 返回 undefined。 */
147241
+ reduce(sessionID, event) {
147242
+ if (!sessionID) return void 0;
147243
+ if (!HANDLED_EVENT_TYPES.has(event.type)) return void 0;
147244
+ const input = this.input(sessionID);
147245
+ const data = event.data ?? {};
147246
+ switch (event.type) {
147247
+ case "session.execution.started":
147248
+ input.running = true;
147249
+ input.terminal = void 0;
147250
+ break;
147251
+ case "session.execution.succeeded":
147252
+ input.running = false;
147253
+ input.terminal = "done";
147254
+ break;
147255
+ case "session.execution.failed":
147256
+ input.running = false;
147257
+ input.terminal = "failed";
147258
+ break;
147259
+ case "session.execution.interrupted":
147260
+ input.running = false;
147261
+ input.terminal = "interrupted";
147262
+ break;
147263
+ case "session.status": {
147264
+ const statusType = data.status?.type;
147265
+ if (statusType === "idle") {
147266
+ input.running = false;
147267
+ input.terminal = "done";
147268
+ } else if (statusType === "busy" || statusType === "retry") {
147269
+ input.running = true;
147270
+ input.terminal = void 0;
147271
+ } else {
147272
+ return void 0;
147273
+ }
147274
+ break;
147275
+ }
147276
+ case "session.idle":
147277
+ input.running = false;
147278
+ input.terminal = "done";
147279
+ break;
147280
+ case "permission.asked": {
147281
+ const id2 = str4(data.id) || `p${this.permissionSeq += 1}`;
147282
+ input.permissions.add(id2);
147283
+ const action = str4(data.action);
147284
+ if (action) input.reviewAction = action;
147285
+ break;
147286
+ }
147287
+ case "permission.replied": {
147288
+ const requestID = str4(data.requestID);
147289
+ if (requestID) input.permissions.delete(requestID);
147290
+ break;
147291
+ }
147292
+ case "session.inbox.enqueued": {
147293
+ const inboxID = str4(data.inboxID) || str4(data.item?.id);
147294
+ if (inboxID) input.queued.add(inboxID);
147295
+ break;
147296
+ }
147297
+ case "session.inbox.delivered":
147298
+ case "session.inbox.cancelled": {
147299
+ const inboxID = str4(data.inboxID);
147300
+ if (inboxID) input.queued.delete(inboxID);
147301
+ break;
147302
+ }
147303
+ default:
147304
+ return void 0;
147305
+ }
147306
+ return this.record(sessionID, input);
147307
+ }
147308
+ /** 运行卡终态等**非事件流**信号的显式入口(如 prompt 发送失败)。 */
147309
+ markTerminal(sessionID, kind) {
147310
+ if (!sessionID) return void 0;
147311
+ const input = this.input(sessionID);
147312
+ input.running = false;
147313
+ input.terminal = kind;
147314
+ return this.record(sessionID, input);
147315
+ }
147316
+ /** 清理某会话状态(卸载 / 测试用)。 */
147317
+ forget(sessionID) {
147318
+ this.sessions.delete(sessionID);
147319
+ this.lastKind.delete(sessionID);
147320
+ }
147321
+ clear() {
147322
+ this.sessions.clear();
147323
+ this.lastKind.clear();
147324
+ }
147325
+ input(sessionID) {
147326
+ let input = this.sessions.get(sessionID);
147327
+ if (!input) {
147328
+ input = newSessionStatusInput();
147329
+ this.sessions.set(sessionID, input);
147330
+ }
147331
+ return input;
147332
+ }
147333
+ computeView(input) {
147334
+ return computeTopicStatus({
147335
+ running: input.running,
147336
+ permissionPending: input.permissions.size,
147337
+ queued: input.queued.size,
147338
+ ...input.terminal ? { terminal: input.terminal } : {},
147339
+ ...input.reviewAction ? { reviewAction: input.reviewAction } : {}
147340
+ });
147341
+ }
147342
+ record(sessionID, input) {
147343
+ const view = this.computeView(input);
147344
+ const changed = this.lastKind.get(sessionID) !== view.kind;
147345
+ this.lastKind.set(sessionID, view.kind);
147346
+ return { view, changed };
147347
+ }
147348
+ };
147349
+ function str4(value3) {
147350
+ return typeof value3 === "string" ? value3 : "";
147351
+ }
147352
+
147353
+ // src/feishu/session-cards.ts
147354
+ function button2(text, type, value3) {
147355
+ return {
147356
+ tag: "button",
147357
+ text: { tag: "plain_text", content: text },
147358
+ type,
147359
+ behaviors: [{ type: "callback", value: value3 }]
147360
+ };
147361
+ }
147362
+ function sessionRowLine(row, now3) {
147363
+ const title = truncateTitle(row.title.trim() || "(\u672A\u547D\u540D)");
147364
+ const marks = [relativeTime(row.updatedAt, now3)];
147365
+ if (row.bound) marks.push("\u{1F4AC} \u5DF2\u7ED1\u8BDD\u9898");
147366
+ if (row.directory) marks.push(`\u{1F4CD} ${directoryTail(row.directory)}`);
147367
+ if (row.active) marks.push("\u2190 \u5F53\u524D");
147368
+ return `${row.index}. ${title}\uFF08\`${shortSessionId(row.sessionID)}\`\uFF09\xB7 ${marks.join(" \xB7 ")}`;
147369
+ }
147370
+ function buildSessionListCard(input) {
147371
+ const now3 = input.now ?? Date.now();
147372
+ const elements = [];
147373
+ if (input.rows.length === 0) {
147374
+ elements.push({
147375
+ tag: "markdown",
147376
+ content: truncateCardContent(
147377
+ "\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u3002\u70B9\u4E0B\u65B9\u300C\u2795 \u65B0\u5EFA\u4F1A\u8BDD\u300D\u521B\u5EFA\uFF0C\u6216\u76F4\u63A5\u5728\u8BDD\u9898\u91CC\u53D1\u6D88\u606F\u81EA\u52A8\u521B\u5EFA\u3002"
147378
+ )
147379
+ });
147380
+ } else {
147381
+ input.rows.forEach((row, i) => {
147382
+ if (i > 0) elements.push({ tag: "hr", margin: "2px 0px 2px 0px" });
147383
+ elements.push({
147384
+ tag: "column_set",
147385
+ flex_mode: "none",
147386
+ horizontal_spacing: "small",
147387
+ columns: [
147388
+ {
147389
+ tag: "column",
147390
+ width: "weighted",
147391
+ weight: 5,
147392
+ vertical_align: "center",
147393
+ elements: [{ tag: "markdown", content: truncateCardContent(sessionRowLine(row, now3)) }]
147394
+ },
147395
+ {
147396
+ tag: "column",
147397
+ width: "auto",
147398
+ vertical_align: "center",
147399
+ elements: [
147400
+ button2(row.bound ? "\u25B6\uFE0F \u518D\u5F00" : "\u25B6\uFE0F \u8FDB\u5165", row.active ? "primary" : "default", {
147401
+ cmd: "open",
147402
+ s: row.sessionID,
147403
+ c: input.chatId
147404
+ })
147405
+ ]
147406
+ }
147407
+ ]
147408
+ });
147409
+ });
147410
+ }
147411
+ if (input.page > 0) {
147412
+ elements.push(button2("\u2B05\uFE0F \u4E0A\u4E00\u9875", "default", { cmd: "list", p: input.page - 1, c: input.chatId }));
147413
+ }
147414
+ if (input.page + 1 < input.pageCount) {
147415
+ elements.push(button2("\u27A1\uFE0F \u4E0B\u4E00\u9875", "default", { cmd: "list", p: input.page + 1, c: input.chatId }));
147416
+ }
147417
+ elements.push(button2("\u2795 \u65B0\u5EFA\u4F1A\u8BDD", "default", { cmd: "new", c: input.chatId }));
147418
+ if (input.total > 0) {
147419
+ elements.push({
147420
+ tag: "markdown",
147421
+ content: `\u7B2C ${input.page + 1}/${input.pageCount} \u9875 \xB7 \u5171 ${input.total} \u4E2A\u4F1A\u8BDD`,
147422
+ text_size: "notation"
147423
+ });
147424
+ }
147425
+ const template2 = "blue";
147426
+ return guardCard({
147427
+ schema: "2.0",
147428
+ config: { update_multi: true },
147429
+ header: {
147430
+ title: { tag: "plain_text", content: input.title ?? "\u{1F9E9} OpenCode \u4F1A\u8BDD\uFF08\u5168\u90E8\uFF09" },
147431
+ template: template2
147432
+ },
147433
+ body: { elements }
147434
+ });
147435
+ }
147436
+ function parseSessionCardValue(raw) {
147437
+ if (typeof raw !== "object" || raw === null) return void 0;
147438
+ const record2 = raw;
147439
+ const chatId = typeof record2.c === "string" ? record2.c : "";
147440
+ if (record2.cmd === "new") return { cmd: "new", chatId };
147441
+ if (record2.cmd === "use" || record2.cmd === "open") {
147442
+ const sessionID = typeof record2.s === "string" ? record2.s : "";
147443
+ if (!sessionID) return void 0;
147444
+ return record2.cmd === "open" ? { cmd: "open", sessionID, chatId } : { cmd: "use", sessionID, chatId };
147445
+ }
147446
+ if (record2.cmd === "list") {
147447
+ const page = parsePage(record2.p);
147448
+ return { cmd: "list", page, chatId };
147449
+ }
147450
+ if (record2.cmd === "compact") {
147451
+ const sessionID = typeof record2.s === "string" ? record2.s : "";
147452
+ const token = typeof record2.t === "string" ? record2.t : "";
147453
+ if (!sessionID || !token) return void 0;
147454
+ return { cmd: "compact", sessionID, token };
147455
+ }
147456
+ return void 0;
147457
+ }
147458
+ function buildSessionRootCard(base2, status, options2 = {}) {
147459
+ const now3 = options2.now ?? Date.now();
147460
+ const elements = [];
147461
+ let headerTitle;
147462
+ if (base2.style === "created") {
147463
+ const title = base2.title.trim() || "(\u672A\u547D\u540D)";
147464
+ headerTitle = `\u2705 \u5DF2\u521B\u5EFA \xB7 ${title}`;
147465
+ elements.push({ tag: "markdown", content: truncateCardContent(renderCreatedBody(title, base2)) });
147466
+ } else {
147467
+ const title = truncateTitle(base2.title.trim() || "(\u672A\u547D\u540D)");
147468
+ headerTitle = `\u{1F504} ${title}`;
147469
+ elements.push({ tag: "markdown", content: truncateCardContent(renderResumedBody(title, base2, now3)) });
147470
+ if (base2.compactButton && options2.compactToken) {
147471
+ elements.push(
147472
+ button2("\u{1F5DC} \u538B\u7F29\u5E76\u603B\u7ED3", "default", {
147473
+ cmd: "compact",
147474
+ s: base2.sessionID,
147475
+ t: options2.compactToken
147476
+ })
147477
+ );
147478
+ }
147479
+ }
147480
+ if (status) {
147481
+ elements.push({ tag: "markdown", content: topicStatusFooter(status, now3), text_size: "notation" });
147482
+ }
147483
+ const template2 = status ? topicStatusMeta(status.kind).color : "green";
147484
+ const content = status && options2.statusInTitle ? topicStatusTitle(headerTitle, status) : headerTitle;
147485
+ return guardCard(
147486
+ {
147487
+ schema: "2.0",
147488
+ config: { update_multi: true },
147489
+ header: { title: { tag: "plain_text", content }, template: template2 },
147490
+ body: { elements }
147491
+ },
147492
+ options2
147493
+ );
147494
+ }
147495
+ function guardCard(card, options2 = {}) {
147496
+ const { card: guarded, report } = enforceCardLimits(card, { maxTables: options2.maxTables });
147497
+ if (options2.onLimit && (report.degradedTables > 0 || report.droppedElements > 0)) {
147498
+ options2.onLimit(report);
147499
+ }
147500
+ return guarded;
147501
+ }
147502
+ function renderResumedBody(title, base2, now3) {
147503
+ const lines = [`\u4F1A\u8BDD\u300C${title}\u300D\uFF1A\`${base2.sessionID}\``];
147504
+ const setup = [];
147505
+ if (base2.dir) setup.push(`- \u76EE\u5F55\uFF1A\`${base2.dir}\``);
147506
+ if (base2.model) setup.push(`- \u6A21\u578B\uFF1A${base2.model}`);
147507
+ if (base2.updatedAt && base2.updatedAt > 0) {
147508
+ setup.push(`- \u6700\u8FD1\u6D3B\u52A8\uFF1A${relativeTime(base2.updatedAt, now3)}`);
147509
+ }
147510
+ if (setup.length > 0) lines.push("", ...setup);
147511
+ lines.push("", "**\u56DE\u590D\u672C\u5361\u7247**\u5373\u53EF\u7EE7\u7EED\u8FD9\u4E2A\u5386\u53F2\u4F1A\u8BDD\uFF08\u98DE\u4E66\u56DE\u590D\u4F1A\u5728\u672C\u5361\u4E0B\u5F62\u6210\u8BDD\u9898\uFF09\u3002");
147512
+ if (base2.summary || base2.summaryPending || base2.compactPending || base2.compactError) {
147513
+ lines.push("", `**${base2.summaryLabel ?? "\u6458\u8981"}**\uFF1A`);
147514
+ if (base2.summaryPending) lines.push("\u23F3 \u6B63\u5728\u603B\u7ED3\u8BE5\u4F1A\u8BDD\u2026");
147515
+ if (base2.compactPending) lines.push("\u{1F5DC} \u6B63\u5728\u538B\u7F29\u4F1A\u8BDD\u2026\uFF08\u538B\u7F29\u4F1A\u4FEE\u6539\u4F1A\u8BDD\u5386\u53F2\uFF0C\u8BF7\u7A0D\u5019\uFF09");
147516
+ if (base2.summary) lines.push(base2.summary);
147517
+ if (base2.compactError) lines.push(base2.compactError);
147518
+ }
147519
+ lines.push(
147520
+ "",
147521
+ "\u8BDD\u9898\u5185\u53EF\u7528\uFF1A`/current` `/stop` `/model` `/perm` `/cd` `/help`\u3002",
147522
+ "\u4F1A\u8BDD\u7BA1\u7406\uFF08`/new` `/sessions` `/resume`\uFF09\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u3002"
147523
+ );
147524
+ return lines.join("\n");
147525
+ }
147526
+ function renderCreatedBody(title, base2) {
147527
+ const lines = [`\u4F1A\u8BDD\u300C${title}\u300D\u5DF2\u521B\u5EFA\uFF1A\`${base2.sessionID}\``];
147528
+ const setup = [];
147529
+ if (base2.dir) setup.push(`- \u76EE\u5F55\uFF1A\`${base2.dir}\``);
147530
+ if (base2.model) setup.push(`- \u6A21\u578B\uFF1A${base2.model}`);
147531
+ if (base2.perm) setup.push(`- \u6743\u9650\uFF1A${base2.perm}`);
147532
+ if (setup.length > 0) lines.push("", ...setup);
147533
+ lines.push("", "\u70B9\u8FDB\u672C\u8BDD\u9898\u76F4\u63A5\u53D1\u6D88\u606F\u5373\u53EF\uFF0COpenCode \u5C31\u5728\u8FD9\u4E2A\u4F1A\u8BDD\u91CC\u5E72\u6D3B\u3002");
147534
+ if (base2.note) lines.push("", base2.note);
147535
+ return lines.join("\n");
147536
+ }
147537
+ function buildSessionOpenedCard(input) {
147538
+ const base2 = {
147539
+ style: "resumed",
147540
+ title: input.title,
147541
+ sessionID: input.sessionID,
147542
+ ...input.dir ? { dir: input.dir } : {},
147543
+ ...input.model ? { model: input.model } : {},
147544
+ ...input.updatedAt ? { updatedAt: input.updatedAt } : {},
147545
+ ...input.summary ? { summary: input.summary } : {},
147546
+ ...input.summaryLabel ? { summaryLabel: input.summaryLabel } : {},
147547
+ ...input.summaryPending ? { summaryPending: true } : {},
147548
+ ...input.compactPending ? { compactPending: true } : {},
147549
+ ...input.compactError ? { compactError: input.compactError } : {},
147550
+ ...input.compactButton ? { compactButton: true } : {}
147551
+ };
147552
+ return buildSessionRootCard(base2, void 0, {
147553
+ now: input.now ?? Date.now(),
147554
+ ...input.compactButton ? { compactToken: input.compactButton.token } : {},
147555
+ ...input.maxTables !== void 0 ? { maxTables: input.maxTables } : {},
147556
+ ...input.onLimit ? { onLimit: input.onLimit } : {}
147557
+ });
147558
+ }
147559
+ function buildResumeCompactPendingCard(title, sessionID, token, now3) {
147560
+ return buildSessionOpenedCard({
147561
+ title,
147562
+ sessionID,
147563
+ now: now3,
147564
+ compactPending: true,
147565
+ compactButton: { sessionID, token }
147566
+ });
147567
+ }
147568
+ function buildSessionMissingCard(sessionID, reason) {
147569
+ const lines = [
147570
+ `\u4F1A\u8BDD \`${sessionID}\` \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u7528\uFF08\u53EF\u80FD\u5DF2\u88AB\u5220\u9664\uFF0C\u6216\u4E0D\u5C5E\u4E8E\u672C\u673A\u53EF\u89C1\u8303\u56F4\uFF09\u3002`,
147571
+ "",
147572
+ "\u53D1\u9001 `/sessions` \u91CD\u65B0\u83B7\u53D6\u5217\u8868\u3002"
147573
+ ];
147574
+ if (reason) lines.splice(1, 0, "", `\u539F\u56E0\uFF1A${reason}`);
147575
+ return guardCard({
147576
+ schema: "2.0",
147577
+ config: { update_multi: true },
147578
+ header: { title: { tag: "plain_text", content: "\u26A0\uFE0F \u4F1A\u8BDD\u4E0D\u5B58\u5728" }, template: "orange" },
147579
+ body: { elements: [{ tag: "markdown", content: truncateCardContent(lines.join("\n")) }] }
147580
+ });
147581
+ }
147582
+ function buildSessionReadyCard(input) {
147583
+ const title = input.title.trim() || "(\u672A\u547D\u540D)";
147584
+ const lines = [`\u4F1A\u8BDD\u300C${title}\u300D\u5DF2\u521B\u5EFA\uFF1A\`${input.sessionID}\``];
147585
+ const setup = [];
147586
+ if (input.dir) setup.push(`- \u76EE\u5F55\uFF1A\`${input.dir}\``);
147587
+ if (input.model) setup.push(`- \u6A21\u578B\uFF1A${input.model}`);
147588
+ if (input.perm) setup.push(`- \u6743\u9650\uFF1A${input.perm}`);
147589
+ if (setup.length > 0) lines.push("", ...setup);
147590
+ lines.push(
147591
+ "",
147592
+ "**\u5728\u672C\u8BDD\u9898\u5185\u76F4\u63A5\u53D1\u6D88\u606F**\uFF0COpenCode \u5C31\u5728\u8FD9\u4E2A\u4F1A\u8BDD\u91CC\u5E72\u6D3B\u3002",
147593
+ "",
147594
+ "\u8BDD\u9898\u5185\u53EF\u7528\uFF1A`/current` `/stop` `/model` `/perm` `/cd` `/help`\u3002",
147595
+ "\u4F1A\u8BDD\u7BA1\u7406\uFF08`/new` `/sessions` `/use`\uFF09\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u3002"
147596
+ );
147597
+ return guardCard({
147598
+ schema: "2.0",
147599
+ config: { update_multi: true },
147600
+ header: { title: { tag: "plain_text", content: "\u2705 \u4F1A\u8BDD\u5DF2\u5C31\u7EEA" }, template: "green" },
147601
+ body: {
147602
+ elements: [{ tag: "markdown", content: truncateCardContent(lines.join("\n")) }]
147603
+ }
147604
+ });
147605
+ }
147606
+ function buildSessionCreatedCard(input) {
147607
+ return buildSessionRootCard({
147608
+ style: "created",
147609
+ title: input.title,
147610
+ sessionID: input.sessionID,
147611
+ ...input.dir ? { dir: input.dir } : {},
147612
+ ...input.model ? { model: input.model } : {},
147613
+ ...input.perm ? { perm: input.perm } : {},
147614
+ ...input.note ? { note: input.note } : {}
147615
+ });
147616
+ }
147617
+ function truncateTitle(title, max6 = 30) {
147618
+ return title.length > max6 ? `${title.slice(0, max6)}\u2026` : title;
147619
+ }
147620
+ function parsePage(value3) {
147621
+ const parsed = typeof value3 === "number" ? value3 : typeof value3 === "string" ? Number.parseInt(value3, 10) : Number.NaN;
147622
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
147623
+ }
146693
147624
  var SYSTEM_DIRS = [
146694
147625
  "/etc",
146695
147626
  "/usr",
@@ -146828,6 +147759,21 @@ function presetGateMode(preset) {
146828
147759
  function presetAskActions(preset) {
146829
147760
  return ASK_ACTIONS[preset] ?? [];
146830
147761
  }
147762
+ function allowActionsForGrant(action) {
147763
+ return SHELL_ACTIONS.includes(action) ? [...SHELL_ACTIONS] : [action];
147764
+ }
147765
+ function appendAllowRules(base2, actions) {
147766
+ const granted = new Set(actions);
147767
+ const kept = base2.filter(
147768
+ (rule) => !(granted.has(rule.action) && rule.resource === "*" && rule.effect === "allow")
147769
+ );
147770
+ const appended = actions.map((action) => ({
147771
+ action,
147772
+ resource: "*",
147773
+ effect: "allow"
147774
+ }));
147775
+ return [...kept, ...appended];
147776
+ }
146831
147777
 
146832
147778
  // src/feishu/wizard.ts
146833
147779
  var WIZARD_KEY_PREFIX = "feishu:v2:setup:";
@@ -146941,14 +147887,14 @@ var WizardStore = class {
146941
147887
  // src/feishu/recent.ts
146942
147888
  var RECENT_DIRS_KEY = "feishu:v2:recent:dirs";
146943
147889
  var RECENT_MODELS_KEY = "feishu:v2:recent:models";
146944
- function isRecord5(value3) {
147890
+ function isRecord6(value3) {
146945
147891
  return typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
146946
147892
  }
146947
147893
  function modelKey(model) {
146948
147894
  return `${model.providerID}/${model.id}`;
146949
147895
  }
146950
147896
  function parseModel2(value3) {
146951
- if (!isRecord5(value3)) return void 0;
147897
+ if (!isRecord6(value3)) return void 0;
146952
147898
  const providerID = typeof value3.providerID === "string" ? value3.providerID : "";
146953
147899
  const id2 = typeof value3.id === "string" ? value3.id : "";
146954
147900
  if (!providerID || !id2) return void 0;
@@ -147003,32 +147949,32 @@ var RecentStore = class {
147003
147949
  };
147004
147950
 
147005
147951
  // src/feishu/models.ts
147006
- function isRecord6(value3) {
147952
+ function isRecord7(value3) {
147007
147953
  return typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
147008
147954
  }
147009
- function str3(value3) {
147955
+ function str5(value3) {
147010
147956
  return typeof value3 === "string" ? value3 : "";
147011
147957
  }
147012
147958
  function normalizeModelList(raw) {
147013
- const arr = Array.isArray(raw) ? raw : isRecord6(raw) && Array.isArray(raw.data) ? raw.data : [];
147959
+ const arr = Array.isArray(raw) ? raw : isRecord7(raw) && Array.isArray(raw.data) ? raw.data : [];
147014
147960
  const out = [];
147015
147961
  const seen = /* @__PURE__ */ new Set();
147016
147962
  for (const item of arr) {
147017
- if (!isRecord6(item)) continue;
147963
+ if (!isRecord7(item)) continue;
147018
147964
  if (item.enabled === false) continue;
147019
- const providerID = str3(item.providerID);
147020
- const id2 = str3(item.id) || str3(item.modelID);
147965
+ const providerID = str5(item.providerID);
147966
+ const id2 = str5(item.id) || str5(item.modelID);
147021
147967
  if (!providerID || !id2) continue;
147022
147968
  const key = `${providerID}/${id2}`;
147023
147969
  if (seen.has(key)) continue;
147024
147970
  seen.add(key);
147025
- const time = isRecord6(item.time) ? item.time : {};
147971
+ const time = isRecord7(item.time) ? item.time : {};
147026
147972
  const released = typeof time.released === "number" && Number.isFinite(time.released) ? time.released : void 0;
147027
- const status = str3(item.status) || void 0;
147973
+ const status = str5(item.status) || void 0;
147028
147974
  out.push({
147029
147975
  providerID,
147030
147976
  id: id2,
147031
- name: str3(item.name) || id2,
147977
+ name: str5(item.name) || id2,
147032
147978
  ...released !== void 0 ? { released } : {},
147033
147979
  ...status ? { status } : {}
147034
147980
  });
@@ -147043,10 +147989,10 @@ function sameModel(a, b) {
147043
147989
  return a.providerID === b.providerID && a.id === b.id;
147044
147990
  }
147045
147991
  function extractSessionModel(raw) {
147046
- if (!isRecord6(raw)) return void 0;
147047
- const candidate = isRecord6(raw.model) ? raw.model : raw;
147048
- const providerID = str3(candidate.providerID);
147049
- const id2 = str3(candidate.id);
147992
+ if (!isRecord7(raw)) return void 0;
147993
+ const candidate = isRecord7(raw.model) ? raw.model : raw;
147994
+ const providerID = str5(candidate.providerID);
147995
+ const id2 = str5(candidate.id);
147050
147996
  if (!providerID || !id2) return void 0;
147051
147997
  return { providerID, id: id2 };
147052
147998
  }
@@ -147095,139 +148041,6 @@ function modelMatchErrorText(reason, candidates) {
147095
148041
  }
147096
148042
  }
147097
148043
 
147098
- // src/feishu/session-list.ts
147099
- function normalizeSessionList(raw) {
147100
- const array3 = extractArray(raw);
147101
- if (!array3) return void 0;
147102
- const out = [];
147103
- const seen = /* @__PURE__ */ new Set();
147104
- for (const item of array3) {
147105
- const entry = normalizeSessionInfo(item);
147106
- if (!entry || seen.has(entry.sessionID)) continue;
147107
- seen.add(entry.sessionID);
147108
- out.push(entry);
147109
- }
147110
- return sortByUpdatedDesc(out);
147111
- }
147112
- function normalizeSessionInfo(raw) {
147113
- if (!isRecord7(raw)) return void 0;
147114
- const sessionID = str4(raw.id) || str4(raw.sessionID);
147115
- if (!sessionID) return void 0;
147116
- const title = str4(raw.title) || str4(raw.slug);
147117
- const updatedAt = extractUpdatedAt(raw);
147118
- const directory = extractDirectory(raw);
147119
- return {
147120
- sessionID,
147121
- title,
147122
- updatedAt,
147123
- ...directory ? { directory } : {}
147124
- };
147125
- }
147126
- function extractSessionTitle(raw) {
147127
- const title = normalizeSessionInfo(raw)?.title.trim();
147128
- return title ? title : void 0;
147129
- }
147130
- function toMillis2(value3) {
147131
- if (typeof value3 === "number") {
147132
- return Number.isFinite(value3) && value3 > 0 ? value3 : 0;
147133
- }
147134
- if (typeof value3 === "string") {
147135
- const trimmed = value3.trim();
147136
- if (!trimmed) return 0;
147137
- if (/^\d+$/.test(trimmed)) {
147138
- const parsed2 = Number.parseInt(trimmed, 10);
147139
- return Number.isFinite(parsed2) && parsed2 > 0 ? parsed2 : 0;
147140
- }
147141
- const parsed = Date.parse(trimmed);
147142
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
147143
- }
147144
- if (value3 instanceof Date) {
147145
- const ms = value3.getTime();
147146
- return Number.isFinite(ms) && ms > 0 ? ms : 0;
147147
- }
147148
- if (isRecord7(value3)) {
147149
- for (const key of ["epochMillis", "epochMilliseconds", "millis", "value"]) {
147150
- const ms = toMillis2(value3[key]);
147151
- if (ms > 0) return ms;
147152
- }
147153
- }
147154
- return 0;
147155
- }
147156
- function sortByUpdatedDesc(entries3) {
147157
- return [...entries3].sort(
147158
- (a, b) => b.updatedAt - a.updatedAt || a.sessionID.localeCompare(b.sessionID)
147159
- );
147160
- }
147161
- function fallbackEntries(entries3) {
147162
- return sortByUpdatedDesc(
147163
- entries3.map((entry) => ({
147164
- sessionID: entry.sessionID,
147165
- title: entry.title,
147166
- updatedAt: Number.isFinite(entry.updatedAt) && entry.updatedAt > 0 ? entry.updatedAt : 0
147167
- }))
147168
- );
147169
- }
147170
- function relativeTime(fromMs, nowMs) {
147171
- if (!Number.isFinite(fromMs) || fromMs <= 0) return "\u65F6\u95F4\u672A\u77E5";
147172
- const diff = nowMs - fromMs;
147173
- if (diff < 6e4) return "\u521A\u521A";
147174
- const minutes2 = Math.floor(diff / 6e4);
147175
- if (minutes2 < 60) return `${minutes2} \u5206\u949F\u524D`;
147176
- const hours2 = Math.floor(minutes2 / 60);
147177
- if (hours2 < 24) return `${hours2} \u5C0F\u65F6\u524D`;
147178
- const days2 = Math.floor(hours2 / 24);
147179
- if (days2 < 30) return `${days2} \u5929\u524D`;
147180
- const date = new Date(fromMs);
147181
- return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
147182
- }
147183
- function directoryTail(dir) {
147184
- const trimmed = dir.trim().replace(/\/+$/, "");
147185
- if (!trimmed) return "";
147186
- const parts = trimmed.split("/");
147187
- return parts[parts.length - 1] || trimmed;
147188
- }
147189
- function extractArray(raw) {
147190
- if (Array.isArray(raw)) return raw;
147191
- if (isRecord7(raw)) {
147192
- for (const key of ["data", "sessions", "items"]) {
147193
- if (Array.isArray(raw[key])) return raw[key];
147194
- }
147195
- }
147196
- return void 0;
147197
- }
147198
- function extractUpdatedAt(raw) {
147199
- const time = isRecord7(raw.time) ? raw.time : void 0;
147200
- const candidates = [
147201
- time?.updated,
147202
- raw.updatedAt,
147203
- raw.updated,
147204
- time?.created,
147205
- raw.createdAt,
147206
- raw.created
147207
- ];
147208
- for (const candidate of candidates) {
147209
- const ms = toMillis2(candidate);
147210
- if (ms > 0) return ms;
147211
- }
147212
- return 0;
147213
- }
147214
- function extractDirectory(raw) {
147215
- if (isRecord7(raw.location)) {
147216
- const directory = str4(raw.location.directory);
147217
- if (directory) return directory;
147218
- }
147219
- return str4(raw.directory) || (typeof raw.location === "string" ? raw.location : "");
147220
- }
147221
- function pad2(value3) {
147222
- return value3 < 10 ? `0${value3}` : String(value3);
147223
- }
147224
- function isRecord7(value3) {
147225
- return typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
147226
- }
147227
- function str4(value3) {
147228
- return typeof value3 === "string" ? value3 : "";
147229
- }
147230
-
147231
148044
  // src/feishu/topic-guidance.ts
147232
148045
  var TOPIC_GUIDANCE_MARKER = "opencode-feishu-topic-guidance";
147233
148046
  function buildTopicGuidance(title) {
@@ -147274,8 +148087,14 @@ function decideEffect(action, config) {
147274
148087
  }
147275
148088
  return { effect: "ask", message: `opencode-feishu-v2: \u9700\u8981\u4EBA\u5DE5\u6279\u51C6 ${action}` };
147276
148089
  }
147277
- function decideEffectForSession(action, config, session) {
147278
- if (!session) return decideEffect(action, config);
148090
+ function decideEffectForSession(action, config, session, allowActions) {
148091
+ if (!session) {
148092
+ const base2 = decideEffect(action, config);
148093
+ if (base2.effect === "ask" && allowActions && allowActions.length > 0 && matchesAny(action, allowActions)) {
148094
+ return { effect: "allow" };
148095
+ }
148096
+ return base2;
148097
+ }
147279
148098
  if (session.gateMode === "off") return {};
147280
148099
  if (matchesAny(action, config.denyTools)) {
147281
148100
  return { effect: "deny", message: `feishu policy: ${action} \u5DF2\u5728 denyTools` };
@@ -147283,6 +148102,9 @@ function decideEffectForSession(action, config, session) {
147283
148102
  if (matchesAny(action, config.allowTools)) {
147284
148103
  return { effect: "allow" };
147285
148104
  }
148105
+ if (allowActions && allowActions.length > 0 && matchesAny(action, allowActions)) {
148106
+ return { effect: "allow" };
148107
+ }
147286
148108
  if (session.askActions && matchesAny(action, session.askActions)) {
147287
148109
  return { effect: "ask", message: `opencode-feishu-v2: \u4F1A\u8BDD\u9884\u8BBE\u9700\u4EBA\u5DE5\u6279\u51C6 ${action}` };
147288
148110
  }
@@ -147297,6 +148119,15 @@ function parseApprovalValue(rawValue) {
147297
148119
  if (d !== "once" && d !== "always" && d !== "reject") return void 0;
147298
148120
  return { token, decision: d };
147299
148121
  }
148122
+ function parseAllowSessionValue(rawValue) {
148123
+ if (typeof rawValue !== "object" || rawValue === null) return void 0;
148124
+ const record2 = rawValue;
148125
+ if (record2.cmd !== "allow_session") return void 0;
148126
+ const action = typeof record2.a === "string" ? record2.a : "";
148127
+ const token = typeof record2.t === "string" ? record2.t : "";
148128
+ if (!action || !token) return void 0;
148129
+ return { action, token };
148130
+ }
147300
148131
  var ApprovalManager = class {
147301
148132
  constructor(deps) {
147302
148133
  this.deps = deps;
@@ -147324,6 +148155,11 @@ var ApprovalManager = class {
147324
148155
  sessionID: request3.sessionID,
147325
148156
  openId: link2.openId
147326
148157
  });
148158
+ const allowSessionToken = this.deps.sessionAllowButton !== false && this.deps.signAllowSession ? this.deps.signAllowSession({
148159
+ requestID,
148160
+ sessionID: request3.sessionID,
148161
+ action: request3.action
148162
+ }) : void 0;
147327
148163
  const input = {
147328
148164
  requestID,
147329
148165
  sessionID: request3.sessionID,
@@ -147332,6 +148168,7 @@ var ApprovalManager = class {
147332
148168
  ...request3.message ? { message: request3.message } : {},
147333
148169
  canPersistAlways,
147334
148170
  token,
148171
+ ...allowSessionToken ? { allowSessionToken } : {},
147335
148172
  maxResourcesShown: this.deps.config.maxResourcesShown
147336
148173
  };
147337
148174
  const result3 = link2.replyMessageId ? await this.deps.sender.replyCard(link2.replyMessageId, buildApprovalCard(input)) : await this.deps.sender.sendCard(link2.chatId, buildApprovalCard(input));
@@ -147371,6 +148208,8 @@ var ApprovalManager = class {
147371
148208
  * 校验顺序:白名单 → 签名 → 绑定字段 → 防重放。
147372
148209
  */
147373
148210
  handleCardAction(action) {
148211
+ const allowValue = parseAllowSessionValue(action.rawValue);
148212
+ if (allowValue) return this.handleAllowSession(action, allowValue);
147374
148213
  const parsed = parseApprovalValue(action.rawValue);
147375
148214
  if (!parsed) {
147376
148215
  return toast2("error", "\u65E0\u6CD5\u8BC6\u522B\u7684\u64CD\u4F5C");
@@ -147407,6 +148246,89 @@ var ApprovalManager = class {
147407
148246
  this.seenRequests.clear();
147408
148247
  this.cards.clear();
147409
148248
  }
148249
+ /**
148250
+ * 任务 A:「✅ 本会话内允许该工具」。
148251
+ *
148252
+ * 校验顺序严格按:**白名单 → 验签 → sessionID 匹配 → nonce 消费**。
148253
+ * 命中后:后台持久化 `allowActions` + 追加会话级 ruleset,并对当前挂起的请求回 `once`
148254
+ * (否则本次请求仍会卡住),最后把审批卡 patch 成「✅ 已允许本会话内 <action>」(无按钮)。
148255
+ * 重复点击 / 已生效只回 toast,不报错。
148256
+ */
148257
+ handleAllowSession(action, value3) {
148258
+ if (!this.deps.isAllowed(action.operatorOpenId)) {
148259
+ this.deps.log.warn("\u62D2\u7EDD\u975E\u767D\u540D\u5355\u7528\u6237\u7684\u4F1A\u8BDD\u653E\u884C\u70B9\u51FB", {
148260
+ operator: action.operatorOpenId.slice(0, 8)
148261
+ });
148262
+ return toast2("error", "\u65E0\u5BA1\u6279\u6743\u9650");
148263
+ }
148264
+ if (!this.deps.verifyAllowSession || !this.deps.allowSession) {
148265
+ return toast2("error", "\u672C\u4F1A\u8BDD\u5185\u5141\u8BB8\u6682\u4E0D\u53EF\u7528");
148266
+ }
148267
+ const verified = this.deps.verifyAllowSession(value3.token, { action: value3.action });
148268
+ if (!verified.ok) {
148269
+ this.deps.log.warn("\u4F1A\u8BDD\u653E\u884C token \u6821\u9A8C\u5931\u8D25", { reason: verified.reason });
148270
+ return toast2("error", `\u5BA1\u6279\u51ED\u8BC1\u65E0\u6548\uFF08${verified.reason}\uFF09`);
148271
+ }
148272
+ const claims = verified.claims;
148273
+ const tracked = this.cards.get(claims.r);
148274
+ if (tracked && tracked.input.sessionID !== claims.s) {
148275
+ this.deps.log.warn("\u4F1A\u8BDD\u653E\u884C sessionID \u4E0D\u5339\u914D", {
148276
+ tokenSession: claims.s,
148277
+ cardSession: tracked.input.sessionID
148278
+ });
148279
+ return toast2("error", "\u4F1A\u8BDD\u4E0D\u5339\u914D");
148280
+ }
148281
+ const ttl = Math.max(1e3, claims.e - this.now());
148282
+ if (!this.deps.replay.consume(claims.n, ttl)) {
148283
+ return toast2("warning", "\u8BE5\u64CD\u4F5C\u5DF2\u5904\u7406\uFF0C\u8BF7\u52FF\u91CD\u590D\u70B9\u51FB");
148284
+ }
148285
+ const already = this.deps.hasSessionAllow?.({ sessionID: claims.s, action: claims.a }) ?? false;
148286
+ if (tracked) tracked.resolved = true;
148287
+ void this.applyAllowSession(claims, tracked, action).catch(
148288
+ (err2) => this.deps.log.error("\u4F1A\u8BDD\u653E\u884C\u5904\u7406\u5931\u8D25", {
148289
+ sessionID: claims.s,
148290
+ action: claims.a,
148291
+ error: errorMessage(err2)
148292
+ })
148293
+ );
148294
+ return already ? toast2("info", "\u8BE5\u5DE5\u5177\u5DF2\u5728\u672C\u4F1A\u8BDD\u5185\u5141\u8BB8") : toast2("success", `\u5DF2\u5141\u8BB8\u672C\u4F1A\u8BDD\u5185 ${claims.a}`);
148295
+ }
148296
+ async applyAllowSession(claims, tracked, action) {
148297
+ try {
148298
+ await this.deps.allowSession({ sessionID: claims.s, action: claims.a });
148299
+ } catch (err2) {
148300
+ this.deps.log.error("\u4F1A\u8BDD\u5185\u653E\u884C\u5199\u5165\u5931\u8D25", {
148301
+ sessionID: claims.s,
148302
+ action: claims.a,
148303
+ error: errorMessage(err2)
148304
+ });
148305
+ }
148306
+ const directory = tracked?.directory ?? (await this.deps.getLink(claims.s))?.dir;
148307
+ try {
148308
+ await this.deps.reply({
148309
+ sessionID: claims.s,
148310
+ requestID: claims.r,
148311
+ reply: "once",
148312
+ ...directory ? { directory } : {}
148313
+ });
148314
+ } catch (err2) {
148315
+ this.deps.log.error("permission.reply \u5931\u8D25", {
148316
+ requestID: claims.r,
148317
+ error: errorMessage(err2)
148318
+ });
148319
+ }
148320
+ const messageId = tracked?.messageId;
148321
+ if (!messageId || !tracked) return;
148322
+ const card = buildSessionAllowResolvedCard(tracked.input, {
148323
+ action: claims.a,
148324
+ operatorOpenId: action.operatorOpenId,
148325
+ at: this.now()
148326
+ });
148327
+ const res = await this.deps.sender.patchCard(messageId, card);
148328
+ if (!res.ok) {
148329
+ this.deps.log.warn("\u4F1A\u8BDD\u653E\u884C\u7ED3\u679C\u5361\u7247\u66F4\u65B0\u5931\u8D25", { requestID: claims.r, error: res.error ?? "unknown" });
148330
+ }
148331
+ }
147410
148332
  async applyReply(requestID, sessionID, reply, tracked, outcome, directory) {
147411
148333
  try {
147412
148334
  await this.deps.reply({ sessionID, requestID, reply, ...directory ? { directory } : {} });
@@ -147431,177 +148353,6 @@ var ApprovalManager = class {
147431
148353
  function toast2(type, content) {
147432
148354
  return { toast: { type, content } };
147433
148355
  }
147434
-
147435
- // src/feishu/session-cards.ts
147436
- function button2(text, type, value3) {
147437
- return {
147438
- tag: "button",
147439
- text: { tag: "plain_text", content: text },
147440
- type,
147441
- behaviors: [{ type: "callback", value: value3 }]
147442
- };
147443
- }
147444
- function sessionRowLine(row, now3) {
147445
- const title = truncateTitle(row.title.trim() || "(\u672A\u547D\u540D)");
147446
- const marks = [relativeTime(row.updatedAt, now3)];
147447
- if (row.bound) marks.push("\u{1F4AC} \u5DF2\u7ED1\u8BDD\u9898");
147448
- if (row.directory) marks.push(`\u{1F4CD} ${directoryTail(row.directory)}`);
147449
- if (row.active) marks.push("\u2190 \u5F53\u524D");
147450
- return `${row.index}. ${title}\uFF08\`${shortSessionId(row.sessionID)}\`\uFF09\xB7 ${marks.join(" \xB7 ")}`;
147451
- }
147452
- function buildSessionListCard(input) {
147453
- const now3 = input.now ?? Date.now();
147454
- const elements = [];
147455
- if (input.rows.length === 0) {
147456
- elements.push({
147457
- tag: "markdown",
147458
- content: truncateCardContent(
147459
- "\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u3002\u70B9\u4E0B\u65B9\u300C\u2795 \u65B0\u5EFA\u4F1A\u8BDD\u300D\u521B\u5EFA\uFF0C\u6216\u76F4\u63A5\u5728\u8BDD\u9898\u91CC\u53D1\u6D88\u606F\u81EA\u52A8\u521B\u5EFA\u3002"
147460
- )
147461
- });
147462
- } else {
147463
- const lines = input.rows.map((row) => sessionRowLine(row, now3)).join("\n");
147464
- elements.push({ tag: "markdown", content: truncateCardContent(lines) });
147465
- for (const row of input.rows) {
147466
- elements.push(
147467
- button2(row.bound ? "\u25B6\uFE0F \u518D\u5F00\u8BDD\u9898" : "\u25B6\uFE0F \u8FDB\u5165\u8BDD\u9898", row.active ? "primary" : "default", {
147468
- cmd: "open",
147469
- s: row.sessionID,
147470
- c: input.chatId
147471
- })
147472
- );
147473
- }
147474
- }
147475
- if (input.page > 0) {
147476
- elements.push(button2("\u2B05\uFE0F \u4E0A\u4E00\u9875", "default", { cmd: "list", p: input.page - 1, c: input.chatId }));
147477
- }
147478
- if (input.page + 1 < input.pageCount) {
147479
- elements.push(button2("\u27A1\uFE0F \u4E0B\u4E00\u9875", "default", { cmd: "list", p: input.page + 1, c: input.chatId }));
147480
- }
147481
- elements.push(button2("\u2795 \u65B0\u5EFA\u4F1A\u8BDD", "default", { cmd: "new", c: input.chatId }));
147482
- if (input.total > 0) {
147483
- elements.push({
147484
- tag: "markdown",
147485
- content: `\u7B2C ${input.page + 1}/${input.pageCount} \u9875 \xB7 \u5171 ${input.total} \u4E2A\u4F1A\u8BDD`,
147486
- text_size: "notation"
147487
- });
147488
- }
147489
- const template2 = "blue";
147490
- return {
147491
- schema: "2.0",
147492
- config: { update_multi: true },
147493
- header: {
147494
- title: { tag: "plain_text", content: input.title ?? "\u{1F9E9} OpenCode \u4F1A\u8BDD\uFF08\u5168\u90E8\uFF09" },
147495
- template: template2
147496
- },
147497
- body: { elements }
147498
- };
147499
- }
147500
- function parseSessionCardValue(raw) {
147501
- if (typeof raw !== "object" || raw === null) return void 0;
147502
- const record2 = raw;
147503
- const chatId = typeof record2.c === "string" ? record2.c : "";
147504
- if (record2.cmd === "new") return { cmd: "new", chatId };
147505
- if (record2.cmd === "use" || record2.cmd === "open") {
147506
- const sessionID = typeof record2.s === "string" ? record2.s : "";
147507
- if (!sessionID) return void 0;
147508
- return record2.cmd === "open" ? { cmd: "open", sessionID, chatId } : { cmd: "use", sessionID, chatId };
147509
- }
147510
- if (record2.cmd === "list") {
147511
- const page = parsePage(record2.p);
147512
- return { cmd: "list", page, chatId };
147513
- }
147514
- return void 0;
147515
- }
147516
- function buildSessionOpenedCard(input) {
147517
- const title = input.title.trim() || "(\u672A\u547D\u540D)";
147518
- const lines = [`\u4F1A\u8BDD\u300C${title}\u300D\uFF1A\`${input.sessionID}\``];
147519
- const setup = [];
147520
- if (input.dir) setup.push(`- \u76EE\u5F55\uFF1A\`${input.dir}\``);
147521
- if (input.updatedAt && input.updatedAt > 0) {
147522
- setup.push(`- \u6700\u8FD1\u6D3B\u52A8\uFF1A${relativeTime(input.updatedAt, input.now ?? Date.now())}`);
147523
- }
147524
- if (setup.length > 0) lines.push("", ...setup);
147525
- lines.push(
147526
- "",
147527
- "**\u5728\u672C\u8BDD\u9898\u5185\u76F4\u63A5\u53D1\u6D88\u606F**\uFF0COpenCode \u5C31\u63A5\u7740\u8FD9\u4E2A\u5386\u53F2\u4F1A\u8BDD\u7EE7\u7EED\u5E72\u6D3B\u3002",
147528
- "",
147529
- "\u8BDD\u9898\u5185\u53EF\u7528\uFF1A`/current` `/stop` `/model` `/perm` `/cd` `/help`\u3002",
147530
- "\u4F1A\u8BDD\u7BA1\u7406\uFF08`/new` `/sessions` `/resume`\uFF09\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u3002"
147531
- );
147532
- return {
147533
- schema: "2.0",
147534
- config: { update_multi: true },
147535
- header: { title: { tag: "plain_text", content: `\u2705 \u5DF2\u8FDB\u5165\u4F1A\u8BDD \xB7 ${title}` }, template: "green" },
147536
- body: {
147537
- elements: [{ tag: "markdown", content: truncateCardContent(lines.join("\n")) }]
147538
- }
147539
- };
147540
- }
147541
- function buildSessionMissingCard(sessionID, reason) {
147542
- const lines = [
147543
- `\u4F1A\u8BDD \`${sessionID}\` \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u7528\uFF08\u53EF\u80FD\u5DF2\u88AB\u5220\u9664\uFF0C\u6216\u4E0D\u5C5E\u4E8E\u672C\u673A\u53EF\u89C1\u8303\u56F4\uFF09\u3002`,
147544
- "",
147545
- "\u53D1\u9001 `/sessions` \u91CD\u65B0\u83B7\u53D6\u5217\u8868\u3002"
147546
- ];
147547
- if (reason) lines.splice(1, 0, "", `\u539F\u56E0\uFF1A${reason}`);
147548
- return {
147549
- schema: "2.0",
147550
- config: { update_multi: true },
147551
- header: { title: { tag: "plain_text", content: "\u26A0\uFE0F \u4F1A\u8BDD\u4E0D\u5B58\u5728" }, template: "orange" },
147552
- body: { elements: [{ tag: "markdown", content: truncateCardContent(lines.join("\n")) }] }
147553
- };
147554
- }
147555
- function buildSessionReadyCard(input) {
147556
- const title = input.title.trim() || "(\u672A\u547D\u540D)";
147557
- const lines = [`\u4F1A\u8BDD\u300C${title}\u300D\u5DF2\u521B\u5EFA\uFF1A\`${input.sessionID}\``];
147558
- const setup = [];
147559
- if (input.dir) setup.push(`- \u76EE\u5F55\uFF1A\`${input.dir}\``);
147560
- if (input.model) setup.push(`- \u6A21\u578B\uFF1A${input.model}`);
147561
- if (input.perm) setup.push(`- \u6743\u9650\uFF1A${input.perm}`);
147562
- if (setup.length > 0) lines.push("", ...setup);
147563
- lines.push(
147564
- "",
147565
- "**\u5728\u672C\u8BDD\u9898\u5185\u76F4\u63A5\u53D1\u6D88\u606F**\uFF0COpenCode \u5C31\u5728\u8FD9\u4E2A\u4F1A\u8BDD\u91CC\u5E72\u6D3B\u3002",
147566
- "",
147567
- "\u8BDD\u9898\u5185\u53EF\u7528\uFF1A`/current` `/stop` `/model` `/perm` `/cd` `/help`\u3002",
147568
- "\u4F1A\u8BDD\u7BA1\u7406\uFF08`/new` `/sessions` `/use`\uFF09\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u3002"
147569
- );
147570
- return {
147571
- schema: "2.0",
147572
- config: { update_multi: true },
147573
- header: { title: { tag: "plain_text", content: "\u2705 \u4F1A\u8BDD\u5DF2\u5C31\u7EEA" }, template: "green" },
147574
- body: {
147575
- elements: [{ tag: "markdown", content: truncateCardContent(lines.join("\n")) }]
147576
- }
147577
- };
147578
- }
147579
- function buildSessionCreatedCard(input) {
147580
- const title = input.title.trim() || "(\u672A\u547D\u540D)";
147581
- const lines = [`\u4F1A\u8BDD\u300C${title}\u300D\u5DF2\u521B\u5EFA\uFF1A\`${input.sessionID}\``];
147582
- const setup = [];
147583
- if (input.dir) setup.push(`- \u76EE\u5F55\uFF1A\`${input.dir}\``);
147584
- if (input.model) setup.push(`- \u6A21\u578B\uFF1A${input.model}`);
147585
- if (input.perm) setup.push(`- \u6743\u9650\uFF1A${input.perm}`);
147586
- if (setup.length > 0) lines.push("", ...setup);
147587
- lines.push("", "\u70B9\u8FDB\u672C\u8BDD\u9898\u76F4\u63A5\u53D1\u6D88\u606F\u5373\u53EF\uFF0COpenCode \u5C31\u5728\u8FD9\u4E2A\u4F1A\u8BDD\u91CC\u5E72\u6D3B\u3002");
147588
- if (input.note) lines.push("", input.note);
147589
- return {
147590
- schema: "2.0",
147591
- config: { update_multi: true },
147592
- header: { title: { tag: "plain_text", content: `\u2705 \u5DF2\u521B\u5EFA \xB7 ${title}` }, template: "green" },
147593
- body: {
147594
- elements: [{ tag: "markdown", content: truncateCardContent(lines.join("\n")) }]
147595
- }
147596
- };
147597
- }
147598
- function truncateTitle(title, max6 = 30) {
147599
- return title.length > max6 ? `${title.slice(0, max6)}\u2026` : title;
147600
- }
147601
- function parsePage(value3) {
147602
- const parsed = typeof value3 === "number" ? value3 : typeof value3 === "string" ? Number.parseInt(value3, 10) : Number.NaN;
147603
- return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
147604
- }
147605
148356
  var SETUP_FORM_CMD = "setup.form";
147606
148357
  var SETUP_FORM_NAME = "setup_form";
147607
148358
  var SETUP_FORM_FIELDS = {
@@ -148545,6 +149296,14 @@ async function createSessionFromSetup(ctx, chatId, action, opts) {
148545
149296
  } else {
148546
149297
  ctx.deps.log.warn("\u4E00\u952E\u5F00\u8BDD\u9898\u540E\u672A\u8BFB\u5230 thread_id\uFF0C\u8BE5\u4F1A\u8BDD\u6682\u65E0\u6CD5\u81EA\u52A8\u8DEF\u7531", { messageId: res.messageId });
148547
149298
  }
149299
+ await ctx.deps.sessionMap.setRootCard(created.id, {
149300
+ style: "created",
149301
+ sessionID: created.id,
149302
+ title,
149303
+ dir,
149304
+ ...model ? { model: modelLabel(model) } : {},
149305
+ perm: presetLabel(perm)
149306
+ });
148548
149307
  await ctx.patchCard(
148549
149308
  action.messageId,
148550
149309
  buildSessionCreatedCard({
@@ -148674,26 +149433,38 @@ async function enterSessionThread(ctx, input) {
148674
149433
  return { ok: false, error: "missing anchor/chat" };
148675
149434
  }
148676
149435
  const info = input.info;
148677
- const card = buildSessionOpenedCard({
149436
+ const link2 = await ctx.deps.sessionMap.resolveBySession(sessionID);
149437
+ const dir = info?.directory ?? link2?.dir;
149438
+ const showSummary = (ctx.deps.resumeSummary ?? true) && Boolean(ctx.deps.summarizeSession);
149439
+ const onLimit = (report) => {
149440
+ ctx.deps.log.warn("\u4F1A\u8BDD\u6062\u590D\u5361\u5185\u5BB9\u8D85\u9650\uFF0C\u5DF2\u964D\u7EA7", { sessionID, ...report });
149441
+ };
149442
+ const cardInput = {
148678
149443
  title: info?.title ?? "",
148679
149444
  sessionID,
148680
- ...info?.directory ? { dir: info.directory } : {},
149445
+ ...dir ? { dir } : {},
149446
+ ...link2?.model ? { model: modelLabel(link2.model) } : {},
148681
149447
  ...info?.updatedAt ? { updatedAt: info.updatedAt } : {},
148682
- now: ctx.now()
148683
- });
148684
- const res = await ctx.deps.sender.replyCard(anchorMessageId, card, { replyInThread: true });
148685
- ctx.deps.log.info("\u8FDB\u5165\u4F1A\u8BDD\u5E76\u5F00\u8BDD\u9898", {
149448
+ now: ctx.now(),
149449
+ ...ctx.deps.cardMaxTables !== void 0 ? { maxTables: ctx.deps.cardMaxTables } : {},
149450
+ onLimit,
149451
+ ...showSummary ? { summaryPending: true } : {},
149452
+ // 「🗜 压缩并总结」只在开关开启且运行时装配了签名时渲染。
149453
+ ...showSummary && ctx.deps.signCompact ? { compactButton: { sessionID, token: ctx.deps.signCompact(sessionID) } } : {}
149454
+ };
149455
+ const card = buildSessionOpenedCard(cardInput);
149456
+ const res = await ctx.deps.sender.sendCard(chatId, card);
149457
+ ctx.deps.log.info("\u53D1\u9001\u4F1A\u8BDD\u6062\u590D\u5361", {
148686
149458
  sessionID,
148687
149459
  source: input.source,
148688
149460
  anchorMessageId,
148689
- replyOk: res.ok,
148690
- replyMessageId: res.messageId,
148691
- replyThreadId: res.threadId,
148692
- replyError: res.error
149461
+ sendOk: res.ok,
149462
+ cardMessageId: res.messageId,
149463
+ sendError: res.error
148693
149464
  });
148694
149465
  if (!res.ok || !res.messageId) {
148695
149466
  const error = res.error ?? "unknown";
148696
- ctx.deps.log.warn("\u8FDB\u5165\u8BDD\u9898\u5931\u8D25", { sessionID, anchorMessageId, error });
149467
+ ctx.deps.log.warn("\u53D1\u9001\u6062\u590D\u5361\u5931\u8D25", { sessionID, anchorMessageId, error });
148697
149468
  if (input.patchMessageId) {
148698
149469
  await ctx.deps.sender.patchCard(
148699
149470
  input.patchMessageId,
@@ -148702,16 +149473,54 @@ async function enterSessionThread(ctx, input) {
148702
149473
  }
148703
149474
  return { ok: false, error };
148704
149475
  }
148705
- const meta = res.threadId ? void 0 : await ctx.deps.sender.getMessageMeta(res.messageId);
148706
- const threadId = res.threadId ?? meta?.threadId;
148707
- if (!threadId) {
148708
- ctx.deps.log.warn("\u8FDB\u5165\u8BDD\u9898\u540E\u672A\u8BFB\u5230 thread_id\uFF0C\u8BE5\u4F1A\u8BDD\u6682\u65E0\u6CD5\u81EA\u52A8\u8DEF\u7531", { messageId: res.messageId });
148709
- return { ok: false, error: "\u672A\u62FF\u5230\u8BDD\u9898 ID" };
148710
- }
148711
- await ctx.deps.sessionMap.bindThread(threadId, sessionID, chatId, operatorOpenId, anchorMessageId);
148712
149476
  await ctx.deps.sessionMap.bindRoot(res.messageId, sessionID);
148713
- ctx.deps.log.info("\u5DF2\u7ED1\u5B9A\u8BDD\u9898\u4E0E\u4F1A\u8BDD", { sessionID, threadId, rootId: res.messageId });
148714
- return { ok: true, threadId };
149477
+ ctx.deps.log.info("resume card bound root to session", { sessionID, rootId: res.messageId });
149478
+ await ctx.deps.sessionMap.setRootCard(sessionID, {
149479
+ style: "resumed",
149480
+ sessionID,
149481
+ title: info?.title ?? "",
149482
+ ...dir ? { dir } : {},
149483
+ ...link2?.model ? { model: modelLabel(link2.model) } : {},
149484
+ ...info?.updatedAt ? { updatedAt: info.updatedAt } : {},
149485
+ ...showSummary ? { summaryPending: true, compactButton: Boolean(cardInput.compactButton) } : {}
149486
+ });
149487
+ if (showSummary) {
149488
+ void patchResumeSummary(ctx, sessionID, dir, res.messageId, cardInput);
149489
+ }
149490
+ return { ok: true, messageId: res.messageId };
149491
+ }
149492
+ var SUMMARY_LABEL = {
149493
+ reused: "\u4F1A\u8BDD\u6458\u8981",
149494
+ generated: "\u6458\u8981\uFF08\u5FEB\u6458\u8981\uFF09",
149495
+ compacted: "\u5DF2\u538B\u7F29 \xB7 \u4F1A\u8BDD\u6458\u8981"
149496
+ };
149497
+ async function patchResumeSummary(ctx, sessionID, dir, messageId, cardInput) {
149498
+ try {
149499
+ const outcome = await ctx.deps.summarizeSession({
149500
+ sessionID,
149501
+ ...dir ? { directory: dir } : {},
149502
+ timeoutMs: ctx.deps.resumeSummaryTimeoutMs ?? 15e3
149503
+ });
149504
+ const summary = outcome.summary ?? "\uFF08\u6458\u8981\u751F\u6210\u5931\u8D25\uFF0C\u53EF\u76F4\u63A5\u53D1\u6D88\u606F\u7EE7\u7EED\uFF09";
149505
+ const summaryLabel = SUMMARY_LABEL[outcome.source] ?? "\u6458\u8981";
149506
+ const base2 = await ctx.deps.sessionMap.getRootCard(sessionID);
149507
+ if (base2) {
149508
+ await ctx.deps.sessionMap.setRootCard(sessionID, { ...base2, summaryPending: false, summary, summaryLabel });
149509
+ }
149510
+ const res = await ctx.deps.sender.patchCard(
149511
+ messageId,
149512
+ buildSessionOpenedCard({
149513
+ ...cardInput,
149514
+ summaryPending: false,
149515
+ summary,
149516
+ summaryLabel
149517
+ })
149518
+ );
149519
+ if (!res.ok) ctx.deps.log.warn("\u6062\u590D\u5361\u6458\u8981\u66F4\u65B0\u5931\u8D25", { sessionID, error: res.error ?? "unknown" });
149520
+ ctx.deps.log.info("\u6062\u590D\u5361\u6458\u8981\u5DF2\u66F4\u65B0", { sessionID, source: outcome.source, hasSummary: Boolean(outcome.summary) });
149521
+ } catch (err2) {
149522
+ ctx.deps.log.warn("\u6062\u590D\u5361\u6458\u8981\u751F\u6210\u5F02\u5E38", { sessionID, error: errorMessage(err2) });
149523
+ }
148715
149524
  }
148716
149525
  async function handleOpenCardAction(ctx, action, value3) {
148717
149526
  if (!ctx.threadRouting) {
@@ -148746,6 +149555,7 @@ async function patchMissingCard(ctx, messageId, sessionID) {
148746
149555
  await ctx.deps.sender.patchCard(messageId, buildSessionMissingCard(sessionID));
148747
149556
  }
148748
149557
  async function applySessionCardAction(ctx, action, value3) {
149558
+ if (value3.cmd === "compact") return;
148749
149559
  const chatId = action.chatId || value3.chatId;
148750
149560
  if (!chatId) return;
148751
149561
  if (value3.cmd === "new") {
@@ -149111,6 +149921,7 @@ function setupToast(value3) {
149111
149921
 
149112
149922
  // src/runtime/event-router.ts
149113
149923
  async function routeEvent(event, deps) {
149924
+ deps.onTopicStatus?.(event);
149114
149925
  const touched = event.data?.sessionID;
149115
149926
  if (typeof touched === "string") deps.touch(touched);
149116
149927
  switch (event.type) {
@@ -149260,6 +150071,256 @@ function contentToText(content) {
149260
150071
  return "";
149261
150072
  }
149262
150073
 
150074
+ // src/session/resume-summary.ts
150075
+ var RESUME_SUMMARY_PROMPT = "\u7528\u4E0D\u8D85\u8FC7 5 \u6761\u8981\u70B9\u603B\u7ED3\u672C\u4F1A\u8BDD\uFF1A\u76EE\u6807\u3001\u5DF2\u5B8C\u6210\u3001\u5F53\u524D\u72B6\u6001/\u963B\u585E\u3001\u4E0B\u4E00\u6B65\u3002\u53EA\u8F93\u51FA\u8981\u70B9\uFF0C\u4E0D\u8981\u5C55\u5F00\u3002";
150076
+ var TRANSCRIPT_LIMIT = 6e3;
150077
+ var TRANSCRIPT_LINE_LIMIT = 600;
150078
+ var SummaryTimeoutError = class extends Error {
150079
+ constructor() {
150080
+ super("summary-timeout");
150081
+ this.name = "SummaryTimeoutError";
150082
+ }
150083
+ };
150084
+ function withTimeout(promise3, ms) {
150085
+ return new Promise((resolve3, reject) => {
150086
+ const timer = setTimeout(() => reject(new SummaryTimeoutError()), Math.max(1, ms));
150087
+ timer.unref?.();
150088
+ promise3.then(
150089
+ (value3) => {
150090
+ clearTimeout(timer);
150091
+ resolve3(value3);
150092
+ },
150093
+ (err2) => {
150094
+ clearTimeout(timer);
150095
+ reject(err2);
150096
+ }
150097
+ );
150098
+ });
150099
+ }
150100
+ function extractLatestSummary(raw) {
150101
+ const list = extractArray2(raw);
150102
+ if (!list) return void 0;
150103
+ for (let i = list.length - 1; i >= 0; i -= 1) {
150104
+ const item = list[i];
150105
+ if (!isRecord8(item)) continue;
150106
+ if (item.type !== "compaction") continue;
150107
+ if (item.status !== "completed") continue;
150108
+ const summary = typeof item.summary === "string" ? item.summary.trim() : "";
150109
+ if (summary) return summary;
150110
+ }
150111
+ return void 0;
150112
+ }
150113
+ function buildTranscript(raw, limit = TRANSCRIPT_LIMIT, lineLimit = TRANSCRIPT_LINE_LIMIT) {
150114
+ const list = extractArray2(raw);
150115
+ if (!list || list.length === 0) return void 0;
150116
+ const lines = [];
150117
+ for (const item of list) {
150118
+ if (!isRecord8(item)) continue;
150119
+ if (item.type === "user" && typeof item.text === "string") {
150120
+ const text = clip(item.text.trim(), lineLimit);
150121
+ if (text) lines.push(`\u7528\u6237\uFF1A${text}`);
150122
+ } else if (item.type === "assistant" && Array.isArray(item.content)) {
150123
+ const text = clip(
150124
+ item.content.filter((part) => isRecord8(part) && part.type === "text").map((part) => typeof part.text === "string" ? part.text.trim() : "").filter(Boolean).join("\n"),
150125
+ lineLimit
150126
+ );
150127
+ if (text) lines.push(`\u52A9\u624B\uFF1A${text}`);
150128
+ }
150129
+ }
150130
+ if (lines.length === 0) return void 0;
150131
+ const recent = [];
150132
+ let bytes = 0;
150133
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
150134
+ const line = lines[i];
150135
+ const size6 = Buffer.byteLength(line, "utf8");
150136
+ if (bytes + size6 > limit && recent.length > 0) break;
150137
+ recent.unshift(line);
150138
+ bytes += size6;
150139
+ }
150140
+ return recent.join("\n").slice(0, limit);
150141
+ }
150142
+ function buildSummaryPrompt(transcript) {
150143
+ return transcript ? `${RESUME_SUMMARY_PROMPT}
150144
+
150145
+ \u4F1A\u8BDD\u6700\u8FD1\u8BB0\u5F55\uFF1A
150146
+ ${transcript}` : RESUME_SUMMARY_PROMPT;
150147
+ }
150148
+ async function summarizeSession(deps, input) {
150149
+ const timeoutMs = Math.max(1, Math.floor(input.timeoutMs));
150150
+ let messages;
150151
+ if (deps.readMessages) {
150152
+ try {
150153
+ messages = await withTimeout(deps.readMessages(input.sessionID, input.directory), timeoutMs);
150154
+ const reused = extractLatestSummary(messages);
150155
+ if (reused) return { summary: reused, source: "reused" };
150156
+ } catch (err2) {
150157
+ deps.log.debug("\u8BFB\u53D6\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25\uFF0C\u7EE7\u7EED\u5C1D\u8BD5\u5FEB\u6458\u8981", {
150158
+ sessionID: input.sessionID,
150159
+ error: errorMessage(err2)
150160
+ });
150161
+ }
150162
+ }
150163
+ if (!deps.generateText) return { source: "none" };
150164
+ try {
150165
+ const transcript = buildTranscript(messages);
150166
+ const raw = await withTimeout(
150167
+ deps.generateText(buildSummaryPrompt(transcript), input.directory),
150168
+ timeoutMs
150169
+ );
150170
+ const generated = extractGeneratedText(raw);
150171
+ if (generated) return { summary: generated, source: "generated" };
150172
+ return { source: "none", error: "empty-summary" };
150173
+ } catch (err2) {
150174
+ const error = errorMessage(err2);
150175
+ deps.log.warn("\u751F\u6210\u4F1A\u8BDD\u6458\u8981\u5931\u8D25", {
150176
+ sessionID: input.sessionID,
150177
+ timedOut: err2 instanceof SummaryTimeoutError,
150178
+ error
150179
+ });
150180
+ return { source: "none", error };
150181
+ }
150182
+ }
150183
+ function extractGeneratedText(raw) {
150184
+ if (typeof raw === "string") return raw.trim() || void 0;
150185
+ if (!isRecord8(raw)) return void 0;
150186
+ if (typeof raw.text === "string" && raw.text.trim()) return raw.text.trim();
150187
+ const data = raw.data;
150188
+ if (isRecord8(data) && typeof data.text === "string" && data.text.trim()) return data.text.trim();
150189
+ return void 0;
150190
+ }
150191
+ function clip(text, limit) {
150192
+ if (!text) return "";
150193
+ return text.length > limit ? text.slice(0, limit) : text;
150194
+ }
150195
+ function extractArray2(raw) {
150196
+ if (Array.isArray(raw)) return raw;
150197
+ if (isRecord8(raw)) {
150198
+ for (const key of ["data", "messages", "items"]) {
150199
+ if (Array.isArray(raw[key])) return raw[key];
150200
+ }
150201
+ }
150202
+ return void 0;
150203
+ }
150204
+ function isRecord8(value3) {
150205
+ return typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
150206
+ }
150207
+
150208
+ // src/session/compact.ts
150209
+ var COMPACT_CMD = "compact";
150210
+ function parseCompactActionValue(raw) {
150211
+ if (typeof raw !== "object" || raw === null) return void 0;
150212
+ const record2 = raw;
150213
+ if (record2.cmd !== COMPACT_CMD) return void 0;
150214
+ const s = typeof record2.s === "string" ? record2.s : "";
150215
+ const t = typeof record2.t === "string" ? record2.t : "";
150216
+ if (!s || !t) return void 0;
150217
+ return { cmd: COMPACT_CMD, s, t };
150218
+ }
150219
+ var CompactController = class {
150220
+ constructor(deps) {
150221
+ this.deps = deps;
150222
+ this.now = deps.now ?? (() => Date.now());
150223
+ this.sleep = deps.sleep ?? ((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)));
150224
+ }
150225
+ deps;
150226
+ now;
150227
+ sleep;
150228
+ /**
150229
+ * 处理卡片点击:同步返回 toast(3 秒窗口),压缩 + 轮询在后台完成。
150230
+ * 校验顺序:白名单 → 验签 → 绑定 sessionID → 防重放。
150231
+ */
150232
+ handleCardAction(action) {
150233
+ const parsed = parseCompactActionValue(action.rawValue);
150234
+ if (!parsed) return toast4("error", "\u65E0\u6CD5\u8BC6\u522B\u7684\u64CD\u4F5C");
150235
+ if (!this.deps.isAllowed(action.operatorOpenId)) {
150236
+ this.deps.log.warn("\u62D2\u7EDD\u975E\u767D\u540D\u5355\u7528\u6237\u7684\u538B\u7F29\u70B9\u51FB", { operator: action.operatorOpenId.slice(0, 8) });
150237
+ return toast4("error", "\u65E0\u64CD\u4F5C\u6743\u9650");
150238
+ }
150239
+ const verified = this.deps.verify(parsed.t, parsed.s);
150240
+ if (!verified.ok) {
150241
+ this.deps.log.warn("\u538B\u7F29 token \u6821\u9A8C\u5931\u8D25", { reason: verified.reason, sessionID: parsed.s });
150242
+ return toast4("error", `\u64CD\u4F5C\u51ED\u8BC1\u65E0\u6548\uFF08${verified.reason}\uFF09`);
150243
+ }
150244
+ const ttl = Math.max(1e3, verified.claims.e - this.now());
150245
+ if (!this.deps.replay.consume(verified.claims.n, ttl)) {
150246
+ return toast4("warning", "\u8BE5\u64CD\u4F5C\u5DF2\u5904\u7406\uFF0C\u8BF7\u52FF\u91CD\u590D\u70B9\u51FB");
150247
+ }
150248
+ const messageId = action.messageId;
150249
+ if (this.deps.patchPending && messageId) {
150250
+ void this.deps.patchPending(parsed.s, messageId, parsed.t).catch((err2) => this.deps.log.warn("\u538B\u7F29\u4E2D\u5361\u7247 patch \u5931\u8D25", { sessionID: parsed.s, error: errorMessage(err2) }));
150251
+ }
150252
+ void this.run(parsed.s, messageId).catch(
150253
+ (err2) => this.deps.log.warn("\u538B\u7F29\u4F1A\u8BDD\u5931\u8D25", { sessionID: parsed.s, error: errorMessage(err2) })
150254
+ );
150255
+ return toast4("success", "\u{1F5DC} \u6B63\u5728\u538B\u7F29\u4F1A\u8BDD\u2026");
150256
+ }
150257
+ /** 压缩 + 轮询到新摘要;失败/超时 patch 说明(best-effort)。 */
150258
+ async run(sessionID, messageId = "") {
150259
+ const baseline = this.baselineSummary(await this.readQuiet(sessionID));
150260
+ try {
150261
+ await this.deps.compact(sessionID);
150262
+ } catch (err2) {
150263
+ const error = errorMessage(err2);
150264
+ this.deps.log.warn("\u89E6\u53D1\u4F1A\u8BDD\u538B\u7F29\u5931\u8D25", { sessionID, error });
150265
+ await this.patchQuiet(sessionID, compactFailureText(error), "failed", messageId);
150266
+ return { error };
150267
+ }
150268
+ const result3 = await this.poll(sessionID, baseline);
150269
+ if (result3.summary) {
150270
+ await this.patchQuiet(sessionID, `\u5DF2\u538B\u7F29 \xB7 \u4F1A\u8BDD\u6458\u8981\uFF1A
150271
+
150272
+ ${result3.summary}`, "completed", messageId);
150273
+ return result3;
150274
+ }
150275
+ await this.patchQuiet(sessionID, compactFailureText(result3.error ?? "timeout"), "failed", messageId);
150276
+ return result3;
150277
+ }
150278
+ /**
150279
+ * 轮询直到出现**新的** completed compaction 摘要。
150280
+ * `baseline` = 点击前的已有摘要,避免把旧摘要误当成新结果。
150281
+ */
150282
+ async poll(sessionID, baseline) {
150283
+ const interval = Math.max(50, this.deps.pollIntervalMs ?? 2e3);
150284
+ const deadline = this.now() + Math.max(interval, this.deps.timeoutMs ?? 12e4);
150285
+ for (; ; ) {
150286
+ const summary = extractLatestSummary(await this.readQuiet(sessionID));
150287
+ if (summary && summary !== baseline) return { summary };
150288
+ if (this.now() >= deadline) return { error: "timeout" };
150289
+ await this.sleep(interval);
150290
+ }
150291
+ }
150292
+ /** 读一次当前已完成摘要(失败返回 undefined,不阻断)。 */
150293
+ baselineSummary(raw) {
150294
+ try {
150295
+ return extractLatestSummary(raw);
150296
+ } catch {
150297
+ return void 0;
150298
+ }
150299
+ }
150300
+ async readQuiet(sessionID) {
150301
+ try {
150302
+ return await this.deps.readMessages(sessionID);
150303
+ } catch (err2) {
150304
+ this.deps.log.debug("\u538B\u7F29\u8F6E\u8BE2\u8BFB\u53D6\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25", { sessionID, error: errorMessage(err2) });
150305
+ return void 0;
150306
+ }
150307
+ }
150308
+ async patchQuiet(sessionID, text, kind, messageId) {
150309
+ try {
150310
+ await this.deps.patch(sessionID, text, kind, messageId);
150311
+ } catch (err2) {
150312
+ this.deps.log.warn("\u538B\u7F29\u7ED3\u679C patch \u5931\u8D25", { sessionID, error: errorMessage(err2) });
150313
+ }
150314
+ }
150315
+ };
150316
+ function compactFailureText(error) {
150317
+ const reason = error === "timeout" ? "\u8D85\u65F6\uFF08\u538B\u7F29\u4ECD\u5728\u540E\u53F0\u8FDB\u884C\uFF09" : `\u5931\u8D25\uFF1A${error}`;
150318
+ return `\u26A0\uFE0F \u538B\u7F29\u4F1A\u8BDD${reason}\u3002\u53EF\u7EE7\u7EED\u5728\u672C\u8BDD\u9898/\u5361\u7247\u4E0B\u53D1\u6D88\u606F\uFF1B\u7A0D\u540E\u5982\u9700\u6458\u8981\u53EF\u518D\u70B9\u4E00\u6B21\u300C\u{1F5DC} \u538B\u7F29\u5E76\u603B\u7ED3\u300D\u3002`;
150319
+ }
150320
+ function toast4(type, content) {
150321
+ return { toast: { type, content } };
150322
+ }
150323
+
149263
150324
  // src/runtime/card-action-router.ts
149264
150325
  function routeCardAction(action, deps) {
149265
150326
  const formResponse = deps.handleForm(action);
@@ -149268,6 +150329,9 @@ function routeCardAction(action, deps) {
149268
150329
  if (parseStopActionValue(value3)) {
149269
150330
  return deps.handleStop(action);
149270
150331
  }
150332
+ if (parseCompactActionValue(value3)) {
150333
+ return deps.handleCompact(action);
150334
+ }
149271
150335
  const hasForm = action.formValue !== void 0;
149272
150336
  const routed = hasForm || isSetupFormAction(value3) || Boolean(parseSessionCardValue(value3)) || Boolean(parseSetupCardValue(value3));
149273
150337
  deps.log.debug("\u5361\u7247\u56DE\u8C03\u8DEF\u7531", {
@@ -149282,6 +150346,237 @@ function routeCardAction(action, deps) {
149282
150346
  return deps.handleApprovals(action);
149283
150347
  }
149284
150348
 
150349
+ // src/runtime/topic-status.ts
150350
+ var MAX_CONSECUTIVE_FAILURES = 3;
150351
+ function createTopicStatusController(deps) {
150352
+ const log2 = deps.log;
150353
+ const now3 = deps.now ?? (() => Date.now());
150354
+ const throttleMs = Math.max(0, deps.throttleMs);
150355
+ const machine = new TopicStatusMachine();
150356
+ const throttlers = /* @__PURE__ */ new Map();
150357
+ const failures = /* @__PURE__ */ new Map();
150358
+ const disabled = /* @__PURE__ */ new Set();
150359
+ let disposed = false;
150360
+ const run2 = async (sessionID) => {
150361
+ if (disposed || !deps.enabled || disabled.has(sessionID)) return;
150362
+ const view = machine.view(sessionID);
150363
+ if (!view) return;
150364
+ let target;
150365
+ try {
150366
+ target = await deps.getRoot(sessionID);
150367
+ } catch (err2) {
150368
+ log2.warn("\u8BDD\u9898\u72B6\u6001\uFF1A\u8BFB\u53D6\u6839\u5361\u57FA\u7840\u5185\u5BB9\u5931\u8D25", { sessionID, error: errorMessage(err2) });
150369
+ return;
150370
+ }
150371
+ if (!target) {
150372
+ log2.debug("\u8BDD\u9898\u72B6\u6001\uFF1A\u65E0\u6839\u5361\u57FA\u7840\u5185\u5BB9\uFF0C\u8DF3\u8FC7\u5237\u65B0", { sessionID });
150373
+ return;
150374
+ }
150375
+ const card = buildSessionRootCard(target.base, view, {
150376
+ now: now3(),
150377
+ statusInTitle: deps.statusInTitle,
150378
+ ...deps.compactToken ? { compactToken: deps.compactToken(sessionID) } : {}
150379
+ });
150380
+ let ok = false;
150381
+ let error;
150382
+ try {
150383
+ const res = await deps.patch(target.messageId, card);
150384
+ ok = res.ok;
150385
+ error = res.error;
150386
+ } catch (err2) {
150387
+ error = errorMessage(err2);
150388
+ }
150389
+ if (ok) {
150390
+ failures.delete(sessionID);
150391
+ return;
150392
+ }
150393
+ const count = (failures.get(sessionID) ?? 0) + 1;
150394
+ failures.set(sessionID, count);
150395
+ log2.warn("\u8BDD\u9898\u72B6\u6001\u6839\u5361\u5237\u65B0\u5931\u8D25", { sessionID, attempts: count, error: error ?? "unknown" });
150396
+ if (count >= MAX_CONSECUTIVE_FAILURES) {
150397
+ disabled.add(sessionID);
150398
+ log2.warn("\u8BDD\u9898\u72B6\u6001\uFF1A\u8FDE\u7EED\u5237\u65B0\u5931\u8D25\uFF0C\u505C\u6B62\u8BE5\u4F1A\u8BDD\u540E\u7EED\u72B6\u6001\u5237\u65B0", {
150399
+ sessionID,
150400
+ attempts: count
150401
+ });
150402
+ }
150403
+ };
150404
+ const getThrottler = (sessionID) => {
150405
+ let throttler = throttlers.get(sessionID);
150406
+ if (!throttler) {
150407
+ throttler = createThrottler({
150408
+ intervalMs: throttleMs,
150409
+ now: now3,
150410
+ ...deps.setTimer ? { setTimer: deps.setTimer } : {},
150411
+ ...deps.clearTimer ? { clearTimer: deps.clearTimer } : {},
150412
+ onFire: () => {
150413
+ if (!disposed) void run2(sessionID);
150414
+ }
150415
+ });
150416
+ throttlers.set(sessionID, throttler);
150417
+ }
150418
+ return throttler;
150419
+ };
150420
+ const schedule2 = (sessionID) => {
150421
+ if (!deps.enabled || disposed || disabled.has(sessionID)) return;
150422
+ getThrottler(sessionID).schedule();
150423
+ };
150424
+ const sessionIDOf = (data) => {
150425
+ const value3 = data?.sessionID;
150426
+ return typeof value3 === "string" ? value3 : "";
150427
+ };
150428
+ return {
150429
+ onEvent(event) {
150430
+ if (!deps.enabled) return;
150431
+ const sessionID = sessionIDOf(event.data);
150432
+ if (!sessionID) return;
150433
+ const change = machine.reduce(sessionID, event);
150434
+ if (!change?.changed) return;
150435
+ schedule2(sessionID);
150436
+ },
150437
+ markTerminal(sessionID, kind) {
150438
+ if (!deps.enabled || !sessionID) return;
150439
+ const change = machine.markTerminal(sessionID, kind);
150440
+ if (!change?.changed) return;
150441
+ schedule2(sessionID);
150442
+ },
150443
+ statusOf(sessionID) {
150444
+ return machine.view(sessionID);
150445
+ },
150446
+ dispose() {
150447
+ disposed = true;
150448
+ for (const throttler of throttlers.values()) throttler.cancel();
150449
+ throttlers.clear();
150450
+ machine.clear();
150451
+ failures.clear();
150452
+ disabled.clear();
150453
+ }
150454
+ };
150455
+ }
150456
+
150457
+ // src/session/compact-http.ts
150458
+ function authHeaders2(service3) {
150459
+ if (!service3.password) return {};
150460
+ const token = Buffer.from(`opencode:${service3.password}`, "utf8").toString("base64");
150461
+ return { authorization: `Basic ${token}` };
150462
+ }
150463
+ async function fetchSessionMessagesHttp(sessionID, directory, deps) {
150464
+ const discover = deps.discover ?? (() => discoverLocalService());
150465
+ const doFetch = deps.fetchImpl ?? fetch;
150466
+ const service3 = await discover();
150467
+ if (!service3) throw new Error("\u672A\u53D1\u73B0\u672C\u673A opencode \u670D\u52A1\u6CE8\u518C\uFF08service.json \u7F3A\u5931\u6216\u635F\u574F\uFF09");
150468
+ const path2 = `/api/session/${encodeURIComponent(sessionID)}/message?limit=200`;
150469
+ const headers = { ...authHeaders2(service3) };
150470
+ if (directory) headers["x-opencode-directory"] = encodeURIComponent(directory);
150471
+ const response2 = await doFetch(`${service3.url}${path2}`, {
150472
+ method: "GET",
150473
+ headers,
150474
+ signal: AbortSignal.timeout(deps.timeoutMs ?? 15e3)
150475
+ });
150476
+ if (!response2.ok) {
150477
+ const detail = await response2.text().catch(() => "");
150478
+ deps.log.debug("\u4F1A\u8BDD\u6D88\u606F HTTP \u8BFB\u53D6\u5931\u8D25", { status: response2.status, sessionID });
150479
+ throw new Error(
150480
+ `\u4F1A\u8BDD\u6D88\u606F\u8BFB\u53D6\u5931\u8D25 HTTP ${response2.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
150481
+ );
150482
+ }
150483
+ return response2.json();
150484
+ }
150485
+ async function compactSessionHttp(sessionID, directory, deps) {
150486
+ const discover = deps.discover ?? (() => discoverLocalService());
150487
+ const doFetch = deps.fetchImpl ?? fetch;
150488
+ const service3 = await discover();
150489
+ if (!service3) throw new Error("\u672A\u53D1\u73B0\u672C\u673A opencode \u670D\u52A1\u6CE8\u518C\uFF08service.json \u7F3A\u5931\u6216\u635F\u574F\uFF09");
150490
+ const path2 = `/api/session/${encodeURIComponent(sessionID)}/compact`;
150491
+ const headers = {
150492
+ "content-type": "application/json",
150493
+ ...authHeaders2(service3)
150494
+ };
150495
+ if (directory) headers["x-opencode-directory"] = encodeURIComponent(directory);
150496
+ const response2 = await doFetch(`${service3.url}${path2}`, {
150497
+ method: "POST",
150498
+ headers,
150499
+ body: JSON.stringify({}),
150500
+ signal: AbortSignal.timeout(deps.timeoutMs ?? 3e4)
150501
+ });
150502
+ if (!response2.ok) {
150503
+ const detail = await response2.text().catch(() => "");
150504
+ deps.log.debug("\u4F1A\u8BDD\u538B\u7F29 HTTP \u89E6\u53D1\u5931\u8D25", { status: response2.status, sessionID });
150505
+ throw new Error(
150506
+ `\u4F1A\u8BDD\u538B\u7F29\u89E6\u53D1\u5931\u8D25 HTTP ${response2.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
150507
+ );
150508
+ }
150509
+ }
150510
+
150511
+ // src/session/quick-generate.ts
150512
+ function sessionRoutingHeaders(sessionID, directory) {
150513
+ const headers = { "x-opencode-session": sessionID };
150514
+ if (directory) headers["x-opencode-directory"] = encodeURIComponent(directory);
150515
+ return headers;
150516
+ }
150517
+ function authHeaders3(service3) {
150518
+ if (!service3.password) return {};
150519
+ const token = Buffer.from(`opencode:${service3.password}`, "utf8").toString("base64");
150520
+ return { authorization: `Basic ${token}` };
150521
+ }
150522
+ async function quickGenerateWithSession(deps, input) {
150523
+ const headers = sessionRoutingHeaders(input.sessionID, input.directory);
150524
+ if (deps.generateText) {
150525
+ try {
150526
+ const result4 = await withAttemptTimeout(
150527
+ deps.generateText(input.prompt, { headers }),
150528
+ deps.attemptTimeoutMs ?? 8e3
150529
+ );
150530
+ if (result4 !== void 0 && result4 !== null) {
150531
+ deps.log.info("\u5FEB\u6458\u8981\u751F\u6210\u8D70\u751F\u6210 API", { sessionID: input.sessionID, route: "generate" });
150532
+ return { route: "generate", result: result4 };
150533
+ }
150534
+ deps.log.debug("\u751F\u6210 API \u8FD4\u56DE\u7A7A\u7ED3\u679C\uFF0C\u56DE\u9000\u672C\u673A HTTP \u751F\u6210", { sessionID: input.sessionID });
150535
+ } catch (err2) {
150536
+ deps.log.debug("\u751F\u6210 API \u5931\u8D25\uFF0C\u56DE\u9000\u672C\u673A HTTP \u751F\u6210", {
150537
+ sessionID: input.sessionID,
150538
+ error: errorMessage(err2)
150539
+ });
150540
+ }
150541
+ }
150542
+ const discover = deps.discover ?? (() => discoverLocalService());
150543
+ const doFetch = deps.fetchImpl ?? fetch;
150544
+ const service3 = await discover();
150545
+ if (!service3) throw new Error("\u751F\u6210 API \u4E0E HTTP \u901A\u9053\u5747\u4E0D\u53EF\u7528\uFF08\u672A\u53D1\u73B0\u672C\u673A opencode \u670D\u52A1\u6CE8\u518C\uFF09");
150546
+ const response2 = await doFetch(`${service3.url}/api/experimental/generate`, {
150547
+ method: "POST",
150548
+ headers: { "content-type": "application/json", ...authHeaders3(service3), ...headers },
150549
+ body: JSON.stringify({ prompt: input.prompt }),
150550
+ signal: AbortSignal.timeout(deps.timeoutMs ?? 15e3)
150551
+ });
150552
+ if (!response2.ok) {
150553
+ const detail = await response2.text().catch(() => "");
150554
+ deps.log.debug("HTTP \u751F\u6210\u5931\u8D25", { status: response2.status, sessionID: input.sessionID });
150555
+ throw new Error(
150556
+ `HTTP \u751F\u6210\u5931\u8D25 HTTP ${response2.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`
150557
+ );
150558
+ }
150559
+ const result3 = await response2.json();
150560
+ deps.log.info("\u5FEB\u6458\u8981\u751F\u6210\u8D70\u672C\u673A HTTP \u751F\u6210", { sessionID: input.sessionID, route: "http" });
150561
+ return { route: "http", result: result3 };
150562
+ }
150563
+ function withAttemptTimeout(promise3, ms) {
150564
+ return new Promise((resolve3, reject) => {
150565
+ const timer = setTimeout(() => reject(new Error("generate-attempt-timeout")), Math.max(1, ms));
150566
+ timer.unref?.();
150567
+ promise3.then(
150568
+ (value3) => {
150569
+ clearTimeout(timer);
150570
+ resolve3(value3);
150571
+ },
150572
+ (err2) => {
150573
+ clearTimeout(timer);
150574
+ reject(err2);
150575
+ }
150576
+ );
150577
+ });
150578
+ }
150579
+
149285
150580
  // src/index.ts
149286
150581
  var index_default = plugin_exports.define({
149287
150582
  id: "feishu",
@@ -149344,7 +150639,7 @@ async function start(ctx, config, log2, logSink) {
149344
150639
  appSecret: config.appSecret,
149345
150640
  domain: config.domain === "lark" ? Domain.Lark : Domain.Feishu
149346
150641
  });
149347
- const sender = createFeishuSender(client, log2);
150642
+ const sender = createFeishuSender(client, log2, { cardMaxTables: config.cardMaxTables });
149348
150643
  await owner.load().catch((err2) => log2.warn("owner \u8BFB\u53D6\u5931\u8D25", { error: errorMessage(err2) }));
149349
150644
  const dedup = new MessageDedup(storage, log2);
149350
150645
  const executions = new ExecutionTracker();
@@ -149353,6 +150648,7 @@ async function start(ctx, config, log2, logSink) {
149353
150648
  log: log2,
149354
150649
  enabled: config.stream,
149355
150650
  throttleMs: config.streamThrottleMs,
150651
+ cardMaxTables: config.cardMaxTables,
149356
150652
  // 每次 patch 重签强停 token(`stop` 在下方定义,闭包运行时才求值)。
149357
150653
  buildStopValue: (sessionID) => stop.buildStopValue(sessionID)
149358
150654
  });
@@ -149380,6 +150676,19 @@ async function start(ctx, config, log2, logSink) {
149380
150676
  isRunning: (sessionID) => executions.isRunning(sessionID) || runs.hasActive(sessionID),
149381
150677
  interrupt: (sessionID, reason) => recovery.interrupt(sessionID, reason)
149382
150678
  });
150679
+ const topicStatus = createTopicStatusController({
150680
+ log: log2,
150681
+ enabled: config.topicStatus,
150682
+ statusInTitle: config.topicStatusInTitle,
150683
+ throttleMs: config.topicStatusThrottleMs,
150684
+ getRoot: async (sessionID) => {
150685
+ const link2 = await sessionMap.resolveBySession(sessionID);
150686
+ if (!link2?.rootCard || !link2.replyMessageId) return void 0;
150687
+ return { base: link2.rootCard, messageId: link2.replyMessageId };
150688
+ },
150689
+ patch: (messageId, card) => sender.patchCard(messageId, card),
150690
+ ...config.resumeSummary ? { compactToken: (sessionID) => signCompactToken(sessionID, config.signSecret) } : {}
150691
+ });
149383
150692
  const formRelay = new FormRelay({
149384
150693
  sender,
149385
150694
  log: log2,
@@ -149473,12 +150782,79 @@ async function start(ctx, config, log2, logSink) {
149473
150782
  async function applyPermissionPreset(sessionID, preset) {
149474
150783
  const permissions = presetToRuleset(preset);
149475
150784
  await ctx.session.update({ sessionID, permissions });
149476
- await sessionMap.setSessionMeta(sessionID, { perm: preset, gateMode: presetGateMode(preset) });
150785
+ await sessionMap.setSessionMeta(sessionID, {
150786
+ perm: preset,
150787
+ gateMode: presetGateMode(preset),
150788
+ allowActions: void 0
150789
+ });
149477
150790
  }
149478
150791
  async function moveSessionDir(sessionID, directory) {
149479
150792
  await ctx.session.move({ sessionID, directory });
149480
150793
  await sessionMap.setSessionMeta(sessionID, { dir: directory });
149481
150794
  }
150795
+ async function grantSessionAllow(input) {
150796
+ const link2 = await sessionMap.resolveBySession(input.sessionID);
150797
+ const granted = allowActionsForGrant(input.action);
150798
+ const next = [...link2?.allowActions ?? []];
150799
+ for (const action of granted) if (!next.includes(action)) next.push(action);
150800
+ await sessionMap.setSessionMeta(input.sessionID, { allowActions: next });
150801
+ const serverRules = extractSessionPermissions(await getSessionInfoRaw(input.sessionID, link2?.dir));
150802
+ const base2 = serverRules ?? (link2?.perm ? presetToRuleset(link2.perm) : []);
150803
+ const permissions = appendAllowRules(base2, granted);
150804
+ try {
150805
+ const update2 = ctx.session.update;
150806
+ await update2(
150807
+ { sessionID: input.sessionID, permissions },
150808
+ link2?.dir ? { headers: { "x-opencode-directory": link2.dir } } : void 0
150809
+ );
150810
+ } catch (err2) {
150811
+ log2.warn("\u4F1A\u8BDD\u7EA7\u6743\u9650\u89C4\u5219\u96C6\u66F4\u65B0\u5931\u8D25\uFF08allowActions \u4ECD\u751F\u6548\uFF09", {
150812
+ sessionID: input.sessionID,
150813
+ action: input.action,
150814
+ error: errorMessage(err2)
150815
+ });
150816
+ }
150817
+ log2.info("\u5DF2\u5728\u672C\u4F1A\u8BDD\u5185\u653E\u884C\u5DE5\u5177", { sessionID: input.sessionID, action: input.action, granted });
150818
+ }
150819
+ const compact2 = new CompactController({
150820
+ log: log2,
150821
+ isAllowed: (openId) => owner.isAllowed(openId),
150822
+ // 与强停同族校验:绑定 sessionID + 用途标签("compact") + TTL + nonce。
150823
+ verify: (token, sessionID) => verifyStop(token, config.signSecret, { expectSessionID: sessionID }),
150824
+ replay: new ReplayGuard(config.approvalTtlMs),
150825
+ compact: (sessionID) => compactSession(ctx, sessionID, sessionMap, log2),
150826
+ readMessages: (sessionID) => readSessionMessages(ctx, sessionID, void 0, log2),
150827
+ patchPending: (sessionID, messageId, token) => patchResumeCompactPendingCard(sessionID, messageId, token, sessionMap, sender, log2),
150828
+ patch: (sessionID, summary, kind, messageId) => patchResumeCompactCard(
150829
+ sessionID,
150830
+ summary,
150831
+ kind,
150832
+ messageId,
150833
+ sessionMap,
150834
+ sender,
150835
+ log2,
150836
+ (sid) => signCompactToken(sid, config.signSecret)
150837
+ ),
150838
+ pollIntervalMs: 2e3,
150839
+ timeoutMs: config.resumeCompactTimeoutMs
150840
+ });
150841
+ async function summarizeSessionForResume(input) {
150842
+ const readMessages = async (sessionID, directory) => readSessionMessages(ctx, sessionID, directory, log2);
150843
+ const generateText = async (prompt, directory) => {
150844
+ const api = ctx.generate.text;
150845
+ const outcome = await quickGenerateWithSession(
150846
+ {
150847
+ log: log2,
150848
+ ...typeof api === "function" ? {
150849
+ generateText: (p, requestOptions) => api({ prompt: p }, requestOptions)
150850
+ } : {}
150851
+ },
150852
+ { prompt, sessionID: input.sessionID, ...directory ? { directory } : {} }
150853
+ );
150854
+ return outcome.result;
150855
+ };
150856
+ return summarizeSession({ log: log2, readMessages, generateText }, input);
150857
+ }
149482
150858
  async function listAllSessionsRaw() {
149483
150859
  const api = ctx.session.list;
149484
150860
  if (typeof api !== "function") {
@@ -149549,7 +150925,13 @@ async function start(ctx, config, log2, logSink) {
149549
150925
  sessionPageSize: config.sessionPageSize,
149550
150926
  threadRouting: config.threadRouting,
149551
150927
  listAllSessions: listAllSessionsRaw,
149552
- getSessionInfo: getSessionInfoRaw
150928
+ getSessionInfo: getSessionInfoRaw,
150929
+ resumeSummary: config.resumeSummary,
150930
+ resumeSummaryTimeoutMs: config.resumeSummaryTimeoutMs,
150931
+ resumeCompactTimeoutMs: config.resumeCompactTimeoutMs,
150932
+ cardMaxTables: config.cardMaxTables,
150933
+ summarizeSession: summarizeSessionForResume,
150934
+ signCompact: (sessionID) => signCompactToken(sessionID, config.signSecret)
149553
150935
  });
149554
150936
  const approvals = new ApprovalManager({
149555
150937
  config,
@@ -149560,12 +150942,29 @@ async function start(ctx, config, log2, logSink) {
149560
150942
  sender,
149561
150943
  getLink: (sessionID) => sessionMap.resolveBySession(sessionID),
149562
150944
  isAllowed: (openId) => owner.isAllowed(openId),
149563
- reply: (input) => replyPermission(ctx, input)
150945
+ reply: (input) => replyPermission(ctx, input),
150946
+ // 任务 A:审批卡「✅ 本会话内允许该工具」。
150947
+ sessionAllowButton: config.sessionAllowButton,
150948
+ signAllowSession: ({ requestID, sessionID, action }) => signAllowSession(
150949
+ { requestID, sessionID, action, ttlMs: config.approvalTtlMs },
150950
+ config.signSecret
150951
+ ),
150952
+ verifyAllowSession: (token, expect) => verifyAllowSession(token, config.signSecret, {
150953
+ ...expect?.sessionID ? { expectSessionID: expect.sessionID } : {},
150954
+ ...expect?.action ? { expectAction: expect.action } : {}
150955
+ }),
150956
+ allowSession: (input) => grantSessionAllow(input),
150957
+ hasSessionAllow: ({ sessionID, action }) => {
150958
+ const link2 = sessionMap.getLink(sessionID);
150959
+ const allowed = link2?.allowActions ?? [];
150960
+ return allowActionsForGrant(action).every((a) => allowed.includes(a));
150961
+ }
149564
150962
  });
149565
150963
  const evaluateRegistration = await ctx.permission.hook("evaluate", async (event) => {
149566
150964
  const link2 = await sessionMap.resolveBySession(event.sessionID);
149567
150965
  const sessionGate = link2 && link2.gateMode ? { gateMode: link2.gateMode, ...link2.perm ? { askActions: presetAskActions(link2.perm) } : {} } : void 0;
149568
- const decision = decideEffectForSession(event.action, config, sessionGate);
150966
+ const allowActions = link2?.allowActions && link2.allowActions.length > 0 ? link2.allowActions : void 0;
150967
+ const decision = decideEffectForSession(event.action, config, sessionGate, allowActions);
149569
150968
  if (decision.effect === void 0) return;
149570
150969
  if (decision.effect === "ask" && !link2) {
149571
150970
  log2.debug("\u8DF3\u8FC7 ask\uFF1A\u4F1A\u8BDD\u65E0\u98DE\u4E66\u6620\u5C04", { sessionID: event.sessionID, action: event.action });
@@ -149603,7 +151002,7 @@ async function start(ctx, config, log2, logSink) {
149603
151002
  }
149604
151003
  const hasThread = Boolean(message.threadId);
149605
151004
  let threadLink = hasThread ? await sessionMap.resolveByThread(message.threadId) : void 0;
149606
- const rootLink = hasThread && !threadLink && message.rootId ? await sessionMap.resolveByRoot(message.rootId) : void 0;
151005
+ const rootLink = !threadLink && message.rootId ? await sessionMap.resolveByRoot(message.rootId) : void 0;
149607
151006
  const decision = decideRoute({
149608
151007
  hasThread,
149609
151008
  threadKnown: Boolean(threadLink),
@@ -149619,7 +151018,10 @@ async function start(ctx, config, log2, logSink) {
149619
151018
  if (!sessionID) return;
149620
151019
  const anchor2 = message.rootId ?? message.messageId;
149621
151020
  if (decision.source === "root" || threadLink && !threadLink.anchorMessageId) {
149622
- await sessionMap.bindThread(message.threadId, sessionID, message.chatId, message.senderOpenId, anchor2);
151021
+ const threadId = message.threadId ?? (rootLink ? (await sender.getMessageMeta(message.messageId))?.threadId : void 0);
151022
+ if (threadId) {
151023
+ await sessionMap.bindThread(threadId, sessionID, message.chatId, message.senderOpenId, anchor2);
151024
+ }
149623
151025
  }
149624
151026
  log2.debug("\u8BDD\u9898\u8DEF\u7531\u547D\u4E2D\u4F1A\u8BDD", { source: decision.source, sessionID, threadId: message.threadId });
149625
151027
  if (formRelay.consumeText(sessionID, message.text)) {
@@ -149675,6 +151077,7 @@ async function start(ctx, config, log2, logSink) {
149675
151077
  } catch (err2) {
149676
151078
  log2.warn("prompt \u53D1\u9001\u5931\u8D25", { sessionID, error: errorMessage(err2) });
149677
151079
  runs.apply(sessionID, { type: "execution.failed", error: errorMessage(err2) });
151080
+ topicStatus.markTerminal(sessionID, "failed");
149678
151081
  }
149679
151082
  }
149680
151083
  const gateway = startGateway({
@@ -149688,6 +151091,7 @@ async function start(ctx, config, log2, logSink) {
149688
151091
  log: log2,
149689
151092
  handleForm: (a) => formRelay.handleCardAction(a),
149690
151093
  handleStop: (a) => stop.handleCardAction(a),
151094
+ handleCompact: (a) => compact2.handleCardAction(a),
149691
151095
  handleCommands: (a) => commands.handleCardAction(a),
149692
151096
  handleApprovals: (a) => approvals.handleCardAction(a)
149693
151097
  })
@@ -149713,7 +151117,8 @@ async function start(ctx, config, log2, logSink) {
149713
151117
  onFormCreated: (data) => formRelay.onCreated(data),
149714
151118
  onFormReplied: (data) => formRelay.onReplied(data),
149715
151119
  onFormCancelled: (data) => formRelay.onCancelled(data),
149716
- notifyFailure: (sessionID, error) => notifyFailure(sessionID, error)
151120
+ notifyFailure: (sessionID, error) => notifyFailure(sessionID, error),
151121
+ onTopicStatus: (event) => topicStatus.onEvent(event)
149717
151122
  };
149718
151123
  async function handleEvent(event) {
149719
151124
  await routeEvent(event, eventDeps);
@@ -149767,6 +151172,7 @@ async function start(ctx, config, log2, logSink) {
149767
151172
  await subscription.catch(() => void 0);
149768
151173
  runs.dispose();
149769
151174
  executions.clear();
151175
+ topicStatus.dispose();
149770
151176
  approvals.dispose();
149771
151177
  formRelay.dispose();
149772
151178
  await evaluateRegistration.dispose().catch((err2) => log2.warn("evaluate hook \u91CA\u653E\u5931\u8D25", { error: errorMessage(err2) }));
@@ -149869,6 +151275,85 @@ async function promptSession(ctx, sessionID, text, delivery) {
149869
151275
  const api = ctx.session.prompt;
149870
151276
  await api({ sessionID, text, delivery });
149871
151277
  }
151278
+ async function readSessionMessages(ctx, sessionID, directory, log2) {
151279
+ const options2 = directory ? { headers: { "x-opencode-directory": directory } } : void 0;
151280
+ const session = ctx.session;
151281
+ const messageApi = ctx.message;
151282
+ const sessionMessageApi = session.message;
151283
+ for (const api of [sessionMessageApi, messageApi]) {
151284
+ if (typeof api?.list === "function") {
151285
+ try {
151286
+ const raw = await api.list({ sessionID, limit: 200 }, options2);
151287
+ if (raw !== void 0) return raw;
151288
+ } catch (err2) {
151289
+ log2.debug("session.message.list \u8BFB\u53D6\u5931\u8D25\uFF0C\u7EE7\u7EED\u56DE\u9000", { sessionID, error: errorMessage(err2) });
151290
+ }
151291
+ }
151292
+ }
151293
+ try {
151294
+ return await fetchSessionMessagesHttp(sessionID, directory, { log: log2 });
151295
+ } catch (err2) {
151296
+ log2.debug("HTTP \u8BFB\u53D6\u4F1A\u8BDD\u6D88\u606F\u5931\u8D25\uFF0C\u56DE\u9000 session.context", { sessionID, error: errorMessage(err2) });
151297
+ }
151298
+ if (typeof session.context === "function") {
151299
+ return session.context({ sessionID }, options2);
151300
+ }
151301
+ return void 0;
151302
+ }
151303
+ async function compactSession(ctx, sessionID, sessionMap, log2) {
151304
+ const api = ctx.session.compact;
151305
+ const link2 = await sessionMap.resolveBySession(sessionID);
151306
+ const options2 = link2?.dir ? { headers: { "x-opencode-directory": link2.dir } } : void 0;
151307
+ if (typeof api === "function") {
151308
+ await api({ sessionID }, options2);
151309
+ return;
151310
+ }
151311
+ await compactSessionHttp(sessionID, link2?.dir, { log: log2 });
151312
+ }
151313
+ async function patchResumeCompactPendingCard(sessionID, messageId, token, sessionMap, sender, log2) {
151314
+ const link2 = await sessionMap.resolveBySession(sessionID);
151315
+ const entry = link2 ? await sessionMap.getSession(link2.chatId, sessionID) : void 0;
151316
+ const base2 = await sessionMap.getRootCard(sessionID);
151317
+ if (base2) await sessionMap.setRootCard(sessionID, { ...base2, compactPending: true, compactError: void 0 });
151318
+ const card = buildResumeCompactPendingCard(entry?.title ?? "", sessionID, token, Date.now());
151319
+ const res = await sender.patchCard(messageId, card);
151320
+ if (!res.ok) log2.warn("\u538B\u7F29\u4E2D\u5361\u7247\u66F4\u65B0\u5931\u8D25", { sessionID, error: res.error ?? "unknown" });
151321
+ }
151322
+ async function patchResumeCompactCard(sessionID, summary, kind, messageId, sessionMap, sender, log2, signCompact) {
151323
+ const link2 = await sessionMap.resolveBySession(sessionID);
151324
+ const entry = link2 ? await sessionMap.getSession(link2.chatId, sessionID) : void 0;
151325
+ const base2 = await sessionMap.getRootCard(sessionID);
151326
+ if (base2) {
151327
+ await sessionMap.setRootCard(
151328
+ sessionID,
151329
+ kind === "completed" ? { ...base2, compactPending: false, compactError: void 0, summary, summaryLabel: "\u5DF2\u538B\u7F29 \xB7 \u4F1A\u8BDD\u6458\u8981" } : { ...base2, compactPending: false, compactError: summary }
151330
+ );
151331
+ }
151332
+ const card = kind === "completed" ? buildSessionOpenedCard({
151333
+ title: entry?.title ?? "",
151334
+ sessionID,
151335
+ ...link2?.dir ? { dir: link2.dir } : {},
151336
+ summary,
151337
+ summaryLabel: "\u5DF2\u538B\u7F29 \xB7 \u4F1A\u8BDD\u6458\u8981",
151338
+ compactButton: { sessionID, token: signCompact(sessionID) }
151339
+ }) : buildSessionOpenedCard({
151340
+ title: entry?.title ?? "",
151341
+ sessionID,
151342
+ ...link2?.dir ? { dir: link2.dir } : {},
151343
+ compactError: summary,
151344
+ compactButton: { sessionID, token: signCompact(sessionID) }
151345
+ });
151346
+ const target = messageId || link2?.replyMessageId;
151347
+ if (!target) {
151348
+ log2.warn("\u538B\u7F29\u7ED3\u679C patch \u8DF3\u8FC7\uFF1A\u65E0\u5361\u7247\u6D88\u606F id", { sessionID });
151349
+ return;
151350
+ }
151351
+ const res = await sender.patchCard(target, card);
151352
+ if (!res.ok) log2.warn("\u538B\u7F29\u7ED3\u679C\u5361\u7247\u66F4\u65B0\u5931\u8D25", { sessionID, error: res.error ?? "unknown" });
151353
+ }
151354
+ function signCompactToken(sessionID, secret) {
151355
+ return signStop({ sessionID, ttlMs: 24 * 60 * 60 * 1e3 }, secret);
151356
+ }
149872
151357
  /*! Bundled license information:
149873
151358
 
149874
151359
  mime-db/index.js: