larkway 0.3.26 → 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/README.md +1 -1
- package/README.zh.md +1 -1
- package/dist/cli/index.js +917 -87
- package/dist/main.js +1368 -1303
- package/package.json +1 -1
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 =
|
|
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
|
|
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 :
|
|
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
|
-
//
|
|
116130
|
-
//
|
|
116131
|
-
//
|
|
116132
|
-
//
|
|
116133
|
-
//
|
|
116134
|
-
//
|
|
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 =
|
|
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
|
-
|
|
116486
|
-
|
|
117309
|
+
const firstDelay = Math.min(this.openChatDiscoveryJitterMs, intervalMs);
|
|
117310
|
+
const startTimer = setTimeout(() => {
|
|
116487
117311
|
void this.discoverOpenChatsAndGapFill(log);
|
|
116488
|
-
|
|
116489
|
-
|
|
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)
|
|
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
|
-
|
|
116539
|
-
|
|
116540
|
-
|
|
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
|
-
|
|
117388
|
+
targetChatIds,
|
|
117389
|
+
isBootstrap ? void 0 : targetedLookbackMs
|
|
116545
117390
|
);
|
|
116546
117391
|
}
|
|
117392
|
+
this.openChatDiscoveryFailures = 0;
|
|
117393
|
+
this.openChatDiscoverySkips = 0;
|
|
116547
117394
|
} catch (e) {
|
|
116548
|
-
|
|
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
|
-
|
|
116625
|
-
|
|
116626
|
-
|
|
116627
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
117442
|
-
import
|
|
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
|
-
|
|
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
|
-
* Post-first gate. Default true means the bridge starts a lightweight post
|
|
118712
|
-
* as the live main surface when post outbound is available, and creates a
|
|
118713
|
-
* card only for fallback or card-only capabilities.
|
|
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
|
|
@@ -118720,8 +119575,8 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
|
|
|
118720
119575
|
*/
|
|
118721
119576
|
kill_switch: external_exports.boolean().default(false),
|
|
118722
119577
|
/**
|
|
118723
|
-
* Gate for real post outbound. Defaults on
|
|
118724
|
-
*
|
|
119578
|
+
* Gate for real post outbound. Defaults on so create-only visible fallback
|
|
119579
|
+
* posts remain available when CardKit and legacy card rendering both fail.
|
|
118725
119580
|
*/
|
|
118726
119581
|
post_outbound_enabled: external_exports.boolean().default(true),
|
|
118727
119582
|
/**
|
|
@@ -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
|
-
* Hard cap for surface dispatch. PR4 still keeps production post outbound
|
|
118747
|
-
* unavailable, but fake-channel dispatch tests enforce this bounded shape.
|
|
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
|
-
* Future channel threshold for lazy card creation. Bounded so config cannot
|
|
118767
|
-
* grow unbounded or encode 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
|
|
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);
|
|
@@ -118793,9 +119623,6 @@ function shouldProvideResponseSurfaceCardKitClient(config) {
|
|
|
118793
119623
|
function isResponseSurfaceCardKitAvailable(config, facts, opts) {
|
|
118794
119624
|
return !!(opts.cardKitClientAvailable && shouldProvideResponseSurfaceCardKitClient(config) && isResponseSurfacePrototypeAllowlisted(config, facts));
|
|
118795
119625
|
}
|
|
118796
|
-
function isResponseSurfacePostOutboundAvailable(config, facts, opts) {
|
|
118797
|
-
return !!(opts.postClientAvailable && shouldProvideResponseSurfacePostClient(config) && isResponseSurfacePrototypeAllowlisted(config, facts));
|
|
118798
|
-
}
|
|
118799
119626
|
|
|
118800
119627
|
// src/bridge/stateFile.ts
|
|
118801
119628
|
var optionalUrl = external_exports.preprocess(
|
|
@@ -119755,93 +120582,6 @@ async function finalizeExistingCardKitCard(opts) {
|
|
|
119755
120582
|
return sequence;
|
|
119756
120583
|
}
|
|
119757
120584
|
|
|
119758
|
-
// src/bridge/surfaceController.ts
|
|
119759
|
-
var SurfaceController = class _SurfaceController {
|
|
119760
|
-
decision;
|
|
119761
|
-
constructor(decision) {
|
|
119762
|
-
this.decision = decision;
|
|
119763
|
-
}
|
|
119764
|
-
static create(input) {
|
|
119765
|
-
const cfg = input.prototypeConfig;
|
|
119766
|
-
if (!cfg?.enabled) {
|
|
119767
|
-
return new _SurfaceController({
|
|
119768
|
-
startCardImmediately: true,
|
|
119769
|
-
prototypeEnabled: false,
|
|
119770
|
-
lazyCardCreationEnabled: false,
|
|
119771
|
-
reason: "prototype-disabled"
|
|
119772
|
-
});
|
|
119773
|
-
}
|
|
119774
|
-
if (cfg.kill_switch) {
|
|
119775
|
-
return new _SurfaceController({
|
|
119776
|
-
startCardImmediately: true,
|
|
119777
|
-
prototypeEnabled: false,
|
|
119778
|
-
lazyCardCreationEnabled: false,
|
|
119779
|
-
reason: "kill-switch-active"
|
|
119780
|
-
});
|
|
119781
|
-
}
|
|
119782
|
-
if (!isResponseSurfacePrototypeAllowlisted(cfg, {
|
|
119783
|
-
chatId: input.chatId,
|
|
119784
|
-
threadId: input.threadId
|
|
119785
|
-
})) {
|
|
119786
|
-
return new _SurfaceController({
|
|
119787
|
-
startCardImmediately: true,
|
|
119788
|
-
prototypeEnabled: false,
|
|
119789
|
-
lazyCardCreationEnabled: false,
|
|
119790
|
-
reason: "not-allowlisted"
|
|
119791
|
-
});
|
|
119792
|
-
}
|
|
119793
|
-
if (!cfg.lazy_card_creation) {
|
|
119794
|
-
return new _SurfaceController({
|
|
119795
|
-
startCardImmediately: true,
|
|
119796
|
-
prototypeEnabled: true,
|
|
119797
|
-
lazyCardCreationEnabled: false,
|
|
119798
|
-
reason: "lazy-card-disabled"
|
|
119799
|
-
});
|
|
119800
|
-
}
|
|
119801
|
-
if (!cfg.post_outbound_enabled) {
|
|
119802
|
-
return new _SurfaceController({
|
|
119803
|
-
startCardImmediately: true,
|
|
119804
|
-
prototypeEnabled: true,
|
|
119805
|
-
lazyCardCreationEnabled: false,
|
|
119806
|
-
reason: "post-outbound-disabled"
|
|
119807
|
-
});
|
|
119808
|
-
}
|
|
119809
|
-
if (!input.postOutboundAvailable) {
|
|
119810
|
-
return new _SurfaceController({
|
|
119811
|
-
startCardImmediately: true,
|
|
119812
|
-
prototypeEnabled: true,
|
|
119813
|
-
lazyCardCreationEnabled: false,
|
|
119814
|
-
reason: "post-outbound-unavailable-card-fallback"
|
|
119815
|
-
});
|
|
119816
|
-
}
|
|
119817
|
-
if (input.postLedgerAvailable === false) {
|
|
119818
|
-
return new _SurfaceController({
|
|
119819
|
-
startCardImmediately: true,
|
|
119820
|
-
prototypeEnabled: true,
|
|
119821
|
-
lazyCardCreationEnabled: false,
|
|
119822
|
-
reason: "post-ledger-unavailable-card-fallback"
|
|
119823
|
-
});
|
|
119824
|
-
}
|
|
119825
|
-
if (input.visibleFallbackAvailable === false) {
|
|
119826
|
-
return new _SurfaceController({
|
|
119827
|
-
startCardImmediately: true,
|
|
119828
|
-
prototypeEnabled: true,
|
|
119829
|
-
lazyCardCreationEnabled: false,
|
|
119830
|
-
reason: "visible-fallback-unavailable-card-fallback"
|
|
119831
|
-
});
|
|
119832
|
-
}
|
|
119833
|
-
return new _SurfaceController({
|
|
119834
|
-
startCardImmediately: false,
|
|
119835
|
-
prototypeEnabled: true,
|
|
119836
|
-
lazyCardCreationEnabled: true,
|
|
119837
|
-
reason: "lazy-card-ready"
|
|
119838
|
-
});
|
|
119839
|
-
}
|
|
119840
|
-
shouldStartCardImmediately() {
|
|
119841
|
-
return this.decision.startCardImmediately;
|
|
119842
|
-
}
|
|
119843
|
-
};
|
|
119844
|
-
|
|
119845
120585
|
// src/lark/postContent.ts
|
|
119846
120586
|
function assertSafeUserId(userId) {
|
|
119847
120587
|
if (!/^[A-Za-z0-9_:-]+$/.test(userId)) {
|
|
@@ -119918,853 +120658,6 @@ function derivePostIdempotencyKey(input) {
|
|
|
119918
120658
|
return key;
|
|
119919
120659
|
}
|
|
119920
120660
|
|
|
119921
|
-
// src/bridge/postFile.ts
|
|
119922
|
-
import fs6 from "node:fs/promises";
|
|
119923
|
-
import path9 from "node:path";
|
|
119924
|
-
var PostLedgerStatusSchema = external_exports.enum([
|
|
119925
|
-
"planned",
|
|
119926
|
-
"pending",
|
|
119927
|
-
"sent",
|
|
119928
|
-
"failed",
|
|
119929
|
-
"fallback_visible",
|
|
119930
|
-
"policy_blocked"
|
|
119931
|
-
]);
|
|
119932
|
-
var POST_LEDGER_TRANSITIONS = {
|
|
119933
|
-
planned: ["pending", "fallback_visible", "policy_blocked"],
|
|
119934
|
-
pending: ["sent", "failed", "fallback_visible", "policy_blocked"],
|
|
119935
|
-
sent: [],
|
|
119936
|
-
failed: ["fallback_visible"],
|
|
119937
|
-
fallback_visible: [],
|
|
119938
|
-
policy_blocked: []
|
|
119939
|
-
};
|
|
119940
|
-
function canTransitionPostStatus(from, to) {
|
|
119941
|
-
return from === to || POST_LEDGER_TRANSITIONS[from].includes(to);
|
|
119942
|
-
}
|
|
119943
|
-
function assertPostStatusTransition(from, to) {
|
|
119944
|
-
if (!canTransitionPostStatus(from, to)) {
|
|
119945
|
-
throw new Error(`invalid post ledger transition: ${from} -> ${to}`);
|
|
119946
|
-
}
|
|
119947
|
-
}
|
|
119948
|
-
var PostAttemptSchema = external_exports.object({
|
|
119949
|
-
attemptedAt: external_exports.string(),
|
|
119950
|
-
status: external_exports.enum(["sent", "failed"]),
|
|
119951
|
-
retryable: external_exports.boolean().default(false),
|
|
119952
|
-
error: external_exports.string().optional(),
|
|
119953
|
-
code: external_exports.string().optional()
|
|
119954
|
-
});
|
|
119955
|
-
var PostLedgerEntrySchema = external_exports.object({
|
|
119956
|
-
idempotencyKey: external_exports.string().min(1).max(64),
|
|
119957
|
-
status: PostLedgerStatusSchema,
|
|
119958
|
-
botId: external_exports.string().min(1),
|
|
119959
|
-
chatId: external_exports.string().min(1),
|
|
119960
|
-
threadId: external_exports.string().min(1),
|
|
119961
|
-
replyToMessageId: external_exports.string().min(1),
|
|
119962
|
-
role: external_exports.enum(["primary", "secondary", "fallback"]),
|
|
119963
|
-
logicalIndex: external_exports.number().int().nonnegative(),
|
|
119964
|
-
contentDigest: external_exports.string().min(1),
|
|
119965
|
-
mentionCount: external_exports.number().int().nonnegative().default(0),
|
|
119966
|
-
postMessageId: external_exports.string().optional(),
|
|
119967
|
-
fallbackCardMessageId: external_exports.string().optional(),
|
|
119968
|
-
error: external_exports.string().optional(),
|
|
119969
|
-
attempts: external_exports.array(PostAttemptSchema).default([]),
|
|
119970
|
-
createdAt: external_exports.string(),
|
|
119971
|
-
updatedAt: external_exports.string()
|
|
119972
|
-
});
|
|
119973
|
-
var PostFileSchema = external_exports.object({
|
|
119974
|
-
version: external_exports.literal(1),
|
|
119975
|
-
posts: external_exports.array(PostLedgerEntrySchema).max(50)
|
|
119976
|
-
});
|
|
119977
|
-
function emptyPostFile() {
|
|
119978
|
-
return { version: 1, posts: [] };
|
|
119979
|
-
}
|
|
119980
|
-
function summarizePostLedger(data) {
|
|
119981
|
-
const summary = {
|
|
119982
|
-
total: 0,
|
|
119983
|
-
planned: 0,
|
|
119984
|
-
pending: 0,
|
|
119985
|
-
sent: 0,
|
|
119986
|
-
failed: 0,
|
|
119987
|
-
fallback_visible: 0,
|
|
119988
|
-
policy_blocked: 0,
|
|
119989
|
-
withPostMessageId: 0,
|
|
119990
|
-
withFallbackCardMessageId: 0
|
|
119991
|
-
};
|
|
119992
|
-
for (const post of data?.posts ?? []) {
|
|
119993
|
-
summary.total += 1;
|
|
119994
|
-
summary[post.status] += 1;
|
|
119995
|
-
if (post.postMessageId) summary.withPostMessageId += 1;
|
|
119996
|
-
if (post.fallbackCardMessageId) summary.withFallbackCardMessageId += 1;
|
|
119997
|
-
}
|
|
119998
|
-
return summary;
|
|
119999
|
-
}
|
|
120000
|
-
function postDirOf(worktreePath) {
|
|
120001
|
-
return path9.join(worktreePath, ".larkway");
|
|
120002
|
-
}
|
|
120003
|
-
function postFilePathOf(worktreePath) {
|
|
120004
|
-
return path9.join(postDirOf(worktreePath), "post.json");
|
|
120005
|
-
}
|
|
120006
|
-
async function readPostFile(worktreePath) {
|
|
120007
|
-
const file = postFilePathOf(worktreePath);
|
|
120008
|
-
let raw;
|
|
120009
|
-
try {
|
|
120010
|
-
raw = await fs6.readFile(file, "utf8");
|
|
120011
|
-
} catch (err) {
|
|
120012
|
-
if (err.code === "ENOENT") return null;
|
|
120013
|
-
console.warn(`[postFile] read ${file} failed:`, err);
|
|
120014
|
-
return null;
|
|
120015
|
-
}
|
|
120016
|
-
let parsed;
|
|
120017
|
-
try {
|
|
120018
|
-
parsed = JSON.parse(raw);
|
|
120019
|
-
} catch (err) {
|
|
120020
|
-
console.warn(`[postFile] ${file} not valid JSON:`, err);
|
|
120021
|
-
return null;
|
|
120022
|
-
}
|
|
120023
|
-
const result = PostFileSchema.safeParse(parsed);
|
|
120024
|
-
if (!result.success) {
|
|
120025
|
-
console.warn(`[postFile] ${file} failed schema validation:`, result.error.issues);
|
|
120026
|
-
return null;
|
|
120027
|
-
}
|
|
120028
|
-
return result.data;
|
|
120029
|
-
}
|
|
120030
|
-
async function writePostFile(worktreePath, data) {
|
|
120031
|
-
const dir = postDirOf(worktreePath);
|
|
120032
|
-
const file = postFilePathOf(worktreePath);
|
|
120033
|
-
await fs6.mkdir(dir, { recursive: true });
|
|
120034
|
-
const parsed = PostFileSchema.parse(data);
|
|
120035
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
120036
|
-
await fs6.writeFile(tmp, JSON.stringify(parsed, null, 2), "utf8");
|
|
120037
|
-
try {
|
|
120038
|
-
await fs6.rename(tmp, file);
|
|
120039
|
-
} catch (err) {
|
|
120040
|
-
await fs6.rm(tmp, { force: true }).catch(() => {
|
|
120041
|
-
});
|
|
120042
|
-
throw err;
|
|
120043
|
-
}
|
|
120044
|
-
}
|
|
120045
|
-
async function upsertPostLedgerEntry(worktreePath, entry) {
|
|
120046
|
-
const existing = await readPostFile(worktreePath) ?? emptyPostFile();
|
|
120047
|
-
const idx = existing.posts.findIndex((p) => p.idempotencyKey === entry.idempotencyKey);
|
|
120048
|
-
const nextPosts = [...existing.posts];
|
|
120049
|
-
if (idx >= 0) {
|
|
120050
|
-
assertPostStatusTransition(nextPosts[idx].status, entry.status);
|
|
120051
|
-
nextPosts[idx] = entry;
|
|
120052
|
-
} else {
|
|
120053
|
-
nextPosts.push(entry);
|
|
120054
|
-
}
|
|
120055
|
-
const next = { version: 1, posts: nextPosts };
|
|
120056
|
-
await writePostFile(worktreePath, next);
|
|
120057
|
-
return next;
|
|
120058
|
-
}
|
|
120059
|
-
var DEFAULT_POST_RECONCILE_MIN_AGE_MS = 6e4;
|
|
120060
|
-
function timestampAgeMs(iso, nowMs) {
|
|
120061
|
-
if (!Number.isFinite(nowMs)) return null;
|
|
120062
|
-
const then = Date.parse(iso);
|
|
120063
|
-
if (!Number.isFinite(then)) return null;
|
|
120064
|
-
return nowMs - then;
|
|
120065
|
-
}
|
|
120066
|
-
function reconcilePostEntry(entry, opts) {
|
|
120067
|
-
if (entry.botId !== opts.botId) {
|
|
120068
|
-
return { entry, changed: false, sent: false, needsVisibleFallback: false };
|
|
120069
|
-
}
|
|
120070
|
-
if (entry.status === "sent" || entry.status === "fallback_visible" || entry.status === "policy_blocked") {
|
|
120071
|
-
return { entry, changed: false, sent: false, needsVisibleFallback: false };
|
|
120072
|
-
}
|
|
120073
|
-
const now = opts.now();
|
|
120074
|
-
const ageMs = timestampAgeMs(entry.updatedAt, Date.parse(now));
|
|
120075
|
-
if (ageMs == null || ageMs < opts.minAgeMs) {
|
|
120076
|
-
return { entry, changed: false, sent: false, needsVisibleFallback: false };
|
|
120077
|
-
}
|
|
120078
|
-
if (entry.postMessageId) {
|
|
120079
|
-
return {
|
|
120080
|
-
entry: {
|
|
120081
|
-
...entry,
|
|
120082
|
-
status: "sent",
|
|
120083
|
-
error: void 0,
|
|
120084
|
-
updatedAt: now,
|
|
120085
|
-
attempts: [
|
|
120086
|
-
...entry.attempts,
|
|
120087
|
-
{
|
|
120088
|
-
attemptedAt: now,
|
|
120089
|
-
status: "sent",
|
|
120090
|
-
retryable: false
|
|
120091
|
-
}
|
|
120092
|
-
]
|
|
120093
|
-
},
|
|
120094
|
-
changed: true,
|
|
120095
|
-
sent: true,
|
|
120096
|
-
needsVisibleFallback: false
|
|
120097
|
-
};
|
|
120098
|
-
}
|
|
120099
|
-
return {
|
|
120100
|
-
entry,
|
|
120101
|
-
changed: false,
|
|
120102
|
-
sent: false,
|
|
120103
|
-
needsVisibleFallback: true
|
|
120104
|
-
};
|
|
120105
|
-
}
|
|
120106
|
-
function reconcilePostLedgerEntries(data, opts) {
|
|
120107
|
-
const normalizedOpts = {
|
|
120108
|
-
botId: opts.botId,
|
|
120109
|
-
minAgeMs: opts.minAgeMs ?? DEFAULT_POST_RECONCILE_MIN_AGE_MS,
|
|
120110
|
-
now: opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
|
|
120111
|
-
};
|
|
120112
|
-
let sent = 0;
|
|
120113
|
-
const fallbackVisible = 0;
|
|
120114
|
-
let needsVisibleFallback = 0;
|
|
120115
|
-
let skippedLive = 0;
|
|
120116
|
-
const visibleFallbackCandidates = [];
|
|
120117
|
-
const posts = data.posts.map((post) => {
|
|
120118
|
-
const reconciled = reconcilePostEntry(post, normalizedOpts);
|
|
120119
|
-
if (reconciled.changed) {
|
|
120120
|
-
if (reconciled.sent) sent += 1;
|
|
120121
|
-
} else if (reconciled.needsVisibleFallback) {
|
|
120122
|
-
needsVisibleFallback += 1;
|
|
120123
|
-
visibleFallbackCandidates.push(reconciled.entry);
|
|
120124
|
-
} else if (post.botId === normalizedOpts.botId && (post.status === "planned" || post.status === "pending" || post.status === "failed")) {
|
|
120125
|
-
skippedLive += 1;
|
|
120126
|
-
}
|
|
120127
|
-
return reconciled.entry;
|
|
120128
|
-
});
|
|
120129
|
-
const changed = sent > 0 || fallbackVisible > 0;
|
|
120130
|
-
return {
|
|
120131
|
-
file: changed ? { version: 1, posts } : data,
|
|
120132
|
-
result: { changed, sent, fallbackVisible, needsVisibleFallback, skippedLive },
|
|
120133
|
-
visibleFallbackCandidates
|
|
120134
|
-
};
|
|
120135
|
-
}
|
|
120136
|
-
async function reconcilePostFileOrphans(worktreePath, opts) {
|
|
120137
|
-
const existing = await readPostFile(worktreePath);
|
|
120138
|
-
if (!existing) {
|
|
120139
|
-
return {
|
|
120140
|
-
changed: false,
|
|
120141
|
-
sent: 0,
|
|
120142
|
-
fallbackVisible: 0,
|
|
120143
|
-
needsVisibleFallback: 0,
|
|
120144
|
-
skippedLive: 0,
|
|
120145
|
-
visibleFallbackCandidates: []
|
|
120146
|
-
};
|
|
120147
|
-
}
|
|
120148
|
-
const { file, result, visibleFallbackCandidates } = reconcilePostLedgerEntries(existing, opts);
|
|
120149
|
-
if (result.changed) {
|
|
120150
|
-
await writePostFile(worktreePath, file);
|
|
120151
|
-
}
|
|
120152
|
-
return { ...result, visibleFallbackCandidates };
|
|
120153
|
-
}
|
|
120154
|
-
async function markPostLedgerFallbackVisible(worktreePath, idempotencyKey2, opts) {
|
|
120155
|
-
const existing = await readPostFile(worktreePath) ?? emptyPostFile();
|
|
120156
|
-
const idx = existing.posts.findIndex((post) => post.idempotencyKey === idempotencyKey2);
|
|
120157
|
-
if (idx < 0) {
|
|
120158
|
-
throw new Error(`post ledger entry not found: ${idempotencyKey2}`);
|
|
120159
|
-
}
|
|
120160
|
-
const current = existing.posts[idx];
|
|
120161
|
-
assertPostStatusTransition(current.status, "fallback_visible");
|
|
120162
|
-
const now = opts.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
120163
|
-
const nextPosts = [...existing.posts];
|
|
120164
|
-
nextPosts[idx] = {
|
|
120165
|
-
...current,
|
|
120166
|
-
status: "fallback_visible",
|
|
120167
|
-
fallbackCardMessageId: opts.fallbackCardMessageId,
|
|
120168
|
-
error: opts.error,
|
|
120169
|
-
updatedAt: now,
|
|
120170
|
-
attempts: [
|
|
120171
|
-
...current.attempts,
|
|
120172
|
-
{
|
|
120173
|
-
attemptedAt: now,
|
|
120174
|
-
status: "failed",
|
|
120175
|
-
retryable: false,
|
|
120176
|
-
code: "orphan_reconcile",
|
|
120177
|
-
error: opts.error
|
|
120178
|
-
}
|
|
120179
|
-
]
|
|
120180
|
-
};
|
|
120181
|
-
const next = { version: 1, posts: nextPosts };
|
|
120182
|
-
await writePostFile(worktreePath, next);
|
|
120183
|
-
return next;
|
|
120184
|
-
}
|
|
120185
|
-
async function markPostLedgerPolicyBlockedVisible(worktreePath, idempotencyKey2, opts) {
|
|
120186
|
-
const existing = await readPostFile(worktreePath) ?? emptyPostFile();
|
|
120187
|
-
const idx = existing.posts.findIndex((post) => post.idempotencyKey === idempotencyKey2);
|
|
120188
|
-
if (idx < 0) {
|
|
120189
|
-
throw new Error(`post ledger entry not found: ${idempotencyKey2}`);
|
|
120190
|
-
}
|
|
120191
|
-
const current = existing.posts[idx];
|
|
120192
|
-
assertPostStatusTransition(current.status, "policy_blocked");
|
|
120193
|
-
const now = opts.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
120194
|
-
const nextPosts = [...existing.posts];
|
|
120195
|
-
nextPosts[idx] = {
|
|
120196
|
-
...current,
|
|
120197
|
-
status: "policy_blocked",
|
|
120198
|
-
fallbackCardMessageId: opts.fallbackCardMessageId,
|
|
120199
|
-
error: opts.error,
|
|
120200
|
-
updatedAt: now,
|
|
120201
|
-
attempts: [
|
|
120202
|
-
...current.attempts,
|
|
120203
|
-
{
|
|
120204
|
-
attemptedAt: now,
|
|
120205
|
-
status: "failed",
|
|
120206
|
-
retryable: false,
|
|
120207
|
-
code: "mention_policy_blocked",
|
|
120208
|
-
error: opts.error
|
|
120209
|
-
}
|
|
120210
|
-
]
|
|
120211
|
-
};
|
|
120212
|
-
const next = { version: 1, posts: nextPosts };
|
|
120213
|
-
await writePostFile(worktreePath, next);
|
|
120214
|
-
return next;
|
|
120215
|
-
}
|
|
120216
|
-
|
|
120217
|
-
// src/bridge/surfaceDispatcher.ts
|
|
120218
|
-
function fullCard(input, reason) {
|
|
120219
|
-
return {
|
|
120220
|
-
card: input.baseCard,
|
|
120221
|
-
reason,
|
|
120222
|
-
visible: input.cardStarted || input.visibleFallbackAvailable
|
|
120223
|
-
};
|
|
120224
|
-
}
|
|
120225
|
-
function hasCardOnlyPayload(state) {
|
|
120226
|
-
return !!(state?.choices?.length || state?.image_blocks?.length || state?.content_blocks?.length);
|
|
120227
|
-
}
|
|
120228
|
-
function hasCardOnlyPayloadIn(input) {
|
|
120229
|
-
return !!(hasCardOnlyPayload(input.state) || input.baseCard.choices?.length || input.baseCard.imageBlocks?.length || input.baseCard.contentBlocks?.length);
|
|
120230
|
-
}
|
|
120231
|
-
function compactAuditCard(input, post) {
|
|
120232
|
-
const status = input.state?.status ?? (input.baseCard.success ? "ready" : "failed");
|
|
120233
|
-
const title = input.baseCard.titleOverride ?? "Post \u5DF2\u53D1\u9001";
|
|
120234
|
-
return {
|
|
120235
|
-
success: input.baseCard.success,
|
|
120236
|
-
failureReason: input.baseCard.failureReason,
|
|
120237
|
-
titleOverride: title,
|
|
120238
|
-
colorOverride: input.baseCard.colorOverride ?? "neutral",
|
|
120239
|
-
finalText: `\u4E3B\u56DE\u590D\u5DF2\u901A\u8FC7 post \u53D1\u51FA\u3002
|
|
120240
|
-
status: ${status}
|
|
120241
|
-
post_message_id: ${post.messageId}
|
|
120242
|
-
idempotency_key: ${post.idempotencyKey}`
|
|
120243
|
-
};
|
|
120244
|
-
}
|
|
120245
|
-
function fallbackFailureCard(input, error) {
|
|
120246
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
120247
|
-
return {
|
|
120248
|
-
...input.baseCard,
|
|
120249
|
-
success: false,
|
|
120250
|
-
failureReason: `post outbound failed; visible card fallback used: ${reason}`
|
|
120251
|
-
};
|
|
120252
|
-
}
|
|
120253
|
-
function policyBlockedCard(input) {
|
|
120254
|
-
return {
|
|
120255
|
-
...input.baseCard,
|
|
120256
|
-
success: false,
|
|
120257
|
-
failureReason: "response_surface post mention target is blocked by policy; visible card fallback used"
|
|
120258
|
-
};
|
|
120259
|
-
}
|
|
120260
|
-
function postText(input) {
|
|
120261
|
-
const text = input.baseCard.finalText?.trim() || input.state?.last_message?.trim();
|
|
120262
|
-
if (text) return text;
|
|
120263
|
-
if (input.baseCard.success) return "\u5B8C\u6210";
|
|
120264
|
-
return input.baseCard.failureReason ?? "\u6267\u884C\u5931\u8D25";
|
|
120265
|
-
}
|
|
120266
|
-
function postRole(input) {
|
|
120267
|
-
const surface = input.state?.response_surface;
|
|
120268
|
-
if (surface?.mode === "hybrid") return "primary";
|
|
120269
|
-
return "primary";
|
|
120270
|
-
}
|
|
120271
|
-
function newLedgerEntry(input) {
|
|
120272
|
-
return {
|
|
120273
|
-
idempotencyKey: input.idempotencyKey,
|
|
120274
|
-
status: input.status,
|
|
120275
|
-
botId: input.facts.botId,
|
|
120276
|
-
chatId: input.facts.chatId,
|
|
120277
|
-
threadId: input.facts.threadId,
|
|
120278
|
-
replyToMessageId: input.facts.replyToMessageId,
|
|
120279
|
-
role: input.role,
|
|
120280
|
-
logicalIndex: input.logicalIndex,
|
|
120281
|
-
contentDigest: input.contentDigest,
|
|
120282
|
-
mentionCount: input.mentionCount,
|
|
120283
|
-
postMessageId: input.postMessageId,
|
|
120284
|
-
error: input.error,
|
|
120285
|
-
attempts: input.attempts ?? [],
|
|
120286
|
-
createdAt: input.now,
|
|
120287
|
-
updatedAt: input.now
|
|
120288
|
-
};
|
|
120289
|
-
}
|
|
120290
|
-
async function writeLedger(input, entry) {
|
|
120291
|
-
if (!input.worktreePath) return;
|
|
120292
|
-
await upsertPostLedgerEntry(input.worktreePath, entry);
|
|
120293
|
-
}
|
|
120294
|
-
async function existingLedgerEntry(input, idempotencyKey2) {
|
|
120295
|
-
if (!input.worktreePath) return null;
|
|
120296
|
-
const ledger = await readPostFile(input.worktreePath);
|
|
120297
|
-
return ledger?.posts.find((post) => post.idempotencyKey === idempotencyKey2) ?? null;
|
|
120298
|
-
}
|
|
120299
|
-
function sentResult(input, surface, post, reason) {
|
|
120300
|
-
if (hasCardOnlyPayloadIn(input)) {
|
|
120301
|
-
return {
|
|
120302
|
-
card: input.baseCard,
|
|
120303
|
-
reason: "post-sent-card-capability-required",
|
|
120304
|
-
visible: true,
|
|
120305
|
-
post
|
|
120306
|
-
};
|
|
120307
|
-
}
|
|
120308
|
-
if (surface.mode === "hybrid" || input.cardStarted) {
|
|
120309
|
-
return {
|
|
120310
|
-
card: compactAuditCard(input, post),
|
|
120311
|
-
reason: reason === "post-ledger-already-sent" ? "post-ledger-already-sent" : surface.mode === "hybrid" ? "hybrid-post-sent-compact-card" : reason,
|
|
120312
|
-
visible: true,
|
|
120313
|
-
post
|
|
120314
|
-
};
|
|
120315
|
-
}
|
|
120316
|
-
return {
|
|
120317
|
-
card: null,
|
|
120318
|
-
reason,
|
|
120319
|
-
visible: true,
|
|
120320
|
-
post
|
|
120321
|
-
};
|
|
120322
|
-
}
|
|
120323
|
-
async function ledgerSummaryFor(input) {
|
|
120324
|
-
if (!input.worktreePath) return summarizePostLedger(null);
|
|
120325
|
-
return summarizePostLedger(await readPostFile(input.worktreePath));
|
|
120326
|
-
}
|
|
120327
|
-
function emitSurfaceObservation(input) {
|
|
120328
|
-
console.log(
|
|
120329
|
-
"[response_surface.dispatch]",
|
|
120330
|
-
JSON.stringify({
|
|
120331
|
-
event: "response_surface.dispatch",
|
|
120332
|
-
botId: input.facts.botId,
|
|
120333
|
-
chatId: input.facts.chatId,
|
|
120334
|
-
threadId: input.facts.threadId,
|
|
120335
|
-
reason: input.result.reason,
|
|
120336
|
-
visible: input.result.visible,
|
|
120337
|
-
hasCard: !!input.result.card,
|
|
120338
|
-
hasPost: !!input.result.post,
|
|
120339
|
-
postMessageIdPresent: !!input.result.post?.messageId,
|
|
120340
|
-
budget: input.result.budget ? {
|
|
120341
|
-
allowed: input.result.budget.allowed,
|
|
120342
|
-
used: input.result.budget.used,
|
|
120343
|
-
limit: input.result.budget.limit,
|
|
120344
|
-
windowMs: input.result.budget.windowMs,
|
|
120345
|
-
resetAt: input.result.budget.resetAt,
|
|
120346
|
-
reason: input.result.budget.reason
|
|
120347
|
-
} : void 0,
|
|
120348
|
-
ledger: input.ledger,
|
|
120349
|
-
durationMs: input.durationMs
|
|
120350
|
-
})
|
|
120351
|
-
);
|
|
120352
|
-
}
|
|
120353
|
-
async function dispatchResponseSurfaceInner(input) {
|
|
120354
|
-
const declaredSurface = input.state?.response_surface;
|
|
120355
|
-
if (declaredSurface?.mode === "card" || declaredSurface?.primary === "card") {
|
|
120356
|
-
return fullCard(input, "legacy-card-mode");
|
|
120357
|
-
}
|
|
120358
|
-
const surface = declaredSurface ?? { mode: "post", primary: "post" };
|
|
120359
|
-
const cfg = input.prototypeConfig;
|
|
120360
|
-
if (!cfg?.enabled) return fullCard(input, "prototype-disabled");
|
|
120361
|
-
if (cfg.kill_switch) return fullCard(input, "kill-switch-active");
|
|
120362
|
-
if (!isResponseSurfacePrototypeAllowlisted(cfg, {
|
|
120363
|
-
chatId: input.facts.chatId,
|
|
120364
|
-
threadId: input.facts.threadId
|
|
120365
|
-
})) {
|
|
120366
|
-
return fullCard(input, "not-allowlisted");
|
|
120367
|
-
}
|
|
120368
|
-
if (!cfg.post_outbound_enabled) return fullCard(input, "post-outbound-disabled");
|
|
120369
|
-
if (cfg.max_posts_per_turn < 1) return fullCard(input, "post-outbound-disabled");
|
|
120370
|
-
if (cfg.max_posts_per_window < 1) return fullCard(input, "post-rate-limit-exhausted");
|
|
120371
|
-
if (!input.postOutboundAvailable || !input.postClient) {
|
|
120372
|
-
return fullCard(input, "post-outbound-unavailable");
|
|
120373
|
-
}
|
|
120374
|
-
if (!input.visibleFallbackAvailable) {
|
|
120375
|
-
return fullCard(input, "visible-fallback-unavailable");
|
|
120376
|
-
}
|
|
120377
|
-
if (!input.postLedgerAvailable || !input.worktreePath) {
|
|
120378
|
-
return fullCard(input, "post-ledger-unavailable");
|
|
120379
|
-
}
|
|
120380
|
-
const mentions = surface.post?.mentions ?? [];
|
|
120381
|
-
const blockedMention = mentions.find(
|
|
120382
|
-
(mention) => !isResponseSurfaceMentionAllowed(cfg, mention.user_id)
|
|
120383
|
-
);
|
|
120384
|
-
const text = postText(input);
|
|
120385
|
-
const policyDigest = digestPostContent(text);
|
|
120386
|
-
const role = postRole(input);
|
|
120387
|
-
const logicalIndex = 0;
|
|
120388
|
-
const policyIdempotencyKey = derivePostIdempotencyKey({
|
|
120389
|
-
botId: input.facts.botId,
|
|
120390
|
-
threadId: input.facts.threadId,
|
|
120391
|
-
triggerMessageId: input.facts.triggerMessageId,
|
|
120392
|
-
role,
|
|
120393
|
-
logicalIndex,
|
|
120394
|
-
contentDigest: policyDigest
|
|
120395
|
-
});
|
|
120396
|
-
const now = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
120397
|
-
if (blockedMention) {
|
|
120398
|
-
const policyError = `mention target is not allowed by response surface policy: ${blockedMention.user_id}`;
|
|
120399
|
-
if (input.livePost) {
|
|
120400
|
-
try {
|
|
120401
|
-
await input.postClient.updatePost(input.livePost.messageId, buildPostContent({ text }));
|
|
120402
|
-
} catch (err) {
|
|
120403
|
-
console.warn(
|
|
120404
|
-
"[surface_dispatch] live post policy-blocked cleanup update failed:",
|
|
120405
|
-
err
|
|
120406
|
-
);
|
|
120407
|
-
}
|
|
120408
|
-
}
|
|
120409
|
-
await writeLedger(
|
|
120410
|
-
input,
|
|
120411
|
-
newLedgerEntry({
|
|
120412
|
-
status: "planned",
|
|
120413
|
-
idempotencyKey: policyIdempotencyKey,
|
|
120414
|
-
now,
|
|
120415
|
-
facts: input.facts,
|
|
120416
|
-
role,
|
|
120417
|
-
logicalIndex,
|
|
120418
|
-
contentDigest: policyDigest,
|
|
120419
|
-
mentionCount: mentions.length,
|
|
120420
|
-
error: policyError
|
|
120421
|
-
})
|
|
120422
|
-
);
|
|
120423
|
-
return {
|
|
120424
|
-
card: policyBlockedCard(input),
|
|
120425
|
-
reason: "mention-policy-blocked",
|
|
120426
|
-
visible: true,
|
|
120427
|
-
post: {
|
|
120428
|
-
idempotencyKey: policyIdempotencyKey,
|
|
120429
|
-
role,
|
|
120430
|
-
requiresPolicyLedgerMark: true,
|
|
120431
|
-
policyError
|
|
120432
|
-
}
|
|
120433
|
-
};
|
|
120434
|
-
}
|
|
120435
|
-
const content = buildPostContent({
|
|
120436
|
-
text,
|
|
120437
|
-
mentions: mentions.map((mention) => ({
|
|
120438
|
-
userId: mention.user_id,
|
|
120439
|
-
label: mention.label
|
|
120440
|
-
}))
|
|
120441
|
-
});
|
|
120442
|
-
const contentDigest = digestPostContent(content);
|
|
120443
|
-
const idempotencyKey2 = input.livePost?.idempotencyKey ?? derivePostIdempotencyKey({
|
|
120444
|
-
botId: input.facts.botId,
|
|
120445
|
-
threadId: input.facts.threadId,
|
|
120446
|
-
triggerMessageId: input.facts.triggerMessageId,
|
|
120447
|
-
role,
|
|
120448
|
-
logicalIndex,
|
|
120449
|
-
contentDigest
|
|
120450
|
-
});
|
|
120451
|
-
const existing = await existingLedgerEntry(input, idempotencyKey2);
|
|
120452
|
-
if (existing?.status === "sent" && existing.postMessageId) {
|
|
120453
|
-
return sentResult(
|
|
120454
|
-
input,
|
|
120455
|
-
surface,
|
|
120456
|
-
{ idempotencyKey: idempotencyKey2, messageId: existing.postMessageId, role },
|
|
120457
|
-
"post-ledger-already-sent"
|
|
120458
|
-
);
|
|
120459
|
-
}
|
|
120460
|
-
if (existing?.status === "sent") {
|
|
120461
|
-
return fullCard(input, "post-orphan-reconciled-fallback-card");
|
|
120462
|
-
}
|
|
120463
|
-
if (existing?.status === "fallback_visible") {
|
|
120464
|
-
return {
|
|
120465
|
-
card: fallbackFailureCard(
|
|
120466
|
-
input,
|
|
120467
|
-
existing.error ?? "post ledger already reconciled to visible fallback"
|
|
120468
|
-
),
|
|
120469
|
-
reason: "post-orphan-reconciled-fallback-card",
|
|
120470
|
-
visible: true,
|
|
120471
|
-
post: { idempotencyKey: idempotencyKey2, role }
|
|
120472
|
-
};
|
|
120473
|
-
}
|
|
120474
|
-
if (existing?.status === "policy_blocked") {
|
|
120475
|
-
return {
|
|
120476
|
-
card: policyBlockedCard(input),
|
|
120477
|
-
reason: "mention-policy-blocked",
|
|
120478
|
-
visible: true,
|
|
120479
|
-
post: { idempotencyKey: idempotencyKey2, role }
|
|
120480
|
-
};
|
|
120481
|
-
}
|
|
120482
|
-
if (existing) {
|
|
120483
|
-
const error = existing.status === "failed" && existing.error ? existing.error : "orphaned post ledger entry reconciled without resend; visible card fallback used";
|
|
120484
|
-
return {
|
|
120485
|
-
card: fallbackFailureCard(input, error),
|
|
120486
|
-
reason: "post-orphan-reconciled-fallback-card",
|
|
120487
|
-
visible: true,
|
|
120488
|
-
post: {
|
|
120489
|
-
idempotencyKey: idempotencyKey2,
|
|
120490
|
-
role,
|
|
120491
|
-
requiresFallbackLedgerMark: true,
|
|
120492
|
-
fallbackError: error
|
|
120493
|
-
}
|
|
120494
|
-
};
|
|
120495
|
-
}
|
|
120496
|
-
const budget = input.livePost ? void 0 : input.postBudget?.reserve();
|
|
120497
|
-
if (budget && !budget.allowed) {
|
|
120498
|
-
return {
|
|
120499
|
-
...fullCard(input, "post-rate-limit-exhausted"),
|
|
120500
|
-
budget
|
|
120501
|
-
};
|
|
120502
|
-
}
|
|
120503
|
-
await writeLedger(
|
|
120504
|
-
input,
|
|
120505
|
-
newLedgerEntry({
|
|
120506
|
-
status: "planned",
|
|
120507
|
-
idempotencyKey: idempotencyKey2,
|
|
120508
|
-
now,
|
|
120509
|
-
facts: input.facts,
|
|
120510
|
-
role,
|
|
120511
|
-
logicalIndex,
|
|
120512
|
-
contentDigest,
|
|
120513
|
-
mentionCount: mentions.length
|
|
120514
|
-
})
|
|
120515
|
-
);
|
|
120516
|
-
await writeLedger(
|
|
120517
|
-
input,
|
|
120518
|
-
newLedgerEntry({
|
|
120519
|
-
status: "pending",
|
|
120520
|
-
idempotencyKey: idempotencyKey2,
|
|
120521
|
-
now,
|
|
120522
|
-
facts: input.facts,
|
|
120523
|
-
role,
|
|
120524
|
-
logicalIndex,
|
|
120525
|
-
contentDigest,
|
|
120526
|
-
mentionCount: mentions.length
|
|
120527
|
-
})
|
|
120528
|
-
);
|
|
120529
|
-
try {
|
|
120530
|
-
const sent = input.livePost ? await input.postClient.updatePost(input.livePost.messageId, content) : await input.postClient.createPostReply(input.facts.replyToMessageId, content, {
|
|
120531
|
-
replyInThread: input.facts.replyInThread,
|
|
120532
|
-
idempotencyKey: idempotencyKey2
|
|
120533
|
-
});
|
|
120534
|
-
await writeLedger(
|
|
120535
|
-
input,
|
|
120536
|
-
newLedgerEntry({
|
|
120537
|
-
status: "sent",
|
|
120538
|
-
idempotencyKey: idempotencyKey2,
|
|
120539
|
-
now: input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
120540
|
-
facts: input.facts,
|
|
120541
|
-
role,
|
|
120542
|
-
logicalIndex,
|
|
120543
|
-
contentDigest,
|
|
120544
|
-
mentionCount: mentions.length,
|
|
120545
|
-
postMessageId: sent.messageId,
|
|
120546
|
-
attempts: [
|
|
120547
|
-
{
|
|
120548
|
-
attemptedAt: input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
120549
|
-
status: "sent",
|
|
120550
|
-
retryable: false
|
|
120551
|
-
}
|
|
120552
|
-
]
|
|
120553
|
-
})
|
|
120554
|
-
);
|
|
120555
|
-
const post = { idempotencyKey: idempotencyKey2, messageId: sent.messageId, role };
|
|
120556
|
-
return {
|
|
120557
|
-
...sentResult(input, surface, post, input.livePost ? "post-updated" : "post-sent"),
|
|
120558
|
-
budget
|
|
120559
|
-
};
|
|
120560
|
-
} catch (err) {
|
|
120561
|
-
const failedAt = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
120562
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
120563
|
-
await writeLedger(
|
|
120564
|
-
input,
|
|
120565
|
-
newLedgerEntry({
|
|
120566
|
-
status: "failed",
|
|
120567
|
-
idempotencyKey: idempotencyKey2,
|
|
120568
|
-
now: failedAt,
|
|
120569
|
-
facts: input.facts,
|
|
120570
|
-
role,
|
|
120571
|
-
logicalIndex,
|
|
120572
|
-
contentDigest,
|
|
120573
|
-
mentionCount: mentions.length,
|
|
120574
|
-
error,
|
|
120575
|
-
attempts: [
|
|
120576
|
-
{
|
|
120577
|
-
attemptedAt: failedAt,
|
|
120578
|
-
status: "failed",
|
|
120579
|
-
retryable: false,
|
|
120580
|
-
error
|
|
120581
|
-
}
|
|
120582
|
-
]
|
|
120583
|
-
})
|
|
120584
|
-
);
|
|
120585
|
-
return {
|
|
120586
|
-
card: fallbackFailureCard(input, err),
|
|
120587
|
-
reason: "post-failed-fallback-card",
|
|
120588
|
-
visible: true,
|
|
120589
|
-
post: {
|
|
120590
|
-
idempotencyKey: idempotencyKey2,
|
|
120591
|
-
role,
|
|
120592
|
-
requiresFallbackLedgerMark: true,
|
|
120593
|
-
fallbackError: error
|
|
120594
|
-
},
|
|
120595
|
-
budget
|
|
120596
|
-
};
|
|
120597
|
-
}
|
|
120598
|
-
}
|
|
120599
|
-
async function dispatchResponseSurface(input) {
|
|
120600
|
-
const startedAt = Date.now();
|
|
120601
|
-
const result = await dispatchResponseSurfaceInner(input);
|
|
120602
|
-
const ledger = await ledgerSummaryFor(input);
|
|
120603
|
-
emitSurfaceObservation({
|
|
120604
|
-
facts: input.facts,
|
|
120605
|
-
result,
|
|
120606
|
-
ledger,
|
|
120607
|
-
durationMs: Date.now() - startedAt
|
|
120608
|
-
});
|
|
120609
|
-
return result;
|
|
120610
|
-
}
|
|
120611
|
-
|
|
120612
|
-
// src/bridge/postProgress.ts
|
|
120613
|
-
var DEFAULT_PLACEHOLDER_TEXT = "\u52AA\u529B\u56DE\u7B54\u4E2D...";
|
|
120614
|
-
var DEFAULT_PATCH_INTERVAL_MS2 = 1500;
|
|
120615
|
-
var DEFAULT_MAX_PROGRESS_EDITS = 16;
|
|
120616
|
-
function normalizeText2(text) {
|
|
120617
|
-
return text.replace(/\r\n/g, "\n").trim();
|
|
120618
|
-
}
|
|
120619
|
-
function progressText(raw, fallback) {
|
|
120620
|
-
const text = normalizeText2(raw);
|
|
120621
|
-
if (!text) return fallback;
|
|
120622
|
-
return text.length > 3500 ? `${text.slice(0, 3500)}
|
|
120623
|
-
|
|
120624
|
-
_\u5904\u7406\u4E2D\u5185\u5BB9\u8F83\u957F\uFF0C\u7EC8\u7A3F\u4F1A\u6536\u655B\u3002_` : text;
|
|
120625
|
-
}
|
|
120626
|
-
var LivePostProgressHandle = class {
|
|
120627
|
-
messageId;
|
|
120628
|
-
idempotencyKey;
|
|
120629
|
-
role = "primary";
|
|
120630
|
-
postClient;
|
|
120631
|
-
patchIntervalMs;
|
|
120632
|
-
maxProgressEdits;
|
|
120633
|
-
initialText;
|
|
120634
|
-
textBuffer = "";
|
|
120635
|
-
lastPatchedText = "";
|
|
120636
|
-
progressEdits = 0;
|
|
120637
|
-
pendingPatch = null;
|
|
120638
|
-
inFlight = Promise.resolve();
|
|
120639
|
-
closed = false;
|
|
120640
|
-
constructor(opts) {
|
|
120641
|
-
this.postClient = opts.postClient;
|
|
120642
|
-
this.messageId = opts.messageId;
|
|
120643
|
-
this.idempotencyKey = opts.idempotencyKey;
|
|
120644
|
-
this.initialText = opts.initialText;
|
|
120645
|
-
this.lastPatchedText = opts.initialText;
|
|
120646
|
-
this.patchIntervalMs = opts.patchIntervalMs;
|
|
120647
|
-
this.maxProgressEdits = opts.maxProgressEdits;
|
|
120648
|
-
}
|
|
120649
|
-
handle(event) {
|
|
120650
|
-
if (this.closed) return;
|
|
120651
|
-
if (event.type === "answer_delta") {
|
|
120652
|
-
this.textBuffer += event.text;
|
|
120653
|
-
} else if (event.type === "answer_snapshot") {
|
|
120654
|
-
this.textBuffer = event.text;
|
|
120655
|
-
} else {
|
|
120656
|
-
return;
|
|
120657
|
-
}
|
|
120658
|
-
this.schedulePatch();
|
|
120659
|
-
}
|
|
120660
|
-
async finalize(opts) {
|
|
120661
|
-
await this.drain();
|
|
120662
|
-
const finalText = normalizeText2(opts.text) || this.initialText;
|
|
120663
|
-
await this.update(finalText, opts.mentions);
|
|
120664
|
-
}
|
|
120665
|
-
async drain() {
|
|
120666
|
-
this.closed = true;
|
|
120667
|
-
if (this.pendingPatch) {
|
|
120668
|
-
clearTimeout(this.pendingPatch);
|
|
120669
|
-
this.pendingPatch = null;
|
|
120670
|
-
}
|
|
120671
|
-
await this.inFlight;
|
|
120672
|
-
}
|
|
120673
|
-
close() {
|
|
120674
|
-
this.closed = true;
|
|
120675
|
-
if (this.pendingPatch) {
|
|
120676
|
-
clearTimeout(this.pendingPatch);
|
|
120677
|
-
this.pendingPatch = null;
|
|
120678
|
-
}
|
|
120679
|
-
}
|
|
120680
|
-
schedulePatch() {
|
|
120681
|
-
if (this.pendingPatch || this.progressEdits >= this.maxProgressEdits) return;
|
|
120682
|
-
this.pendingPatch = setTimeout(() => {
|
|
120683
|
-
this.pendingPatch = null;
|
|
120684
|
-
void this.patchProgress();
|
|
120685
|
-
}, this.patchIntervalMs);
|
|
120686
|
-
this.pendingPatch.unref?.();
|
|
120687
|
-
}
|
|
120688
|
-
async patchProgress() {
|
|
120689
|
-
if (this.closed || this.progressEdits >= this.maxProgressEdits) return;
|
|
120690
|
-
const nextText = progressText(this.textBuffer, this.initialText);
|
|
120691
|
-
if (nextText === this.lastPatchedText) return;
|
|
120692
|
-
this.progressEdits += 1;
|
|
120693
|
-
this.inFlight = this.inFlight.then(() => this.update(nextText)).catch((err) => {
|
|
120694
|
-
console.warn("[post_progress] progress update failed (continuing):", err);
|
|
120695
|
-
});
|
|
120696
|
-
await this.inFlight;
|
|
120697
|
-
}
|
|
120698
|
-
async update(text, mentions = []) {
|
|
120699
|
-
const content = buildPostContent({ text, mentions });
|
|
120700
|
-
await this.postClient.updatePost(this.messageId, content);
|
|
120701
|
-
this.lastPatchedText = text;
|
|
120702
|
-
}
|
|
120703
|
-
};
|
|
120704
|
-
async function createPostProgressHandle(opts) {
|
|
120705
|
-
const role = "primary";
|
|
120706
|
-
const initialText = normalizeText2(opts.initialText ?? DEFAULT_PLACEHOLDER_TEXT);
|
|
120707
|
-
const idempotencyKey2 = derivePostIdempotencyKey({
|
|
120708
|
-
botId: opts.facts.botId,
|
|
120709
|
-
threadId: opts.facts.threadId,
|
|
120710
|
-
triggerMessageId: opts.facts.triggerMessageId,
|
|
120711
|
-
role,
|
|
120712
|
-
logicalIndex: 0,
|
|
120713
|
-
contentDigest: digestPostContent("live-progress-placeholder")
|
|
120714
|
-
});
|
|
120715
|
-
const sent = await opts.postClient.createPostReply(
|
|
120716
|
-
opts.replyToMessageId,
|
|
120717
|
-
buildPostContent({ text: initialText }),
|
|
120718
|
-
{
|
|
120719
|
-
replyInThread: opts.replyInThread,
|
|
120720
|
-
idempotencyKey: idempotencyKey2
|
|
120721
|
-
}
|
|
120722
|
-
);
|
|
120723
|
-
return new LivePostProgressHandle({
|
|
120724
|
-
postClient: opts.postClient,
|
|
120725
|
-
messageId: sent.messageId,
|
|
120726
|
-
idempotencyKey: idempotencyKey2,
|
|
120727
|
-
initialText,
|
|
120728
|
-
patchIntervalMs: opts.patchIntervalMs ?? DEFAULT_PATCH_INTERVAL_MS2,
|
|
120729
|
-
maxProgressEdits: opts.maxProgressEdits ?? DEFAULT_MAX_PROGRESS_EDITS
|
|
120730
|
-
});
|
|
120731
|
-
}
|
|
120732
|
-
|
|
120733
|
-
// src/bridge/postBudget.ts
|
|
120734
|
-
function scopeKey(scope) {
|
|
120735
|
-
return `${scope.botId}\0${scope.chatId}\0${scope.threadId}`;
|
|
120736
|
-
}
|
|
120737
|
-
var ResponseSurfacePostBudget = class {
|
|
120738
|
-
buckets = /* @__PURE__ */ new Map();
|
|
120739
|
-
reserve(input) {
|
|
120740
|
-
const nowMs = input.nowMs ?? Date.now();
|
|
120741
|
-
const limit = Math.max(0, Math.floor(input.maxPosts));
|
|
120742
|
-
const windowMs = Math.max(1, Math.floor(input.windowMs));
|
|
120743
|
-
const key = scopeKey(input.scope);
|
|
120744
|
-
const cutoff = nowMs - windowMs;
|
|
120745
|
-
const existing = (this.buckets.get(key) ?? []).filter((ts) => ts > cutoff);
|
|
120746
|
-
if (limit < 1 || existing.length >= limit) {
|
|
120747
|
-
this.buckets.set(key, existing);
|
|
120748
|
-
return {
|
|
120749
|
-
allowed: false,
|
|
120750
|
-
used: existing.length,
|
|
120751
|
-
limit,
|
|
120752
|
-
windowMs,
|
|
120753
|
-
resetAt: existing.length > 0 ? new Date(Math.min(...existing) + windowMs).toISOString() : void 0,
|
|
120754
|
-
reason: "post-window-exhausted"
|
|
120755
|
-
};
|
|
120756
|
-
}
|
|
120757
|
-
const next = [...existing, nowMs];
|
|
120758
|
-
this.buckets.set(key, next);
|
|
120759
|
-
return {
|
|
120760
|
-
allowed: true,
|
|
120761
|
-
used: next.length,
|
|
120762
|
-
limit,
|
|
120763
|
-
windowMs
|
|
120764
|
-
};
|
|
120765
|
-
}
|
|
120766
|
-
};
|
|
120767
|
-
|
|
120768
120661
|
// src/bridge/handler.ts
|
|
120769
120662
|
var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
120770
120663
|
function execGit(cwd, args) {
|
|
@@ -120790,7 +120683,7 @@ stderr: ${stderr}`)
|
|
|
120790
120683
|
}
|
|
120791
120684
|
async function pathExists(p) {
|
|
120792
120685
|
try {
|
|
120793
|
-
await
|
|
120686
|
+
await fs6.stat(p);
|
|
120794
120687
|
return true;
|
|
120795
120688
|
} catch {
|
|
120796
120689
|
return false;
|
|
@@ -120808,7 +120701,7 @@ async function isWorktreeGitHealthy(worktreePath) {
|
|
|
120808
120701
|
});
|
|
120809
120702
|
}
|
|
120810
120703
|
async function ensureRepoClone(basePath, url, token, label) {
|
|
120811
|
-
const gitDir =
|
|
120704
|
+
const gitDir = path9.join(basePath, ".git");
|
|
120812
120705
|
if (await pathExists(gitDir)) {
|
|
120813
120706
|
return;
|
|
120814
120707
|
}
|
|
@@ -120818,15 +120711,15 @@ async function ensureRepoClone(basePath, url, token, label) {
|
|
|
120818
120711
|
);
|
|
120819
120712
|
}
|
|
120820
120713
|
const uniq = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
120821
|
-
const tmpScript =
|
|
120714
|
+
const tmpScript = path9.join(basePath, "..", `.askpass-${uniq}.sh`);
|
|
120822
120715
|
const tokenEnvVar = `LARKWAY_GIT_TOKEN_${uniq.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
120823
120716
|
try {
|
|
120824
|
-
await
|
|
120717
|
+
await fs6.mkdir(path9.dirname(basePath), { recursive: true });
|
|
120825
120718
|
const scriptContent = [
|
|
120826
120719
|
"#!/bin/sh",
|
|
120827
120720
|
`echo "\${${tokenEnvVar}}"`
|
|
120828
120721
|
].join("\n") + "\n";
|
|
120829
|
-
await
|
|
120722
|
+
await fs6.writeFile(tmpScript, scriptContent, { mode: 448, encoding: "utf8" });
|
|
120830
120723
|
console.log(`[bridge.handler] cloning ${label} into ${basePath} \u2026`);
|
|
120831
120724
|
const env = {
|
|
120832
120725
|
...process.env,
|
|
@@ -120854,7 +120747,7 @@ stderr: ${stderr}`));
|
|
|
120854
120747
|
console.log(`[bridge.handler] clone of ${label} complete.`);
|
|
120855
120748
|
await execGit(basePath, ["remote", "set-url", "origin", url]);
|
|
120856
120749
|
} finally {
|
|
120857
|
-
await
|
|
120750
|
+
await fs6.unlink(tmpScript).catch(() => {
|
|
120858
120751
|
});
|
|
120859
120752
|
}
|
|
120860
120753
|
}
|
|
@@ -120908,8 +120801,8 @@ var CORE_DENY_RULES = [
|
|
|
120908
120801
|
"Bash(npm publish *)"
|
|
120909
120802
|
];
|
|
120910
120803
|
async function writeWorktreeSettings(worktreePath, opts = {}) {
|
|
120911
|
-
const dir =
|
|
120912
|
-
await
|
|
120804
|
+
const dir = path9.join(worktreePath, ".claude");
|
|
120805
|
+
await fs6.mkdir(dir, { recursive: true });
|
|
120913
120806
|
const allow = Array.from(/* @__PURE__ */ new Set([...CORE_ALLOW_RULES, ...opts.allowExtra ?? []]));
|
|
120914
120807
|
const settings = {
|
|
120915
120808
|
permissions: {
|
|
@@ -120917,17 +120810,17 @@ async function writeWorktreeSettings(worktreePath, opts = {}) {
|
|
|
120917
120810
|
deny: CORE_DENY_RULES
|
|
120918
120811
|
}
|
|
120919
120812
|
};
|
|
120920
|
-
await
|
|
120921
|
-
|
|
120813
|
+
await fs6.writeFile(
|
|
120814
|
+
path9.join(dir, "settings.local.json"),
|
|
120922
120815
|
JSON.stringify(settings, null, 2),
|
|
120923
120816
|
"utf8"
|
|
120924
120817
|
);
|
|
120925
120818
|
}
|
|
120926
120819
|
async function ensureNodeModules(worktreePath) {
|
|
120927
|
-
const monorepDir =
|
|
120928
|
-
const pkgJson =
|
|
120820
|
+
const monorepDir = path9.join(worktreePath, "monorep");
|
|
120821
|
+
const pkgJson = path9.join(monorepDir, "package.json");
|
|
120929
120822
|
if (!await pathExists(pkgJson)) return;
|
|
120930
|
-
const modulesMarker =
|
|
120823
|
+
const modulesMarker = path9.join(monorepDir, "node_modules", ".modules.yaml");
|
|
120931
120824
|
if (await pathExists(modulesMarker)) return;
|
|
120932
120825
|
const start = Date.now();
|
|
120933
120826
|
try {
|
|
@@ -121021,7 +120914,6 @@ async function createOnlyPostFallback(opts) {
|
|
|
121021
120914
|
}
|
|
121022
120915
|
var BridgeHandler = class {
|
|
121023
120916
|
deps;
|
|
121024
|
-
responseSurfacePostBudget = new ResponseSurfacePostBudget();
|
|
121025
120917
|
closed = false;
|
|
121026
120918
|
constructor(deps) {
|
|
121027
120919
|
this.deps = deps;
|
|
@@ -121134,31 +121026,19 @@ var BridgeHandler = class {
|
|
|
121134
121026
|
const isTopLevel = !(typeof parsed.raw.root_id === "string" && parsed.raw.root_id);
|
|
121135
121027
|
const replyInThread = isTopLevel;
|
|
121136
121028
|
const prototypeConfig = this.deps.botConfig?.response_surface_prototype;
|
|
121137
|
-
const postOutboundAvailable = isResponseSurfacePostOutboundAvailable(
|
|
121138
|
-
prototypeConfig,
|
|
121139
|
-
{ chatId: parsed.chatId, threadId },
|
|
121140
|
-
{ postClientAvailable: !!this.deps.postClient }
|
|
121141
|
-
);
|
|
121142
121029
|
const cardKitAvailable = isResponseSurfaceCardKitAvailable(
|
|
121143
121030
|
prototypeConfig,
|
|
121144
121031
|
{ chatId: parsed.chatId, threadId },
|
|
121145
121032
|
{ cardKitClientAvailable: !!this.deps.cardKitClient }
|
|
121146
121033
|
);
|
|
121147
|
-
const surfaceController = SurfaceController.create({
|
|
121148
|
-
prototypeConfig,
|
|
121149
|
-
chatId: parsed.chatId,
|
|
121150
|
-
threadId,
|
|
121151
|
-
postOutboundAvailable: false,
|
|
121152
|
-
postLedgerAvailable: true,
|
|
121153
|
-
visibleFallbackAvailable: true
|
|
121154
|
-
});
|
|
121155
121034
|
let card;
|
|
121156
121035
|
let cardKitProgress;
|
|
121157
121036
|
let cardKitRecord;
|
|
121158
121037
|
let cardKitStartFailed = false;
|
|
121159
|
-
let
|
|
121160
|
-
let
|
|
121161
|
-
|
|
121038
|
+
let legacyCardStartFailed = false;
|
|
121039
|
+
let legacyCardStartFailureReason;
|
|
121040
|
+
let startFailurePostFallbackSent = false;
|
|
121041
|
+
if (!cardKitAvailable) {
|
|
121162
121042
|
try {
|
|
121163
121043
|
card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
|
|
121164
121044
|
await recordEvent({
|
|
@@ -121168,6 +121048,8 @@ var BridgeHandler = class {
|
|
|
121168
121048
|
reason: "\u5DF2\u4EA4\u7ED9\u672C\u5730 Agent \u5904\u7406\u3002"
|
|
121169
121049
|
});
|
|
121170
121050
|
} catch (err) {
|
|
121051
|
+
legacyCardStartFailed = true;
|
|
121052
|
+
legacyCardStartFailureReason = String(err);
|
|
121171
121053
|
console.error("[bridge.handler] Failed to start card for thread", threadId, err);
|
|
121172
121054
|
await recordEvent({
|
|
121173
121055
|
status: "running",
|
|
@@ -121183,7 +121065,7 @@ var BridgeHandler = class {
|
|
|
121183
121065
|
status: "running",
|
|
121184
121066
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
121185
121067
|
appendPath: "\u5EF6\u8FDF\u521B\u5EFA\u5361\u7247",
|
|
121186
|
-
reason: "
|
|
121068
|
+
reason: "CardKit response surface is available; legacy card is reserved for fallback."
|
|
121187
121069
|
});
|
|
121188
121070
|
}
|
|
121189
121071
|
try {
|
|
@@ -121194,7 +121076,7 @@ var BridgeHandler = class {
|
|
|
121194
121076
|
throw new Error("agent_workspace runtime requires workspace path conventions");
|
|
121195
121077
|
}
|
|
121196
121078
|
}
|
|
121197
|
-
const worktreePath = isAgentWorkspace ?
|
|
121079
|
+
const worktreePath = isAgentWorkspace ? path9.join(conventions.workspaceSessionsDir, threadId) : path9.join(conventions.worktreesDir, threadId);
|
|
121198
121080
|
const runCwd = isAgentWorkspace ? conventions.agentWorkspacePath : worktreePath;
|
|
121199
121081
|
const hasRepo = !isAgentWorkspace && !!conventions.repoCachePath;
|
|
121200
121082
|
const buildWorktree = hasRepo && !conventions.readOnly;
|
|
@@ -121265,7 +121147,7 @@ var BridgeHandler = class {
|
|
|
121265
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)`
|
|
121266
121148
|
);
|
|
121267
121149
|
try {
|
|
121268
|
-
await
|
|
121150
|
+
await fs6.rm(worktreePath, { recursive: true, force: true });
|
|
121269
121151
|
} catch (rmErr) {
|
|
121270
121152
|
console.warn("[bridge.handler] failed to remove stale worktree (will attempt rebuild anyway):", rmErr);
|
|
121271
121153
|
}
|
|
@@ -121289,7 +121171,7 @@ var BridgeHandler = class {
|
|
|
121289
121171
|
`[bridge.handler] created worktree ${worktreePath} on branch ${branchName}`
|
|
121290
121172
|
);
|
|
121291
121173
|
} else {
|
|
121292
|
-
await
|
|
121174
|
+
await fs6.mkdir(worktreePath, { recursive: true });
|
|
121293
121175
|
if (conventions.readOnly && conventions.repoCachePath) {
|
|
121294
121176
|
console.log(
|
|
121295
121177
|
`[bridge.handler] created scratch dir ${worktreePath} (read_only bot: repo read-only at ${conventions.repoCachePath}, no worktree)`
|
|
@@ -121405,72 +121287,34 @@ var BridgeHandler = class {
|
|
|
121405
121287
|
}
|
|
121406
121288
|
}
|
|
121407
121289
|
}
|
|
121408
|
-
if (!card &&
|
|
121409
|
-
const budget = prototypeConfig ? this.responseSurfacePostBudget.reserve({
|
|
121410
|
-
scope: {
|
|
121411
|
-
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
121412
|
-
chatId: parsed.chatId,
|
|
121413
|
-
threadId
|
|
121414
|
-
},
|
|
121415
|
-
maxPosts: prototypeConfig.max_posts_per_window,
|
|
121416
|
-
windowMs: prototypeConfig.post_window_ms
|
|
121417
|
-
}) : void 0;
|
|
121418
|
-
if (budget?.allowed === false) {
|
|
121419
|
-
console.warn("[bridge.handler] post progress budget exhausted; using card fallback");
|
|
121420
|
-
progressPostStartFailed = true;
|
|
121421
|
-
} else {
|
|
121422
|
-
try {
|
|
121423
|
-
progressPost = await createPostProgressHandle({
|
|
121424
|
-
postClient: this.deps.postClient,
|
|
121425
|
-
replyToMessageId: messageId,
|
|
121426
|
-
replyInThread,
|
|
121427
|
-
facts: {
|
|
121428
|
-
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
121429
|
-
threadId,
|
|
121430
|
-
triggerMessageId: messageId
|
|
121431
|
-
},
|
|
121432
|
-
initialText: "\u52AA\u529B\u56DE\u7B54\u4E2D..."
|
|
121433
|
-
});
|
|
121434
|
-
await this.deps.client.removeProcessingReaction?.(messageId);
|
|
121435
|
-
await recordEvent({
|
|
121436
|
-
status: "running",
|
|
121437
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
121438
|
-
appendPath: "\u5DF2\u521B\u5EFA post",
|
|
121439
|
-
reason: "response surface \u4F7F\u7528 post \u4F5C\u4E3A\u672C\u8F6E\u4E3B\u56DE\u590D\u9762\u3002"
|
|
121440
|
-
});
|
|
121441
|
-
} catch (err) {
|
|
121442
|
-
progressPostStartFailed = true;
|
|
121443
|
-
console.warn("[bridge.handler] create progress post failed; using card fallback:", err);
|
|
121444
|
-
}
|
|
121445
|
-
}
|
|
121446
|
-
}
|
|
121447
|
-
if (!card && (progressPostStartFailed || cardKitStartFailed)) {
|
|
121290
|
+
if (!card && cardKitStartFailed) {
|
|
121448
121291
|
try {
|
|
121449
121292
|
card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
|
|
121450
121293
|
await this.deps.client.removeProcessingReaction?.(messageId);
|
|
121451
121294
|
await recordEvent({
|
|
121452
121295
|
status: "running",
|
|
121453
121296
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
121454
|
-
appendPath:
|
|
121455
|
-
reason:
|
|
121297
|
+
appendPath: "CardKit \u5931\u8D25\uFF0C\u5DF2\u521B\u5EFA\u5361\u7247",
|
|
121298
|
+
reason: "CardKit \u4E3B\u9762\u521B\u5EFA\u5931\u8D25\uFF0Cbridge \u4F7F\u7528\u53EF\u89C1\u5361\u7247\u515C\u5E95\u3002"
|
|
121456
121299
|
});
|
|
121457
121300
|
} catch (err) {
|
|
121458
121301
|
console.error(
|
|
121459
121302
|
"[bridge.handler] visible card fallback start failed after primary surface start failure:",
|
|
121460
121303
|
err
|
|
121461
121304
|
);
|
|
121462
|
-
await createOnlyPostFallback({
|
|
121305
|
+
const postFallback = await createOnlyPostFallback({
|
|
121463
121306
|
postClient: this.deps.postClient,
|
|
121464
121307
|
replyToMessageId: messageId,
|
|
121465
121308
|
replyInThread,
|
|
121466
121309
|
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
121467
121310
|
threadId,
|
|
121468
121311
|
triggerMessageId: messageId,
|
|
121469
|
-
finalText:
|
|
121312
|
+
finalText: "CardKit \u4E3B\u56DE\u590D\u9762\u521B\u5EFA\u5931\u8D25, legacy \u53EF\u89C1\u5361\u7247\u515C\u5E95\u4E5F\u521B\u5EFA\u5931\u8D25\u3002",
|
|
121470
121313
|
failureReason: String(err),
|
|
121471
121314
|
title: "Larkway fallback",
|
|
121472
121315
|
logPrefix: "[bridge.handler]"
|
|
121473
121316
|
});
|
|
121317
|
+
if (postFallback) startFailurePostFallbackSent = true;
|
|
121474
121318
|
await this.deps.client.removeProcessingReaction?.(messageId);
|
|
121475
121319
|
await recordEvent({
|
|
121476
121320
|
status: "running",
|
|
@@ -121560,7 +121404,6 @@ var BridgeHandler = class {
|
|
|
121560
121404
|
for await (const ev of handle.events) {
|
|
121561
121405
|
if (cardKitProgress) cardKitProgress.handle(ev);
|
|
121562
121406
|
else if (card) card.handle(ev);
|
|
121563
|
-
else progressPost?.handle(ev);
|
|
121564
121407
|
if (ev.type === "system_init") {
|
|
121565
121408
|
sessionId = ev.sessionId;
|
|
121566
121409
|
}
|
|
@@ -121707,44 +121550,8 @@ var BridgeHandler = class {
|
|
|
121707
121550
|
}
|
|
121708
121551
|
}
|
|
121709
121552
|
} else {
|
|
121710
|
-
|
|
121711
|
-
|
|
121712
|
-
state: reportedState,
|
|
121713
|
-
prototypeConfig,
|
|
121714
|
-
facts: {
|
|
121715
|
-
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
121716
|
-
chatId: parsed.chatId,
|
|
121717
|
-
threadId,
|
|
121718
|
-
triggerMessageId: messageId,
|
|
121719
|
-
replyToMessageId: messageId,
|
|
121720
|
-
replyInThread
|
|
121721
|
-
},
|
|
121722
|
-
worktreePath,
|
|
121723
|
-
baseCard: baseCardPayload,
|
|
121724
|
-
cardStarted: !!card,
|
|
121725
|
-
postOutboundAvailable: false,
|
|
121726
|
-
postLedgerAvailable: true,
|
|
121727
|
-
visibleFallbackAvailable: true,
|
|
121728
|
-
postClient: this.deps.postClient,
|
|
121729
|
-
livePost: progressPost ? {
|
|
121730
|
-
messageId: progressPost.messageId,
|
|
121731
|
-
idempotencyKey: progressPost.idempotencyKey,
|
|
121732
|
-
role: progressPost.role
|
|
121733
|
-
} : void 0,
|
|
121734
|
-
postBudget: prototypeConfig ? {
|
|
121735
|
-
reserve: () => this.responseSurfacePostBudget.reserve({
|
|
121736
|
-
scope: {
|
|
121737
|
-
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
121738
|
-
chatId: parsed.chatId,
|
|
121739
|
-
threadId
|
|
121740
|
-
},
|
|
121741
|
-
maxPosts: prototypeConfig.max_posts_per_window,
|
|
121742
|
-
windowMs: prototypeConfig.post_window_ms
|
|
121743
|
-
})
|
|
121744
|
-
} : void 0
|
|
121745
|
-
});
|
|
121746
|
-
if (surfaceDispatch.card) {
|
|
121747
|
-
if (!card) {
|
|
121553
|
+
if (!card) {
|
|
121554
|
+
try {
|
|
121748
121555
|
card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
|
|
121749
121556
|
try {
|
|
121750
121557
|
await writeCardFile(worktreePath, {
|
|
@@ -121758,49 +121565,34 @@ var BridgeHandler = class {
|
|
|
121758
121565
|
} catch (err) {
|
|
121759
121566
|
console.warn("[bridge.handler] writeCardFile(late) failed (continuing):", err);
|
|
121760
121567
|
}
|
|
121761
|
-
}
|
|
121762
|
-
|
|
121763
|
-
|
|
121764
|
-
|
|
121765
|
-
|
|
121766
|
-
|
|
121767
|
-
|
|
121768
|
-
|
|
121769
|
-
|
|
121770
|
-
|
|
121771
|
-
|
|
121772
|
-
|
|
121773
|
-
|
|
121774
|
-
|
|
121775
|
-
|
|
121776
|
-
|
|
121777
|
-
|
|
121778
|
-
|
|
121779
|
-
|
|
121780
|
-
|
|
121781
|
-
|
|
121782
|
-
|
|
121783
|
-
try {
|
|
121784
|
-
await markPostLedgerPolicyBlockedVisible(
|
|
121785
|
-
worktreePath,
|
|
121786
|
-
surfaceDispatch.post.idempotencyKey,
|
|
121787
|
-
{
|
|
121788
|
-
fallbackCardMessageId: card.messageId,
|
|
121789
|
-
error: surfaceDispatch.post.policyError ?? surfaceDispatch.card.failureReason ?? "mention policy blocked; visible card fallback used"
|
|
121790
|
-
}
|
|
121791
|
-
);
|
|
121792
|
-
} catch (err) {
|
|
121793
|
-
keepCardFileForRetry = true;
|
|
121794
|
-
console.warn(
|
|
121795
|
-
"[bridge.handler] policy-blocked ledger mark failed after visible card finalize; keeping card.json for retry:",
|
|
121796
|
-
err
|
|
121797
|
-
);
|
|
121798
|
-
}
|
|
121799
|
-
}
|
|
121800
|
-
if (!keepCardFileForRetry) {
|
|
121801
|
-
await deleteCardFile(worktreePath);
|
|
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;
|
|
121802
121590
|
}
|
|
121803
121591
|
}
|
|
121592
|
+
if (card) {
|
|
121593
|
+
await card.finalize(baseCardPayload);
|
|
121594
|
+
await deleteCardFile(worktreePath);
|
|
121595
|
+
}
|
|
121804
121596
|
}
|
|
121805
121597
|
settle(true);
|
|
121806
121598
|
await recordEvent({
|
|
@@ -121835,7 +121627,7 @@ var BridgeHandler = class {
|
|
|
121835
121627
|
reason: String(err)
|
|
121836
121628
|
});
|
|
121837
121629
|
settle(false);
|
|
121838
|
-
const wtPath = this.deps.conventions.runtime === "agent_workspace" && this.deps.conventions.workspaceSessionsDir ?
|
|
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);
|
|
121839
121631
|
const hardFailureText = `\u6267\u884C\u5931\u8D25: ${String(err)}`;
|
|
121840
121632
|
const createHardFailurePostFallback = async (failureReason) => {
|
|
121841
121633
|
const fallback = await createOnlyPostFallback({
|
|
@@ -121877,25 +121669,10 @@ var BridgeHandler = class {
|
|
|
121877
121669
|
}
|
|
121878
121670
|
}
|
|
121879
121671
|
}
|
|
121880
|
-
if (!card &&
|
|
121881
|
-
|
|
121882
|
-
|
|
121883
|
-
|
|
121884
|
-
});
|
|
121885
|
-
} catch (postFinalizeErr) {
|
|
121886
|
-
console.error(
|
|
121887
|
-
"[bridge.handler] progress post failure update failed; creating card fallback:",
|
|
121888
|
-
postFinalizeErr
|
|
121889
|
-
);
|
|
121890
|
-
try {
|
|
121891
|
-
card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
|
|
121892
|
-
} catch (cardStartErr) {
|
|
121893
|
-
console.error("[bridge.handler] failure card start also failed:", cardStartErr);
|
|
121894
|
-
await createHardFailurePostFallback(
|
|
121895
|
-
`progress post failure update failed: ${String(postFinalizeErr)}; legacy visible card fallback also failed: ${String(cardStartErr)}`
|
|
121896
|
-
);
|
|
121897
|
-
}
|
|
121898
|
-
}
|
|
121672
|
+
if (!card && !cardKitProgress && !startFailurePostFallbackSent) {
|
|
121673
|
+
await createHardFailurePostFallback(
|
|
121674
|
+
legacyCardStartFailed ? `legacy visible card was unavailable before agent failure: ${legacyCardStartFailureReason ?? "unknown"}` : "no visible response surface was available before agent failure"
|
|
121675
|
+
);
|
|
121899
121676
|
}
|
|
121900
121677
|
if (card) {
|
|
121901
121678
|
try {
|
|
@@ -121921,18 +121698,18 @@ var BridgeHandler = class {
|
|
|
121921
121698
|
};
|
|
121922
121699
|
|
|
121923
121700
|
// src/bridge/eventLog.ts
|
|
121924
|
-
import
|
|
121925
|
-
import
|
|
121701
|
+
import fs7 from "node:fs/promises";
|
|
121702
|
+
import path10 from "node:path";
|
|
121926
121703
|
var DEFAULT_RUNTIME_EVENT_LIMIT = 20;
|
|
121927
121704
|
var writeQueues = /* @__PURE__ */ new Map();
|
|
121928
121705
|
function resolveRuntimeEventsPath(larkwayHome2, botId) {
|
|
121929
|
-
const dir = botId ?
|
|
121930
|
-
return
|
|
121706
|
+
const dir = botId ? path10.join(larkwayHome2, botId) : larkwayHome2;
|
|
121707
|
+
return path10.join(dir, "recent-events.json");
|
|
121931
121708
|
}
|
|
121932
121709
|
async function readRuntimeEvents(larkwayHome2, botId, limit = DEFAULT_RUNTIME_EVENT_LIMIT) {
|
|
121933
121710
|
const file = resolveRuntimeEventsPath(larkwayHome2, botId);
|
|
121934
121711
|
try {
|
|
121935
|
-
const raw = await
|
|
121712
|
+
const raw = await fs7.readFile(file, "utf-8");
|
|
121936
121713
|
const parsed = JSON.parse(raw);
|
|
121937
121714
|
if (!Array.isArray(parsed)) return [];
|
|
121938
121715
|
return parsed.filter(isRuntimeEventRecord).sort((a, b) => tsOf(b.receivedAt) - tsOf(a.receivedAt)).slice(0, limit);
|
|
@@ -121990,11 +121767,11 @@ async function enqueue(key, fn) {
|
|
|
121990
121767
|
}
|
|
121991
121768
|
}
|
|
121992
121769
|
async function writeEvents(file, events) {
|
|
121993
|
-
await
|
|
121770
|
+
await fs7.mkdir(path10.dirname(file), { recursive: true });
|
|
121994
121771
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
121995
|
-
await
|
|
121772
|
+
await fs7.writeFile(tmp, `${JSON.stringify(events, null, 2)}
|
|
121996
121773
|
`, "utf-8");
|
|
121997
|
-
await
|
|
121774
|
+
await fs7.rename(tmp, file);
|
|
121998
121775
|
}
|
|
121999
121776
|
function mergeStatusPath(prev, next, append) {
|
|
122000
121777
|
const out = [...next ?? prev ?? []];
|
|
@@ -122260,7 +122037,7 @@ async function cleanupWorktree(threadId, botId, dryRun) {
|
|
|
122260
122037
|
|
|
122261
122038
|
// src/config/botLoader.ts
|
|
122262
122039
|
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
122263
|
-
import
|
|
122040
|
+
import path11 from "node:path";
|
|
122264
122041
|
|
|
122265
122042
|
// node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/dist/js-yaml.mjs
|
|
122266
122043
|
function isNothing(subject) {
|
|
@@ -125096,7 +124873,7 @@ async function loadBots(botsDir) {
|
|
|
125096
124873
|
}
|
|
125097
124874
|
const bots = [];
|
|
125098
124875
|
for (const filename of yamlFiles.sort()) {
|
|
125099
|
-
const filePath =
|
|
124876
|
+
const filePath = path11.join(botsDir, filename);
|
|
125100
124877
|
let raw;
|
|
125101
124878
|
try {
|
|
125102
124879
|
raw = await readFile5(filePath, "utf-8");
|
|
@@ -125117,7 +124894,7 @@ ${issues}`);
|
|
|
125117
124894
|
}
|
|
125118
124895
|
const bot = result.data;
|
|
125119
124896
|
if (bot.memory_file) {
|
|
125120
|
-
const memoryPath =
|
|
124897
|
+
const memoryPath = path11.join(botsDir, bot.memory_file);
|
|
125121
124898
|
try {
|
|
125122
124899
|
bot.agent_memory = await readFile5(memoryPath, "utf-8");
|
|
125123
124900
|
} catch (err) {
|
|
@@ -125150,6 +124927,239 @@ ${issues}`);
|
|
|
125150
124927
|
// src/bridge/reconcile.ts
|
|
125151
124928
|
import { readdir as readdir3, stat as stat2 } from "node:fs/promises";
|
|
125152
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
|
|
125153
125163
|
var DEFAULT_MIN_AGE_MS = 6e4;
|
|
125154
125164
|
var RETRY_CAP = 3;
|
|
125155
125165
|
function isStateFreshForCard(state, card) {
|
|
@@ -125696,7 +125706,7 @@ async function writeStatusFile(botId, w) {
|
|
|
125696
125706
|
|
|
125697
125707
|
// src/claude/runner.ts
|
|
125698
125708
|
import { spawn as spawn2 } from "node:child_process";
|
|
125699
|
-
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";
|
|
125700
125710
|
import { join as join2 } from "node:path";
|
|
125701
125711
|
import { createInterface } from "node:readline";
|
|
125702
125712
|
var SIGKILL_GRACE_MS = 5e3;
|
|
@@ -125903,7 +125913,7 @@ function runClaude(opts) {
|
|
|
125903
125913
|
clearTimeout(totalTimeoutFallbackHandle);
|
|
125904
125914
|
rlAbortController.abort();
|
|
125905
125915
|
if (pidFilePath !== null) {
|
|
125906
|
-
void
|
|
125916
|
+
void unlink2(pidFilePath).catch(() => {
|
|
125907
125917
|
});
|
|
125908
125918
|
}
|
|
125909
125919
|
if (exitCode !== 0 && !killScheduled) {
|
|
@@ -125933,7 +125943,7 @@ stderr: ${stderr}` : "")
|
|
|
125933
125943
|
clearTimeout(totalTimeoutFallbackHandle);
|
|
125934
125944
|
rlAbortController.abort();
|
|
125935
125945
|
if (pidFilePath !== null) {
|
|
125936
|
-
void
|
|
125946
|
+
void unlink2(pidFilePath).catch(() => {
|
|
125937
125947
|
});
|
|
125938
125948
|
}
|
|
125939
125949
|
if (err.code === "ENOENT") {
|
|
@@ -126045,127 +126055,104 @@ function buildCodexEnv(botGitIdentity, gitlabToken) {
|
|
|
126045
126055
|
}
|
|
126046
126056
|
return env;
|
|
126047
126057
|
}
|
|
126048
|
-
function
|
|
126049
|
-
|
|
126050
|
-
|
|
126051
|
-
return ["--dangerously-bypass-approvals-and-sandbox"];
|
|
126052
|
-
case "acceptEdits":
|
|
126053
|
-
return ["--dangerously-bypass-approvals-and-sandbox"];
|
|
126054
|
-
case "ask":
|
|
126055
|
-
return ["--sandbox", "read-only"];
|
|
126056
|
-
}
|
|
126058
|
+
function buildCodexCommand(opts, codexBinPath = "codex") {
|
|
126059
|
+
void opts;
|
|
126060
|
+
return [codexBinPath, ["app-server", "--stdio"]];
|
|
126057
126061
|
}
|
|
126058
|
-
function
|
|
126059
|
-
return mode === "
|
|
126062
|
+
function codexThreadSandboxMode(mode) {
|
|
126063
|
+
return mode === "ask" ? "read-only" : "danger-full-access";
|
|
126060
126064
|
}
|
|
126061
|
-
function
|
|
126062
|
-
|
|
126063
|
-
|
|
126064
|
-
|
|
126065
|
-
|
|
126066
|
-
|
|
126067
|
-
];
|
|
126068
|
-
if (opts.resumeSessionId != null) {
|
|
126069
|
-
const resumeFlags = [
|
|
126070
|
-
"--json",
|
|
126071
|
-
"--skip-git-repo-check",
|
|
126072
|
-
...resumePermissionFlag(mode)
|
|
126073
|
-
];
|
|
126074
|
-
return [
|
|
126075
|
-
codexBinPath,
|
|
126076
|
-
["exec", "resume", opts.resumeSessionId, ...resumeFlags, "-"]
|
|
126077
|
-
];
|
|
126078
|
-
}
|
|
126079
|
-
const freshFlags = opts.cwd != null ? ["-C", opts.cwd, ...commonFlags] : commonFlags;
|
|
126080
|
-
return [codexBinPath, ["exec", ...freshFlags]];
|
|
126065
|
+
function codexTurnSandboxPolicy(mode) {
|
|
126066
|
+
if (mode === "ask") return { type: "readOnly", networkAccess: false };
|
|
126067
|
+
return { type: "dangerFullAccess" };
|
|
126068
|
+
}
|
|
126069
|
+
function codexApprovalPolicy(mode) {
|
|
126070
|
+
return mode === "ask" ? "on-request" : "never";
|
|
126081
126071
|
}
|
|
126082
|
-
var
|
|
126072
|
+
var CodexAppServerLineParser = class {
|
|
126083
126073
|
answerExtractor = new AnswerChannelExtractor();
|
|
126084
|
-
*
|
|
126085
|
-
const trimmed = line.trim();
|
|
126086
|
-
if (trimmed === "") return;
|
|
126087
|
-
let obj;
|
|
126088
|
-
try {
|
|
126089
|
-
obj = JSON.parse(trimmed);
|
|
126090
|
-
} catch {
|
|
126091
|
-
yield { type: "raw", raw: trimmed };
|
|
126092
|
-
return;
|
|
126093
|
-
}
|
|
126074
|
+
*parseMessage(obj) {
|
|
126094
126075
|
if (typeof obj !== "object" || obj === null) {
|
|
126095
126076
|
yield { type: "raw", raw: obj };
|
|
126096
126077
|
return;
|
|
126097
126078
|
}
|
|
126098
126079
|
const record = obj;
|
|
126099
|
-
const
|
|
126100
|
-
if (
|
|
126101
|
-
yield { type: "
|
|
126080
|
+
const method = record["method"];
|
|
126081
|
+
if (typeof method !== "string") {
|
|
126082
|
+
yield { type: "raw", raw: obj };
|
|
126083
|
+
return;
|
|
126084
|
+
}
|
|
126085
|
+
const params = asRecord(record["params"]);
|
|
126086
|
+
if (method === "thread/started") {
|
|
126087
|
+
const thread = asRecord(params?.["thread"]);
|
|
126088
|
+
const threadId = typeof thread?.["id"] === "string" ? thread["id"] : void 0;
|
|
126089
|
+
if (threadId) yield { type: "system_init", sessionId: threadId, raw: obj };
|
|
126102
126090
|
return;
|
|
126103
126091
|
}
|
|
126104
|
-
if (
|
|
126092
|
+
if (method === "turn/completed") {
|
|
126105
126093
|
yield { type: "result", stopReason: "end_turn", raw: obj };
|
|
126106
126094
|
return;
|
|
126107
126095
|
}
|
|
126108
|
-
|
|
126109
|
-
|
|
126110
|
-
|
|
126111
|
-
yield* this.answerExtractor.ingestSnapshot(agentMessageText, obj);
|
|
126112
|
-
return;
|
|
126113
|
-
}
|
|
126114
|
-
yield* this.answerExtractor.ingestGrowingSnapshot(agentMessageText, obj);
|
|
126096
|
+
if (method === "item/agentMessage/delta") {
|
|
126097
|
+
const delta = typeof params?.["delta"] === "string" ? params["delta"] : "";
|
|
126098
|
+
yield* this.answerExtractor.ingestDelta(delta, obj);
|
|
126115
126099
|
return;
|
|
126116
126100
|
}
|
|
126117
|
-
if (
|
|
126118
|
-
const item =
|
|
126119
|
-
if (
|
|
126120
|
-
|
|
126121
|
-
|
|
126122
|
-
|
|
126123
|
-
|
|
126124
|
-
|
|
126125
|
-
|
|
126126
|
-
|
|
126127
|
-
};
|
|
126128
|
-
return;
|
|
126129
|
-
}
|
|
126101
|
+
if (method === "item/started") {
|
|
126102
|
+
const item = asRecord(params?.["item"]);
|
|
126103
|
+
if (item?.["type"] === "commandExecution" && typeof item["command"] === "string") {
|
|
126104
|
+
yield {
|
|
126105
|
+
type: "tool_use",
|
|
126106
|
+
toolName: "shell",
|
|
126107
|
+
toolInput: { command: item["command"] },
|
|
126108
|
+
raw: obj
|
|
126109
|
+
};
|
|
126110
|
+
return;
|
|
126130
126111
|
}
|
|
126131
|
-
yield { type: "raw", raw: obj };
|
|
126132
126112
|
return;
|
|
126133
126113
|
}
|
|
126134
|
-
if (
|
|
126135
|
-
const item =
|
|
126136
|
-
if (
|
|
126137
|
-
|
|
126138
|
-
|
|
126139
|
-
|
|
126140
|
-
|
|
126141
|
-
|
|
126114
|
+
if (method === "item/completed") {
|
|
126115
|
+
const item = asRecord(params?.["item"]);
|
|
126116
|
+
if (item?.["type"] === "commandExecution") {
|
|
126117
|
+
yield { type: "tool_result", raw: obj };
|
|
126118
|
+
return;
|
|
126119
|
+
}
|
|
126120
|
+
if (item?.["type"] === "agentMessage" && typeof item["text"] === "string") {
|
|
126121
|
+
yield* this.answerExtractor.ingestSnapshot(item["text"], obj);
|
|
126122
|
+
return;
|
|
126142
126123
|
}
|
|
126143
|
-
yield { type: "raw", raw: obj };
|
|
126144
126124
|
return;
|
|
126145
126125
|
}
|
|
126146
126126
|
yield { type: "raw", raw: obj };
|
|
126147
126127
|
}
|
|
126148
126128
|
};
|
|
126149
|
-
function
|
|
126150
|
-
|
|
126151
|
-
|
|
126152
|
-
|
|
126153
|
-
|
|
126154
|
-
const
|
|
126155
|
-
|
|
126129
|
+
function asRecord(value) {
|
|
126130
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
126131
|
+
}
|
|
126132
|
+
function extractThreadIdFromThreadResponse(obj) {
|
|
126133
|
+
const record = asRecord(obj);
|
|
126134
|
+
const result = asRecord(record?.["result"]);
|
|
126135
|
+
const thread = asRecord(result?.["thread"]);
|
|
126136
|
+
const threadId = thread?.["id"];
|
|
126137
|
+
return typeof threadId === "string" ? threadId : void 0;
|
|
126156
126138
|
}
|
|
126157
126139
|
function runCodex(opts, codexBinPath = "codex") {
|
|
126158
126140
|
const timeoutMs = opts.timeoutMs ?? 15 * 60 * 1e3;
|
|
126159
126141
|
const [bin, args] = buildCodexCommand(opts, codexBinPath);
|
|
126160
126142
|
const env = buildCodexEnv(opts.botGitIdentity, opts.gitlabToken);
|
|
126143
|
+
const mode = opts.permissionMode ?? "acceptEdits";
|
|
126144
|
+
const requestById = /* @__PURE__ */ new Map();
|
|
126145
|
+
let nextRequestId = 1;
|
|
126161
126146
|
const child = spawn3(bin, args, {
|
|
126162
126147
|
env,
|
|
126163
126148
|
stdio: ["pipe", "pipe", "pipe"],
|
|
126164
126149
|
...opts.cwd != null ? { cwd: opts.cwd } : {}
|
|
126165
126150
|
});
|
|
126166
|
-
|
|
126167
|
-
|
|
126168
|
-
|
|
126151
|
+
function sendRequest(method, params) {
|
|
126152
|
+
const id = nextRequestId++;
|
|
126153
|
+
requestById.set(id, method);
|
|
126154
|
+
child.stdin?.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
|
|
126155
|
+
return id;
|
|
126169
126156
|
}
|
|
126170
126157
|
let discoveredSessionId;
|
|
126171
126158
|
let killScheduled = false;
|
|
@@ -126179,6 +126166,10 @@ function runCodex(opts, codexBinPath = "codex") {
|
|
|
126179
126166
|
}, SIGKILL_GRACE_MS2);
|
|
126180
126167
|
killTimer.unref();
|
|
126181
126168
|
}
|
|
126169
|
+
function stopAppServerAfterTurn() {
|
|
126170
|
+
if (child.killed || killScheduled) return;
|
|
126171
|
+
child.kill("SIGTERM");
|
|
126172
|
+
}
|
|
126182
126173
|
const TOTAL_TIMEOUT_EXTRA_MS = SIGKILL_GRACE_MS2 + 2e3;
|
|
126183
126174
|
let totalTimeoutFallbackHandle;
|
|
126184
126175
|
let _forceFinalizeForTimeout = () => {
|
|
@@ -126201,6 +126192,8 @@ function runCodex(opts, codexBinPath = "codex") {
|
|
|
126201
126192
|
const stderrChunks = [];
|
|
126202
126193
|
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
126203
126194
|
const rlAbortController = new AbortController();
|
|
126195
|
+
let finishAppServerTurn;
|
|
126196
|
+
let failAppServerTurn;
|
|
126204
126197
|
const done = new Promise(
|
|
126205
126198
|
(resolve2, reject) => {
|
|
126206
126199
|
let settled = false;
|
|
@@ -126230,6 +126223,17 @@ stderr: ${stderr}` : "")
|
|
|
126230
126223
|
}
|
|
126231
126224
|
resolve2({ exitCode, sessionId: discoveredSessionId });
|
|
126232
126225
|
};
|
|
126226
|
+
const finalizeReject = (err) => {
|
|
126227
|
+
if (settled) return;
|
|
126228
|
+
settled = true;
|
|
126229
|
+
clearTimeout(timeoutHandle);
|
|
126230
|
+
clearTimeout(killTimer);
|
|
126231
|
+
clearTimeout(totalTimeoutFallbackHandle);
|
|
126232
|
+
rlAbortController.abort();
|
|
126233
|
+
reject(err);
|
|
126234
|
+
};
|
|
126235
|
+
finishAppServerTurn = finalizeResolve;
|
|
126236
|
+
failAppServerTurn = finalizeReject;
|
|
126233
126237
|
_forceFinalizeForTimeout = () => {
|
|
126234
126238
|
if (settled) return;
|
|
126235
126239
|
console.warn(
|
|
@@ -126277,15 +126281,76 @@ stderr: ${stderr}` : "")
|
|
|
126277
126281
|
crlfDelay: Infinity,
|
|
126278
126282
|
signal: rlAbortController.signal
|
|
126279
126283
|
});
|
|
126280
|
-
const parser = new
|
|
126284
|
+
const parser = new CodexAppServerLineParser();
|
|
126285
|
+
let turnCompleted = false;
|
|
126286
|
+
sendRequest("initialize", {
|
|
126287
|
+
clientInfo: { name: "larkway", version: "0.3" },
|
|
126288
|
+
capabilities: {}
|
|
126289
|
+
});
|
|
126281
126290
|
try {
|
|
126282
126291
|
for await (const line of rl) {
|
|
126283
|
-
|
|
126292
|
+
const trimmed = line.trim();
|
|
126293
|
+
if (!trimmed) continue;
|
|
126294
|
+
let obj;
|
|
126295
|
+
try {
|
|
126296
|
+
obj = JSON.parse(trimmed);
|
|
126297
|
+
} catch {
|
|
126298
|
+
yield { type: "raw", raw: trimmed };
|
|
126299
|
+
continue;
|
|
126300
|
+
}
|
|
126301
|
+
const response = asRecord(obj);
|
|
126302
|
+
if (typeof response?.["id"] === "number") {
|
|
126303
|
+
const id = response["id"];
|
|
126304
|
+
const method = requestById.get(id);
|
|
126305
|
+
requestById.delete(id);
|
|
126306
|
+
const error = asRecord(response["error"]);
|
|
126307
|
+
if (error) {
|
|
126308
|
+
const message = typeof error["message"] === "string" ? error["message"] : JSON.stringify(error);
|
|
126309
|
+
failAppServerTurn?.(new Error(`codex app-server ${method ?? "request"} failed: ${message}`));
|
|
126310
|
+
stopAppServerAfterTurn();
|
|
126311
|
+
return;
|
|
126312
|
+
}
|
|
126313
|
+
if (method === "initialize") {
|
|
126314
|
+
const threadMethod = opts.resumeSessionId != null ? "thread/resume" : "thread/start";
|
|
126315
|
+
const threadParams = opts.resumeSessionId != null ? { threadId: opts.resumeSessionId } : { ephemeral: false, sessionStartSource: "startup" };
|
|
126316
|
+
if (opts.cwd != null) threadParams["cwd"] = opts.cwd;
|
|
126317
|
+
threadParams["approvalPolicy"] = codexApprovalPolicy(mode);
|
|
126318
|
+
threadParams["sandbox"] = codexThreadSandboxMode(mode);
|
|
126319
|
+
sendRequest(threadMethod, threadParams);
|
|
126320
|
+
continue;
|
|
126321
|
+
}
|
|
126322
|
+
if (method === "thread/start" || method === "thread/resume") {
|
|
126323
|
+
const threadId = extractThreadIdFromThreadResponse(obj);
|
|
126324
|
+
if (threadId) {
|
|
126325
|
+
discoveredSessionId = threadId;
|
|
126326
|
+
yield { type: "system_init", sessionId: threadId, raw: obj };
|
|
126327
|
+
const turnParams = {
|
|
126328
|
+
threadId,
|
|
126329
|
+
input: [{ type: "text", text: opts.prompt, text_elements: [] }],
|
|
126330
|
+
approvalPolicy: codexApprovalPolicy(mode),
|
|
126331
|
+
sandboxPolicy: codexTurnSandboxPolicy(mode)
|
|
126332
|
+
};
|
|
126333
|
+
if (opts.cwd != null) turnParams["cwd"] = opts.cwd;
|
|
126334
|
+
sendRequest("turn/start", turnParams);
|
|
126335
|
+
}
|
|
126336
|
+
continue;
|
|
126337
|
+
}
|
|
126338
|
+
continue;
|
|
126339
|
+
}
|
|
126340
|
+
for (const event of parser.parseMessage(obj)) {
|
|
126284
126341
|
if (event.type === "system_init") {
|
|
126285
126342
|
discoveredSessionId = event.sessionId;
|
|
126286
126343
|
}
|
|
126344
|
+
if (event.type === "result") {
|
|
126345
|
+
turnCompleted = true;
|
|
126346
|
+
}
|
|
126287
126347
|
yield event;
|
|
126288
126348
|
}
|
|
126349
|
+
if (turnCompleted) {
|
|
126350
|
+
stopAppServerAfterTurn();
|
|
126351
|
+
finishAppServerTurn?.(0);
|
|
126352
|
+
return;
|
|
126353
|
+
}
|
|
126289
126354
|
}
|
|
126290
126355
|
} catch (err) {
|
|
126291
126356
|
const isAbort = err instanceof Error && (err.name === "AbortError" || err.code === "ABORT_ERR");
|