larkway 0.3.27 → 0.3.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -10379,6 +10379,769 @@ var require_proxy_from_env = __commonJS({
10379
10379
  }
10380
10380
  });
10381
10381
 
10382
+ // node_modules/.pnpm/ms@2.1.3/node_modules/ms/index.js
10383
+ var require_ms = __commonJS({
10384
+ "node_modules/.pnpm/ms@2.1.3/node_modules/ms/index.js"(exports, module) {
10385
+ var s = 1e3;
10386
+ var m = s * 60;
10387
+ var h = m * 60;
10388
+ var d = h * 24;
10389
+ var w = d * 7;
10390
+ var y = d * 365.25;
10391
+ module.exports = function(val, options) {
10392
+ options = options || {};
10393
+ var type2 = typeof val;
10394
+ if (type2 === "string" && val.length > 0) {
10395
+ return parse(val);
10396
+ } else if (type2 === "number" && isFinite(val)) {
10397
+ return options.long ? fmtLong(val) : fmtShort(val);
10398
+ }
10399
+ throw new Error(
10400
+ "val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
10401
+ );
10402
+ };
10403
+ function parse(str2) {
10404
+ str2 = String(str2);
10405
+ if (str2.length > 100) {
10406
+ return;
10407
+ }
10408
+ var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
10409
+ str2
10410
+ );
10411
+ if (!match) {
10412
+ return;
10413
+ }
10414
+ var n = parseFloat(match[1]);
10415
+ var type2 = (match[2] || "ms").toLowerCase();
10416
+ switch (type2) {
10417
+ case "years":
10418
+ case "year":
10419
+ case "yrs":
10420
+ case "yr":
10421
+ case "y":
10422
+ return n * y;
10423
+ case "weeks":
10424
+ case "week":
10425
+ case "w":
10426
+ return n * w;
10427
+ case "days":
10428
+ case "day":
10429
+ case "d":
10430
+ return n * d;
10431
+ case "hours":
10432
+ case "hour":
10433
+ case "hrs":
10434
+ case "hr":
10435
+ case "h":
10436
+ return n * h;
10437
+ case "minutes":
10438
+ case "minute":
10439
+ case "mins":
10440
+ case "min":
10441
+ case "m":
10442
+ return n * m;
10443
+ case "seconds":
10444
+ case "second":
10445
+ case "secs":
10446
+ case "sec":
10447
+ case "s":
10448
+ return n * s;
10449
+ case "milliseconds":
10450
+ case "millisecond":
10451
+ case "msecs":
10452
+ case "msec":
10453
+ case "ms":
10454
+ return n;
10455
+ default:
10456
+ return void 0;
10457
+ }
10458
+ }
10459
+ function fmtShort(ms) {
10460
+ var msAbs = Math.abs(ms);
10461
+ if (msAbs >= d) {
10462
+ return Math.round(ms / d) + "d";
10463
+ }
10464
+ if (msAbs >= h) {
10465
+ return Math.round(ms / h) + "h";
10466
+ }
10467
+ if (msAbs >= m) {
10468
+ return Math.round(ms / m) + "m";
10469
+ }
10470
+ if (msAbs >= s) {
10471
+ return Math.round(ms / s) + "s";
10472
+ }
10473
+ return ms + "ms";
10474
+ }
10475
+ function fmtLong(ms) {
10476
+ var msAbs = Math.abs(ms);
10477
+ if (msAbs >= d) {
10478
+ return plural(ms, msAbs, d, "day");
10479
+ }
10480
+ if (msAbs >= h) {
10481
+ return plural(ms, msAbs, h, "hour");
10482
+ }
10483
+ if (msAbs >= m) {
10484
+ return plural(ms, msAbs, m, "minute");
10485
+ }
10486
+ if (msAbs >= s) {
10487
+ return plural(ms, msAbs, s, "second");
10488
+ }
10489
+ return ms + " ms";
10490
+ }
10491
+ function plural(ms, msAbs, n, name) {
10492
+ var isPlural = msAbs >= n * 1.5;
10493
+ return Math.round(ms / n) + " " + name + (isPlural ? "s" : "");
10494
+ }
10495
+ }
10496
+ });
10497
+
10498
+ // node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/common.js
10499
+ var require_common = __commonJS({
10500
+ "node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/common.js"(exports, module) {
10501
+ function setup(env) {
10502
+ createDebug.debug = createDebug;
10503
+ createDebug.default = createDebug;
10504
+ createDebug.coerce = coerce2;
10505
+ createDebug.disable = disable;
10506
+ createDebug.enable = enable;
10507
+ createDebug.enabled = enabled;
10508
+ createDebug.humanize = require_ms();
10509
+ createDebug.destroy = destroy;
10510
+ Object.keys(env).forEach((key) => {
10511
+ createDebug[key] = env[key];
10512
+ });
10513
+ createDebug.names = [];
10514
+ createDebug.skips = [];
10515
+ createDebug.formatters = {};
10516
+ function selectColor(namespace) {
10517
+ let hash = 0;
10518
+ for (let i = 0; i < namespace.length; i++) {
10519
+ hash = (hash << 5) - hash + namespace.charCodeAt(i);
10520
+ hash |= 0;
10521
+ }
10522
+ return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
10523
+ }
10524
+ createDebug.selectColor = selectColor;
10525
+ function createDebug(namespace) {
10526
+ let prevTime;
10527
+ let enableOverride = null;
10528
+ let namespacesCache;
10529
+ let enabledCache;
10530
+ function debug(...args) {
10531
+ if (!debug.enabled) {
10532
+ return;
10533
+ }
10534
+ const self2 = debug;
10535
+ const curr = Number(/* @__PURE__ */ new Date());
10536
+ const ms = curr - (prevTime || curr);
10537
+ self2.diff = ms;
10538
+ self2.prev = prevTime;
10539
+ self2.curr = curr;
10540
+ prevTime = curr;
10541
+ args[0] = createDebug.coerce(args[0]);
10542
+ if (typeof args[0] !== "string") {
10543
+ args.unshift("%O");
10544
+ }
10545
+ let index = 0;
10546
+ args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
10547
+ if (match === "%%") {
10548
+ return "%";
10549
+ }
10550
+ index++;
10551
+ const formatter = createDebug.formatters[format];
10552
+ if (typeof formatter === "function") {
10553
+ const val = args[index];
10554
+ match = formatter.call(self2, val);
10555
+ args.splice(index, 1);
10556
+ index--;
10557
+ }
10558
+ return match;
10559
+ });
10560
+ createDebug.formatArgs.call(self2, args);
10561
+ const logFn = self2.log || createDebug.log;
10562
+ logFn.apply(self2, args);
10563
+ }
10564
+ debug.namespace = namespace;
10565
+ debug.useColors = createDebug.useColors();
10566
+ debug.color = createDebug.selectColor(namespace);
10567
+ debug.extend = extend3;
10568
+ debug.destroy = createDebug.destroy;
10569
+ Object.defineProperty(debug, "enabled", {
10570
+ enumerable: true,
10571
+ configurable: false,
10572
+ get: () => {
10573
+ if (enableOverride !== null) {
10574
+ return enableOverride;
10575
+ }
10576
+ if (namespacesCache !== createDebug.namespaces) {
10577
+ namespacesCache = createDebug.namespaces;
10578
+ enabledCache = createDebug.enabled(namespace);
10579
+ }
10580
+ return enabledCache;
10581
+ },
10582
+ set: (v) => {
10583
+ enableOverride = v;
10584
+ }
10585
+ });
10586
+ if (typeof createDebug.init === "function") {
10587
+ createDebug.init(debug);
10588
+ }
10589
+ return debug;
10590
+ }
10591
+ function extend3(namespace, delimiter) {
10592
+ const newDebug = createDebug(this.namespace + (typeof delimiter === "undefined" ? ":" : delimiter) + namespace);
10593
+ newDebug.log = this.log;
10594
+ return newDebug;
10595
+ }
10596
+ function enable(namespaces) {
10597
+ createDebug.save(namespaces);
10598
+ createDebug.namespaces = namespaces;
10599
+ createDebug.names = [];
10600
+ createDebug.skips = [];
10601
+ const split = (typeof namespaces === "string" ? namespaces : "").trim().replace(/\s+/g, ",").split(",").filter(Boolean);
10602
+ for (const ns of split) {
10603
+ if (ns[0] === "-") {
10604
+ createDebug.skips.push(ns.slice(1));
10605
+ } else {
10606
+ createDebug.names.push(ns);
10607
+ }
10608
+ }
10609
+ }
10610
+ function matchesTemplate(search, template) {
10611
+ let searchIndex = 0;
10612
+ let templateIndex = 0;
10613
+ let starIndex = -1;
10614
+ let matchIndex = 0;
10615
+ while (searchIndex < search.length) {
10616
+ if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) {
10617
+ if (template[templateIndex] === "*") {
10618
+ starIndex = templateIndex;
10619
+ matchIndex = searchIndex;
10620
+ templateIndex++;
10621
+ } else {
10622
+ searchIndex++;
10623
+ templateIndex++;
10624
+ }
10625
+ } else if (starIndex !== -1) {
10626
+ templateIndex = starIndex + 1;
10627
+ matchIndex++;
10628
+ searchIndex = matchIndex;
10629
+ } else {
10630
+ return false;
10631
+ }
10632
+ }
10633
+ while (templateIndex < template.length && template[templateIndex] === "*") {
10634
+ templateIndex++;
10635
+ }
10636
+ return templateIndex === template.length;
10637
+ }
10638
+ function disable() {
10639
+ const namespaces = [
10640
+ ...createDebug.names,
10641
+ ...createDebug.skips.map((namespace) => "-" + namespace)
10642
+ ].join(",");
10643
+ createDebug.enable("");
10644
+ return namespaces;
10645
+ }
10646
+ function enabled(name) {
10647
+ for (const skip of createDebug.skips) {
10648
+ if (matchesTemplate(name, skip)) {
10649
+ return false;
10650
+ }
10651
+ }
10652
+ for (const ns of createDebug.names) {
10653
+ if (matchesTemplate(name, ns)) {
10654
+ return true;
10655
+ }
10656
+ }
10657
+ return false;
10658
+ }
10659
+ function coerce2(val) {
10660
+ if (val instanceof Error) {
10661
+ return val.stack || val.message;
10662
+ }
10663
+ return val;
10664
+ }
10665
+ function destroy() {
10666
+ console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
10667
+ }
10668
+ createDebug.enable(createDebug.load());
10669
+ return createDebug;
10670
+ }
10671
+ module.exports = setup;
10672
+ }
10673
+ });
10674
+
10675
+ // node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/browser.js
10676
+ var require_browser = __commonJS({
10677
+ "node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/browser.js"(exports, module) {
10678
+ exports.formatArgs = formatArgs;
10679
+ exports.save = save;
10680
+ exports.load = load2;
10681
+ exports.useColors = useColors;
10682
+ exports.storage = localstorage();
10683
+ exports.destroy = /* @__PURE__ */ (() => {
10684
+ let warned = false;
10685
+ return () => {
10686
+ if (!warned) {
10687
+ warned = true;
10688
+ console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
10689
+ }
10690
+ };
10691
+ })();
10692
+ exports.colors = [
10693
+ "#0000CC",
10694
+ "#0000FF",
10695
+ "#0033CC",
10696
+ "#0033FF",
10697
+ "#0066CC",
10698
+ "#0066FF",
10699
+ "#0099CC",
10700
+ "#0099FF",
10701
+ "#00CC00",
10702
+ "#00CC33",
10703
+ "#00CC66",
10704
+ "#00CC99",
10705
+ "#00CCCC",
10706
+ "#00CCFF",
10707
+ "#3300CC",
10708
+ "#3300FF",
10709
+ "#3333CC",
10710
+ "#3333FF",
10711
+ "#3366CC",
10712
+ "#3366FF",
10713
+ "#3399CC",
10714
+ "#3399FF",
10715
+ "#33CC00",
10716
+ "#33CC33",
10717
+ "#33CC66",
10718
+ "#33CC99",
10719
+ "#33CCCC",
10720
+ "#33CCFF",
10721
+ "#6600CC",
10722
+ "#6600FF",
10723
+ "#6633CC",
10724
+ "#6633FF",
10725
+ "#66CC00",
10726
+ "#66CC33",
10727
+ "#9900CC",
10728
+ "#9900FF",
10729
+ "#9933CC",
10730
+ "#9933FF",
10731
+ "#99CC00",
10732
+ "#99CC33",
10733
+ "#CC0000",
10734
+ "#CC0033",
10735
+ "#CC0066",
10736
+ "#CC0099",
10737
+ "#CC00CC",
10738
+ "#CC00FF",
10739
+ "#CC3300",
10740
+ "#CC3333",
10741
+ "#CC3366",
10742
+ "#CC3399",
10743
+ "#CC33CC",
10744
+ "#CC33FF",
10745
+ "#CC6600",
10746
+ "#CC6633",
10747
+ "#CC9900",
10748
+ "#CC9933",
10749
+ "#CCCC00",
10750
+ "#CCCC33",
10751
+ "#FF0000",
10752
+ "#FF0033",
10753
+ "#FF0066",
10754
+ "#FF0099",
10755
+ "#FF00CC",
10756
+ "#FF00FF",
10757
+ "#FF3300",
10758
+ "#FF3333",
10759
+ "#FF3366",
10760
+ "#FF3399",
10761
+ "#FF33CC",
10762
+ "#FF33FF",
10763
+ "#FF6600",
10764
+ "#FF6633",
10765
+ "#FF9900",
10766
+ "#FF9933",
10767
+ "#FFCC00",
10768
+ "#FFCC33"
10769
+ ];
10770
+ function useColors() {
10771
+ if (typeof window !== "undefined" && window.process && (window.process.type === "renderer" || window.process.__nwjs)) {
10772
+ return true;
10773
+ }
10774
+ if (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
10775
+ return false;
10776
+ }
10777
+ let m;
10778
+ return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773
10779
+ typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31?
10780
+ // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
10781
+ typeof navigator !== "undefined" && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
10782
+ typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
10783
+ }
10784
+ function formatArgs(args) {
10785
+ args[0] = (this.useColors ? "%c" : "") + this.namespace + (this.useColors ? " %c" : " ") + args[0] + (this.useColors ? "%c " : " ") + "+" + module.exports.humanize(this.diff);
10786
+ if (!this.useColors) {
10787
+ return;
10788
+ }
10789
+ const c = "color: " + this.color;
10790
+ args.splice(1, 0, c, "color: inherit");
10791
+ let index = 0;
10792
+ let lastC = 0;
10793
+ args[0].replace(/%[a-zA-Z%]/g, (match) => {
10794
+ if (match === "%%") {
10795
+ return;
10796
+ }
10797
+ index++;
10798
+ if (match === "%c") {
10799
+ lastC = index;
10800
+ }
10801
+ });
10802
+ args.splice(lastC, 0, c);
10803
+ }
10804
+ exports.log = console.debug || console.log || (() => {
10805
+ });
10806
+ function save(namespaces) {
10807
+ try {
10808
+ if (namespaces) {
10809
+ exports.storage.setItem("debug", namespaces);
10810
+ } else {
10811
+ exports.storage.removeItem("debug");
10812
+ }
10813
+ } catch (error) {
10814
+ }
10815
+ }
10816
+ function load2() {
10817
+ let r;
10818
+ try {
10819
+ r = exports.storage.getItem("debug") || exports.storage.getItem("DEBUG");
10820
+ } catch (error) {
10821
+ }
10822
+ if (!r && typeof process !== "undefined" && "env" in process) {
10823
+ r = process.env.DEBUG;
10824
+ }
10825
+ return r;
10826
+ }
10827
+ function localstorage() {
10828
+ try {
10829
+ return localStorage;
10830
+ } catch (error) {
10831
+ }
10832
+ }
10833
+ module.exports = require_common()(exports);
10834
+ var { formatters } = module.exports;
10835
+ formatters.j = function(v) {
10836
+ try {
10837
+ return JSON.stringify(v);
10838
+ } catch (error) {
10839
+ return "[UnexpectedJSONParseError]: " + error.message;
10840
+ }
10841
+ };
10842
+ }
10843
+ });
10844
+
10845
+ // node_modules/.pnpm/has-flag@4.0.0/node_modules/has-flag/index.js
10846
+ var require_has_flag = __commonJS({
10847
+ "node_modules/.pnpm/has-flag@4.0.0/node_modules/has-flag/index.js"(exports, module) {
10848
+ "use strict";
10849
+ module.exports = (flag, argv = process.argv) => {
10850
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
10851
+ const position = argv.indexOf(prefix + flag);
10852
+ const terminatorPosition = argv.indexOf("--");
10853
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
10854
+ };
10855
+ }
10856
+ });
10857
+
10858
+ // node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js
10859
+ var require_supports_color = __commonJS({
10860
+ "node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js"(exports, module) {
10861
+ "use strict";
10862
+ var os2 = __require("os");
10863
+ var tty = __require("tty");
10864
+ var hasFlag = require_has_flag();
10865
+ var { env } = process;
10866
+ var forceColor;
10867
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
10868
+ forceColor = 0;
10869
+ } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
10870
+ forceColor = 1;
10871
+ }
10872
+ if ("FORCE_COLOR" in env) {
10873
+ if (env.FORCE_COLOR === "true") {
10874
+ forceColor = 1;
10875
+ } else if (env.FORCE_COLOR === "false") {
10876
+ forceColor = 0;
10877
+ } else {
10878
+ forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3);
10879
+ }
10880
+ }
10881
+ function translateLevel(level) {
10882
+ if (level === 0) {
10883
+ return false;
10884
+ }
10885
+ return {
10886
+ level,
10887
+ hasBasic: true,
10888
+ has256: level >= 2,
10889
+ has16m: level >= 3
10890
+ };
10891
+ }
10892
+ function supportsColor(haveStream, streamIsTTY) {
10893
+ if (forceColor === 0) {
10894
+ return 0;
10895
+ }
10896
+ if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
10897
+ return 3;
10898
+ }
10899
+ if (hasFlag("color=256")) {
10900
+ return 2;
10901
+ }
10902
+ if (haveStream && !streamIsTTY && forceColor === void 0) {
10903
+ return 0;
10904
+ }
10905
+ const min = forceColor || 0;
10906
+ if (env.TERM === "dumb") {
10907
+ return min;
10908
+ }
10909
+ if (process.platform === "win32") {
10910
+ const osRelease = os2.release().split(".");
10911
+ if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
10912
+ return Number(osRelease[2]) >= 14931 ? 3 : 2;
10913
+ }
10914
+ return 1;
10915
+ }
10916
+ if ("CI" in env) {
10917
+ if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
10918
+ return 1;
10919
+ }
10920
+ return min;
10921
+ }
10922
+ if ("TEAMCITY_VERSION" in env) {
10923
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
10924
+ }
10925
+ if (env.COLORTERM === "truecolor") {
10926
+ return 3;
10927
+ }
10928
+ if ("TERM_PROGRAM" in env) {
10929
+ const version = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
10930
+ switch (env.TERM_PROGRAM) {
10931
+ case "iTerm.app":
10932
+ return version >= 3 ? 3 : 2;
10933
+ case "Apple_Terminal":
10934
+ return 2;
10935
+ }
10936
+ }
10937
+ if (/-256(color)?$/i.test(env.TERM)) {
10938
+ return 2;
10939
+ }
10940
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
10941
+ return 1;
10942
+ }
10943
+ if ("COLORTERM" in env) {
10944
+ return 1;
10945
+ }
10946
+ return min;
10947
+ }
10948
+ function getSupportLevel(stream) {
10949
+ const level = supportsColor(stream, stream && stream.isTTY);
10950
+ return translateLevel(level);
10951
+ }
10952
+ module.exports = {
10953
+ supportsColor: getSupportLevel,
10954
+ stdout: translateLevel(supportsColor(true, tty.isatty(1))),
10955
+ stderr: translateLevel(supportsColor(true, tty.isatty(2)))
10956
+ };
10957
+ }
10958
+ });
10959
+
10960
+ // node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/node.js
10961
+ var require_node = __commonJS({
10962
+ "node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/node.js"(exports, module) {
10963
+ var tty = __require("tty");
10964
+ var util2 = __require("util");
10965
+ exports.init = init;
10966
+ exports.log = log;
10967
+ exports.formatArgs = formatArgs;
10968
+ exports.save = save;
10969
+ exports.load = load2;
10970
+ exports.useColors = useColors;
10971
+ exports.destroy = util2.deprecate(
10972
+ () => {
10973
+ },
10974
+ "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."
10975
+ );
10976
+ exports.colors = [6, 2, 3, 4, 5, 1];
10977
+ try {
10978
+ const supportsColor = require_supports_color();
10979
+ if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
10980
+ exports.colors = [
10981
+ 20,
10982
+ 21,
10983
+ 26,
10984
+ 27,
10985
+ 32,
10986
+ 33,
10987
+ 38,
10988
+ 39,
10989
+ 40,
10990
+ 41,
10991
+ 42,
10992
+ 43,
10993
+ 44,
10994
+ 45,
10995
+ 56,
10996
+ 57,
10997
+ 62,
10998
+ 63,
10999
+ 68,
11000
+ 69,
11001
+ 74,
11002
+ 75,
11003
+ 76,
11004
+ 77,
11005
+ 78,
11006
+ 79,
11007
+ 80,
11008
+ 81,
11009
+ 92,
11010
+ 93,
11011
+ 98,
11012
+ 99,
11013
+ 112,
11014
+ 113,
11015
+ 128,
11016
+ 129,
11017
+ 134,
11018
+ 135,
11019
+ 148,
11020
+ 149,
11021
+ 160,
11022
+ 161,
11023
+ 162,
11024
+ 163,
11025
+ 164,
11026
+ 165,
11027
+ 166,
11028
+ 167,
11029
+ 168,
11030
+ 169,
11031
+ 170,
11032
+ 171,
11033
+ 172,
11034
+ 173,
11035
+ 178,
11036
+ 179,
11037
+ 184,
11038
+ 185,
11039
+ 196,
11040
+ 197,
11041
+ 198,
11042
+ 199,
11043
+ 200,
11044
+ 201,
11045
+ 202,
11046
+ 203,
11047
+ 204,
11048
+ 205,
11049
+ 206,
11050
+ 207,
11051
+ 208,
11052
+ 209,
11053
+ 214,
11054
+ 215,
11055
+ 220,
11056
+ 221
11057
+ ];
11058
+ }
11059
+ } catch (error) {
11060
+ }
11061
+ exports.inspectOpts = Object.keys(process.env).filter((key) => {
11062
+ return /^debug_/i.test(key);
11063
+ }).reduce((obj, key) => {
11064
+ const prop = key.substring(6).toLowerCase().replace(/_([a-z])/g, (_, k) => {
11065
+ return k.toUpperCase();
11066
+ });
11067
+ let val = process.env[key];
11068
+ if (/^(yes|on|true|enabled)$/i.test(val)) {
11069
+ val = true;
11070
+ } else if (/^(no|off|false|disabled)$/i.test(val)) {
11071
+ val = false;
11072
+ } else if (val === "null") {
11073
+ val = null;
11074
+ } else {
11075
+ val = Number(val);
11076
+ }
11077
+ obj[prop] = val;
11078
+ return obj;
11079
+ }, {});
11080
+ function useColors() {
11081
+ return "colors" in exports.inspectOpts ? Boolean(exports.inspectOpts.colors) : tty.isatty(process.stderr.fd);
11082
+ }
11083
+ function formatArgs(args) {
11084
+ const { namespace: name, useColors: useColors2 } = this;
11085
+ if (useColors2) {
11086
+ const c = this.color;
11087
+ const colorCode = "\x1B[3" + (c < 8 ? c : "8;5;" + c);
11088
+ const prefix = ` ${colorCode};1m${name} \x1B[0m`;
11089
+ args[0] = prefix + args[0].split("\n").join("\n" + prefix);
11090
+ args.push(colorCode + "m+" + module.exports.humanize(this.diff) + "\x1B[0m");
11091
+ } else {
11092
+ args[0] = getDate() + name + " " + args[0];
11093
+ }
11094
+ }
11095
+ function getDate() {
11096
+ if (exports.inspectOpts.hideDate) {
11097
+ return "";
11098
+ }
11099
+ return (/* @__PURE__ */ new Date()).toISOString() + " ";
11100
+ }
11101
+ function log(...args) {
11102
+ return process.stderr.write(util2.formatWithOptions(exports.inspectOpts, ...args) + "\n");
11103
+ }
11104
+ function save(namespaces) {
11105
+ if (namespaces) {
11106
+ process.env.DEBUG = namespaces;
11107
+ } else {
11108
+ delete process.env.DEBUG;
11109
+ }
11110
+ }
11111
+ function load2() {
11112
+ return process.env.DEBUG;
11113
+ }
11114
+ function init(debug) {
11115
+ debug.inspectOpts = {};
11116
+ const keys = Object.keys(exports.inspectOpts);
11117
+ for (let i = 0; i < keys.length; i++) {
11118
+ debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
11119
+ }
11120
+ }
11121
+ module.exports = require_common()(exports);
11122
+ var { formatters } = module.exports;
11123
+ formatters.o = function(v) {
11124
+ this.inspectOpts.colors = this.useColors;
11125
+ return util2.inspect(v, this.inspectOpts).split("\n").map((str2) => str2.trim()).join(" ");
11126
+ };
11127
+ formatters.O = function(v) {
11128
+ this.inspectOpts.colors = this.useColors;
11129
+ return util2.inspect(v, this.inspectOpts);
11130
+ };
11131
+ }
11132
+ });
11133
+
11134
+ // node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/index.js
11135
+ var require_src = __commonJS({
11136
+ "node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/index.js"(exports, module) {
11137
+ if (typeof process === "undefined" || process.type === "renderer" || process.browser === true || process.__nwjs) {
11138
+ module.exports = require_browser();
11139
+ } else {
11140
+ module.exports = require_node();
11141
+ }
11142
+ }
11143
+ });
11144
+
10382
11145
  // node_modules/.pnpm/follow-redirects@1.16.0/node_modules/follow-redirects/debug.js
10383
11146
  var require_debug = __commonJS({
10384
11147
  "node_modules/.pnpm/follow-redirects@1.16.0/node_modules/follow-redirects/debug.js"(exports, module) {
@@ -10386,7 +11149,7 @@ var require_debug = __commonJS({
10386
11149
  module.exports = function() {
10387
11150
  if (!debug) {
10388
11151
  try {
10389
- debug = __require("debug")("follow-redirects");
11152
+ debug = require_src()("follow-redirects");
10390
11153
  } catch (error) {
10391
11154
  }
10392
11155
  if (typeof debug !== "function") {
@@ -115245,7 +116008,7 @@ See examples/config.example.json for the correct format.`
115245
116008
  // src/lark/channelClient.ts
115246
116009
  var import_node_sdk = __toESM(require_lib2(), 1);
115247
116010
  import { execFile as execFileCallback } from "node:child_process";
115248
- import { mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
116011
+ import { mkdir, readFile as readFile2, writeFile, rename, unlink } from "node:fs/promises";
115249
116012
  import path3 from "node:path";
115250
116013
  import { promisify } from "node:util";
115251
116014
 
@@ -115766,9 +116529,12 @@ var execFile = promisify(execFileCallback);
115766
116529
  var LEARNED_CHATS_LIMIT = 100;
115767
116530
  var SEEN_MESSAGES_LIMIT = 1e3;
115768
116531
  var MAX_MESSAGE_ATTEMPTS = 5;
115769
- var OPEN_CHAT_DISCOVERY_LOOKBACK_MS = 9e4;
116532
+ var OPEN_CHAT_DISCOVERY_LOOKBACK_BUFFER_MS = 3e4;
115770
116533
  var OPEN_CHAT_DISCOVERY_BOOTSTRAP_LOOKBACK_MS = 30 * 60 * 1e3;
115771
116534
  var PROCESSING_REACTION_EMOJI = "Typing";
116535
+ var DEFAULT_OPEN_CHAT_DISCOVERY_MS = 3e5;
116536
+ var OPEN_CHAT_DISCOVERY_JITTER_CAP_MS = 3e4;
116537
+ var OPEN_CHAT_DISCOVERY_MAX_BACKOFF_CYCLES = 8;
115772
116538
  var GAP_FILL_MAX_ATTEMPTS = 3;
115773
116539
  var GAP_FILL_BACKOFF_BASE_MS = 1e3;
115774
116540
  var UNRESOLVED_WINDOW_MAX_CHATS = 50;
@@ -115798,7 +116564,7 @@ function resolveOpenChatDiscoveryMs(ctorValue) {
115798
116564
  } else {
115799
116565
  const env = process.env["LARKWAY_OPEN_CHAT_DISCOVERY_MS"];
115800
116566
  const parsed = env !== void 0 ? Number(env) : Number.NaN;
115801
- raw = Number.isFinite(parsed) ? parsed : 6e4;
116567
+ raw = Number.isFinite(parsed) ? parsed : DEFAULT_OPEN_CHAT_DISCOVERY_MS;
115802
116568
  }
115803
116569
  return Number.isFinite(raw) && raw > 0 ? raw : 0;
115804
116570
  }
@@ -115997,6 +116763,25 @@ var ChannelClient = class {
115997
116763
  openChatDiscoveryTimer = null;
115998
116764
  openChatDiscoveryRunning = false;
115999
116765
  openChatDiscoveryBootstrapped = false;
116766
+ /**
116767
+ * Consecutive discovery-cycle failures. Used to SKIP cycles with exponential
116768
+ * backoff (storm: a failing +chat-list/gap-fill shouldn't re-fire every
116769
+ * interval). Reset to 0 on the first clean cycle. {@link openChatDiscoverySkips}
116770
+ * counts how many remaining cycles to skip before the next real attempt.
116771
+ */
116772
+ openChatDiscoveryFailures = 0;
116773
+ openChatDiscoverySkips = 0;
116774
+ /**
116775
+ * Per-instance jitter offset (ms) applied to discovery scheduling so multiple
116776
+ * bots on one host don't run discovery (and its history-pull burst) in
116777
+ * lockstep. Computed once at startup. `Math.random` is fine here — this is
116778
+ * runtime scheduling code, not a determinism-sensitive workflow script.
116779
+ */
116780
+ openChatDiscoveryJitterMs = Math.floor(
116781
+ Math.random() * OPEN_CHAT_DISCOVERY_JITTER_CAP_MS
116782
+ );
116783
+ /** Monotonic suffix so overlapping atomic writes get distinct temp files. */
116784
+ atomicWriteSeq = 0;
116000
116785
  processingReactions = /* @__PURE__ */ new Map();
116001
116786
  /**
116002
116787
  * Shared messageId -> threadId map. Populated by ChannelCardClient.createCard
@@ -116028,10 +116813,31 @@ var ChannelClient = class {
116028
116813
  setGapFillSleepForTest(fn) {
116029
116814
  this.gapFillSleep = fn;
116030
116815
  }
116816
+ /**
116817
+ * TEST SEAM (deletable): override the per-instance open-chat discovery startup
116818
+ * jitter so tests can make the FIRST discovery run fire deterministically
116819
+ * (jitter=0) instead of waiting up to {@link OPEN_CHAT_DISCOVERY_JITTER_CAP_MS}.
116820
+ * Must be called before connect()/startOpenChatDiscovery(). No-op in production.
116821
+ */
116822
+ setOpenChatDiscoveryJitterForTest(ms) {
116823
+ this.openChatDiscoveryJitterMs = Math.max(0, ms);
116824
+ }
116031
116825
  /** TEST-ONLY read of the per-chat unresolved-window replay map (chatId → windowStart). */
116032
116826
  unresolvedGapWindowsForTest() {
116033
116827
  return new Map(this.unresolvedGapWindowByChat);
116034
116828
  }
116829
+ /**
116830
+ * TEST-ONLY: run exactly ONE open-chat discovery cycle (same code path the
116831
+ * interval timer invokes), awaited to completion. Lets tests assert the
116832
+ * storm-control behaviour (steady-state no-pull, new-chat pull, failure
116833
+ * backoff skip) deterministically without standing up real timers.
116834
+ */
116835
+ async discoverOpenChatsForTest() {
116836
+ for (let i = 0; i < 200 && this.openChatDiscoveryRunning; i++) {
116837
+ await sleep(5);
116838
+ }
116839
+ await this.discoverOpenChatsAndGapFill((s) => console.log(`[channel.client] ${s}`));
116840
+ }
116035
116841
  /**
116036
116842
  * TEST-ONLY direct gapFill invocation with an explicit chat-set override —
116037
116843
  * mirrors exactly how open-chat discovery calls gapFill on a SUBSET of chats.
@@ -116126,12 +116932,26 @@ var ChannelClient = class {
116126
116932
  // real network flap. If a future node-sdk / @larksuite/channel exposes
116127
116933
  // the ws or attaches its own listener, prefer that and drop the guard.
116128
116934
  handshakeTimeoutMs: 15e3,
116129
- // Liveness watchdog (SECONDS): if no inbound frame arrives within this
116130
- // window after the last ping, treat the socket as dead and reconnect —
116131
- // catches silently half-open connections that never emit a close event.
116132
- // This IS the 1.64 keepalive fix; the app-level `keepalive` watchdog
116133
- // option only exists in the split-out @larksuite/channel package, not in
116134
- // node-sdk's bundled createLarkChannel, so we don't pass it here.
116935
+ // HALF-OPEN DETECTION (SECONDS) keep this on. It terminates a socket that
116936
+ // has gone half-open (server stopped responding but never sent a FIN/close),
116937
+ // so the SDK's 'close' handler runs the normal reconnect → our gap-fill
116938
+ // recovers anything missed during the dead window.
116939
+ //
116940
+ // This does NOT mis-fire on healthy idle connections. Verified against
116941
+ // node-sdk 1.67.0 source (WSClient liveness):
116942
+ // - clearLiveness() is called on EVERY inbound frame (incl. the server's
116943
+ // pong) — a live connection cancels the watchdog within ms, so an
116944
+ // idle-but-healthy socket that still answers the ~120s server ping is
116945
+ // never killed.
116946
+ // - armLiveness() only (re)arms for pingTimeout SECONDS after each ping and
116947
+ // is a NO-OP when pingTimeout is unset → unsetting it removes half-open
116948
+ // detection ENTIRELY (no 'close' event → no reconnect → no gap-fill).
116949
+ // A silently-half-open WS on a KNOWN chat would then drop an @ that
116950
+ // neither reconnect-gap-fill nor the (now targeted) steady-state
116951
+ // discovery — which pulls 0 for already-known chats — could recover.
116952
+ //
116953
+ // So this stays as the half-open safety net; the discovery-storm fix is
116954
+ // orthogonal and SAFE precisely because this net still triggers reconnect.
116135
116955
  wsConfig: { pingTimeout: 60 }
116136
116956
  });
116137
116957
  channel.on("message", (msg) => {
@@ -116269,8 +117089,12 @@ var ChannelClient = class {
116269
117089
  * profile. Reactions are intentionally skipped because gap-fill only needs
116270
117090
  * message IDs and mentions.
116271
117091
  */
116272
- async gapFill(disconnectAt, log, chatIdsOverride) {
116273
- const MAX_GAP_FILL_WINDOW_MS = 5 * 60 * 1e3;
117092
+ async gapFill(disconnectAt, log, chatIdsOverride, minWindowMs) {
117093
+ const MAX_GAP_FILL_WINDOW_MS = Math.min(
117094
+ Math.max(5 * 60 * 1e3, minWindowMs ?? 0),
117095
+ // ≥5 minutes, or the caller's floor
117096
+ UNRESOLVED_WINDOW_MAX_AGE_MS
117097
+ );
116274
117098
  const BUFFER_MS = 3e4;
116275
117099
  const now = Date.now();
116276
117100
  const larkCli = this.opts.larkCliPath ?? "lark-cli";
@@ -116482,14 +117306,23 @@ var ChannelClient = class {
116482
117306
  if (this.openChatDiscoveryTimer) return;
116483
117307
  const intervalMs = resolveOpenChatDiscoveryMs(this.opts.openChatDiscoveryMs);
116484
117308
  if (intervalMs <= 0) return;
116485
- void this.discoverOpenChatsAndGapFill(log);
116486
- this.openChatDiscoveryTimer = setInterval(() => {
117309
+ const firstDelay = Math.min(this.openChatDiscoveryJitterMs, intervalMs);
117310
+ const startTimer = setTimeout(() => {
116487
117311
  void this.discoverOpenChatsAndGapFill(log);
116488
- }, intervalMs);
116489
- this.openChatDiscoveryTimer.unref?.();
117312
+ this.openChatDiscoveryTimer = setInterval(() => {
117313
+ void this.discoverOpenChatsAndGapFill(log);
117314
+ }, intervalMs);
117315
+ this.openChatDiscoveryTimer.unref?.();
117316
+ }, firstDelay);
117317
+ startTimer.unref?.();
117318
+ this.openChatDiscoveryTimer = startTimer;
116490
117319
  }
116491
117320
  async discoverOpenChatsAndGapFill(log) {
116492
117321
  if (this.closed || this.openChatDiscoveryRunning) return;
117322
+ if (this.openChatDiscoverySkips > 0) {
117323
+ this.openChatDiscoverySkips--;
117324
+ return;
117325
+ }
116493
117326
  this.openChatDiscoveryRunning = true;
116494
117327
  try {
116495
117328
  const larkCli = this.opts.larkCliPath ?? "lark-cli";
@@ -116498,6 +117331,7 @@ var ChannelClient = class {
116498
117331
  let fetched = 0;
116499
117332
  let newlyLearned = 0;
116500
117333
  const discoveredChatIds = /* @__PURE__ */ new Set();
117334
+ const newChatIds = /* @__PURE__ */ new Set();
116501
117335
  for (let page = 0; page < 10 && !this.closed; page++) {
116502
117336
  const args = [
116503
117337
  "im",
@@ -116521,7 +117355,10 @@ var ChannelClient = class {
116521
117355
  discoveredChatIds.add(chatId);
116522
117356
  const before = this.recentlySeenChatIds.size;
116523
117357
  this.noteSeenChat(chatId);
116524
- if (this.recentlySeenChatIds.size > before) newlyLearned++;
117358
+ if (this.recentlySeenChatIds.size > before) {
117359
+ newlyLearned++;
117360
+ newChatIds.add(chatId);
117361
+ }
116525
117362
  }
116526
117363
  const data = parsed && typeof parsed === "object" ? parsed["data"] : void 0;
116527
117364
  const hasMore = Boolean(
@@ -116535,17 +117372,34 @@ var ChannelClient = class {
116535
117372
  `open-chat discovery: learned ${newlyLearned} new chat(s) (known=${this.recentlySeenChatIds.size}, fetched=${fetched})`
116536
117373
  );
116537
117374
  }
116538
- if (discoveredChatIds.size > 0) {
116539
- const lookbackMs = this.openChatDiscoveryBootstrapped ? OPEN_CHAT_DISCOVERY_LOOKBACK_MS : OPEN_CHAT_DISCOVERY_BOOTSTRAP_LOOKBACK_MS;
116540
- this.openChatDiscoveryBootstrapped = true;
117375
+ const isBootstrap = !this.openChatDiscoveryBootstrapped;
117376
+ const targetChatIds = isBootstrap ? new Set(discoveredChatIds) : /* @__PURE__ */ new Set([
117377
+ ...newChatIds,
117378
+ ...[...discoveredChatIds].filter((c) => this.unresolvedGapWindowByChat.has(c))
117379
+ ]);
117380
+ this.openChatDiscoveryBootstrapped = true;
117381
+ if (targetChatIds.size > 0) {
117382
+ const intervalMs = resolveOpenChatDiscoveryMs(this.opts.openChatDiscoveryMs);
117383
+ const targetedLookbackMs = intervalMs + OPEN_CHAT_DISCOVERY_LOOKBACK_BUFFER_MS;
117384
+ const lookbackMs = isBootstrap ? OPEN_CHAT_DISCOVERY_BOOTSTRAP_LOOKBACK_MS : targetedLookbackMs;
116541
117385
  await this.gapFill(
116542
117386
  Date.now() - lookbackMs,
116543
117387
  log,
116544
- discoveredChatIds
117388
+ targetChatIds,
117389
+ isBootstrap ? void 0 : targetedLookbackMs
116545
117390
  );
116546
117391
  }
117392
+ this.openChatDiscoveryFailures = 0;
117393
+ this.openChatDiscoverySkips = 0;
116547
117394
  } catch (e) {
116548
- log(`open-chat discovery failed: ${e instanceof Error ? e.message : String(e)}`);
117395
+ this.openChatDiscoveryFailures = Math.min(
117396
+ this.openChatDiscoveryFailures + 1,
117397
+ OPEN_CHAT_DISCOVERY_MAX_BACKOFF_CYCLES
117398
+ );
117399
+ this.openChatDiscoverySkips = 2 ** (this.openChatDiscoveryFailures - 1);
117400
+ log(
117401
+ `open-chat discovery failed (backing off ${this.openChatDiscoverySkips} cycle(s)): ${e instanceof Error ? e.message : String(e)}`
117402
+ );
116549
117403
  } finally {
116550
117404
  this.openChatDiscoveryRunning = false;
116551
117405
  }
@@ -116621,25 +117475,44 @@ var ChannelClient = class {
116621
117475
  if (this.seenMessageIds.size === before) return;
116622
117476
  void this.persistSeenMessageIds();
116623
117477
  }
116624
- async persistSeenMessageIds() {
116625
- const file = this.seenMessagesPath();
116626
- if (!file) return;
116627
- const messages = [...this.seenMessageIds].slice(-SEEN_MESSAGES_LIMIT);
117478
+ /**
117479
+ * Atomic JSON write: serialize to a UNIQUE temp file, then `rename` over the
117480
+ * destination. Two concerns motivate this:
117481
+ * 1. Atomicity `rename` is atomic on a POSIX filesystem, so a reader (or a
117482
+ * crash) never observes a half-written file. The persist methods are
117483
+ * fire-and-forget (`void persist…`), so under a multi-bot storm several
117484
+ * writes to the SAME path can overlap; a plain `writeFile` interleaves
117485
+ * their bytes → the "Bad control character in string literal" JSON
117486
+ * corruption we saw. With tmp+rename each write lands whole-or-not-at-all.
117487
+ * 2. Per-write unique tmp name — a fixed `${file}.tmp` would itself be raced
117488
+ * by two concurrent writers. The pid + monotonic counter suffix gives each
117489
+ * in-flight write its own tmp so they can't clobber each other before the
117490
+ * rename. Best-effort cleanup on failure; losing the cache is non-fatal.
117491
+ */
117492
+ async atomicWriteJson(file, value) {
117493
+ const tmp = `${file}.${process.pid}.${this.atomicWriteSeq++}.tmp`;
116628
117494
  try {
116629
117495
  await mkdir(path3.dirname(file), { recursive: true });
116630
- await writeFile(file, JSON.stringify(messages, null, 2), "utf8");
117496
+ await writeFile(tmp, JSON.stringify(value, null, 2), "utf8");
117497
+ await rename(tmp, file);
116631
117498
  } catch {
117499
+ try {
117500
+ await unlink(tmp);
117501
+ } catch {
117502
+ }
116632
117503
  }
116633
117504
  }
117505
+ async persistSeenMessageIds() {
117506
+ const file = this.seenMessagesPath();
117507
+ if (!file) return;
117508
+ const messages = [...this.seenMessageIds].slice(-SEEN_MESSAGES_LIMIT);
117509
+ await this.atomicWriteJson(file, messages);
117510
+ }
116634
117511
  async persistRecentlySeenChatIds() {
116635
117512
  const file = this.learnedChatsPath();
116636
117513
  if (!file) return;
116637
117514
  const chats = [...this.recentlySeenChatIds].sort().slice(-LEARNED_CHATS_LIMIT);
116638
- try {
116639
- await mkdir(path3.dirname(file), { recursive: true });
116640
- await writeFile(file, JSON.stringify(chats, null, 2), "utf8");
116641
- } catch {
116642
- }
117515
+ await this.atomicWriteJson(file, chats);
116643
117516
  }
116644
117517
  async addProcessingReaction(messageId) {
116645
117518
  if (this.processingReactions.has(messageId)) return;
@@ -117200,7 +118073,7 @@ var CardRenderer = class {
117200
118073
  };
117201
118074
 
117202
118075
  // src/claude/sessionStore.ts
117203
- import { rename, readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2, copyFile } from "node:fs/promises";
118076
+ import { rename as rename2, readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2, copyFile } from "node:fs/promises";
117204
118077
  import { dirname } from "node:path";
117205
118078
  var STORE_VERSION = 2;
117206
118079
  var TOUCH_DEBOUNCE_MS = 1e3;
@@ -117427,7 +118300,7 @@ var SessionStore = class _SessionStore {
117427
118300
  const tmpPath = `${this.#filePath}.tmp`;
117428
118301
  await mkdir2(dirname(this.#filePath), { recursive: true });
117429
118302
  await writeFile2(tmpPath, json2, "utf8");
117430
- await rename(tmpPath, this.#filePath);
118303
+ await rename2(tmpPath, this.#filePath);
117431
118304
  }
117432
118305
  };
117433
118306
  function isStoredRecord(value) {
@@ -117438,8 +118311,8 @@ function isStoredRecord(value) {
117438
118311
 
117439
118312
  // src/bridge/handler.ts
117440
118313
  import child_process from "node:child_process";
117441
- import fs7 from "node:fs/promises";
117442
- import path10 from "node:path";
118314
+ import fs6 from "node:fs/promises";
118315
+ import path9 from "node:path";
117443
118316
 
117444
118317
  // src/lark/message.ts
117445
118318
  var AT_PLACEHOLDER_RE = /@_\w+\s*/g;
@@ -117810,7 +118683,7 @@ function renderStateContract(stateFilePath) {
117810
118683
  "- card_title/card_color: \u517C\u5BB9\u5B57\u6BB5; \u9ED8\u8BA4 CardKit \u4E0D\u6E32\u67D3\u9876\u90E8\u6807\u9898\u8272\u6761,legacy/fallback \u5361\u7247\u8DEF\u5F84\u53EF\u80FD\u4F7F\u7528",
117811
118684
  "- image_blocks: \u53EF\u9009\u56FE\u7247\u9884\u89C8\u5757\u6570\u7EC4,\u6700\u591A 4 \u4E2A\u3002\u6BCF\u9879 `{img_key, alt?, title?, mode?, preview?}`; `img_key` \u5FC5\u987B\u662F\u5DF2\u4E0A\u4F20/\u53EF\u7528\u4E8E\u5361\u7247\u7684 Feishu \u56FE\u7247 key,`alt` \u7701\u7565\u65F6 bridge \u9ED8\u8BA4\u201C\u56FE\u7247\u9884\u89C8\u201D,`mode` \u53EA\u5141\u8BB8 `crop_center`/`fit_horizontal` \u5E76\u6620\u5C04\u5230 Card JSON 2.0 `scale_type`,`preview` \u9ED8\u8BA4 true\u3002bridge \u4E0D\u8D1F\u8D23\u4E0B\u8F7D/\u4E0A\u4F20/\u9009\u62E9\u56FE\u7247;\u8FD9\u4E9B\u7531\u4F60\u7528 lark-cli \u7B49\u5DE5\u5177\u5148\u5B8C\u6210\u3002",
117812
118685
  '- content_blocks: \u53EF\u9009\u6709\u5E8F\u6B63\u6587\u5757\u6570\u7EC4,\u6700\u591A 12 \u4E2A block\u3001\u6700\u591A 4 \u4E2A image block\u3002\u53EA\u652F\u6301\u7A84 union:`{type:"markdown", content}` \u548C `{type:"image", img_key, alt?, title?, mode?, preview?}`;\u4E0D\u652F\u6301 raw card JSON\u3002\u7528\u4E8E\u6B63\u6587\u4E0E\u56FE\u7247\u4EA4\u9519\u6392\u7248,\u4F8B\u5982 markdown -> image -> markdown -> image\u3002\u82E5 `content_blocks` \u975E\u7A7A,bridge \u4EE5\u5B83\u4F5C\u4E3A\u4E3B\u6B63\u6587\u5E76\u5FFD\u7565 `last_message` + `image_blocks` \u7684\u6B63\u6587\u6E32\u67D3,\u907F\u514D\u91CD\u590D;\u82E5\u7701\u7565\u5219\u4FDD\u6301\u65E7 `last_message` + `image_blocks` \u884C\u4E3A\u3002',
117813
- '- response_surface: \u53EF\u9009\u8986\u76D6\u5B57\u6BB5,\u5F62\u5982 `{mode:"card"|"post"|"hybrid", primary?:"card"|"post", post?:{mentions:[{user_id,label?}]}, card?:{compact?:boolean, capabilities?:[...]}}`\u3002\u9ED8\u8BA4\u53EF\u4E0D\u5199;\u65E0\u663E\u5F0F card \u610F\u56FE\u65F6 bridge \u6309 CardKit \u6D41\u5F0F\u5361\u7247\u5904\u7406,\u6700\u7EC8\u6536\u6210\u5E72\u51C0\u603B\u7ED3\u5361\u3002\u9700\u8981\u771F\u5B9E @ \u65F6\u628A\u76EE\u6807\u5199\u5165 `post.mentions`;\u4E0D\u8981\u5199 raw Feishu post/card JSON\u3002',
118686
+ "- response_surface: \u53EF\u9009\u8986\u76D6\u5B57\u6BB5,\u4E3B\u8981\u7528\u4E8E `{post:{mentions:[{user_id,label?}]}}` late peer @\u3002\u9ED8\u8BA4\u53EF\u4E0D\u5199;bridge \u6309 CardKit \u6D41\u5F0F\u5361\u7247\u5904\u7406,\u6700\u7EC8\u6536\u6210\u5E72\u51C0\u603B\u7ED3\u5361\u3002\u65E7 `mode`/`primary` \u4EC5\u517C\u5BB9\u89E3\u6790,\u4E0D\u518D\u9009\u62E9 post-only/hybrid \u4E3B\u54CD\u5E94\u9762\u3002\u9700\u8981\u771F\u5B9E @ \u65F6\u628A\u76EE\u6807\u5199\u5165 `post.mentions`;\u4E0D\u8981\u5199 raw Feishu post/card JSON\u3002",
117814
118687
  "- scheduled reply / daily social ops review card \u7B49\u9700\u8981\u201C\u5E73\u53F0\u6B63\u6587 + \u5339\u914D\u56FE\u7247\u201D\u540C\u6BB5\u76F8\u90BB\u5C55\u793A\u7684\u573A\u666F,\u5E94\u5148\u53D6\u5F97\u5404\u56FE\u7247 `img_key`,\u518D\u5199 `content_blocks` \u4E3A `\u5E73\u53F0 markdown -> \u5BF9\u5E94 image -> \u4E0B\u4E2A\u5E73\u53F0 markdown -> \u5BF9\u5E94 image`;\u4E0D\u8981\u7528\u5355\u72EC\u8BDD\u9898\u56FE\u7247\u6D88\u606F\u6216\u5C3E\u90E8 `image_blocks` \u4EE3\u66FF\u9A8C\u6536\u9762\u3002",
117815
118688
  "- dev_url / mr_url / \u5176\u4F59\u4E1A\u52A1\u5B57\u6BB5:\u81EA\u7531\u5199\u5165,bridge \u4E0D\u611F\u77E5\u5176\u4E1A\u52A1\u542B\u4E49;\u8981\u8BA9\u8FD0\u8425\u770B\u5230,\u8BF7\u5199\u8FDB last_message",
117816
118689
  "- updated_at: ISO 8601 timestamp",
@@ -118622,17 +119495,11 @@ function defaultResponseSurfacePrototypeConfig() {
118622
119495
  enabled: true,
118623
119496
  allowed_chats: [],
118624
119497
  allowed_threads: [],
118625
- lazy_card_creation: true,
118626
119498
  kill_switch: false,
118627
119499
  post_outbound_enabled: true,
118628
119500
  cardkit_streaming_enabled: true,
118629
119501
  allow_agent_mentions: true,
118630
- allowed_mention_open_ids: [],
118631
- max_posts_per_turn: 1,
118632
- max_posts_per_window: 4,
118633
- post_window_ms: 6e4,
118634
- max_post_attempts: 3,
118635
- text_threshold_chars: 1200
119502
+ allowed_mention_open_ids: []
118636
119503
  };
118637
119504
  }
118638
119505
  var DEFAULT_RESPONSE_SURFACE_PROTOTYPE = defaultResponseSurfacePrototypeConfig();
@@ -118640,17 +119507,11 @@ var responseSurfacePrototypeConfigDefaults = () => ({
118640
119507
  enabled: true,
118641
119508
  allowed_chats: [],
118642
119509
  allowed_threads: [],
118643
- lazy_card_creation: true,
118644
119510
  kill_switch: false,
118645
119511
  post_outbound_enabled: true,
118646
119512
  cardkit_streaming_enabled: true,
118647
119513
  allow_agent_mentions: true,
118648
- allowed_mention_open_ids: [],
118649
- max_posts_per_turn: 1,
118650
- max_posts_per_window: 4,
118651
- post_window_ms: 6e4,
118652
- max_post_attempts: 3,
118653
- text_threshold_chars: 1200
119514
+ allowed_mention_open_ids: []
118654
119515
  });
118655
119516
  var ResponseSurfaceModeSchema = external_exports.enum(["card", "post", "hybrid"]);
118656
119517
  var ResponseSurfacePrimarySchema = external_exports.enum(["card", "post"]);
@@ -118707,12 +119568,6 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118707
119568
  * all threads are allowed by this gate.
118708
119569
  */
118709
119570
  allowed_threads: external_exports.array(external_exports.string().min(1)).default([]),
118710
- /**
118711
- * Historical post-first gate retained for config compatibility. The default
118712
- * runtime no longer uses post-first lazy card creation; CardKit is the main
118713
- * surface and legacy cards remain the visible fallback.
118714
- */
118715
- lazy_card_creation: external_exports.boolean().default(true),
118716
119571
  /**
118717
119572
  * Runtime kill switch for emergency rollback. When true, every response
118718
119573
  * surface post path is treated as disabled even if enabled/allowlists are
@@ -118741,32 +119596,7 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118741
119596
  * may choose mention targets; non-empty narrows mentions to this exact set.
118742
119597
  * Keep real IDs in private bot config, never in public docs/tests.
118743
119598
  */
118744
- allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([]),
118745
- /**
118746
- * Historical post dispatch cap retained for schema compatibility and
118747
- * isolated dispatcher tests.
118748
- */
118749
- max_posts_per_turn: external_exports.number().int().min(0).max(10).default(1),
118750
- /**
118751
- * Sliding-window hard cap for real post sends within one bot/chat/thread
118752
- * runtime scope. This is enforced in the production handler before a post
118753
- * transport call is attempted; exhausted windows degrade to visible cards.
118754
- */
118755
- max_posts_per_window: external_exports.number().int().min(0).max(100).default(4),
118756
- /**
118757
- * Sliding-window duration for max_posts_per_window.
118758
- */
118759
- post_window_ms: external_exports.number().int().min(1e3).max(864e5).default(6e4),
118760
- /**
118761
- * Max attempts for one logical post. Retry classification is intentionally
118762
- * narrow in PR3: only 5xx errors are retryable.
118763
- */
118764
- max_post_attempts: external_exports.number().int().min(1).max(5).default(3),
118765
- /**
118766
- * Historical threshold for removed lazy card creation experiments. Bounded
118767
- * so older config remains parseable without growing arbitrary business rules.
118768
- */
118769
- text_threshold_chars: external_exports.number().int().min(1).max(2e4).default(1200)
119599
+ allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([])
118770
119600
  }).default(responseSurfacePrototypeConfigDefaults);
118771
119601
  function isResponseSurfacePrototypeAllowlisted(config, facts) {
118772
119602
  if (!config?.enabled) return false;
@@ -118785,7 +119615,7 @@ function isResponseSurfaceMentionAllowed(config, userId) {
118785
119615
  return config.allowed_mention_open_ids.includes(userId);
118786
119616
  }
118787
119617
  function shouldProvideResponseSurfacePostClient(config) {
118788
- return !!(config?.enabled && !config.kill_switch && config.post_outbound_enabled && config.max_posts_per_turn >= 1 && config.max_posts_per_window >= 1);
119618
+ return !!(config?.enabled && !config.kill_switch && config.post_outbound_enabled);
118789
119619
  }
118790
119620
  function shouldProvideResponseSurfaceCardKitClient(config) {
118791
119621
  return !!(config?.enabled && !config.kill_switch && config.cardkit_streaming_enabled);
@@ -119828,697 +120658,6 @@ function derivePostIdempotencyKey(input) {
119828
120658
  return key;
119829
120659
  }
119830
120660
 
119831
- // src/bridge/postFile.ts
119832
- import fs6 from "node:fs/promises";
119833
- import path9 from "node:path";
119834
- var PostLedgerStatusSchema = external_exports.enum([
119835
- "planned",
119836
- "pending",
119837
- "sent",
119838
- "failed",
119839
- "fallback_visible",
119840
- "policy_blocked"
119841
- ]);
119842
- var POST_LEDGER_TRANSITIONS = {
119843
- planned: ["pending", "fallback_visible", "policy_blocked"],
119844
- pending: ["sent", "failed", "fallback_visible", "policy_blocked"],
119845
- sent: [],
119846
- failed: ["fallback_visible"],
119847
- fallback_visible: [],
119848
- policy_blocked: []
119849
- };
119850
- function canTransitionPostStatus(from, to) {
119851
- return from === to || POST_LEDGER_TRANSITIONS[from].includes(to);
119852
- }
119853
- function assertPostStatusTransition(from, to) {
119854
- if (!canTransitionPostStatus(from, to)) {
119855
- throw new Error(`invalid post ledger transition: ${from} -> ${to}`);
119856
- }
119857
- }
119858
- var PostAttemptSchema = external_exports.object({
119859
- attemptedAt: external_exports.string(),
119860
- status: external_exports.enum(["sent", "failed"]),
119861
- retryable: external_exports.boolean().default(false),
119862
- error: external_exports.string().optional(),
119863
- code: external_exports.string().optional()
119864
- });
119865
- var PostLedgerEntrySchema = external_exports.object({
119866
- idempotencyKey: external_exports.string().min(1).max(64),
119867
- status: PostLedgerStatusSchema,
119868
- botId: external_exports.string().min(1),
119869
- chatId: external_exports.string().min(1),
119870
- threadId: external_exports.string().min(1),
119871
- replyToMessageId: external_exports.string().min(1),
119872
- role: external_exports.enum(["primary", "secondary", "fallback"]),
119873
- logicalIndex: external_exports.number().int().nonnegative(),
119874
- contentDigest: external_exports.string().min(1),
119875
- mentionCount: external_exports.number().int().nonnegative().default(0),
119876
- postMessageId: external_exports.string().optional(),
119877
- fallbackCardMessageId: external_exports.string().optional(),
119878
- error: external_exports.string().optional(),
119879
- attempts: external_exports.array(PostAttemptSchema).default([]),
119880
- createdAt: external_exports.string(),
119881
- updatedAt: external_exports.string()
119882
- });
119883
- var PostFileSchema = external_exports.object({
119884
- version: external_exports.literal(1),
119885
- posts: external_exports.array(PostLedgerEntrySchema).max(50)
119886
- });
119887
- function emptyPostFile() {
119888
- return { version: 1, posts: [] };
119889
- }
119890
- function summarizePostLedger(data) {
119891
- const summary = {
119892
- total: 0,
119893
- planned: 0,
119894
- pending: 0,
119895
- sent: 0,
119896
- failed: 0,
119897
- fallback_visible: 0,
119898
- policy_blocked: 0,
119899
- withPostMessageId: 0,
119900
- withFallbackCardMessageId: 0
119901
- };
119902
- for (const post of data?.posts ?? []) {
119903
- summary.total += 1;
119904
- summary[post.status] += 1;
119905
- if (post.postMessageId) summary.withPostMessageId += 1;
119906
- if (post.fallbackCardMessageId) summary.withFallbackCardMessageId += 1;
119907
- }
119908
- return summary;
119909
- }
119910
- function postDirOf(worktreePath) {
119911
- return path9.join(worktreePath, ".larkway");
119912
- }
119913
- function postFilePathOf(worktreePath) {
119914
- return path9.join(postDirOf(worktreePath), "post.json");
119915
- }
119916
- async function readPostFile(worktreePath) {
119917
- const file = postFilePathOf(worktreePath);
119918
- let raw;
119919
- try {
119920
- raw = await fs6.readFile(file, "utf8");
119921
- } catch (err) {
119922
- if (err.code === "ENOENT") return null;
119923
- console.warn(`[postFile] read ${file} failed:`, err);
119924
- return null;
119925
- }
119926
- let parsed;
119927
- try {
119928
- parsed = JSON.parse(raw);
119929
- } catch (err) {
119930
- console.warn(`[postFile] ${file} not valid JSON:`, err);
119931
- return null;
119932
- }
119933
- const result = PostFileSchema.safeParse(parsed);
119934
- if (!result.success) {
119935
- console.warn(`[postFile] ${file} failed schema validation:`, result.error.issues);
119936
- return null;
119937
- }
119938
- return result.data;
119939
- }
119940
- async function writePostFile(worktreePath, data) {
119941
- const dir = postDirOf(worktreePath);
119942
- const file = postFilePathOf(worktreePath);
119943
- await fs6.mkdir(dir, { recursive: true });
119944
- const parsed = PostFileSchema.parse(data);
119945
- const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
119946
- await fs6.writeFile(tmp, JSON.stringify(parsed, null, 2), "utf8");
119947
- try {
119948
- await fs6.rename(tmp, file);
119949
- } catch (err) {
119950
- await fs6.rm(tmp, { force: true }).catch(() => {
119951
- });
119952
- throw err;
119953
- }
119954
- }
119955
- async function upsertPostLedgerEntry(worktreePath, entry) {
119956
- const existing = await readPostFile(worktreePath) ?? emptyPostFile();
119957
- const idx = existing.posts.findIndex((p) => p.idempotencyKey === entry.idempotencyKey);
119958
- const nextPosts = [...existing.posts];
119959
- if (idx >= 0) {
119960
- assertPostStatusTransition(nextPosts[idx].status, entry.status);
119961
- nextPosts[idx] = entry;
119962
- } else {
119963
- nextPosts.push(entry);
119964
- }
119965
- const next = { version: 1, posts: nextPosts };
119966
- await writePostFile(worktreePath, next);
119967
- return next;
119968
- }
119969
- var DEFAULT_POST_RECONCILE_MIN_AGE_MS = 6e4;
119970
- function timestampAgeMs(iso, nowMs) {
119971
- if (!Number.isFinite(nowMs)) return null;
119972
- const then = Date.parse(iso);
119973
- if (!Number.isFinite(then)) return null;
119974
- return nowMs - then;
119975
- }
119976
- function reconcilePostEntry(entry, opts) {
119977
- if (entry.botId !== opts.botId) {
119978
- return { entry, changed: false, sent: false, needsVisibleFallback: false };
119979
- }
119980
- if (entry.status === "sent" || entry.status === "fallback_visible" || entry.status === "policy_blocked") {
119981
- return { entry, changed: false, sent: false, needsVisibleFallback: false };
119982
- }
119983
- const now = opts.now();
119984
- const ageMs = timestampAgeMs(entry.updatedAt, Date.parse(now));
119985
- if (ageMs == null || ageMs < opts.minAgeMs) {
119986
- return { entry, changed: false, sent: false, needsVisibleFallback: false };
119987
- }
119988
- if (entry.postMessageId) {
119989
- return {
119990
- entry: {
119991
- ...entry,
119992
- status: "sent",
119993
- error: void 0,
119994
- updatedAt: now,
119995
- attempts: [
119996
- ...entry.attempts,
119997
- {
119998
- attemptedAt: now,
119999
- status: "sent",
120000
- retryable: false
120001
- }
120002
- ]
120003
- },
120004
- changed: true,
120005
- sent: true,
120006
- needsVisibleFallback: false
120007
- };
120008
- }
120009
- return {
120010
- entry,
120011
- changed: false,
120012
- sent: false,
120013
- needsVisibleFallback: true
120014
- };
120015
- }
120016
- function reconcilePostLedgerEntries(data, opts) {
120017
- const normalizedOpts = {
120018
- botId: opts.botId,
120019
- minAgeMs: opts.minAgeMs ?? DEFAULT_POST_RECONCILE_MIN_AGE_MS,
120020
- now: opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
120021
- };
120022
- let sent = 0;
120023
- const fallbackVisible = 0;
120024
- let needsVisibleFallback = 0;
120025
- let skippedLive = 0;
120026
- const visibleFallbackCandidates = [];
120027
- const posts = data.posts.map((post) => {
120028
- const reconciled = reconcilePostEntry(post, normalizedOpts);
120029
- if (reconciled.changed) {
120030
- if (reconciled.sent) sent += 1;
120031
- } else if (reconciled.needsVisibleFallback) {
120032
- needsVisibleFallback += 1;
120033
- visibleFallbackCandidates.push(reconciled.entry);
120034
- } else if (post.botId === normalizedOpts.botId && (post.status === "planned" || post.status === "pending" || post.status === "failed")) {
120035
- skippedLive += 1;
120036
- }
120037
- return reconciled.entry;
120038
- });
120039
- const changed = sent > 0 || fallbackVisible > 0;
120040
- return {
120041
- file: changed ? { version: 1, posts } : data,
120042
- result: { changed, sent, fallbackVisible, needsVisibleFallback, skippedLive },
120043
- visibleFallbackCandidates
120044
- };
120045
- }
120046
- async function reconcilePostFileOrphans(worktreePath, opts) {
120047
- const existing = await readPostFile(worktreePath);
120048
- if (!existing) {
120049
- return {
120050
- changed: false,
120051
- sent: 0,
120052
- fallbackVisible: 0,
120053
- needsVisibleFallback: 0,
120054
- skippedLive: 0,
120055
- visibleFallbackCandidates: []
120056
- };
120057
- }
120058
- const { file, result, visibleFallbackCandidates } = reconcilePostLedgerEntries(existing, opts);
120059
- if (result.changed) {
120060
- await writePostFile(worktreePath, file);
120061
- }
120062
- return { ...result, visibleFallbackCandidates };
120063
- }
120064
- async function markPostLedgerFallbackVisible(worktreePath, idempotencyKey2, opts) {
120065
- const existing = await readPostFile(worktreePath) ?? emptyPostFile();
120066
- const idx = existing.posts.findIndex((post) => post.idempotencyKey === idempotencyKey2);
120067
- if (idx < 0) {
120068
- throw new Error(`post ledger entry not found: ${idempotencyKey2}`);
120069
- }
120070
- const current = existing.posts[idx];
120071
- assertPostStatusTransition(current.status, "fallback_visible");
120072
- const now = opts.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
120073
- const nextPosts = [...existing.posts];
120074
- nextPosts[idx] = {
120075
- ...current,
120076
- status: "fallback_visible",
120077
- fallbackCardMessageId: opts.fallbackCardMessageId,
120078
- error: opts.error,
120079
- updatedAt: now,
120080
- attempts: [
120081
- ...current.attempts,
120082
- {
120083
- attemptedAt: now,
120084
- status: "failed",
120085
- retryable: false,
120086
- code: "orphan_reconcile",
120087
- error: opts.error
120088
- }
120089
- ]
120090
- };
120091
- const next = { version: 1, posts: nextPosts };
120092
- await writePostFile(worktreePath, next);
120093
- return next;
120094
- }
120095
- async function markPostLedgerPolicyBlockedVisible(worktreePath, idempotencyKey2, opts) {
120096
- const existing = await readPostFile(worktreePath) ?? emptyPostFile();
120097
- const idx = existing.posts.findIndex((post) => post.idempotencyKey === idempotencyKey2);
120098
- if (idx < 0) {
120099
- throw new Error(`post ledger entry not found: ${idempotencyKey2}`);
120100
- }
120101
- const current = existing.posts[idx];
120102
- assertPostStatusTransition(current.status, "policy_blocked");
120103
- const now = opts.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
120104
- const nextPosts = [...existing.posts];
120105
- nextPosts[idx] = {
120106
- ...current,
120107
- status: "policy_blocked",
120108
- fallbackCardMessageId: opts.fallbackCardMessageId,
120109
- error: opts.error,
120110
- updatedAt: now,
120111
- attempts: [
120112
- ...current.attempts,
120113
- {
120114
- attemptedAt: now,
120115
- status: "failed",
120116
- retryable: false,
120117
- code: "mention_policy_blocked",
120118
- error: opts.error
120119
- }
120120
- ]
120121
- };
120122
- const next = { version: 1, posts: nextPosts };
120123
- await writePostFile(worktreePath, next);
120124
- return next;
120125
- }
120126
-
120127
- // src/bridge/surfaceDispatcher.ts
120128
- function fullCard(input, reason) {
120129
- return {
120130
- card: input.baseCard,
120131
- reason,
120132
- visible: input.cardStarted || input.visibleFallbackAvailable
120133
- };
120134
- }
120135
- function hasCardOnlyPayload(state) {
120136
- return !!(state?.choices?.length || state?.image_blocks?.length || state?.content_blocks?.length);
120137
- }
120138
- function hasCardOnlyPayloadIn(input) {
120139
- return !!(hasCardOnlyPayload(input.state) || input.baseCard.choices?.length || input.baseCard.imageBlocks?.length || input.baseCard.contentBlocks?.length);
120140
- }
120141
- function compactAuditCard(input, post) {
120142
- const status = input.state?.status ?? (input.baseCard.success ? "ready" : "failed");
120143
- const title = input.baseCard.titleOverride ?? "Post \u5DF2\u53D1\u9001";
120144
- return {
120145
- success: input.baseCard.success,
120146
- failureReason: input.baseCard.failureReason,
120147
- titleOverride: title,
120148
- colorOverride: input.baseCard.colorOverride ?? "neutral",
120149
- finalText: `\u4E3B\u56DE\u590D\u5DF2\u901A\u8FC7 post \u53D1\u51FA\u3002
120150
- status: ${status}
120151
- post_message_id: ${post.messageId}
120152
- idempotency_key: ${post.idempotencyKey}`
120153
- };
120154
- }
120155
- function fallbackFailureCard(input, error) {
120156
- const reason = error instanceof Error ? error.message : String(error);
120157
- return {
120158
- ...input.baseCard,
120159
- success: false,
120160
- failureReason: `post outbound failed; visible card fallback used: ${reason}`
120161
- };
120162
- }
120163
- function policyBlockedCard(input) {
120164
- return {
120165
- ...input.baseCard,
120166
- success: false,
120167
- failureReason: "response_surface post mention target is blocked by policy; visible card fallback used"
120168
- };
120169
- }
120170
- function postText(input) {
120171
- const text = input.baseCard.finalText?.trim() || input.state?.last_message?.trim();
120172
- if (text) return text;
120173
- if (input.baseCard.success) return "\u5B8C\u6210";
120174
- return input.baseCard.failureReason ?? "\u6267\u884C\u5931\u8D25";
120175
- }
120176
- function postRole(input) {
120177
- const surface = input.state?.response_surface;
120178
- if (surface?.mode === "hybrid") return "primary";
120179
- return "primary";
120180
- }
120181
- function newLedgerEntry(input) {
120182
- return {
120183
- idempotencyKey: input.idempotencyKey,
120184
- status: input.status,
120185
- botId: input.facts.botId,
120186
- chatId: input.facts.chatId,
120187
- threadId: input.facts.threadId,
120188
- replyToMessageId: input.facts.replyToMessageId,
120189
- role: input.role,
120190
- logicalIndex: input.logicalIndex,
120191
- contentDigest: input.contentDigest,
120192
- mentionCount: input.mentionCount,
120193
- postMessageId: input.postMessageId,
120194
- error: input.error,
120195
- attempts: input.attempts ?? [],
120196
- createdAt: input.now,
120197
- updatedAt: input.now
120198
- };
120199
- }
120200
- async function writeLedger(input, entry) {
120201
- if (!input.worktreePath) return;
120202
- await upsertPostLedgerEntry(input.worktreePath, entry);
120203
- }
120204
- async function existingLedgerEntry(input, idempotencyKey2) {
120205
- if (!input.worktreePath) return null;
120206
- const ledger = await readPostFile(input.worktreePath);
120207
- return ledger?.posts.find((post) => post.idempotencyKey === idempotencyKey2) ?? null;
120208
- }
120209
- function sentResult(input, surface, post, reason) {
120210
- if (hasCardOnlyPayloadIn(input)) {
120211
- return {
120212
- card: input.baseCard,
120213
- reason: "post-sent-card-capability-required",
120214
- visible: true,
120215
- post
120216
- };
120217
- }
120218
- if (surface.mode === "hybrid" || input.cardStarted) {
120219
- return {
120220
- card: compactAuditCard(input, post),
120221
- reason: reason === "post-ledger-already-sent" ? "post-ledger-already-sent" : surface.mode === "hybrid" ? "hybrid-post-sent-compact-card" : reason,
120222
- visible: true,
120223
- post
120224
- };
120225
- }
120226
- return {
120227
- card: null,
120228
- reason,
120229
- visible: true,
120230
- post
120231
- };
120232
- }
120233
- async function ledgerSummaryFor(input) {
120234
- if (!input.worktreePath) return summarizePostLedger(null);
120235
- return summarizePostLedger(await readPostFile(input.worktreePath));
120236
- }
120237
- function emitSurfaceObservation(input) {
120238
- console.log(
120239
- "[response_surface.dispatch]",
120240
- JSON.stringify({
120241
- event: "response_surface.dispatch",
120242
- botId: input.facts.botId,
120243
- chatId: input.facts.chatId,
120244
- threadId: input.facts.threadId,
120245
- reason: input.result.reason,
120246
- visible: input.result.visible,
120247
- hasCard: !!input.result.card,
120248
- hasPost: !!input.result.post,
120249
- postMessageIdPresent: !!input.result.post?.messageId,
120250
- budget: input.result.budget ? {
120251
- allowed: input.result.budget.allowed,
120252
- used: input.result.budget.used,
120253
- limit: input.result.budget.limit,
120254
- windowMs: input.result.budget.windowMs,
120255
- resetAt: input.result.budget.resetAt,
120256
- reason: input.result.budget.reason
120257
- } : void 0,
120258
- ledger: input.ledger,
120259
- durationMs: input.durationMs
120260
- })
120261
- );
120262
- }
120263
- async function dispatchResponseSurfaceInner(input) {
120264
- const declaredSurface = input.state?.response_surface;
120265
- if (declaredSurface?.mode === "card" || declaredSurface?.primary === "card") {
120266
- return fullCard(input, "legacy-card-mode");
120267
- }
120268
- const surface = declaredSurface ?? { mode: "post", primary: "post" };
120269
- const cfg = input.prototypeConfig;
120270
- if (!cfg?.enabled) return fullCard(input, "prototype-disabled");
120271
- if (cfg.kill_switch) return fullCard(input, "kill-switch-active");
120272
- if (!isResponseSurfacePrototypeAllowlisted(cfg, {
120273
- chatId: input.facts.chatId,
120274
- threadId: input.facts.threadId
120275
- })) {
120276
- return fullCard(input, "not-allowlisted");
120277
- }
120278
- if (!cfg.post_outbound_enabled) return fullCard(input, "post-outbound-disabled");
120279
- if (cfg.max_posts_per_turn < 1) return fullCard(input, "post-outbound-disabled");
120280
- if (cfg.max_posts_per_window < 1) return fullCard(input, "post-rate-limit-exhausted");
120281
- if (!input.postOutboundAvailable || !input.postClient) {
120282
- return fullCard(input, "post-outbound-unavailable");
120283
- }
120284
- if (!input.visibleFallbackAvailable) {
120285
- return fullCard(input, "visible-fallback-unavailable");
120286
- }
120287
- if (!input.postLedgerAvailable || !input.worktreePath) {
120288
- return fullCard(input, "post-ledger-unavailable");
120289
- }
120290
- const mentions = surface.post?.mentions ?? [];
120291
- const blockedMention = mentions.find(
120292
- (mention) => !isResponseSurfaceMentionAllowed(cfg, mention.user_id)
120293
- );
120294
- const text = postText(input);
120295
- const policyDigest = digestPostContent(text);
120296
- const role = postRole(input);
120297
- const logicalIndex = 0;
120298
- const policyIdempotencyKey = derivePostIdempotencyKey({
120299
- botId: input.facts.botId,
120300
- threadId: input.facts.threadId,
120301
- triggerMessageId: input.facts.triggerMessageId,
120302
- role,
120303
- logicalIndex,
120304
- contentDigest: policyDigest
120305
- });
120306
- const now = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
120307
- if (blockedMention) {
120308
- const policyError = `mention target is not allowed by response surface policy: ${blockedMention.user_id}`;
120309
- if (input.livePost) {
120310
- try {
120311
- await input.postClient.updatePost(input.livePost.messageId, buildPostContent({ text }));
120312
- } catch (err) {
120313
- console.warn(
120314
- "[surface_dispatch] live post policy-blocked cleanup update failed:",
120315
- err
120316
- );
120317
- }
120318
- }
120319
- await writeLedger(
120320
- input,
120321
- newLedgerEntry({
120322
- status: "planned",
120323
- idempotencyKey: policyIdempotencyKey,
120324
- now,
120325
- facts: input.facts,
120326
- role,
120327
- logicalIndex,
120328
- contentDigest: policyDigest,
120329
- mentionCount: mentions.length,
120330
- error: policyError
120331
- })
120332
- );
120333
- return {
120334
- card: policyBlockedCard(input),
120335
- reason: "mention-policy-blocked",
120336
- visible: true,
120337
- post: {
120338
- idempotencyKey: policyIdempotencyKey,
120339
- role,
120340
- requiresPolicyLedgerMark: true,
120341
- policyError
120342
- }
120343
- };
120344
- }
120345
- const content = buildPostContent({
120346
- text,
120347
- mentions: mentions.map((mention) => ({
120348
- userId: mention.user_id,
120349
- label: mention.label
120350
- }))
120351
- });
120352
- const contentDigest = digestPostContent(content);
120353
- const idempotencyKey2 = input.livePost?.idempotencyKey ?? derivePostIdempotencyKey({
120354
- botId: input.facts.botId,
120355
- threadId: input.facts.threadId,
120356
- triggerMessageId: input.facts.triggerMessageId,
120357
- role,
120358
- logicalIndex,
120359
- contentDigest
120360
- });
120361
- const existing = await existingLedgerEntry(input, idempotencyKey2);
120362
- if (existing?.status === "sent" && existing.postMessageId) {
120363
- return sentResult(
120364
- input,
120365
- surface,
120366
- { idempotencyKey: idempotencyKey2, messageId: existing.postMessageId, role },
120367
- "post-ledger-already-sent"
120368
- );
120369
- }
120370
- if (existing?.status === "sent") {
120371
- return fullCard(input, "post-orphan-reconciled-fallback-card");
120372
- }
120373
- if (existing?.status === "fallback_visible") {
120374
- return {
120375
- card: fallbackFailureCard(
120376
- input,
120377
- existing.error ?? "post ledger already reconciled to visible fallback"
120378
- ),
120379
- reason: "post-orphan-reconciled-fallback-card",
120380
- visible: true,
120381
- post: { idempotencyKey: idempotencyKey2, role }
120382
- };
120383
- }
120384
- if (existing?.status === "policy_blocked") {
120385
- return {
120386
- card: policyBlockedCard(input),
120387
- reason: "mention-policy-blocked",
120388
- visible: true,
120389
- post: { idempotencyKey: idempotencyKey2, role }
120390
- };
120391
- }
120392
- if (existing) {
120393
- const error = existing.status === "failed" && existing.error ? existing.error : "orphaned post ledger entry reconciled without resend; visible card fallback used";
120394
- return {
120395
- card: fallbackFailureCard(input, error),
120396
- reason: "post-orphan-reconciled-fallback-card",
120397
- visible: true,
120398
- post: {
120399
- idempotencyKey: idempotencyKey2,
120400
- role,
120401
- requiresFallbackLedgerMark: true,
120402
- fallbackError: error
120403
- }
120404
- };
120405
- }
120406
- const budget = input.livePost ? void 0 : input.postBudget?.reserve();
120407
- if (budget && !budget.allowed) {
120408
- return {
120409
- ...fullCard(input, "post-rate-limit-exhausted"),
120410
- budget
120411
- };
120412
- }
120413
- await writeLedger(
120414
- input,
120415
- newLedgerEntry({
120416
- status: "planned",
120417
- idempotencyKey: idempotencyKey2,
120418
- now,
120419
- facts: input.facts,
120420
- role,
120421
- logicalIndex,
120422
- contentDigest,
120423
- mentionCount: mentions.length
120424
- })
120425
- );
120426
- await writeLedger(
120427
- input,
120428
- newLedgerEntry({
120429
- status: "pending",
120430
- idempotencyKey: idempotencyKey2,
120431
- now,
120432
- facts: input.facts,
120433
- role,
120434
- logicalIndex,
120435
- contentDigest,
120436
- mentionCount: mentions.length
120437
- })
120438
- );
120439
- try {
120440
- const sent = input.livePost ? await input.postClient.updatePost(input.livePost.messageId, content) : await input.postClient.createPostReply(input.facts.replyToMessageId, content, {
120441
- replyInThread: input.facts.replyInThread,
120442
- idempotencyKey: idempotencyKey2
120443
- });
120444
- await writeLedger(
120445
- input,
120446
- newLedgerEntry({
120447
- status: "sent",
120448
- idempotencyKey: idempotencyKey2,
120449
- now: input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
120450
- facts: input.facts,
120451
- role,
120452
- logicalIndex,
120453
- contentDigest,
120454
- mentionCount: mentions.length,
120455
- postMessageId: sent.messageId,
120456
- attempts: [
120457
- {
120458
- attemptedAt: input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
120459
- status: "sent",
120460
- retryable: false
120461
- }
120462
- ]
120463
- })
120464
- );
120465
- const post = { idempotencyKey: idempotencyKey2, messageId: sent.messageId, role };
120466
- return {
120467
- ...sentResult(input, surface, post, input.livePost ? "post-updated" : "post-sent"),
120468
- budget
120469
- };
120470
- } catch (err) {
120471
- const failedAt = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
120472
- const error = err instanceof Error ? err.message : String(err);
120473
- await writeLedger(
120474
- input,
120475
- newLedgerEntry({
120476
- status: "failed",
120477
- idempotencyKey: idempotencyKey2,
120478
- now: failedAt,
120479
- facts: input.facts,
120480
- role,
120481
- logicalIndex,
120482
- contentDigest,
120483
- mentionCount: mentions.length,
120484
- error,
120485
- attempts: [
120486
- {
120487
- attemptedAt: failedAt,
120488
- status: "failed",
120489
- retryable: false,
120490
- error
120491
- }
120492
- ]
120493
- })
120494
- );
120495
- return {
120496
- card: fallbackFailureCard(input, err),
120497
- reason: "post-failed-fallback-card",
120498
- visible: true,
120499
- post: {
120500
- idempotencyKey: idempotencyKey2,
120501
- role,
120502
- requiresFallbackLedgerMark: true,
120503
- fallbackError: error
120504
- },
120505
- budget
120506
- };
120507
- }
120508
- }
120509
- async function dispatchResponseSurface(input) {
120510
- const startedAt = Date.now();
120511
- const result = await dispatchResponseSurfaceInner(input);
120512
- const ledger = await ledgerSummaryFor(input);
120513
- emitSurfaceObservation({
120514
- facts: input.facts,
120515
- result,
120516
- ledger,
120517
- durationMs: Date.now() - startedAt
120518
- });
120519
- return result;
120520
- }
120521
-
120522
120661
  // src/bridge/handler.ts
120523
120662
  var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
120524
120663
  function execGit(cwd, args) {
@@ -120544,7 +120683,7 @@ stderr: ${stderr}`)
120544
120683
  }
120545
120684
  async function pathExists(p) {
120546
120685
  try {
120547
- await fs7.stat(p);
120686
+ await fs6.stat(p);
120548
120687
  return true;
120549
120688
  } catch {
120550
120689
  return false;
@@ -120562,7 +120701,7 @@ async function isWorktreeGitHealthy(worktreePath) {
120562
120701
  });
120563
120702
  }
120564
120703
  async function ensureRepoClone(basePath, url, token, label) {
120565
- const gitDir = path10.join(basePath, ".git");
120704
+ const gitDir = path9.join(basePath, ".git");
120566
120705
  if (await pathExists(gitDir)) {
120567
120706
  return;
120568
120707
  }
@@ -120572,15 +120711,15 @@ async function ensureRepoClone(basePath, url, token, label) {
120572
120711
  );
120573
120712
  }
120574
120713
  const uniq = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
120575
- const tmpScript = path10.join(basePath, "..", `.askpass-${uniq}.sh`);
120714
+ const tmpScript = path9.join(basePath, "..", `.askpass-${uniq}.sh`);
120576
120715
  const tokenEnvVar = `LARKWAY_GIT_TOKEN_${uniq.replace(/[^a-zA-Z0-9]/g, "_")}`;
120577
120716
  try {
120578
- await fs7.mkdir(path10.dirname(basePath), { recursive: true });
120717
+ await fs6.mkdir(path9.dirname(basePath), { recursive: true });
120579
120718
  const scriptContent = [
120580
120719
  "#!/bin/sh",
120581
120720
  `echo "\${${tokenEnvVar}}"`
120582
120721
  ].join("\n") + "\n";
120583
- await fs7.writeFile(tmpScript, scriptContent, { mode: 448, encoding: "utf8" });
120722
+ await fs6.writeFile(tmpScript, scriptContent, { mode: 448, encoding: "utf8" });
120584
120723
  console.log(`[bridge.handler] cloning ${label} into ${basePath} \u2026`);
120585
120724
  const env = {
120586
120725
  ...process.env,
@@ -120608,7 +120747,7 @@ stderr: ${stderr}`));
120608
120747
  console.log(`[bridge.handler] clone of ${label} complete.`);
120609
120748
  await execGit(basePath, ["remote", "set-url", "origin", url]);
120610
120749
  } finally {
120611
- await fs7.unlink(tmpScript).catch(() => {
120750
+ await fs6.unlink(tmpScript).catch(() => {
120612
120751
  });
120613
120752
  }
120614
120753
  }
@@ -120662,8 +120801,8 @@ var CORE_DENY_RULES = [
120662
120801
  "Bash(npm publish *)"
120663
120802
  ];
120664
120803
  async function writeWorktreeSettings(worktreePath, opts = {}) {
120665
- const dir = path10.join(worktreePath, ".claude");
120666
- await fs7.mkdir(dir, { recursive: true });
120804
+ const dir = path9.join(worktreePath, ".claude");
120805
+ await fs6.mkdir(dir, { recursive: true });
120667
120806
  const allow = Array.from(/* @__PURE__ */ new Set([...CORE_ALLOW_RULES, ...opts.allowExtra ?? []]));
120668
120807
  const settings = {
120669
120808
  permissions: {
@@ -120671,17 +120810,17 @@ async function writeWorktreeSettings(worktreePath, opts = {}) {
120671
120810
  deny: CORE_DENY_RULES
120672
120811
  }
120673
120812
  };
120674
- await fs7.writeFile(
120675
- path10.join(dir, "settings.local.json"),
120813
+ await fs6.writeFile(
120814
+ path9.join(dir, "settings.local.json"),
120676
120815
  JSON.stringify(settings, null, 2),
120677
120816
  "utf8"
120678
120817
  );
120679
120818
  }
120680
120819
  async function ensureNodeModules(worktreePath) {
120681
- const monorepDir = path10.join(worktreePath, "monorep");
120682
- const pkgJson = path10.join(monorepDir, "package.json");
120820
+ const monorepDir = path9.join(worktreePath, "monorep");
120821
+ const pkgJson = path9.join(monorepDir, "package.json");
120683
120822
  if (!await pathExists(pkgJson)) return;
120684
- const modulesMarker = path10.join(monorepDir, "node_modules", ".modules.yaml");
120823
+ const modulesMarker = path9.join(monorepDir, "node_modules", ".modules.yaml");
120685
120824
  if (await pathExists(modulesMarker)) return;
120686
120825
  const start = Date.now();
120687
120826
  try {
@@ -120937,7 +121076,7 @@ var BridgeHandler = class {
120937
121076
  throw new Error("agent_workspace runtime requires workspace path conventions");
120938
121077
  }
120939
121078
  }
120940
- const worktreePath = isAgentWorkspace ? path10.join(conventions.workspaceSessionsDir, threadId) : path10.join(conventions.worktreesDir, threadId);
121079
+ const worktreePath = isAgentWorkspace ? path9.join(conventions.workspaceSessionsDir, threadId) : path9.join(conventions.worktreesDir, threadId);
120941
121080
  const runCwd = isAgentWorkspace ? conventions.agentWorkspacePath : worktreePath;
120942
121081
  const hasRepo = !isAgentWorkspace && !!conventions.repoCachePath;
120943
121082
  const buildWorktree = hasRepo && !conventions.readOnly;
@@ -121008,7 +121147,7 @@ var BridgeHandler = class {
121008
121147
  `[bridge.handler] worktree ${worktreePath} exists but git health check failed \u2014 removing stale dir and rebuilding (BL-8: migrated worktree with dead .git pointer)`
121009
121148
  );
121010
121149
  try {
121011
- await fs7.rm(worktreePath, { recursive: true, force: true });
121150
+ await fs6.rm(worktreePath, { recursive: true, force: true });
121012
121151
  } catch (rmErr) {
121013
121152
  console.warn("[bridge.handler] failed to remove stale worktree (will attempt rebuild anyway):", rmErr);
121014
121153
  }
@@ -121032,7 +121171,7 @@ var BridgeHandler = class {
121032
121171
  `[bridge.handler] created worktree ${worktreePath} on branch ${branchName}`
121033
121172
  );
121034
121173
  } else {
121035
- await fs7.mkdir(worktreePath, { recursive: true });
121174
+ await fs6.mkdir(worktreePath, { recursive: true });
121036
121175
  if (conventions.readOnly && conventions.repoCachePath) {
121037
121176
  console.log(
121038
121177
  `[bridge.handler] created scratch dir ${worktreePath} (read_only bot: repo read-only at ${conventions.repoCachePath}, no worktree)`
@@ -121411,109 +121550,49 @@ var BridgeHandler = class {
121411
121550
  }
121412
121551
  }
121413
121552
  } else {
121414
- const surfaceDispatch = await dispatchResponseSurface({
121415
- state: reportedState,
121416
- prototypeConfig,
121417
- facts: {
121418
- botId: this.deps.botConfig?.id ?? "v1-default",
121419
- chatId: parsed.chatId,
121420
- threadId,
121421
- triggerMessageId: messageId,
121422
- replyToMessageId: messageId,
121423
- replyInThread
121424
- },
121425
- worktreePath,
121426
- baseCard: baseCardPayload,
121427
- cardStarted: !!card,
121428
- postOutboundAvailable: false,
121429
- postLedgerAvailable: true,
121430
- visibleFallbackAvailable: true,
121431
- postClient: this.deps.postClient
121432
- });
121433
- if (surfaceDispatch.card) {
121434
- if (!card) {
121553
+ if (!card) {
121554
+ try {
121555
+ card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121435
121556
  try {
121436
- card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121437
- try {
121438
- await writeCardFile(worktreePath, {
121439
- messageId: card.messageId,
121440
- chatId: parsed.chatId,
121441
- threadId,
121442
- botId: this.deps.botConfig?.id ?? "",
121443
- replyInThread,
121444
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
121445
- });
121446
- } catch (err) {
121447
- console.warn("[bridge.handler] writeCardFile(late) failed (continuing):", err);
121448
- }
121449
- } catch (err) {
121450
- console.error(
121451
- "[bridge.handler] late visible card fallback start failed; creating post fallback:",
121452
- err
121453
- );
121454
- const failureReason2 = [
121455
- legacyCardStartFailed ? `initial legacy visible card start failed: ${legacyCardStartFailureReason ?? "unknown"}` : void 0,
121456
- `late legacy visible card fallback start failed: ${String(err)}`
121457
- ].filter((part) => !!part).join("; ");
121458
- const postFallback = await createOnlyPostFallback({
121459
- postClient: this.deps.postClient,
121460
- replyToMessageId: messageId,
121461
- replyInThread,
121462
- botId: this.deps.botConfig?.id ?? "v1-default",
121557
+ await writeCardFile(worktreePath, {
121558
+ messageId: card.messageId,
121559
+ chatId: parsed.chatId,
121463
121560
  threadId,
121464
- triggerMessageId: messageId,
121465
- finalText: surfaceDispatch.card.finalText ?? "Larkway completed but no visible card was available.",
121466
- failureReason: failureReason2,
121467
- title: surfaceDispatch.card.titleOverride ?? "Larkway fallback",
121468
- logPrefix: "[bridge.handler]"
121561
+ botId: this.deps.botConfig?.id ?? "",
121562
+ replyInThread,
121563
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
121469
121564
  });
121470
- if (!postFallback) throw err;
121471
- }
121472
- }
121473
- if (card) {
121474
- await card.finalize(surfaceDispatch.card);
121475
- let keepCardFileForRetry = false;
121476
- if (surfaceDispatch.post?.requiresFallbackLedgerMark) {
121477
- try {
121478
- await markPostLedgerFallbackVisible(
121479
- worktreePath,
121480
- surfaceDispatch.post.idempotencyKey,
121481
- {
121482
- fallbackCardMessageId: card.messageId,
121483
- error: surfaceDispatch.post.fallbackError ?? surfaceDispatch.card.failureReason ?? "post outbound failed; visible card fallback used"
121484
- }
121485
- );
121486
- } catch (err) {
121487
- keepCardFileForRetry = true;
121488
- console.warn(
121489
- "[bridge.handler] fallback ledger mark failed after visible card finalize; keeping card.json for retry:",
121490
- err
121491
- );
121492
- }
121493
- }
121494
- if (surfaceDispatch.post?.requiresPolicyLedgerMark) {
121495
- try {
121496
- await markPostLedgerPolicyBlockedVisible(
121497
- worktreePath,
121498
- surfaceDispatch.post.idempotencyKey,
121499
- {
121500
- fallbackCardMessageId: card.messageId,
121501
- error: surfaceDispatch.post.policyError ?? surfaceDispatch.card.failureReason ?? "mention policy blocked; visible card fallback used"
121502
- }
121503
- );
121504
- } catch (err) {
121505
- keepCardFileForRetry = true;
121506
- console.warn(
121507
- "[bridge.handler] policy-blocked ledger mark failed after visible card finalize; keeping card.json for retry:",
121508
- err
121509
- );
121510
- }
121511
- }
121512
- if (!keepCardFileForRetry) {
121513
- await deleteCardFile(worktreePath);
121565
+ } catch (err) {
121566
+ console.warn("[bridge.handler] writeCardFile(late) failed (continuing):", err);
121514
121567
  }
121568
+ } catch (err) {
121569
+ console.error(
121570
+ "[bridge.handler] late visible card fallback start failed; creating post fallback:",
121571
+ err
121572
+ );
121573
+ const failureReason2 = [
121574
+ legacyCardStartFailed ? `initial legacy visible card start failed: ${legacyCardStartFailureReason ?? "unknown"}` : void 0,
121575
+ `late legacy visible card fallback start failed: ${String(err)}`
121576
+ ].filter((part) => !!part).join("; ");
121577
+ const postFallback = await createOnlyPostFallback({
121578
+ postClient: this.deps.postClient,
121579
+ replyToMessageId: messageId,
121580
+ replyInThread,
121581
+ botId: this.deps.botConfig?.id ?? "v1-default",
121582
+ threadId,
121583
+ triggerMessageId: messageId,
121584
+ finalText: baseCardPayload.finalText,
121585
+ failureReason: failureReason2,
121586
+ title: baseCardPayload.titleOverride ?? "Larkway fallback",
121587
+ logPrefix: "[bridge.handler]"
121588
+ });
121589
+ if (!postFallback) throw err;
121515
121590
  }
121516
121591
  }
121592
+ if (card) {
121593
+ await card.finalize(baseCardPayload);
121594
+ await deleteCardFile(worktreePath);
121595
+ }
121517
121596
  }
121518
121597
  settle(true);
121519
121598
  await recordEvent({
@@ -121548,7 +121627,7 @@ var BridgeHandler = class {
121548
121627
  reason: String(err)
121549
121628
  });
121550
121629
  settle(false);
121551
- const wtPath = this.deps.conventions.runtime === "agent_workspace" && this.deps.conventions.workspaceSessionsDir ? path10.join(this.deps.conventions.workspaceSessionsDir, threadId) : path10.join(this.deps.conventions.worktreesDir, threadId);
121630
+ const wtPath = this.deps.conventions.runtime === "agent_workspace" && this.deps.conventions.workspaceSessionsDir ? path9.join(this.deps.conventions.workspaceSessionsDir, threadId) : path9.join(this.deps.conventions.worktreesDir, threadId);
121552
121631
  const hardFailureText = `\u6267\u884C\u5931\u8D25: ${String(err)}`;
121553
121632
  const createHardFailurePostFallback = async (failureReason) => {
121554
121633
  const fallback = await createOnlyPostFallback({
@@ -121619,18 +121698,18 @@ var BridgeHandler = class {
121619
121698
  };
121620
121699
 
121621
121700
  // src/bridge/eventLog.ts
121622
- import fs8 from "node:fs/promises";
121623
- import path11 from "node:path";
121701
+ import fs7 from "node:fs/promises";
121702
+ import path10 from "node:path";
121624
121703
  var DEFAULT_RUNTIME_EVENT_LIMIT = 20;
121625
121704
  var writeQueues = /* @__PURE__ */ new Map();
121626
121705
  function resolveRuntimeEventsPath(larkwayHome2, botId) {
121627
- const dir = botId ? path11.join(larkwayHome2, botId) : larkwayHome2;
121628
- return path11.join(dir, "recent-events.json");
121706
+ const dir = botId ? path10.join(larkwayHome2, botId) : larkwayHome2;
121707
+ return path10.join(dir, "recent-events.json");
121629
121708
  }
121630
121709
  async function readRuntimeEvents(larkwayHome2, botId, limit = DEFAULT_RUNTIME_EVENT_LIMIT) {
121631
121710
  const file = resolveRuntimeEventsPath(larkwayHome2, botId);
121632
121711
  try {
121633
- const raw = await fs8.readFile(file, "utf-8");
121712
+ const raw = await fs7.readFile(file, "utf-8");
121634
121713
  const parsed = JSON.parse(raw);
121635
121714
  if (!Array.isArray(parsed)) return [];
121636
121715
  return parsed.filter(isRuntimeEventRecord).sort((a, b) => tsOf(b.receivedAt) - tsOf(a.receivedAt)).slice(0, limit);
@@ -121688,11 +121767,11 @@ async function enqueue(key, fn) {
121688
121767
  }
121689
121768
  }
121690
121769
  async function writeEvents(file, events) {
121691
- await fs8.mkdir(path11.dirname(file), { recursive: true });
121770
+ await fs7.mkdir(path10.dirname(file), { recursive: true });
121692
121771
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
121693
- await fs8.writeFile(tmp, `${JSON.stringify(events, null, 2)}
121772
+ await fs7.writeFile(tmp, `${JSON.stringify(events, null, 2)}
121694
121773
  `, "utf-8");
121695
- await fs8.rename(tmp, file);
121774
+ await fs7.rename(tmp, file);
121696
121775
  }
121697
121776
  function mergeStatusPath(prev, next, append) {
121698
121777
  const out = [...next ?? prev ?? []];
@@ -121958,7 +122037,7 @@ async function cleanupWorktree(threadId, botId, dryRun) {
121958
122037
 
121959
122038
  // src/config/botLoader.ts
121960
122039
  import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
121961
- import path12 from "node:path";
122040
+ import path11 from "node:path";
121962
122041
 
121963
122042
  // node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/dist/js-yaml.mjs
121964
122043
  function isNothing(subject) {
@@ -124794,7 +124873,7 @@ async function loadBots(botsDir) {
124794
124873
  }
124795
124874
  const bots = [];
124796
124875
  for (const filename of yamlFiles.sort()) {
124797
- const filePath = path12.join(botsDir, filename);
124876
+ const filePath = path11.join(botsDir, filename);
124798
124877
  let raw;
124799
124878
  try {
124800
124879
  raw = await readFile5(filePath, "utf-8");
@@ -124815,7 +124894,7 @@ ${issues}`);
124815
124894
  }
124816
124895
  const bot = result.data;
124817
124896
  if (bot.memory_file) {
124818
- const memoryPath = path12.join(botsDir, bot.memory_file);
124897
+ const memoryPath = path11.join(botsDir, bot.memory_file);
124819
124898
  try {
124820
124899
  bot.agent_memory = await readFile5(memoryPath, "utf-8");
124821
124900
  } catch (err) {
@@ -124848,6 +124927,239 @@ ${issues}`);
124848
124927
  // src/bridge/reconcile.ts
124849
124928
  import { readdir as readdir3, stat as stat2 } from "node:fs/promises";
124850
124929
  import { join as pathJoin2 } from "node:path";
124930
+
124931
+ // src/bridge/postFile.ts
124932
+ import fs8 from "node:fs/promises";
124933
+ import path12 from "node:path";
124934
+ var PostLedgerStatusSchema = external_exports.enum([
124935
+ "planned",
124936
+ "pending",
124937
+ "sent",
124938
+ "failed",
124939
+ "fallback_visible",
124940
+ "policy_blocked"
124941
+ ]);
124942
+ var POST_LEDGER_TRANSITIONS = {
124943
+ planned: ["pending", "fallback_visible", "policy_blocked"],
124944
+ pending: ["sent", "failed", "fallback_visible", "policy_blocked"],
124945
+ sent: [],
124946
+ failed: ["fallback_visible"],
124947
+ fallback_visible: [],
124948
+ policy_blocked: []
124949
+ };
124950
+ function canTransitionPostStatus(from, to) {
124951
+ return from === to || POST_LEDGER_TRANSITIONS[from].includes(to);
124952
+ }
124953
+ function assertPostStatusTransition(from, to) {
124954
+ if (!canTransitionPostStatus(from, to)) {
124955
+ throw new Error(`invalid post ledger transition: ${from} -> ${to}`);
124956
+ }
124957
+ }
124958
+ var PostAttemptSchema = external_exports.object({
124959
+ attemptedAt: external_exports.string(),
124960
+ status: external_exports.enum(["sent", "failed"]),
124961
+ retryable: external_exports.boolean().default(false),
124962
+ error: external_exports.string().optional(),
124963
+ code: external_exports.string().optional()
124964
+ });
124965
+ var PostLedgerEntrySchema = external_exports.object({
124966
+ idempotencyKey: external_exports.string().min(1).max(64),
124967
+ status: PostLedgerStatusSchema,
124968
+ botId: external_exports.string().min(1),
124969
+ chatId: external_exports.string().min(1),
124970
+ threadId: external_exports.string().min(1),
124971
+ replyToMessageId: external_exports.string().min(1),
124972
+ role: external_exports.enum(["primary", "secondary", "fallback"]),
124973
+ logicalIndex: external_exports.number().int().nonnegative(),
124974
+ contentDigest: external_exports.string().min(1),
124975
+ mentionCount: external_exports.number().int().nonnegative().default(0),
124976
+ postMessageId: external_exports.string().optional(),
124977
+ fallbackCardMessageId: external_exports.string().optional(),
124978
+ error: external_exports.string().optional(),
124979
+ attempts: external_exports.array(PostAttemptSchema).default([]),
124980
+ createdAt: external_exports.string(),
124981
+ updatedAt: external_exports.string()
124982
+ });
124983
+ var PostFileSchema = external_exports.object({
124984
+ version: external_exports.literal(1),
124985
+ posts: external_exports.array(PostLedgerEntrySchema).max(50)
124986
+ });
124987
+ function emptyPostFile() {
124988
+ return { version: 1, posts: [] };
124989
+ }
124990
+ function postDirOf(worktreePath) {
124991
+ return path12.join(worktreePath, ".larkway");
124992
+ }
124993
+ function postFilePathOf(worktreePath) {
124994
+ return path12.join(postDirOf(worktreePath), "post.json");
124995
+ }
124996
+ async function readPostFile(worktreePath) {
124997
+ const file = postFilePathOf(worktreePath);
124998
+ let raw;
124999
+ try {
125000
+ raw = await fs8.readFile(file, "utf8");
125001
+ } catch (err) {
125002
+ if (err.code === "ENOENT") return null;
125003
+ console.warn(`[postFile] read ${file} failed:`, err);
125004
+ return null;
125005
+ }
125006
+ let parsed;
125007
+ try {
125008
+ parsed = JSON.parse(raw);
125009
+ } catch (err) {
125010
+ console.warn(`[postFile] ${file} not valid JSON:`, err);
125011
+ return null;
125012
+ }
125013
+ const result = PostFileSchema.safeParse(parsed);
125014
+ if (!result.success) {
125015
+ console.warn(`[postFile] ${file} failed schema validation:`, result.error.issues);
125016
+ return null;
125017
+ }
125018
+ return result.data;
125019
+ }
125020
+ async function writePostFile(worktreePath, data) {
125021
+ const dir = postDirOf(worktreePath);
125022
+ const file = postFilePathOf(worktreePath);
125023
+ await fs8.mkdir(dir, { recursive: true });
125024
+ const parsed = PostFileSchema.parse(data);
125025
+ const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
125026
+ await fs8.writeFile(tmp, JSON.stringify(parsed, null, 2), "utf8");
125027
+ try {
125028
+ await fs8.rename(tmp, file);
125029
+ } catch (err) {
125030
+ await fs8.rm(tmp, { force: true }).catch(() => {
125031
+ });
125032
+ throw err;
125033
+ }
125034
+ }
125035
+ var DEFAULT_POST_RECONCILE_MIN_AGE_MS = 6e4;
125036
+ function timestampAgeMs(iso, nowMs) {
125037
+ if (!Number.isFinite(nowMs)) return null;
125038
+ const then = Date.parse(iso);
125039
+ if (!Number.isFinite(then)) return null;
125040
+ return nowMs - then;
125041
+ }
125042
+ function reconcilePostEntry(entry, opts) {
125043
+ if (entry.botId !== opts.botId) {
125044
+ return { entry, changed: false, sent: false, needsVisibleFallback: false };
125045
+ }
125046
+ if (entry.status === "sent" || entry.status === "fallback_visible" || entry.status === "policy_blocked") {
125047
+ return { entry, changed: false, sent: false, needsVisibleFallback: false };
125048
+ }
125049
+ const now = opts.now();
125050
+ const ageMs = timestampAgeMs(entry.updatedAt, Date.parse(now));
125051
+ if (ageMs == null || ageMs < opts.minAgeMs) {
125052
+ return { entry, changed: false, sent: false, needsVisibleFallback: false };
125053
+ }
125054
+ if (entry.postMessageId) {
125055
+ return {
125056
+ entry: {
125057
+ ...entry,
125058
+ status: "sent",
125059
+ error: void 0,
125060
+ updatedAt: now,
125061
+ attempts: [
125062
+ ...entry.attempts,
125063
+ {
125064
+ attemptedAt: now,
125065
+ status: "sent",
125066
+ retryable: false
125067
+ }
125068
+ ]
125069
+ },
125070
+ changed: true,
125071
+ sent: true,
125072
+ needsVisibleFallback: false
125073
+ };
125074
+ }
125075
+ return {
125076
+ entry,
125077
+ changed: false,
125078
+ sent: false,
125079
+ needsVisibleFallback: true
125080
+ };
125081
+ }
125082
+ function reconcilePostLedgerEntries(data, opts) {
125083
+ const normalizedOpts = {
125084
+ botId: opts.botId,
125085
+ minAgeMs: opts.minAgeMs ?? DEFAULT_POST_RECONCILE_MIN_AGE_MS,
125086
+ now: opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
125087
+ };
125088
+ let sent = 0;
125089
+ const fallbackVisible = 0;
125090
+ let needsVisibleFallback = 0;
125091
+ let skippedLive = 0;
125092
+ const visibleFallbackCandidates = [];
125093
+ const posts = data.posts.map((post) => {
125094
+ const reconciled = reconcilePostEntry(post, normalizedOpts);
125095
+ if (reconciled.changed) {
125096
+ if (reconciled.sent) sent += 1;
125097
+ } else if (reconciled.needsVisibleFallback) {
125098
+ needsVisibleFallback += 1;
125099
+ visibleFallbackCandidates.push(reconciled.entry);
125100
+ } else if (post.botId === normalizedOpts.botId && (post.status === "planned" || post.status === "pending" || post.status === "failed")) {
125101
+ skippedLive += 1;
125102
+ }
125103
+ return reconciled.entry;
125104
+ });
125105
+ const changed = sent > 0 || fallbackVisible > 0;
125106
+ return {
125107
+ file: changed ? { version: 1, posts } : data,
125108
+ result: { changed, sent, fallbackVisible, needsVisibleFallback, skippedLive },
125109
+ visibleFallbackCandidates
125110
+ };
125111
+ }
125112
+ async function reconcilePostFileOrphans(worktreePath, opts) {
125113
+ const existing = await readPostFile(worktreePath);
125114
+ if (!existing) {
125115
+ return {
125116
+ changed: false,
125117
+ sent: 0,
125118
+ fallbackVisible: 0,
125119
+ needsVisibleFallback: 0,
125120
+ skippedLive: 0,
125121
+ visibleFallbackCandidates: []
125122
+ };
125123
+ }
125124
+ const { file, result, visibleFallbackCandidates } = reconcilePostLedgerEntries(existing, opts);
125125
+ if (result.changed) {
125126
+ await writePostFile(worktreePath, file);
125127
+ }
125128
+ return { ...result, visibleFallbackCandidates };
125129
+ }
125130
+ async function markPostLedgerFallbackVisible(worktreePath, idempotencyKey2, opts) {
125131
+ const existing = await readPostFile(worktreePath) ?? emptyPostFile();
125132
+ const idx = existing.posts.findIndex((post) => post.idempotencyKey === idempotencyKey2);
125133
+ if (idx < 0) {
125134
+ throw new Error(`post ledger entry not found: ${idempotencyKey2}`);
125135
+ }
125136
+ const current = existing.posts[idx];
125137
+ assertPostStatusTransition(current.status, "fallback_visible");
125138
+ const now = opts.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
125139
+ const nextPosts = [...existing.posts];
125140
+ nextPosts[idx] = {
125141
+ ...current,
125142
+ status: "fallback_visible",
125143
+ fallbackCardMessageId: opts.fallbackCardMessageId,
125144
+ error: opts.error,
125145
+ updatedAt: now,
125146
+ attempts: [
125147
+ ...current.attempts,
125148
+ {
125149
+ attemptedAt: now,
125150
+ status: "failed",
125151
+ retryable: false,
125152
+ code: "orphan_reconcile",
125153
+ error: opts.error
125154
+ }
125155
+ ]
125156
+ };
125157
+ const next = { version: 1, posts: nextPosts };
125158
+ await writePostFile(worktreePath, next);
125159
+ return next;
125160
+ }
125161
+
125162
+ // src/bridge/reconcile.ts
124851
125163
  var DEFAULT_MIN_AGE_MS = 6e4;
124852
125164
  var RETRY_CAP = 3;
124853
125165
  function isStateFreshForCard(state, card) {
@@ -125394,7 +125706,7 @@ async function writeStatusFile(botId, w) {
125394
125706
 
125395
125707
  // src/claude/runner.ts
125396
125708
  import { spawn as spawn2 } from "node:child_process";
125397
- import { writeFile as writeFile3, unlink, mkdir as mkdir3 } from "node:fs/promises";
125709
+ import { writeFile as writeFile3, unlink as unlink2, mkdir as mkdir3 } from "node:fs/promises";
125398
125710
  import { join as join2 } from "node:path";
125399
125711
  import { createInterface } from "node:readline";
125400
125712
  var SIGKILL_GRACE_MS = 5e3;
@@ -125601,7 +125913,7 @@ function runClaude(opts) {
125601
125913
  clearTimeout(totalTimeoutFallbackHandle);
125602
125914
  rlAbortController.abort();
125603
125915
  if (pidFilePath !== null) {
125604
- void unlink(pidFilePath).catch(() => {
125916
+ void unlink2(pidFilePath).catch(() => {
125605
125917
  });
125606
125918
  }
125607
125919
  if (exitCode !== 0 && !killScheduled) {
@@ -125631,7 +125943,7 @@ stderr: ${stderr}` : "")
125631
125943
  clearTimeout(totalTimeoutFallbackHandle);
125632
125944
  rlAbortController.abort();
125633
125945
  if (pidFilePath !== null) {
125634
- void unlink(pidFilePath).catch(() => {
125946
+ void unlink2(pidFilePath).catch(() => {
125635
125947
  });
125636
125948
  }
125637
125949
  if (err.code === "ENOENT") {