larkway 0.3.27 → 0.3.29
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 +915 -85
- package/dist/main.js +1322 -916
- 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") {
|
|
@@ -115112,6 +115875,10 @@ function resolveAgentWorkspacePath(agentId) {
|
|
|
115112
115875
|
function resolveAgentWorkspaceSessionsDir(agentId) {
|
|
115113
115876
|
return join(resolveAgentWorkspacePath(agentId), "sessions");
|
|
115114
115877
|
}
|
|
115878
|
+
function resolveAgentSessionPath(agentId, threadId) {
|
|
115879
|
+
assertSafePathSegment("threadId", threadId);
|
|
115880
|
+
return join(resolveAgentWorkspaceSessionsDir(agentId), threadId);
|
|
115881
|
+
}
|
|
115115
115882
|
function resolveAgentWorkspaceReposDir(agentId) {
|
|
115116
115883
|
return join(resolveAgentWorkspacePath(agentId), "repos");
|
|
115117
115884
|
}
|
|
@@ -115245,7 +116012,7 @@ See examples/config.example.json for the correct format.`
|
|
|
115245
116012
|
// src/lark/channelClient.ts
|
|
115246
116013
|
var import_node_sdk = __toESM(require_lib2(), 1);
|
|
115247
116014
|
import { execFile as execFileCallback } from "node:child_process";
|
|
115248
|
-
import { mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
|
|
116015
|
+
import { mkdir, readFile as readFile2, writeFile, rename, unlink } from "node:fs/promises";
|
|
115249
116016
|
import path3 from "node:path";
|
|
115250
116017
|
import { promisify } from "node:util";
|
|
115251
116018
|
|
|
@@ -115766,9 +116533,12 @@ var execFile = promisify(execFileCallback);
|
|
|
115766
116533
|
var LEARNED_CHATS_LIMIT = 100;
|
|
115767
116534
|
var SEEN_MESSAGES_LIMIT = 1e3;
|
|
115768
116535
|
var MAX_MESSAGE_ATTEMPTS = 5;
|
|
115769
|
-
var
|
|
116536
|
+
var OPEN_CHAT_DISCOVERY_LOOKBACK_BUFFER_MS = 3e4;
|
|
115770
116537
|
var OPEN_CHAT_DISCOVERY_BOOTSTRAP_LOOKBACK_MS = 30 * 60 * 1e3;
|
|
115771
116538
|
var PROCESSING_REACTION_EMOJI = "Typing";
|
|
116539
|
+
var DEFAULT_OPEN_CHAT_DISCOVERY_MS = 3e5;
|
|
116540
|
+
var OPEN_CHAT_DISCOVERY_JITTER_CAP_MS = 3e4;
|
|
116541
|
+
var OPEN_CHAT_DISCOVERY_MAX_BACKOFF_CYCLES = 8;
|
|
115772
116542
|
var GAP_FILL_MAX_ATTEMPTS = 3;
|
|
115773
116543
|
var GAP_FILL_BACKOFF_BASE_MS = 1e3;
|
|
115774
116544
|
var UNRESOLVED_WINDOW_MAX_CHATS = 50;
|
|
@@ -115798,7 +116568,7 @@ function resolveOpenChatDiscoveryMs(ctorValue) {
|
|
|
115798
116568
|
} else {
|
|
115799
116569
|
const env = process.env["LARKWAY_OPEN_CHAT_DISCOVERY_MS"];
|
|
115800
116570
|
const parsed = env !== void 0 ? Number(env) : Number.NaN;
|
|
115801
|
-
raw = Number.isFinite(parsed) ? parsed :
|
|
116571
|
+
raw = Number.isFinite(parsed) ? parsed : DEFAULT_OPEN_CHAT_DISCOVERY_MS;
|
|
115802
116572
|
}
|
|
115803
116573
|
return Number.isFinite(raw) && raw > 0 ? raw : 0;
|
|
115804
116574
|
}
|
|
@@ -115997,6 +116767,25 @@ var ChannelClient = class {
|
|
|
115997
116767
|
openChatDiscoveryTimer = null;
|
|
115998
116768
|
openChatDiscoveryRunning = false;
|
|
115999
116769
|
openChatDiscoveryBootstrapped = false;
|
|
116770
|
+
/**
|
|
116771
|
+
* Consecutive discovery-cycle failures. Used to SKIP cycles with exponential
|
|
116772
|
+
* backoff (storm: a failing +chat-list/gap-fill shouldn't re-fire every
|
|
116773
|
+
* interval). Reset to 0 on the first clean cycle. {@link openChatDiscoverySkips}
|
|
116774
|
+
* counts how many remaining cycles to skip before the next real attempt.
|
|
116775
|
+
*/
|
|
116776
|
+
openChatDiscoveryFailures = 0;
|
|
116777
|
+
openChatDiscoverySkips = 0;
|
|
116778
|
+
/**
|
|
116779
|
+
* Per-instance jitter offset (ms) applied to discovery scheduling so multiple
|
|
116780
|
+
* bots on one host don't run discovery (and its history-pull burst) in
|
|
116781
|
+
* lockstep. Computed once at startup. `Math.random` is fine here — this is
|
|
116782
|
+
* runtime scheduling code, not a determinism-sensitive workflow script.
|
|
116783
|
+
*/
|
|
116784
|
+
openChatDiscoveryJitterMs = Math.floor(
|
|
116785
|
+
Math.random() * OPEN_CHAT_DISCOVERY_JITTER_CAP_MS
|
|
116786
|
+
);
|
|
116787
|
+
/** Monotonic suffix so overlapping atomic writes get distinct temp files. */
|
|
116788
|
+
atomicWriteSeq = 0;
|
|
116000
116789
|
processingReactions = /* @__PURE__ */ new Map();
|
|
116001
116790
|
/**
|
|
116002
116791
|
* Shared messageId -> threadId map. Populated by ChannelCardClient.createCard
|
|
@@ -116028,10 +116817,31 @@ var ChannelClient = class {
|
|
|
116028
116817
|
setGapFillSleepForTest(fn) {
|
|
116029
116818
|
this.gapFillSleep = fn;
|
|
116030
116819
|
}
|
|
116820
|
+
/**
|
|
116821
|
+
* TEST SEAM (deletable): override the per-instance open-chat discovery startup
|
|
116822
|
+
* jitter so tests can make the FIRST discovery run fire deterministically
|
|
116823
|
+
* (jitter=0) instead of waiting up to {@link OPEN_CHAT_DISCOVERY_JITTER_CAP_MS}.
|
|
116824
|
+
* Must be called before connect()/startOpenChatDiscovery(). No-op in production.
|
|
116825
|
+
*/
|
|
116826
|
+
setOpenChatDiscoveryJitterForTest(ms) {
|
|
116827
|
+
this.openChatDiscoveryJitterMs = Math.max(0, ms);
|
|
116828
|
+
}
|
|
116031
116829
|
/** TEST-ONLY read of the per-chat unresolved-window replay map (chatId → windowStart). */
|
|
116032
116830
|
unresolvedGapWindowsForTest() {
|
|
116033
116831
|
return new Map(this.unresolvedGapWindowByChat);
|
|
116034
116832
|
}
|
|
116833
|
+
/**
|
|
116834
|
+
* TEST-ONLY: run exactly ONE open-chat discovery cycle (same code path the
|
|
116835
|
+
* interval timer invokes), awaited to completion. Lets tests assert the
|
|
116836
|
+
* storm-control behaviour (steady-state no-pull, new-chat pull, failure
|
|
116837
|
+
* backoff skip) deterministically without standing up real timers.
|
|
116838
|
+
*/
|
|
116839
|
+
async discoverOpenChatsForTest() {
|
|
116840
|
+
for (let i = 0; i < 200 && this.openChatDiscoveryRunning; i++) {
|
|
116841
|
+
await sleep(5);
|
|
116842
|
+
}
|
|
116843
|
+
await this.discoverOpenChatsAndGapFill((s) => console.log(`[channel.client] ${s}`));
|
|
116844
|
+
}
|
|
116035
116845
|
/**
|
|
116036
116846
|
* TEST-ONLY direct gapFill invocation with an explicit chat-set override —
|
|
116037
116847
|
* mirrors exactly how open-chat discovery calls gapFill on a SUBSET of chats.
|
|
@@ -116126,12 +116936,26 @@ var ChannelClient = class {
|
|
|
116126
116936
|
// real network flap. If a future node-sdk / @larksuite/channel exposes
|
|
116127
116937
|
// the ws or attaches its own listener, prefer that and drop the guard.
|
|
116128
116938
|
handshakeTimeoutMs: 15e3,
|
|
116129
|
-
//
|
|
116130
|
-
//
|
|
116131
|
-
//
|
|
116132
|
-
//
|
|
116133
|
-
//
|
|
116134
|
-
//
|
|
116939
|
+
// HALF-OPEN DETECTION (SECONDS) — keep this on. It terminates a socket that
|
|
116940
|
+
// has gone half-open (server stopped responding but never sent a FIN/close),
|
|
116941
|
+
// so the SDK's 'close' handler runs the normal reconnect → our gap-fill
|
|
116942
|
+
// recovers anything missed during the dead window.
|
|
116943
|
+
//
|
|
116944
|
+
// This does NOT mis-fire on healthy idle connections. Verified against
|
|
116945
|
+
// node-sdk 1.67.0 source (WSClient liveness):
|
|
116946
|
+
// - clearLiveness() is called on EVERY inbound frame (incl. the server's
|
|
116947
|
+
// pong) — a live connection cancels the watchdog within ms, so an
|
|
116948
|
+
// idle-but-healthy socket that still answers the ~120s server ping is
|
|
116949
|
+
// never killed.
|
|
116950
|
+
// - armLiveness() only (re)arms for pingTimeout SECONDS after each ping and
|
|
116951
|
+
// is a NO-OP when pingTimeout is unset → unsetting it removes half-open
|
|
116952
|
+
// detection ENTIRELY (no 'close' event → no reconnect → no gap-fill).
|
|
116953
|
+
// A silently-half-open WS on a KNOWN chat would then drop an @ that
|
|
116954
|
+
// neither reconnect-gap-fill nor the (now targeted) steady-state
|
|
116955
|
+
// discovery — which pulls 0 for already-known chats — could recover.
|
|
116956
|
+
//
|
|
116957
|
+
// So this stays as the half-open safety net; the discovery-storm fix is
|
|
116958
|
+
// orthogonal and SAFE precisely because this net still triggers reconnect.
|
|
116135
116959
|
wsConfig: { pingTimeout: 60 }
|
|
116136
116960
|
});
|
|
116137
116961
|
channel.on("message", (msg) => {
|
|
@@ -116269,8 +117093,12 @@ var ChannelClient = class {
|
|
|
116269
117093
|
* profile. Reactions are intentionally skipped because gap-fill only needs
|
|
116270
117094
|
* message IDs and mentions.
|
|
116271
117095
|
*/
|
|
116272
|
-
async gapFill(disconnectAt, log, chatIdsOverride) {
|
|
116273
|
-
const MAX_GAP_FILL_WINDOW_MS =
|
|
117096
|
+
async gapFill(disconnectAt, log, chatIdsOverride, minWindowMs) {
|
|
117097
|
+
const MAX_GAP_FILL_WINDOW_MS = Math.min(
|
|
117098
|
+
Math.max(5 * 60 * 1e3, minWindowMs ?? 0),
|
|
117099
|
+
// ≥5 minutes, or the caller's floor
|
|
117100
|
+
UNRESOLVED_WINDOW_MAX_AGE_MS
|
|
117101
|
+
);
|
|
116274
117102
|
const BUFFER_MS = 3e4;
|
|
116275
117103
|
const now = Date.now();
|
|
116276
117104
|
const larkCli = this.opts.larkCliPath ?? "lark-cli";
|
|
@@ -116482,14 +117310,23 @@ var ChannelClient = class {
|
|
|
116482
117310
|
if (this.openChatDiscoveryTimer) return;
|
|
116483
117311
|
const intervalMs = resolveOpenChatDiscoveryMs(this.opts.openChatDiscoveryMs);
|
|
116484
117312
|
if (intervalMs <= 0) return;
|
|
116485
|
-
|
|
116486
|
-
|
|
117313
|
+
const firstDelay = Math.min(this.openChatDiscoveryJitterMs, intervalMs);
|
|
117314
|
+
const startTimer = setTimeout(() => {
|
|
116487
117315
|
void this.discoverOpenChatsAndGapFill(log);
|
|
116488
|
-
|
|
116489
|
-
|
|
117316
|
+
this.openChatDiscoveryTimer = setInterval(() => {
|
|
117317
|
+
void this.discoverOpenChatsAndGapFill(log);
|
|
117318
|
+
}, intervalMs);
|
|
117319
|
+
this.openChatDiscoveryTimer.unref?.();
|
|
117320
|
+
}, firstDelay);
|
|
117321
|
+
startTimer.unref?.();
|
|
117322
|
+
this.openChatDiscoveryTimer = startTimer;
|
|
116490
117323
|
}
|
|
116491
117324
|
async discoverOpenChatsAndGapFill(log) {
|
|
116492
117325
|
if (this.closed || this.openChatDiscoveryRunning) return;
|
|
117326
|
+
if (this.openChatDiscoverySkips > 0) {
|
|
117327
|
+
this.openChatDiscoverySkips--;
|
|
117328
|
+
return;
|
|
117329
|
+
}
|
|
116493
117330
|
this.openChatDiscoveryRunning = true;
|
|
116494
117331
|
try {
|
|
116495
117332
|
const larkCli = this.opts.larkCliPath ?? "lark-cli";
|
|
@@ -116498,6 +117335,7 @@ var ChannelClient = class {
|
|
|
116498
117335
|
let fetched = 0;
|
|
116499
117336
|
let newlyLearned = 0;
|
|
116500
117337
|
const discoveredChatIds = /* @__PURE__ */ new Set();
|
|
117338
|
+
const newChatIds = /* @__PURE__ */ new Set();
|
|
116501
117339
|
for (let page = 0; page < 10 && !this.closed; page++) {
|
|
116502
117340
|
const args = [
|
|
116503
117341
|
"im",
|
|
@@ -116521,7 +117359,10 @@ var ChannelClient = class {
|
|
|
116521
117359
|
discoveredChatIds.add(chatId);
|
|
116522
117360
|
const before = this.recentlySeenChatIds.size;
|
|
116523
117361
|
this.noteSeenChat(chatId);
|
|
116524
|
-
if (this.recentlySeenChatIds.size > before)
|
|
117362
|
+
if (this.recentlySeenChatIds.size > before) {
|
|
117363
|
+
newlyLearned++;
|
|
117364
|
+
newChatIds.add(chatId);
|
|
117365
|
+
}
|
|
116525
117366
|
}
|
|
116526
117367
|
const data = parsed && typeof parsed === "object" ? parsed["data"] : void 0;
|
|
116527
117368
|
const hasMore = Boolean(
|
|
@@ -116535,17 +117376,34 @@ var ChannelClient = class {
|
|
|
116535
117376
|
`open-chat discovery: learned ${newlyLearned} new chat(s) (known=${this.recentlySeenChatIds.size}, fetched=${fetched})`
|
|
116536
117377
|
);
|
|
116537
117378
|
}
|
|
116538
|
-
|
|
116539
|
-
|
|
116540
|
-
|
|
117379
|
+
const isBootstrap = !this.openChatDiscoveryBootstrapped;
|
|
117380
|
+
const targetChatIds = isBootstrap ? new Set(discoveredChatIds) : /* @__PURE__ */ new Set([
|
|
117381
|
+
...newChatIds,
|
|
117382
|
+
...[...discoveredChatIds].filter((c) => this.unresolvedGapWindowByChat.has(c))
|
|
117383
|
+
]);
|
|
117384
|
+
this.openChatDiscoveryBootstrapped = true;
|
|
117385
|
+
if (targetChatIds.size > 0) {
|
|
117386
|
+
const intervalMs = resolveOpenChatDiscoveryMs(this.opts.openChatDiscoveryMs);
|
|
117387
|
+
const targetedLookbackMs = intervalMs + OPEN_CHAT_DISCOVERY_LOOKBACK_BUFFER_MS;
|
|
117388
|
+
const lookbackMs = isBootstrap ? OPEN_CHAT_DISCOVERY_BOOTSTRAP_LOOKBACK_MS : targetedLookbackMs;
|
|
116541
117389
|
await this.gapFill(
|
|
116542
117390
|
Date.now() - lookbackMs,
|
|
116543
117391
|
log,
|
|
116544
|
-
|
|
117392
|
+
targetChatIds,
|
|
117393
|
+
isBootstrap ? void 0 : targetedLookbackMs
|
|
116545
117394
|
);
|
|
116546
117395
|
}
|
|
117396
|
+
this.openChatDiscoveryFailures = 0;
|
|
117397
|
+
this.openChatDiscoverySkips = 0;
|
|
116547
117398
|
} catch (e) {
|
|
116548
|
-
|
|
117399
|
+
this.openChatDiscoveryFailures = Math.min(
|
|
117400
|
+
this.openChatDiscoveryFailures + 1,
|
|
117401
|
+
OPEN_CHAT_DISCOVERY_MAX_BACKOFF_CYCLES
|
|
117402
|
+
);
|
|
117403
|
+
this.openChatDiscoverySkips = 2 ** (this.openChatDiscoveryFailures - 1);
|
|
117404
|
+
log(
|
|
117405
|
+
`open-chat discovery failed (backing off ${this.openChatDiscoverySkips} cycle(s)): ${e instanceof Error ? e.message : String(e)}`
|
|
117406
|
+
);
|
|
116549
117407
|
} finally {
|
|
116550
117408
|
this.openChatDiscoveryRunning = false;
|
|
116551
117409
|
}
|
|
@@ -116621,25 +117479,44 @@ var ChannelClient = class {
|
|
|
116621
117479
|
if (this.seenMessageIds.size === before) return;
|
|
116622
117480
|
void this.persistSeenMessageIds();
|
|
116623
117481
|
}
|
|
116624
|
-
|
|
116625
|
-
|
|
116626
|
-
|
|
116627
|
-
|
|
117482
|
+
/**
|
|
117483
|
+
* Atomic JSON write: serialize to a UNIQUE temp file, then `rename` over the
|
|
117484
|
+
* destination. Two concerns motivate this:
|
|
117485
|
+
* 1. Atomicity — `rename` is atomic on a POSIX filesystem, so a reader (or a
|
|
117486
|
+
* crash) never observes a half-written file. The persist methods are
|
|
117487
|
+
* fire-and-forget (`void persist…`), so under a multi-bot storm several
|
|
117488
|
+
* writes to the SAME path can overlap; a plain `writeFile` interleaves
|
|
117489
|
+
* their bytes → the "Bad control character in string literal" JSON
|
|
117490
|
+
* corruption we saw. With tmp+rename each write lands whole-or-not-at-all.
|
|
117491
|
+
* 2. Per-write unique tmp name — a fixed `${file}.tmp` would itself be raced
|
|
117492
|
+
* by two concurrent writers. The pid + monotonic counter suffix gives each
|
|
117493
|
+
* in-flight write its own tmp so they can't clobber each other before the
|
|
117494
|
+
* rename. Best-effort cleanup on failure; losing the cache is non-fatal.
|
|
117495
|
+
*/
|
|
117496
|
+
async atomicWriteJson(file, value) {
|
|
117497
|
+
const tmp = `${file}.${process.pid}.${this.atomicWriteSeq++}.tmp`;
|
|
116628
117498
|
try {
|
|
116629
117499
|
await mkdir(path3.dirname(file), { recursive: true });
|
|
116630
|
-
await writeFile(
|
|
117500
|
+
await writeFile(tmp, JSON.stringify(value, null, 2), "utf8");
|
|
117501
|
+
await rename(tmp, file);
|
|
116631
117502
|
} catch {
|
|
117503
|
+
try {
|
|
117504
|
+
await unlink(tmp);
|
|
117505
|
+
} catch {
|
|
117506
|
+
}
|
|
116632
117507
|
}
|
|
116633
117508
|
}
|
|
117509
|
+
async persistSeenMessageIds() {
|
|
117510
|
+
const file = this.seenMessagesPath();
|
|
117511
|
+
if (!file) return;
|
|
117512
|
+
const messages = [...this.seenMessageIds].slice(-SEEN_MESSAGES_LIMIT);
|
|
117513
|
+
await this.atomicWriteJson(file, messages);
|
|
117514
|
+
}
|
|
116634
117515
|
async persistRecentlySeenChatIds() {
|
|
116635
117516
|
const file = this.learnedChatsPath();
|
|
116636
117517
|
if (!file) return;
|
|
116637
117518
|
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
|
-
}
|
|
117519
|
+
await this.atomicWriteJson(file, chats);
|
|
116643
117520
|
}
|
|
116644
117521
|
async addProcessingReaction(messageId) {
|
|
116645
117522
|
if (this.processingReactions.has(messageId)) return;
|
|
@@ -117200,7 +118077,7 @@ var CardRenderer = class {
|
|
|
117200
118077
|
};
|
|
117201
118078
|
|
|
117202
118079
|
// src/claude/sessionStore.ts
|
|
117203
|
-
import { rename, readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2, copyFile } from "node:fs/promises";
|
|
118080
|
+
import { rename as rename2, readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2, copyFile } from "node:fs/promises";
|
|
117204
118081
|
import { dirname } from "node:path";
|
|
117205
118082
|
var STORE_VERSION = 2;
|
|
117206
118083
|
var TOUCH_DEBOUNCE_MS = 1e3;
|
|
@@ -117427,7 +118304,7 @@ var SessionStore = class _SessionStore {
|
|
|
117427
118304
|
const tmpPath = `${this.#filePath}.tmp`;
|
|
117428
118305
|
await mkdir2(dirname(this.#filePath), { recursive: true });
|
|
117429
118306
|
await writeFile2(tmpPath, json2, "utf8");
|
|
117430
|
-
await
|
|
118307
|
+
await rename2(tmpPath, this.#filePath);
|
|
117431
118308
|
}
|
|
117432
118309
|
};
|
|
117433
118310
|
function isStoredRecord(value) {
|
|
@@ -117438,8 +118315,8 @@ function isStoredRecord(value) {
|
|
|
117438
118315
|
|
|
117439
118316
|
// src/bridge/handler.ts
|
|
117440
118317
|
import child_process from "node:child_process";
|
|
117441
|
-
import
|
|
117442
|
-
import
|
|
118318
|
+
import fs6 from "node:fs/promises";
|
|
118319
|
+
import path9 from "node:path";
|
|
117443
118320
|
|
|
117444
118321
|
// src/lark/message.ts
|
|
117445
118322
|
var AT_PLACEHOLDER_RE = /@_\w+\s*/g;
|
|
@@ -117810,7 +118687,7 @@ function renderStateContract(stateFilePath) {
|
|
|
117810
118687
|
"- 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
118688
|
"- 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
118689
|
'- 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
|
-
|
|
118690
|
+
"- 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
118691
|
"- 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
118692
|
"- 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
118693
|
"- updated_at: ISO 8601 timestamp",
|
|
@@ -118622,17 +119499,11 @@ function defaultResponseSurfacePrototypeConfig() {
|
|
|
118622
119499
|
enabled: true,
|
|
118623
119500
|
allowed_chats: [],
|
|
118624
119501
|
allowed_threads: [],
|
|
118625
|
-
lazy_card_creation: true,
|
|
118626
119502
|
kill_switch: false,
|
|
118627
119503
|
post_outbound_enabled: true,
|
|
118628
119504
|
cardkit_streaming_enabled: true,
|
|
118629
119505
|
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
|
|
119506
|
+
allowed_mention_open_ids: []
|
|
118636
119507
|
};
|
|
118637
119508
|
}
|
|
118638
119509
|
var DEFAULT_RESPONSE_SURFACE_PROTOTYPE = defaultResponseSurfacePrototypeConfig();
|
|
@@ -118640,17 +119511,11 @@ var responseSurfacePrototypeConfigDefaults = () => ({
|
|
|
118640
119511
|
enabled: true,
|
|
118641
119512
|
allowed_chats: [],
|
|
118642
119513
|
allowed_threads: [],
|
|
118643
|
-
lazy_card_creation: true,
|
|
118644
119514
|
kill_switch: false,
|
|
118645
119515
|
post_outbound_enabled: true,
|
|
118646
119516
|
cardkit_streaming_enabled: true,
|
|
118647
119517
|
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
|
|
119518
|
+
allowed_mention_open_ids: []
|
|
118654
119519
|
});
|
|
118655
119520
|
var ResponseSurfaceModeSchema = external_exports.enum(["card", "post", "hybrid"]);
|
|
118656
119521
|
var ResponseSurfacePrimarySchema = external_exports.enum(["card", "post"]);
|
|
@@ -118707,12 +119572,6 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
|
|
|
118707
119572
|
* all threads are allowed by this gate.
|
|
118708
119573
|
*/
|
|
118709
119574
|
allowed_threads: external_exports.array(external_exports.string().min(1)).default([]),
|
|
118710
|
-
/**
|
|
118711
|
-
* Historical post-first gate retained for config compatibility. The default
|
|
118712
|
-
* runtime no longer uses post-first lazy card creation; CardKit is the main
|
|
118713
|
-
* surface and legacy cards remain the visible fallback.
|
|
118714
|
-
*/
|
|
118715
|
-
lazy_card_creation: external_exports.boolean().default(true),
|
|
118716
119575
|
/**
|
|
118717
119576
|
* Runtime kill switch for emergency rollback. When true, every response
|
|
118718
119577
|
* surface post path is treated as disabled even if enabled/allowlists are
|
|
@@ -118741,32 +119600,7 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
|
|
|
118741
119600
|
* may choose mention targets; non-empty narrows mentions to this exact set.
|
|
118742
119601
|
* Keep real IDs in private bot config, never in public docs/tests.
|
|
118743
119602
|
*/
|
|
118744
|
-
allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([])
|
|
118745
|
-
/**
|
|
118746
|
-
* Historical post dispatch cap retained for schema compatibility and
|
|
118747
|
-
* isolated dispatcher tests.
|
|
118748
|
-
*/
|
|
118749
|
-
max_posts_per_turn: external_exports.number().int().min(0).max(10).default(1),
|
|
118750
|
-
/**
|
|
118751
|
-
* Sliding-window hard cap for real post sends within one bot/chat/thread
|
|
118752
|
-
* runtime scope. This is enforced in the production handler before a post
|
|
118753
|
-
* transport call is attempted; exhausted windows degrade to visible cards.
|
|
118754
|
-
*/
|
|
118755
|
-
max_posts_per_window: external_exports.number().int().min(0).max(100).default(4),
|
|
118756
|
-
/**
|
|
118757
|
-
* Sliding-window duration for max_posts_per_window.
|
|
118758
|
-
*/
|
|
118759
|
-
post_window_ms: external_exports.number().int().min(1e3).max(864e5).default(6e4),
|
|
118760
|
-
/**
|
|
118761
|
-
* Max attempts for one logical post. Retry classification is intentionally
|
|
118762
|
-
* narrow in PR3: only 5xx errors are retryable.
|
|
118763
|
-
*/
|
|
118764
|
-
max_post_attempts: external_exports.number().int().min(1).max(5).default(3),
|
|
118765
|
-
/**
|
|
118766
|
-
* Historical threshold for removed lazy card creation experiments. Bounded
|
|
118767
|
-
* so older config remains parseable without growing arbitrary business rules.
|
|
118768
|
-
*/
|
|
118769
|
-
text_threshold_chars: external_exports.number().int().min(1).max(2e4).default(1200)
|
|
119603
|
+
allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([])
|
|
118770
119604
|
}).default(responseSurfacePrototypeConfigDefaults);
|
|
118771
119605
|
function isResponseSurfacePrototypeAllowlisted(config, facts) {
|
|
118772
119606
|
if (!config?.enabled) return false;
|
|
@@ -118785,7 +119619,7 @@ function isResponseSurfaceMentionAllowed(config, userId) {
|
|
|
118785
119619
|
return config.allowed_mention_open_ids.includes(userId);
|
|
118786
119620
|
}
|
|
118787
119621
|
function shouldProvideResponseSurfacePostClient(config) {
|
|
118788
|
-
return !!(config?.enabled && !config.kill_switch && config.post_outbound_enabled
|
|
119622
|
+
return !!(config?.enabled && !config.kill_switch && config.post_outbound_enabled);
|
|
118789
119623
|
}
|
|
118790
119624
|
function shouldProvideResponseSurfaceCardKitClient(config) {
|
|
118791
119625
|
return !!(config?.enabled && !config.kill_switch && config.cardkit_streaming_enabled);
|
|
@@ -119828,697 +120662,6 @@ function derivePostIdempotencyKey(input) {
|
|
|
119828
120662
|
return key;
|
|
119829
120663
|
}
|
|
119830
120664
|
|
|
119831
|
-
// src/bridge/postFile.ts
|
|
119832
|
-
import fs6 from "node:fs/promises";
|
|
119833
|
-
import path9 from "node:path";
|
|
119834
|
-
var PostLedgerStatusSchema = external_exports.enum([
|
|
119835
|
-
"planned",
|
|
119836
|
-
"pending",
|
|
119837
|
-
"sent",
|
|
119838
|
-
"failed",
|
|
119839
|
-
"fallback_visible",
|
|
119840
|
-
"policy_blocked"
|
|
119841
|
-
]);
|
|
119842
|
-
var POST_LEDGER_TRANSITIONS = {
|
|
119843
|
-
planned: ["pending", "fallback_visible", "policy_blocked"],
|
|
119844
|
-
pending: ["sent", "failed", "fallback_visible", "policy_blocked"],
|
|
119845
|
-
sent: [],
|
|
119846
|
-
failed: ["fallback_visible"],
|
|
119847
|
-
fallback_visible: [],
|
|
119848
|
-
policy_blocked: []
|
|
119849
|
-
};
|
|
119850
|
-
function canTransitionPostStatus(from, to) {
|
|
119851
|
-
return from === to || POST_LEDGER_TRANSITIONS[from].includes(to);
|
|
119852
|
-
}
|
|
119853
|
-
function assertPostStatusTransition(from, to) {
|
|
119854
|
-
if (!canTransitionPostStatus(from, to)) {
|
|
119855
|
-
throw new Error(`invalid post ledger transition: ${from} -> ${to}`);
|
|
119856
|
-
}
|
|
119857
|
-
}
|
|
119858
|
-
var PostAttemptSchema = external_exports.object({
|
|
119859
|
-
attemptedAt: external_exports.string(),
|
|
119860
|
-
status: external_exports.enum(["sent", "failed"]),
|
|
119861
|
-
retryable: external_exports.boolean().default(false),
|
|
119862
|
-
error: external_exports.string().optional(),
|
|
119863
|
-
code: external_exports.string().optional()
|
|
119864
|
-
});
|
|
119865
|
-
var PostLedgerEntrySchema = external_exports.object({
|
|
119866
|
-
idempotencyKey: external_exports.string().min(1).max(64),
|
|
119867
|
-
status: PostLedgerStatusSchema,
|
|
119868
|
-
botId: external_exports.string().min(1),
|
|
119869
|
-
chatId: external_exports.string().min(1),
|
|
119870
|
-
threadId: external_exports.string().min(1),
|
|
119871
|
-
replyToMessageId: external_exports.string().min(1),
|
|
119872
|
-
role: external_exports.enum(["primary", "secondary", "fallback"]),
|
|
119873
|
-
logicalIndex: external_exports.number().int().nonnegative(),
|
|
119874
|
-
contentDigest: external_exports.string().min(1),
|
|
119875
|
-
mentionCount: external_exports.number().int().nonnegative().default(0),
|
|
119876
|
-
postMessageId: external_exports.string().optional(),
|
|
119877
|
-
fallbackCardMessageId: external_exports.string().optional(),
|
|
119878
|
-
error: external_exports.string().optional(),
|
|
119879
|
-
attempts: external_exports.array(PostAttemptSchema).default([]),
|
|
119880
|
-
createdAt: external_exports.string(),
|
|
119881
|
-
updatedAt: external_exports.string()
|
|
119882
|
-
});
|
|
119883
|
-
var PostFileSchema = external_exports.object({
|
|
119884
|
-
version: external_exports.literal(1),
|
|
119885
|
-
posts: external_exports.array(PostLedgerEntrySchema).max(50)
|
|
119886
|
-
});
|
|
119887
|
-
function emptyPostFile() {
|
|
119888
|
-
return { version: 1, posts: [] };
|
|
119889
|
-
}
|
|
119890
|
-
function summarizePostLedger(data) {
|
|
119891
|
-
const summary = {
|
|
119892
|
-
total: 0,
|
|
119893
|
-
planned: 0,
|
|
119894
|
-
pending: 0,
|
|
119895
|
-
sent: 0,
|
|
119896
|
-
failed: 0,
|
|
119897
|
-
fallback_visible: 0,
|
|
119898
|
-
policy_blocked: 0,
|
|
119899
|
-
withPostMessageId: 0,
|
|
119900
|
-
withFallbackCardMessageId: 0
|
|
119901
|
-
};
|
|
119902
|
-
for (const post of data?.posts ?? []) {
|
|
119903
|
-
summary.total += 1;
|
|
119904
|
-
summary[post.status] += 1;
|
|
119905
|
-
if (post.postMessageId) summary.withPostMessageId += 1;
|
|
119906
|
-
if (post.fallbackCardMessageId) summary.withFallbackCardMessageId += 1;
|
|
119907
|
-
}
|
|
119908
|
-
return summary;
|
|
119909
|
-
}
|
|
119910
|
-
function postDirOf(worktreePath) {
|
|
119911
|
-
return path9.join(worktreePath, ".larkway");
|
|
119912
|
-
}
|
|
119913
|
-
function postFilePathOf(worktreePath) {
|
|
119914
|
-
return path9.join(postDirOf(worktreePath), "post.json");
|
|
119915
|
-
}
|
|
119916
|
-
async function readPostFile(worktreePath) {
|
|
119917
|
-
const file = postFilePathOf(worktreePath);
|
|
119918
|
-
let raw;
|
|
119919
|
-
try {
|
|
119920
|
-
raw = await fs6.readFile(file, "utf8");
|
|
119921
|
-
} catch (err) {
|
|
119922
|
-
if (err.code === "ENOENT") return null;
|
|
119923
|
-
console.warn(`[postFile] read ${file} failed:`, err);
|
|
119924
|
-
return null;
|
|
119925
|
-
}
|
|
119926
|
-
let parsed;
|
|
119927
|
-
try {
|
|
119928
|
-
parsed = JSON.parse(raw);
|
|
119929
|
-
} catch (err) {
|
|
119930
|
-
console.warn(`[postFile] ${file} not valid JSON:`, err);
|
|
119931
|
-
return null;
|
|
119932
|
-
}
|
|
119933
|
-
const result = PostFileSchema.safeParse(parsed);
|
|
119934
|
-
if (!result.success) {
|
|
119935
|
-
console.warn(`[postFile] ${file} failed schema validation:`, result.error.issues);
|
|
119936
|
-
return null;
|
|
119937
|
-
}
|
|
119938
|
-
return result.data;
|
|
119939
|
-
}
|
|
119940
|
-
async function writePostFile(worktreePath, data) {
|
|
119941
|
-
const dir = postDirOf(worktreePath);
|
|
119942
|
-
const file = postFilePathOf(worktreePath);
|
|
119943
|
-
await fs6.mkdir(dir, { recursive: true });
|
|
119944
|
-
const parsed = PostFileSchema.parse(data);
|
|
119945
|
-
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
119946
|
-
await fs6.writeFile(tmp, JSON.stringify(parsed, null, 2), "utf8");
|
|
119947
|
-
try {
|
|
119948
|
-
await fs6.rename(tmp, file);
|
|
119949
|
-
} catch (err) {
|
|
119950
|
-
await fs6.rm(tmp, { force: true }).catch(() => {
|
|
119951
|
-
});
|
|
119952
|
-
throw err;
|
|
119953
|
-
}
|
|
119954
|
-
}
|
|
119955
|
-
async function upsertPostLedgerEntry(worktreePath, entry) {
|
|
119956
|
-
const existing = await readPostFile(worktreePath) ?? emptyPostFile();
|
|
119957
|
-
const idx = existing.posts.findIndex((p) => p.idempotencyKey === entry.idempotencyKey);
|
|
119958
|
-
const nextPosts = [...existing.posts];
|
|
119959
|
-
if (idx >= 0) {
|
|
119960
|
-
assertPostStatusTransition(nextPosts[idx].status, entry.status);
|
|
119961
|
-
nextPosts[idx] = entry;
|
|
119962
|
-
} else {
|
|
119963
|
-
nextPosts.push(entry);
|
|
119964
|
-
}
|
|
119965
|
-
const next = { version: 1, posts: nextPosts };
|
|
119966
|
-
await writePostFile(worktreePath, next);
|
|
119967
|
-
return next;
|
|
119968
|
-
}
|
|
119969
|
-
var DEFAULT_POST_RECONCILE_MIN_AGE_MS = 6e4;
|
|
119970
|
-
function timestampAgeMs(iso, nowMs) {
|
|
119971
|
-
if (!Number.isFinite(nowMs)) return null;
|
|
119972
|
-
const then = Date.parse(iso);
|
|
119973
|
-
if (!Number.isFinite(then)) return null;
|
|
119974
|
-
return nowMs - then;
|
|
119975
|
-
}
|
|
119976
|
-
function reconcilePostEntry(entry, opts) {
|
|
119977
|
-
if (entry.botId !== opts.botId) {
|
|
119978
|
-
return { entry, changed: false, sent: false, needsVisibleFallback: false };
|
|
119979
|
-
}
|
|
119980
|
-
if (entry.status === "sent" || entry.status === "fallback_visible" || entry.status === "policy_blocked") {
|
|
119981
|
-
return { entry, changed: false, sent: false, needsVisibleFallback: false };
|
|
119982
|
-
}
|
|
119983
|
-
const now = opts.now();
|
|
119984
|
-
const ageMs = timestampAgeMs(entry.updatedAt, Date.parse(now));
|
|
119985
|
-
if (ageMs == null || ageMs < opts.minAgeMs) {
|
|
119986
|
-
return { entry, changed: false, sent: false, needsVisibleFallback: false };
|
|
119987
|
-
}
|
|
119988
|
-
if (entry.postMessageId) {
|
|
119989
|
-
return {
|
|
119990
|
-
entry: {
|
|
119991
|
-
...entry,
|
|
119992
|
-
status: "sent",
|
|
119993
|
-
error: void 0,
|
|
119994
|
-
updatedAt: now,
|
|
119995
|
-
attempts: [
|
|
119996
|
-
...entry.attempts,
|
|
119997
|
-
{
|
|
119998
|
-
attemptedAt: now,
|
|
119999
|
-
status: "sent",
|
|
120000
|
-
retryable: false
|
|
120001
|
-
}
|
|
120002
|
-
]
|
|
120003
|
-
},
|
|
120004
|
-
changed: true,
|
|
120005
|
-
sent: true,
|
|
120006
|
-
needsVisibleFallback: false
|
|
120007
|
-
};
|
|
120008
|
-
}
|
|
120009
|
-
return {
|
|
120010
|
-
entry,
|
|
120011
|
-
changed: false,
|
|
120012
|
-
sent: false,
|
|
120013
|
-
needsVisibleFallback: true
|
|
120014
|
-
};
|
|
120015
|
-
}
|
|
120016
|
-
function reconcilePostLedgerEntries(data, opts) {
|
|
120017
|
-
const normalizedOpts = {
|
|
120018
|
-
botId: opts.botId,
|
|
120019
|
-
minAgeMs: opts.minAgeMs ?? DEFAULT_POST_RECONCILE_MIN_AGE_MS,
|
|
120020
|
-
now: opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
|
|
120021
|
-
};
|
|
120022
|
-
let sent = 0;
|
|
120023
|
-
const fallbackVisible = 0;
|
|
120024
|
-
let needsVisibleFallback = 0;
|
|
120025
|
-
let skippedLive = 0;
|
|
120026
|
-
const visibleFallbackCandidates = [];
|
|
120027
|
-
const posts = data.posts.map((post) => {
|
|
120028
|
-
const reconciled = reconcilePostEntry(post, normalizedOpts);
|
|
120029
|
-
if (reconciled.changed) {
|
|
120030
|
-
if (reconciled.sent) sent += 1;
|
|
120031
|
-
} else if (reconciled.needsVisibleFallback) {
|
|
120032
|
-
needsVisibleFallback += 1;
|
|
120033
|
-
visibleFallbackCandidates.push(reconciled.entry);
|
|
120034
|
-
} else if (post.botId === normalizedOpts.botId && (post.status === "planned" || post.status === "pending" || post.status === "failed")) {
|
|
120035
|
-
skippedLive += 1;
|
|
120036
|
-
}
|
|
120037
|
-
return reconciled.entry;
|
|
120038
|
-
});
|
|
120039
|
-
const changed = sent > 0 || fallbackVisible > 0;
|
|
120040
|
-
return {
|
|
120041
|
-
file: changed ? { version: 1, posts } : data,
|
|
120042
|
-
result: { changed, sent, fallbackVisible, needsVisibleFallback, skippedLive },
|
|
120043
|
-
visibleFallbackCandidates
|
|
120044
|
-
};
|
|
120045
|
-
}
|
|
120046
|
-
async function reconcilePostFileOrphans(worktreePath, opts) {
|
|
120047
|
-
const existing = await readPostFile(worktreePath);
|
|
120048
|
-
if (!existing) {
|
|
120049
|
-
return {
|
|
120050
|
-
changed: false,
|
|
120051
|
-
sent: 0,
|
|
120052
|
-
fallbackVisible: 0,
|
|
120053
|
-
needsVisibleFallback: 0,
|
|
120054
|
-
skippedLive: 0,
|
|
120055
|
-
visibleFallbackCandidates: []
|
|
120056
|
-
};
|
|
120057
|
-
}
|
|
120058
|
-
const { file, result, visibleFallbackCandidates } = reconcilePostLedgerEntries(existing, opts);
|
|
120059
|
-
if (result.changed) {
|
|
120060
|
-
await writePostFile(worktreePath, file);
|
|
120061
|
-
}
|
|
120062
|
-
return { ...result, visibleFallbackCandidates };
|
|
120063
|
-
}
|
|
120064
|
-
async function markPostLedgerFallbackVisible(worktreePath, idempotencyKey2, opts) {
|
|
120065
|
-
const existing = await readPostFile(worktreePath) ?? emptyPostFile();
|
|
120066
|
-
const idx = existing.posts.findIndex((post) => post.idempotencyKey === idempotencyKey2);
|
|
120067
|
-
if (idx < 0) {
|
|
120068
|
-
throw new Error(`post ledger entry not found: ${idempotencyKey2}`);
|
|
120069
|
-
}
|
|
120070
|
-
const current = existing.posts[idx];
|
|
120071
|
-
assertPostStatusTransition(current.status, "fallback_visible");
|
|
120072
|
-
const now = opts.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
120073
|
-
const nextPosts = [...existing.posts];
|
|
120074
|
-
nextPosts[idx] = {
|
|
120075
|
-
...current,
|
|
120076
|
-
status: "fallback_visible",
|
|
120077
|
-
fallbackCardMessageId: opts.fallbackCardMessageId,
|
|
120078
|
-
error: opts.error,
|
|
120079
|
-
updatedAt: now,
|
|
120080
|
-
attempts: [
|
|
120081
|
-
...current.attempts,
|
|
120082
|
-
{
|
|
120083
|
-
attemptedAt: now,
|
|
120084
|
-
status: "failed",
|
|
120085
|
-
retryable: false,
|
|
120086
|
-
code: "orphan_reconcile",
|
|
120087
|
-
error: opts.error
|
|
120088
|
-
}
|
|
120089
|
-
]
|
|
120090
|
-
};
|
|
120091
|
-
const next = { version: 1, posts: nextPosts };
|
|
120092
|
-
await writePostFile(worktreePath, next);
|
|
120093
|
-
return next;
|
|
120094
|
-
}
|
|
120095
|
-
async function markPostLedgerPolicyBlockedVisible(worktreePath, idempotencyKey2, opts) {
|
|
120096
|
-
const existing = await readPostFile(worktreePath) ?? emptyPostFile();
|
|
120097
|
-
const idx = existing.posts.findIndex((post) => post.idempotencyKey === idempotencyKey2);
|
|
120098
|
-
if (idx < 0) {
|
|
120099
|
-
throw new Error(`post ledger entry not found: ${idempotencyKey2}`);
|
|
120100
|
-
}
|
|
120101
|
-
const current = existing.posts[idx];
|
|
120102
|
-
assertPostStatusTransition(current.status, "policy_blocked");
|
|
120103
|
-
const now = opts.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
120104
|
-
const nextPosts = [...existing.posts];
|
|
120105
|
-
nextPosts[idx] = {
|
|
120106
|
-
...current,
|
|
120107
|
-
status: "policy_blocked",
|
|
120108
|
-
fallbackCardMessageId: opts.fallbackCardMessageId,
|
|
120109
|
-
error: opts.error,
|
|
120110
|
-
updatedAt: now,
|
|
120111
|
-
attempts: [
|
|
120112
|
-
...current.attempts,
|
|
120113
|
-
{
|
|
120114
|
-
attemptedAt: now,
|
|
120115
|
-
status: "failed",
|
|
120116
|
-
retryable: false,
|
|
120117
|
-
code: "mention_policy_blocked",
|
|
120118
|
-
error: opts.error
|
|
120119
|
-
}
|
|
120120
|
-
]
|
|
120121
|
-
};
|
|
120122
|
-
const next = { version: 1, posts: nextPosts };
|
|
120123
|
-
await writePostFile(worktreePath, next);
|
|
120124
|
-
return next;
|
|
120125
|
-
}
|
|
120126
|
-
|
|
120127
|
-
// src/bridge/surfaceDispatcher.ts
|
|
120128
|
-
function fullCard(input, reason) {
|
|
120129
|
-
return {
|
|
120130
|
-
card: input.baseCard,
|
|
120131
|
-
reason,
|
|
120132
|
-
visible: input.cardStarted || input.visibleFallbackAvailable
|
|
120133
|
-
};
|
|
120134
|
-
}
|
|
120135
|
-
function hasCardOnlyPayload(state) {
|
|
120136
|
-
return !!(state?.choices?.length || state?.image_blocks?.length || state?.content_blocks?.length);
|
|
120137
|
-
}
|
|
120138
|
-
function hasCardOnlyPayloadIn(input) {
|
|
120139
|
-
return !!(hasCardOnlyPayload(input.state) || input.baseCard.choices?.length || input.baseCard.imageBlocks?.length || input.baseCard.contentBlocks?.length);
|
|
120140
|
-
}
|
|
120141
|
-
function compactAuditCard(input, post) {
|
|
120142
|
-
const status = input.state?.status ?? (input.baseCard.success ? "ready" : "failed");
|
|
120143
|
-
const title = input.baseCard.titleOverride ?? "Post \u5DF2\u53D1\u9001";
|
|
120144
|
-
return {
|
|
120145
|
-
success: input.baseCard.success,
|
|
120146
|
-
failureReason: input.baseCard.failureReason,
|
|
120147
|
-
titleOverride: title,
|
|
120148
|
-
colorOverride: input.baseCard.colorOverride ?? "neutral",
|
|
120149
|
-
finalText: `\u4E3B\u56DE\u590D\u5DF2\u901A\u8FC7 post \u53D1\u51FA\u3002
|
|
120150
|
-
status: ${status}
|
|
120151
|
-
post_message_id: ${post.messageId}
|
|
120152
|
-
idempotency_key: ${post.idempotencyKey}`
|
|
120153
|
-
};
|
|
120154
|
-
}
|
|
120155
|
-
function fallbackFailureCard(input, error) {
|
|
120156
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
120157
|
-
return {
|
|
120158
|
-
...input.baseCard,
|
|
120159
|
-
success: false,
|
|
120160
|
-
failureReason: `post outbound failed; visible card fallback used: ${reason}`
|
|
120161
|
-
};
|
|
120162
|
-
}
|
|
120163
|
-
function policyBlockedCard(input) {
|
|
120164
|
-
return {
|
|
120165
|
-
...input.baseCard,
|
|
120166
|
-
success: false,
|
|
120167
|
-
failureReason: "response_surface post mention target is blocked by policy; visible card fallback used"
|
|
120168
|
-
};
|
|
120169
|
-
}
|
|
120170
|
-
function postText(input) {
|
|
120171
|
-
const text = input.baseCard.finalText?.trim() || input.state?.last_message?.trim();
|
|
120172
|
-
if (text) return text;
|
|
120173
|
-
if (input.baseCard.success) return "\u5B8C\u6210";
|
|
120174
|
-
return input.baseCard.failureReason ?? "\u6267\u884C\u5931\u8D25";
|
|
120175
|
-
}
|
|
120176
|
-
function postRole(input) {
|
|
120177
|
-
const surface = input.state?.response_surface;
|
|
120178
|
-
if (surface?.mode === "hybrid") return "primary";
|
|
120179
|
-
return "primary";
|
|
120180
|
-
}
|
|
120181
|
-
function newLedgerEntry(input) {
|
|
120182
|
-
return {
|
|
120183
|
-
idempotencyKey: input.idempotencyKey,
|
|
120184
|
-
status: input.status,
|
|
120185
|
-
botId: input.facts.botId,
|
|
120186
|
-
chatId: input.facts.chatId,
|
|
120187
|
-
threadId: input.facts.threadId,
|
|
120188
|
-
replyToMessageId: input.facts.replyToMessageId,
|
|
120189
|
-
role: input.role,
|
|
120190
|
-
logicalIndex: input.logicalIndex,
|
|
120191
|
-
contentDigest: input.contentDigest,
|
|
120192
|
-
mentionCount: input.mentionCount,
|
|
120193
|
-
postMessageId: input.postMessageId,
|
|
120194
|
-
error: input.error,
|
|
120195
|
-
attempts: input.attempts ?? [],
|
|
120196
|
-
createdAt: input.now,
|
|
120197
|
-
updatedAt: input.now
|
|
120198
|
-
};
|
|
120199
|
-
}
|
|
120200
|
-
async function writeLedger(input, entry) {
|
|
120201
|
-
if (!input.worktreePath) return;
|
|
120202
|
-
await upsertPostLedgerEntry(input.worktreePath, entry);
|
|
120203
|
-
}
|
|
120204
|
-
async function existingLedgerEntry(input, idempotencyKey2) {
|
|
120205
|
-
if (!input.worktreePath) return null;
|
|
120206
|
-
const ledger = await readPostFile(input.worktreePath);
|
|
120207
|
-
return ledger?.posts.find((post) => post.idempotencyKey === idempotencyKey2) ?? null;
|
|
120208
|
-
}
|
|
120209
|
-
function sentResult(input, surface, post, reason) {
|
|
120210
|
-
if (hasCardOnlyPayloadIn(input)) {
|
|
120211
|
-
return {
|
|
120212
|
-
card: input.baseCard,
|
|
120213
|
-
reason: "post-sent-card-capability-required",
|
|
120214
|
-
visible: true,
|
|
120215
|
-
post
|
|
120216
|
-
};
|
|
120217
|
-
}
|
|
120218
|
-
if (surface.mode === "hybrid" || input.cardStarted) {
|
|
120219
|
-
return {
|
|
120220
|
-
card: compactAuditCard(input, post),
|
|
120221
|
-
reason: reason === "post-ledger-already-sent" ? "post-ledger-already-sent" : surface.mode === "hybrid" ? "hybrid-post-sent-compact-card" : reason,
|
|
120222
|
-
visible: true,
|
|
120223
|
-
post
|
|
120224
|
-
};
|
|
120225
|
-
}
|
|
120226
|
-
return {
|
|
120227
|
-
card: null,
|
|
120228
|
-
reason,
|
|
120229
|
-
visible: true,
|
|
120230
|
-
post
|
|
120231
|
-
};
|
|
120232
|
-
}
|
|
120233
|
-
async function ledgerSummaryFor(input) {
|
|
120234
|
-
if (!input.worktreePath) return summarizePostLedger(null);
|
|
120235
|
-
return summarizePostLedger(await readPostFile(input.worktreePath));
|
|
120236
|
-
}
|
|
120237
|
-
function emitSurfaceObservation(input) {
|
|
120238
|
-
console.log(
|
|
120239
|
-
"[response_surface.dispatch]",
|
|
120240
|
-
JSON.stringify({
|
|
120241
|
-
event: "response_surface.dispatch",
|
|
120242
|
-
botId: input.facts.botId,
|
|
120243
|
-
chatId: input.facts.chatId,
|
|
120244
|
-
threadId: input.facts.threadId,
|
|
120245
|
-
reason: input.result.reason,
|
|
120246
|
-
visible: input.result.visible,
|
|
120247
|
-
hasCard: !!input.result.card,
|
|
120248
|
-
hasPost: !!input.result.post,
|
|
120249
|
-
postMessageIdPresent: !!input.result.post?.messageId,
|
|
120250
|
-
budget: input.result.budget ? {
|
|
120251
|
-
allowed: input.result.budget.allowed,
|
|
120252
|
-
used: input.result.budget.used,
|
|
120253
|
-
limit: input.result.budget.limit,
|
|
120254
|
-
windowMs: input.result.budget.windowMs,
|
|
120255
|
-
resetAt: input.result.budget.resetAt,
|
|
120256
|
-
reason: input.result.budget.reason
|
|
120257
|
-
} : void 0,
|
|
120258
|
-
ledger: input.ledger,
|
|
120259
|
-
durationMs: input.durationMs
|
|
120260
|
-
})
|
|
120261
|
-
);
|
|
120262
|
-
}
|
|
120263
|
-
async function dispatchResponseSurfaceInner(input) {
|
|
120264
|
-
const declaredSurface = input.state?.response_surface;
|
|
120265
|
-
if (declaredSurface?.mode === "card" || declaredSurface?.primary === "card") {
|
|
120266
|
-
return fullCard(input, "legacy-card-mode");
|
|
120267
|
-
}
|
|
120268
|
-
const surface = declaredSurface ?? { mode: "post", primary: "post" };
|
|
120269
|
-
const cfg = input.prototypeConfig;
|
|
120270
|
-
if (!cfg?.enabled) return fullCard(input, "prototype-disabled");
|
|
120271
|
-
if (cfg.kill_switch) return fullCard(input, "kill-switch-active");
|
|
120272
|
-
if (!isResponseSurfacePrototypeAllowlisted(cfg, {
|
|
120273
|
-
chatId: input.facts.chatId,
|
|
120274
|
-
threadId: input.facts.threadId
|
|
120275
|
-
})) {
|
|
120276
|
-
return fullCard(input, "not-allowlisted");
|
|
120277
|
-
}
|
|
120278
|
-
if (!cfg.post_outbound_enabled) return fullCard(input, "post-outbound-disabled");
|
|
120279
|
-
if (cfg.max_posts_per_turn < 1) return fullCard(input, "post-outbound-disabled");
|
|
120280
|
-
if (cfg.max_posts_per_window < 1) return fullCard(input, "post-rate-limit-exhausted");
|
|
120281
|
-
if (!input.postOutboundAvailable || !input.postClient) {
|
|
120282
|
-
return fullCard(input, "post-outbound-unavailable");
|
|
120283
|
-
}
|
|
120284
|
-
if (!input.visibleFallbackAvailable) {
|
|
120285
|
-
return fullCard(input, "visible-fallback-unavailable");
|
|
120286
|
-
}
|
|
120287
|
-
if (!input.postLedgerAvailable || !input.worktreePath) {
|
|
120288
|
-
return fullCard(input, "post-ledger-unavailable");
|
|
120289
|
-
}
|
|
120290
|
-
const mentions = surface.post?.mentions ?? [];
|
|
120291
|
-
const blockedMention = mentions.find(
|
|
120292
|
-
(mention) => !isResponseSurfaceMentionAllowed(cfg, mention.user_id)
|
|
120293
|
-
);
|
|
120294
|
-
const text = postText(input);
|
|
120295
|
-
const policyDigest = digestPostContent(text);
|
|
120296
|
-
const role = postRole(input);
|
|
120297
|
-
const logicalIndex = 0;
|
|
120298
|
-
const policyIdempotencyKey = derivePostIdempotencyKey({
|
|
120299
|
-
botId: input.facts.botId,
|
|
120300
|
-
threadId: input.facts.threadId,
|
|
120301
|
-
triggerMessageId: input.facts.triggerMessageId,
|
|
120302
|
-
role,
|
|
120303
|
-
logicalIndex,
|
|
120304
|
-
contentDigest: policyDigest
|
|
120305
|
-
});
|
|
120306
|
-
const now = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
120307
|
-
if (blockedMention) {
|
|
120308
|
-
const policyError = `mention target is not allowed by response surface policy: ${blockedMention.user_id}`;
|
|
120309
|
-
if (input.livePost) {
|
|
120310
|
-
try {
|
|
120311
|
-
await input.postClient.updatePost(input.livePost.messageId, buildPostContent({ text }));
|
|
120312
|
-
} catch (err) {
|
|
120313
|
-
console.warn(
|
|
120314
|
-
"[surface_dispatch] live post policy-blocked cleanup update failed:",
|
|
120315
|
-
err
|
|
120316
|
-
);
|
|
120317
|
-
}
|
|
120318
|
-
}
|
|
120319
|
-
await writeLedger(
|
|
120320
|
-
input,
|
|
120321
|
-
newLedgerEntry({
|
|
120322
|
-
status: "planned",
|
|
120323
|
-
idempotencyKey: policyIdempotencyKey,
|
|
120324
|
-
now,
|
|
120325
|
-
facts: input.facts,
|
|
120326
|
-
role,
|
|
120327
|
-
logicalIndex,
|
|
120328
|
-
contentDigest: policyDigest,
|
|
120329
|
-
mentionCount: mentions.length,
|
|
120330
|
-
error: policyError
|
|
120331
|
-
})
|
|
120332
|
-
);
|
|
120333
|
-
return {
|
|
120334
|
-
card: policyBlockedCard(input),
|
|
120335
|
-
reason: "mention-policy-blocked",
|
|
120336
|
-
visible: true,
|
|
120337
|
-
post: {
|
|
120338
|
-
idempotencyKey: policyIdempotencyKey,
|
|
120339
|
-
role,
|
|
120340
|
-
requiresPolicyLedgerMark: true,
|
|
120341
|
-
policyError
|
|
120342
|
-
}
|
|
120343
|
-
};
|
|
120344
|
-
}
|
|
120345
|
-
const content = buildPostContent({
|
|
120346
|
-
text,
|
|
120347
|
-
mentions: mentions.map((mention) => ({
|
|
120348
|
-
userId: mention.user_id,
|
|
120349
|
-
label: mention.label
|
|
120350
|
-
}))
|
|
120351
|
-
});
|
|
120352
|
-
const contentDigest = digestPostContent(content);
|
|
120353
|
-
const idempotencyKey2 = input.livePost?.idempotencyKey ?? derivePostIdempotencyKey({
|
|
120354
|
-
botId: input.facts.botId,
|
|
120355
|
-
threadId: input.facts.threadId,
|
|
120356
|
-
triggerMessageId: input.facts.triggerMessageId,
|
|
120357
|
-
role,
|
|
120358
|
-
logicalIndex,
|
|
120359
|
-
contentDigest
|
|
120360
|
-
});
|
|
120361
|
-
const existing = await existingLedgerEntry(input, idempotencyKey2);
|
|
120362
|
-
if (existing?.status === "sent" && existing.postMessageId) {
|
|
120363
|
-
return sentResult(
|
|
120364
|
-
input,
|
|
120365
|
-
surface,
|
|
120366
|
-
{ idempotencyKey: idempotencyKey2, messageId: existing.postMessageId, role },
|
|
120367
|
-
"post-ledger-already-sent"
|
|
120368
|
-
);
|
|
120369
|
-
}
|
|
120370
|
-
if (existing?.status === "sent") {
|
|
120371
|
-
return fullCard(input, "post-orphan-reconciled-fallback-card");
|
|
120372
|
-
}
|
|
120373
|
-
if (existing?.status === "fallback_visible") {
|
|
120374
|
-
return {
|
|
120375
|
-
card: fallbackFailureCard(
|
|
120376
|
-
input,
|
|
120377
|
-
existing.error ?? "post ledger already reconciled to visible fallback"
|
|
120378
|
-
),
|
|
120379
|
-
reason: "post-orphan-reconciled-fallback-card",
|
|
120380
|
-
visible: true,
|
|
120381
|
-
post: { idempotencyKey: idempotencyKey2, role }
|
|
120382
|
-
};
|
|
120383
|
-
}
|
|
120384
|
-
if (existing?.status === "policy_blocked") {
|
|
120385
|
-
return {
|
|
120386
|
-
card: policyBlockedCard(input),
|
|
120387
|
-
reason: "mention-policy-blocked",
|
|
120388
|
-
visible: true,
|
|
120389
|
-
post: { idempotencyKey: idempotencyKey2, role }
|
|
120390
|
-
};
|
|
120391
|
-
}
|
|
120392
|
-
if (existing) {
|
|
120393
|
-
const error = existing.status === "failed" && existing.error ? existing.error : "orphaned post ledger entry reconciled without resend; visible card fallback used";
|
|
120394
|
-
return {
|
|
120395
|
-
card: fallbackFailureCard(input, error),
|
|
120396
|
-
reason: "post-orphan-reconciled-fallback-card",
|
|
120397
|
-
visible: true,
|
|
120398
|
-
post: {
|
|
120399
|
-
idempotencyKey: idempotencyKey2,
|
|
120400
|
-
role,
|
|
120401
|
-
requiresFallbackLedgerMark: true,
|
|
120402
|
-
fallbackError: error
|
|
120403
|
-
}
|
|
120404
|
-
};
|
|
120405
|
-
}
|
|
120406
|
-
const budget = input.livePost ? void 0 : input.postBudget?.reserve();
|
|
120407
|
-
if (budget && !budget.allowed) {
|
|
120408
|
-
return {
|
|
120409
|
-
...fullCard(input, "post-rate-limit-exhausted"),
|
|
120410
|
-
budget
|
|
120411
|
-
};
|
|
120412
|
-
}
|
|
120413
|
-
await writeLedger(
|
|
120414
|
-
input,
|
|
120415
|
-
newLedgerEntry({
|
|
120416
|
-
status: "planned",
|
|
120417
|
-
idempotencyKey: idempotencyKey2,
|
|
120418
|
-
now,
|
|
120419
|
-
facts: input.facts,
|
|
120420
|
-
role,
|
|
120421
|
-
logicalIndex,
|
|
120422
|
-
contentDigest,
|
|
120423
|
-
mentionCount: mentions.length
|
|
120424
|
-
})
|
|
120425
|
-
);
|
|
120426
|
-
await writeLedger(
|
|
120427
|
-
input,
|
|
120428
|
-
newLedgerEntry({
|
|
120429
|
-
status: "pending",
|
|
120430
|
-
idempotencyKey: idempotencyKey2,
|
|
120431
|
-
now,
|
|
120432
|
-
facts: input.facts,
|
|
120433
|
-
role,
|
|
120434
|
-
logicalIndex,
|
|
120435
|
-
contentDigest,
|
|
120436
|
-
mentionCount: mentions.length
|
|
120437
|
-
})
|
|
120438
|
-
);
|
|
120439
|
-
try {
|
|
120440
|
-
const sent = input.livePost ? await input.postClient.updatePost(input.livePost.messageId, content) : await input.postClient.createPostReply(input.facts.replyToMessageId, content, {
|
|
120441
|
-
replyInThread: input.facts.replyInThread,
|
|
120442
|
-
idempotencyKey: idempotencyKey2
|
|
120443
|
-
});
|
|
120444
|
-
await writeLedger(
|
|
120445
|
-
input,
|
|
120446
|
-
newLedgerEntry({
|
|
120447
|
-
status: "sent",
|
|
120448
|
-
idempotencyKey: idempotencyKey2,
|
|
120449
|
-
now: input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
120450
|
-
facts: input.facts,
|
|
120451
|
-
role,
|
|
120452
|
-
logicalIndex,
|
|
120453
|
-
contentDigest,
|
|
120454
|
-
mentionCount: mentions.length,
|
|
120455
|
-
postMessageId: sent.messageId,
|
|
120456
|
-
attempts: [
|
|
120457
|
-
{
|
|
120458
|
-
attemptedAt: input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
120459
|
-
status: "sent",
|
|
120460
|
-
retryable: false
|
|
120461
|
-
}
|
|
120462
|
-
]
|
|
120463
|
-
})
|
|
120464
|
-
);
|
|
120465
|
-
const post = { idempotencyKey: idempotencyKey2, messageId: sent.messageId, role };
|
|
120466
|
-
return {
|
|
120467
|
-
...sentResult(input, surface, post, input.livePost ? "post-updated" : "post-sent"),
|
|
120468
|
-
budget
|
|
120469
|
-
};
|
|
120470
|
-
} catch (err) {
|
|
120471
|
-
const failedAt = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
120472
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
120473
|
-
await writeLedger(
|
|
120474
|
-
input,
|
|
120475
|
-
newLedgerEntry({
|
|
120476
|
-
status: "failed",
|
|
120477
|
-
idempotencyKey: idempotencyKey2,
|
|
120478
|
-
now: failedAt,
|
|
120479
|
-
facts: input.facts,
|
|
120480
|
-
role,
|
|
120481
|
-
logicalIndex,
|
|
120482
|
-
contentDigest,
|
|
120483
|
-
mentionCount: mentions.length,
|
|
120484
|
-
error,
|
|
120485
|
-
attempts: [
|
|
120486
|
-
{
|
|
120487
|
-
attemptedAt: failedAt,
|
|
120488
|
-
status: "failed",
|
|
120489
|
-
retryable: false,
|
|
120490
|
-
error
|
|
120491
|
-
}
|
|
120492
|
-
]
|
|
120493
|
-
})
|
|
120494
|
-
);
|
|
120495
|
-
return {
|
|
120496
|
-
card: fallbackFailureCard(input, err),
|
|
120497
|
-
reason: "post-failed-fallback-card",
|
|
120498
|
-
visible: true,
|
|
120499
|
-
post: {
|
|
120500
|
-
idempotencyKey: idempotencyKey2,
|
|
120501
|
-
role,
|
|
120502
|
-
requiresFallbackLedgerMark: true,
|
|
120503
|
-
fallbackError: error
|
|
120504
|
-
},
|
|
120505
|
-
budget
|
|
120506
|
-
};
|
|
120507
|
-
}
|
|
120508
|
-
}
|
|
120509
|
-
async function dispatchResponseSurface(input) {
|
|
120510
|
-
const startedAt = Date.now();
|
|
120511
|
-
const result = await dispatchResponseSurfaceInner(input);
|
|
120512
|
-
const ledger = await ledgerSummaryFor(input);
|
|
120513
|
-
emitSurfaceObservation({
|
|
120514
|
-
facts: input.facts,
|
|
120515
|
-
result,
|
|
120516
|
-
ledger,
|
|
120517
|
-
durationMs: Date.now() - startedAt
|
|
120518
|
-
});
|
|
120519
|
-
return result;
|
|
120520
|
-
}
|
|
120521
|
-
|
|
120522
120665
|
// src/bridge/handler.ts
|
|
120523
120666
|
var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
120524
120667
|
function execGit(cwd, args) {
|
|
@@ -120544,7 +120687,7 @@ stderr: ${stderr}`)
|
|
|
120544
120687
|
}
|
|
120545
120688
|
async function pathExists(p) {
|
|
120546
120689
|
try {
|
|
120547
|
-
await
|
|
120690
|
+
await fs6.stat(p);
|
|
120548
120691
|
return true;
|
|
120549
120692
|
} catch {
|
|
120550
120693
|
return false;
|
|
@@ -120562,7 +120705,7 @@ async function isWorktreeGitHealthy(worktreePath) {
|
|
|
120562
120705
|
});
|
|
120563
120706
|
}
|
|
120564
120707
|
async function ensureRepoClone(basePath, url, token, label) {
|
|
120565
|
-
const gitDir =
|
|
120708
|
+
const gitDir = path9.join(basePath, ".git");
|
|
120566
120709
|
if (await pathExists(gitDir)) {
|
|
120567
120710
|
return;
|
|
120568
120711
|
}
|
|
@@ -120572,15 +120715,15 @@ async function ensureRepoClone(basePath, url, token, label) {
|
|
|
120572
120715
|
);
|
|
120573
120716
|
}
|
|
120574
120717
|
const uniq = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
120575
|
-
const tmpScript =
|
|
120718
|
+
const tmpScript = path9.join(basePath, "..", `.askpass-${uniq}.sh`);
|
|
120576
120719
|
const tokenEnvVar = `LARKWAY_GIT_TOKEN_${uniq.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
120577
120720
|
try {
|
|
120578
|
-
await
|
|
120721
|
+
await fs6.mkdir(path9.dirname(basePath), { recursive: true });
|
|
120579
120722
|
const scriptContent = [
|
|
120580
120723
|
"#!/bin/sh",
|
|
120581
120724
|
`echo "\${${tokenEnvVar}}"`
|
|
120582
120725
|
].join("\n") + "\n";
|
|
120583
|
-
await
|
|
120726
|
+
await fs6.writeFile(tmpScript, scriptContent, { mode: 448, encoding: "utf8" });
|
|
120584
120727
|
console.log(`[bridge.handler] cloning ${label} into ${basePath} \u2026`);
|
|
120585
120728
|
const env = {
|
|
120586
120729
|
...process.env,
|
|
@@ -120608,7 +120751,7 @@ stderr: ${stderr}`));
|
|
|
120608
120751
|
console.log(`[bridge.handler] clone of ${label} complete.`);
|
|
120609
120752
|
await execGit(basePath, ["remote", "set-url", "origin", url]);
|
|
120610
120753
|
} finally {
|
|
120611
|
-
await
|
|
120754
|
+
await fs6.unlink(tmpScript).catch(() => {
|
|
120612
120755
|
});
|
|
120613
120756
|
}
|
|
120614
120757
|
}
|
|
@@ -120662,8 +120805,8 @@ var CORE_DENY_RULES = [
|
|
|
120662
120805
|
"Bash(npm publish *)"
|
|
120663
120806
|
];
|
|
120664
120807
|
async function writeWorktreeSettings(worktreePath, opts = {}) {
|
|
120665
|
-
const dir =
|
|
120666
|
-
await
|
|
120808
|
+
const dir = path9.join(worktreePath, ".claude");
|
|
120809
|
+
await fs6.mkdir(dir, { recursive: true });
|
|
120667
120810
|
const allow = Array.from(/* @__PURE__ */ new Set([...CORE_ALLOW_RULES, ...opts.allowExtra ?? []]));
|
|
120668
120811
|
const settings = {
|
|
120669
120812
|
permissions: {
|
|
@@ -120671,17 +120814,17 @@ async function writeWorktreeSettings(worktreePath, opts = {}) {
|
|
|
120671
120814
|
deny: CORE_DENY_RULES
|
|
120672
120815
|
}
|
|
120673
120816
|
};
|
|
120674
|
-
await
|
|
120675
|
-
|
|
120817
|
+
await fs6.writeFile(
|
|
120818
|
+
path9.join(dir, "settings.local.json"),
|
|
120676
120819
|
JSON.stringify(settings, null, 2),
|
|
120677
120820
|
"utf8"
|
|
120678
120821
|
);
|
|
120679
120822
|
}
|
|
120680
120823
|
async function ensureNodeModules(worktreePath) {
|
|
120681
|
-
const monorepDir =
|
|
120682
|
-
const pkgJson =
|
|
120824
|
+
const monorepDir = path9.join(worktreePath, "monorep");
|
|
120825
|
+
const pkgJson = path9.join(monorepDir, "package.json");
|
|
120683
120826
|
if (!await pathExists(pkgJson)) return;
|
|
120684
|
-
const modulesMarker =
|
|
120827
|
+
const modulesMarker = path9.join(monorepDir, "node_modules", ".modules.yaml");
|
|
120685
120828
|
if (await pathExists(modulesMarker)) return;
|
|
120686
120829
|
const start = Date.now();
|
|
120687
120830
|
try {
|
|
@@ -120937,7 +121080,7 @@ var BridgeHandler = class {
|
|
|
120937
121080
|
throw new Error("agent_workspace runtime requires workspace path conventions");
|
|
120938
121081
|
}
|
|
120939
121082
|
}
|
|
120940
|
-
const worktreePath = isAgentWorkspace ?
|
|
121083
|
+
const worktreePath = isAgentWorkspace ? path9.join(conventions.workspaceSessionsDir, threadId) : path9.join(conventions.worktreesDir, threadId);
|
|
120941
121084
|
const runCwd = isAgentWorkspace ? conventions.agentWorkspacePath : worktreePath;
|
|
120942
121085
|
const hasRepo = !isAgentWorkspace && !!conventions.repoCachePath;
|
|
120943
121086
|
const buildWorktree = hasRepo && !conventions.readOnly;
|
|
@@ -121008,7 +121151,7 @@ var BridgeHandler = class {
|
|
|
121008
121151
|
`[bridge.handler] worktree ${worktreePath} exists but git health check failed \u2014 removing stale dir and rebuilding (BL-8: migrated worktree with dead .git pointer)`
|
|
121009
121152
|
);
|
|
121010
121153
|
try {
|
|
121011
|
-
await
|
|
121154
|
+
await fs6.rm(worktreePath, { recursive: true, force: true });
|
|
121012
121155
|
} catch (rmErr) {
|
|
121013
121156
|
console.warn("[bridge.handler] failed to remove stale worktree (will attempt rebuild anyway):", rmErr);
|
|
121014
121157
|
}
|
|
@@ -121032,7 +121175,7 @@ var BridgeHandler = class {
|
|
|
121032
121175
|
`[bridge.handler] created worktree ${worktreePath} on branch ${branchName}`
|
|
121033
121176
|
);
|
|
121034
121177
|
} else {
|
|
121035
|
-
await
|
|
121178
|
+
await fs6.mkdir(worktreePath, { recursive: true });
|
|
121036
121179
|
if (conventions.readOnly && conventions.repoCachePath) {
|
|
121037
121180
|
console.log(
|
|
121038
121181
|
`[bridge.handler] created scratch dir ${worktreePath} (read_only bot: repo read-only at ${conventions.repoCachePath}, no worktree)`
|
|
@@ -121259,6 +121402,13 @@ var BridgeHandler = class {
|
|
|
121259
121402
|
botGitIdentity: this.deps.botConfig?.git_identity,
|
|
121260
121403
|
gitlabToken: this.deps.gitlabToken
|
|
121261
121404
|
});
|
|
121405
|
+
if (isAgentWorkspace && handle.pid != null) {
|
|
121406
|
+
const sessionPidFile = path9.join(worktreePath, ".larkway", "runner.pid");
|
|
121407
|
+
void fs6.mkdir(path9.dirname(sessionPidFile), { recursive: true }).then(
|
|
121408
|
+
() => fs6.writeFile(sessionPidFile, JSON.stringify({ pid: handle.pid }), "utf8")
|
|
121409
|
+
).catch(() => {
|
|
121410
|
+
});
|
|
121411
|
+
}
|
|
121262
121412
|
let sessionId;
|
|
121263
121413
|
let trustedAnswerText = "";
|
|
121264
121414
|
try {
|
|
@@ -121411,109 +121561,49 @@ var BridgeHandler = class {
|
|
|
121411
121561
|
}
|
|
121412
121562
|
}
|
|
121413
121563
|
} else {
|
|
121414
|
-
|
|
121415
|
-
|
|
121416
|
-
|
|
121417
|
-
facts: {
|
|
121418
|
-
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
121419
|
-
chatId: parsed.chatId,
|
|
121420
|
-
threadId,
|
|
121421
|
-
triggerMessageId: messageId,
|
|
121422
|
-
replyToMessageId: messageId,
|
|
121423
|
-
replyInThread
|
|
121424
|
-
},
|
|
121425
|
-
worktreePath,
|
|
121426
|
-
baseCard: baseCardPayload,
|
|
121427
|
-
cardStarted: !!card,
|
|
121428
|
-
postOutboundAvailable: false,
|
|
121429
|
-
postLedgerAvailable: true,
|
|
121430
|
-
visibleFallbackAvailable: true,
|
|
121431
|
-
postClient: this.deps.postClient
|
|
121432
|
-
});
|
|
121433
|
-
if (surfaceDispatch.card) {
|
|
121434
|
-
if (!card) {
|
|
121564
|
+
if (!card) {
|
|
121565
|
+
try {
|
|
121566
|
+
card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
|
|
121435
121567
|
try {
|
|
121436
|
-
|
|
121437
|
-
|
|
121438
|
-
|
|
121439
|
-
messageId: card.messageId,
|
|
121440
|
-
chatId: parsed.chatId,
|
|
121441
|
-
threadId,
|
|
121442
|
-
botId: this.deps.botConfig?.id ?? "",
|
|
121443
|
-
replyInThread,
|
|
121444
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
121445
|
-
});
|
|
121446
|
-
} catch (err) {
|
|
121447
|
-
console.warn("[bridge.handler] writeCardFile(late) failed (continuing):", err);
|
|
121448
|
-
}
|
|
121449
|
-
} catch (err) {
|
|
121450
|
-
console.error(
|
|
121451
|
-
"[bridge.handler] late visible card fallback start failed; creating post fallback:",
|
|
121452
|
-
err
|
|
121453
|
-
);
|
|
121454
|
-
const failureReason2 = [
|
|
121455
|
-
legacyCardStartFailed ? `initial legacy visible card start failed: ${legacyCardStartFailureReason ?? "unknown"}` : void 0,
|
|
121456
|
-
`late legacy visible card fallback start failed: ${String(err)}`
|
|
121457
|
-
].filter((part) => !!part).join("; ");
|
|
121458
|
-
const postFallback = await createOnlyPostFallback({
|
|
121459
|
-
postClient: this.deps.postClient,
|
|
121460
|
-
replyToMessageId: messageId,
|
|
121461
|
-
replyInThread,
|
|
121462
|
-
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
121568
|
+
await writeCardFile(worktreePath, {
|
|
121569
|
+
messageId: card.messageId,
|
|
121570
|
+
chatId: parsed.chatId,
|
|
121463
121571
|
threadId,
|
|
121464
|
-
|
|
121465
|
-
|
|
121466
|
-
|
|
121467
|
-
title: surfaceDispatch.card.titleOverride ?? "Larkway fallback",
|
|
121468
|
-
logPrefix: "[bridge.handler]"
|
|
121572
|
+
botId: this.deps.botConfig?.id ?? "",
|
|
121573
|
+
replyInThread,
|
|
121574
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
121469
121575
|
});
|
|
121470
|
-
|
|
121471
|
-
|
|
121472
|
-
}
|
|
121473
|
-
if (card) {
|
|
121474
|
-
await card.finalize(surfaceDispatch.card);
|
|
121475
|
-
let keepCardFileForRetry = false;
|
|
121476
|
-
if (surfaceDispatch.post?.requiresFallbackLedgerMark) {
|
|
121477
|
-
try {
|
|
121478
|
-
await markPostLedgerFallbackVisible(
|
|
121479
|
-
worktreePath,
|
|
121480
|
-
surfaceDispatch.post.idempotencyKey,
|
|
121481
|
-
{
|
|
121482
|
-
fallbackCardMessageId: card.messageId,
|
|
121483
|
-
error: surfaceDispatch.post.fallbackError ?? surfaceDispatch.card.failureReason ?? "post outbound failed; visible card fallback used"
|
|
121484
|
-
}
|
|
121485
|
-
);
|
|
121486
|
-
} catch (err) {
|
|
121487
|
-
keepCardFileForRetry = true;
|
|
121488
|
-
console.warn(
|
|
121489
|
-
"[bridge.handler] fallback ledger mark failed after visible card finalize; keeping card.json for retry:",
|
|
121490
|
-
err
|
|
121491
|
-
);
|
|
121492
|
-
}
|
|
121493
|
-
}
|
|
121494
|
-
if (surfaceDispatch.post?.requiresPolicyLedgerMark) {
|
|
121495
|
-
try {
|
|
121496
|
-
await markPostLedgerPolicyBlockedVisible(
|
|
121497
|
-
worktreePath,
|
|
121498
|
-
surfaceDispatch.post.idempotencyKey,
|
|
121499
|
-
{
|
|
121500
|
-
fallbackCardMessageId: card.messageId,
|
|
121501
|
-
error: surfaceDispatch.post.policyError ?? surfaceDispatch.card.failureReason ?? "mention policy blocked; visible card fallback used"
|
|
121502
|
-
}
|
|
121503
|
-
);
|
|
121504
|
-
} catch (err) {
|
|
121505
|
-
keepCardFileForRetry = true;
|
|
121506
|
-
console.warn(
|
|
121507
|
-
"[bridge.handler] policy-blocked ledger mark failed after visible card finalize; keeping card.json for retry:",
|
|
121508
|
-
err
|
|
121509
|
-
);
|
|
121510
|
-
}
|
|
121511
|
-
}
|
|
121512
|
-
if (!keepCardFileForRetry) {
|
|
121513
|
-
await deleteCardFile(worktreePath);
|
|
121576
|
+
} catch (err) {
|
|
121577
|
+
console.warn("[bridge.handler] writeCardFile(late) failed (continuing):", err);
|
|
121514
121578
|
}
|
|
121579
|
+
} catch (err) {
|
|
121580
|
+
console.error(
|
|
121581
|
+
"[bridge.handler] late visible card fallback start failed; creating post fallback:",
|
|
121582
|
+
err
|
|
121583
|
+
);
|
|
121584
|
+
const failureReason2 = [
|
|
121585
|
+
legacyCardStartFailed ? `initial legacy visible card start failed: ${legacyCardStartFailureReason ?? "unknown"}` : void 0,
|
|
121586
|
+
`late legacy visible card fallback start failed: ${String(err)}`
|
|
121587
|
+
].filter((part) => !!part).join("; ");
|
|
121588
|
+
const postFallback = await createOnlyPostFallback({
|
|
121589
|
+
postClient: this.deps.postClient,
|
|
121590
|
+
replyToMessageId: messageId,
|
|
121591
|
+
replyInThread,
|
|
121592
|
+
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
121593
|
+
threadId,
|
|
121594
|
+
triggerMessageId: messageId,
|
|
121595
|
+
finalText: baseCardPayload.finalText,
|
|
121596
|
+
failureReason: failureReason2,
|
|
121597
|
+
title: baseCardPayload.titleOverride ?? "Larkway fallback",
|
|
121598
|
+
logPrefix: "[bridge.handler]"
|
|
121599
|
+
});
|
|
121600
|
+
if (!postFallback) throw err;
|
|
121515
121601
|
}
|
|
121516
121602
|
}
|
|
121603
|
+
if (card) {
|
|
121604
|
+
await card.finalize(baseCardPayload);
|
|
121605
|
+
await deleteCardFile(worktreePath);
|
|
121606
|
+
}
|
|
121517
121607
|
}
|
|
121518
121608
|
settle(true);
|
|
121519
121609
|
await recordEvent({
|
|
@@ -121548,7 +121638,7 @@ var BridgeHandler = class {
|
|
|
121548
121638
|
reason: String(err)
|
|
121549
121639
|
});
|
|
121550
121640
|
settle(false);
|
|
121551
|
-
const wtPath = this.deps.conventions.runtime === "agent_workspace" && this.deps.conventions.workspaceSessionsDir ?
|
|
121641
|
+
const wtPath = this.deps.conventions.runtime === "agent_workspace" && this.deps.conventions.workspaceSessionsDir ? path9.join(this.deps.conventions.workspaceSessionsDir, threadId) : path9.join(this.deps.conventions.worktreesDir, threadId);
|
|
121552
121642
|
const hardFailureText = `\u6267\u884C\u5931\u8D25: ${String(err)}`;
|
|
121553
121643
|
const createHardFailurePostFallback = async (failureReason) => {
|
|
121554
121644
|
const fallback = await createOnlyPostFallback({
|
|
@@ -121619,18 +121709,18 @@ var BridgeHandler = class {
|
|
|
121619
121709
|
};
|
|
121620
121710
|
|
|
121621
121711
|
// src/bridge/eventLog.ts
|
|
121622
|
-
import
|
|
121623
|
-
import
|
|
121712
|
+
import fs7 from "node:fs/promises";
|
|
121713
|
+
import path10 from "node:path";
|
|
121624
121714
|
var DEFAULT_RUNTIME_EVENT_LIMIT = 20;
|
|
121625
121715
|
var writeQueues = /* @__PURE__ */ new Map();
|
|
121626
121716
|
function resolveRuntimeEventsPath(larkwayHome2, botId) {
|
|
121627
|
-
const dir = botId ?
|
|
121628
|
-
return
|
|
121717
|
+
const dir = botId ? path10.join(larkwayHome2, botId) : larkwayHome2;
|
|
121718
|
+
return path10.join(dir, "recent-events.json");
|
|
121629
121719
|
}
|
|
121630
121720
|
async function readRuntimeEvents(larkwayHome2, botId, limit = DEFAULT_RUNTIME_EVENT_LIMIT) {
|
|
121631
121721
|
const file = resolveRuntimeEventsPath(larkwayHome2, botId);
|
|
121632
121722
|
try {
|
|
121633
|
-
const raw = await
|
|
121723
|
+
const raw = await fs7.readFile(file, "utf-8");
|
|
121634
121724
|
const parsed = JSON.parse(raw);
|
|
121635
121725
|
if (!Array.isArray(parsed)) return [];
|
|
121636
121726
|
return parsed.filter(isRuntimeEventRecord).sort((a, b) => tsOf(b.receivedAt) - tsOf(a.receivedAt)).slice(0, limit);
|
|
@@ -121688,11 +121778,11 @@ async function enqueue(key, fn) {
|
|
|
121688
121778
|
}
|
|
121689
121779
|
}
|
|
121690
121780
|
async function writeEvents(file, events) {
|
|
121691
|
-
await
|
|
121781
|
+
await fs7.mkdir(path10.dirname(file), { recursive: true });
|
|
121692
121782
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
121693
|
-
await
|
|
121783
|
+
await fs7.writeFile(tmp, `${JSON.stringify(events, null, 2)}
|
|
121694
121784
|
`, "utf-8");
|
|
121695
|
-
await
|
|
121785
|
+
await fs7.rename(tmp, file);
|
|
121696
121786
|
}
|
|
121697
121787
|
function mergeStatusPath(prev, next, append) {
|
|
121698
121788
|
const out = [...next ?? prev ?? []];
|
|
@@ -121714,7 +121804,7 @@ function isRuntimeEventRecord(value) {
|
|
|
121714
121804
|
|
|
121715
121805
|
// src/housekeeping/gc.ts
|
|
121716
121806
|
import { spawn } from "node:child_process";
|
|
121717
|
-
import { readFile as readFile4, readdir, stat } from "node:fs/promises";
|
|
121807
|
+
import { readFile as readFile4, readdir, rm, stat } from "node:fs/promises";
|
|
121718
121808
|
import { join as pathJoin } from "node:path";
|
|
121719
121809
|
var DEFAULT_SCAN_INTERVAL_MS = 30 * 60 * 1e3;
|
|
121720
121810
|
var DEFAULT_IDLE_NOTIFY_MS = 4 * 60 * 60 * 1e3;
|
|
@@ -121726,12 +121816,20 @@ var Housekeeping = class {
|
|
|
121726
121816
|
#idleCleanupMs;
|
|
121727
121817
|
/** This housekeeping's bot scope — resolves which worktrees dir to sweep. */
|
|
121728
121818
|
#botId;
|
|
121819
|
+
/**
|
|
121820
|
+
* Bot runtime. "agent_workspace" reclaims per-thread session dirs under
|
|
121821
|
+
* agents/<id>/workspace/sessions/ via rm -rf (they are plain dirs / full
|
|
121822
|
+
* clones, not git worktrees). Anything else (legacy) reclaims git worktrees
|
|
121823
|
+
* under <botId>/worktrees/ via `git worktree remove`.
|
|
121824
|
+
*/
|
|
121825
|
+
#runtime;
|
|
121729
121826
|
/** thread_ids that have already received an idle-notify warn this session */
|
|
121730
121827
|
#notified = /* @__PURE__ */ new Set();
|
|
121731
121828
|
#timer;
|
|
121732
121829
|
constructor(deps, opts) {
|
|
121733
121830
|
this.#sessionStore = deps.sessionStore;
|
|
121734
121831
|
this.#botId = deps.botId;
|
|
121832
|
+
this.#runtime = deps.runtime;
|
|
121735
121833
|
this.#scanIntervalMs = opts?.scanIntervalMs ?? DEFAULT_SCAN_INTERVAL_MS;
|
|
121736
121834
|
this.#idleNotifyMs = opts?.idleNotifyMs ?? DEFAULT_IDLE_NOTIFY_MS;
|
|
121737
121835
|
this.#idleCleanupMs = opts?.idleCleanupMs ?? DEFAULT_IDLE_CLEANUP_MS;
|
|
@@ -121776,9 +121874,9 @@ var Housekeeping = class {
|
|
|
121776
121874
|
if (idleMs >= this.#idleCleanupMs) {
|
|
121777
121875
|
const idleHours = Math.floor(idleMs / (60 * 60 * 1e3));
|
|
121778
121876
|
console.warn(
|
|
121779
|
-
`[housekeeping] \u8BDD\u9898 ${tid} idle ${idleHours}h
|
|
121877
|
+
`[housekeeping] \u8BDD\u9898 ${tid} idle ${idleHours}h+,\u5DE5\u4F5C\u76EE\u5F55\u53EF\u6E05\u7406`
|
|
121780
121878
|
);
|
|
121781
|
-
void
|
|
121879
|
+
void this.#cleanupThread(tid, record.botId, dryRun);
|
|
121782
121880
|
this.#notified.delete(tid);
|
|
121783
121881
|
continue;
|
|
121784
121882
|
}
|
|
@@ -121799,32 +121897,46 @@ var Housekeeping = class {
|
|
|
121799
121897
|
* cleanupWorktree (kill PIDs → git worktree remove --force).
|
|
121800
121898
|
*/
|
|
121801
121899
|
async #sweepOrphans(liveThreadIds, now, dryRun) {
|
|
121802
|
-
const
|
|
121900
|
+
const reclaimDir = this.#runtime === "agent_workspace" ? resolveAgentWorkspaceSessionsDir(this.#botId ?? "") : resolveWorktreesDir(this.#botId);
|
|
121803
121901
|
let dirNames;
|
|
121804
121902
|
try {
|
|
121805
|
-
const entries = await readdir(
|
|
121903
|
+
const entries = await readdir(reclaimDir, { withFileTypes: true });
|
|
121806
121904
|
dirNames = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
121807
121905
|
} catch (err) {
|
|
121808
121906
|
if (err.code !== "ENOENT") {
|
|
121809
|
-
console.error(`[gc] orphan sweep: cannot read ${
|
|
121907
|
+
console.error(`[gc] orphan sweep: cannot read ${reclaimDir}:`, err);
|
|
121810
121908
|
}
|
|
121811
121909
|
return;
|
|
121812
121910
|
}
|
|
121813
121911
|
for (const name of selectOrphanWorktreeNames(dirNames, liveThreadIds)) {
|
|
121814
|
-
|
|
121912
|
+
if (name.startsWith("_")) continue;
|
|
121913
|
+
const dirPath = pathJoin(reclaimDir, name);
|
|
121815
121914
|
let ageMs;
|
|
121816
121915
|
try {
|
|
121817
|
-
ageMs = now - (await stat(
|
|
121916
|
+
ageMs = now - (await stat(dirPath)).mtimeMs;
|
|
121818
121917
|
} catch {
|
|
121819
121918
|
continue;
|
|
121820
121919
|
}
|
|
121821
121920
|
if (ageMs < this.#idleCleanupMs) continue;
|
|
121822
121921
|
const ageHours = Math.floor(ageMs / (60 * 60 * 1e3));
|
|
121823
121922
|
console.warn(
|
|
121824
|
-
`[housekeeping] orphan
|
|
121923
|
+
`[housekeeping] orphan ${name}(\u65E0 session \u8BB0\u5F55, idle ${ageHours}h+)\u2014 \u6E05\u7406`
|
|
121825
121924
|
);
|
|
121826
|
-
await
|
|
121925
|
+
await this.#cleanupThread(name, this.#botId, dryRun);
|
|
121926
|
+
}
|
|
121927
|
+
}
|
|
121928
|
+
/**
|
|
121929
|
+
* Runtime-aware reclaim of one thread's working dir.
|
|
121930
|
+
* - agent_workspace: rm -rf agents/<id>/workspace/sessions/<tid> (plain dir /
|
|
121931
|
+
* full clones — `git worktree remove` cannot reclaim these).
|
|
121932
|
+
* - legacy: git worktree remove --force <botId>/worktrees/<tid>.
|
|
121933
|
+
* Both kill any lingering runner PIDs first (idle >24h → normally dead).
|
|
121934
|
+
*/
|
|
121935
|
+
#cleanupThread(threadId, botId, dryRun) {
|
|
121936
|
+
if (this.#runtime === "agent_workspace") {
|
|
121937
|
+
return cleanupAgentSession(threadId, botId ?? this.#botId, dryRun);
|
|
121827
121938
|
}
|
|
121939
|
+
return cleanupWorktree(threadId, botId, dryRun);
|
|
121828
121940
|
}
|
|
121829
121941
|
};
|
|
121830
121942
|
function selectOrphanWorktreeNames(dirNames, liveThreadIds) {
|
|
@@ -121955,10 +122067,65 @@ async function cleanupWorktree(threadId, botId, dryRun) {
|
|
|
121955
122067
|
console.error(`[gc] worktree remove failed for path=${worktreePath}:`, err);
|
|
121956
122068
|
}
|
|
121957
122069
|
}
|
|
122070
|
+
function isReclaimableSessionPath(p) {
|
|
122071
|
+
const m = /[/\\]workspace[/\\]sessions[/\\]([^/\\]+)[/\\]?$/.exec(p);
|
|
122072
|
+
if (m === null) return false;
|
|
122073
|
+
const seg = m[1];
|
|
122074
|
+
return seg !== "." && seg !== "..";
|
|
122075
|
+
}
|
|
122076
|
+
async function removeSessionDir(sessionPath, dryRun) {
|
|
122077
|
+
if (!isReclaimableSessionPath(sessionPath)) {
|
|
122078
|
+
console.error(`[gc] refusing to rm -rf non-session path: ${sessionPath}`);
|
|
122079
|
+
return;
|
|
122080
|
+
}
|
|
122081
|
+
if (dryRun) {
|
|
122082
|
+
console.log(`[gc] dry-run: would rm -rf ${sessionPath}`);
|
|
122083
|
+
return;
|
|
122084
|
+
}
|
|
122085
|
+
console.log(`[gc] rm -rf ${sessionPath}`);
|
|
122086
|
+
try {
|
|
122087
|
+
await rm(sessionPath, { recursive: true, force: true });
|
|
122088
|
+
} catch (err) {
|
|
122089
|
+
console.error(`[gc] rm -rf failed for ${sessionPath}:`, err);
|
|
122090
|
+
}
|
|
122091
|
+
}
|
|
122092
|
+
async function cleanupAgentSession(threadId, agentId, dryRun) {
|
|
122093
|
+
if (!agentId) {
|
|
122094
|
+
console.error(
|
|
122095
|
+
`[gc] cleanupAgentSession: missing agentId for thread=${threadId}`
|
|
122096
|
+
);
|
|
122097
|
+
return;
|
|
122098
|
+
}
|
|
122099
|
+
let sessionPath;
|
|
122100
|
+
try {
|
|
122101
|
+
sessionPath = resolveAgentSessionPath(agentId, threadId);
|
|
122102
|
+
} catch (err) {
|
|
122103
|
+
console.error(`[gc] invalid session threadId=${threadId}:`, err);
|
|
122104
|
+
return;
|
|
122105
|
+
}
|
|
122106
|
+
console.log(
|
|
122107
|
+
`[gc] cleanup session thread=${threadId} path=${sessionPath} dryRun=${dryRun}`
|
|
122108
|
+
);
|
|
122109
|
+
let pids;
|
|
122110
|
+
try {
|
|
122111
|
+
pids = await findPidsByWorktree(sessionPath);
|
|
122112
|
+
} catch (err) {
|
|
122113
|
+
console.error(`[gc] pid lookup failed for path=${sessionPath}:`, err);
|
|
122114
|
+
pids = [];
|
|
122115
|
+
}
|
|
122116
|
+
const alivePids = pids.filter(isPidAlive);
|
|
122117
|
+
if (alivePids.length > 0) {
|
|
122118
|
+
console.warn(
|
|
122119
|
+
`[gc] skip live session thread=${threadId} path=${sessionPath}: runner pid(s) [${alivePids.join(", ")}] still alive \u2014 not reclaiming in-flight work`
|
|
122120
|
+
);
|
|
122121
|
+
return;
|
|
122122
|
+
}
|
|
122123
|
+
await removeSessionDir(sessionPath, dryRun);
|
|
122124
|
+
}
|
|
121958
122125
|
|
|
121959
122126
|
// src/config/botLoader.ts
|
|
121960
122127
|
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
121961
|
-
import
|
|
122128
|
+
import path11 from "node:path";
|
|
121962
122129
|
|
|
121963
122130
|
// node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/dist/js-yaml.mjs
|
|
121964
122131
|
function isNothing(subject) {
|
|
@@ -124794,7 +124961,7 @@ async function loadBots(botsDir) {
|
|
|
124794
124961
|
}
|
|
124795
124962
|
const bots = [];
|
|
124796
124963
|
for (const filename of yamlFiles.sort()) {
|
|
124797
|
-
const filePath =
|
|
124964
|
+
const filePath = path11.join(botsDir, filename);
|
|
124798
124965
|
let raw;
|
|
124799
124966
|
try {
|
|
124800
124967
|
raw = await readFile5(filePath, "utf-8");
|
|
@@ -124815,7 +124982,7 @@ ${issues}`);
|
|
|
124815
124982
|
}
|
|
124816
124983
|
const bot = result.data;
|
|
124817
124984
|
if (bot.memory_file) {
|
|
124818
|
-
const memoryPath =
|
|
124985
|
+
const memoryPath = path11.join(botsDir, bot.memory_file);
|
|
124819
124986
|
try {
|
|
124820
124987
|
bot.agent_memory = await readFile5(memoryPath, "utf-8");
|
|
124821
124988
|
} catch (err) {
|
|
@@ -124848,6 +125015,239 @@ ${issues}`);
|
|
|
124848
125015
|
// src/bridge/reconcile.ts
|
|
124849
125016
|
import { readdir as readdir3, stat as stat2 } from "node:fs/promises";
|
|
124850
125017
|
import { join as pathJoin2 } from "node:path";
|
|
125018
|
+
|
|
125019
|
+
// src/bridge/postFile.ts
|
|
125020
|
+
import fs8 from "node:fs/promises";
|
|
125021
|
+
import path12 from "node:path";
|
|
125022
|
+
var PostLedgerStatusSchema = external_exports.enum([
|
|
125023
|
+
"planned",
|
|
125024
|
+
"pending",
|
|
125025
|
+
"sent",
|
|
125026
|
+
"failed",
|
|
125027
|
+
"fallback_visible",
|
|
125028
|
+
"policy_blocked"
|
|
125029
|
+
]);
|
|
125030
|
+
var POST_LEDGER_TRANSITIONS = {
|
|
125031
|
+
planned: ["pending", "fallback_visible", "policy_blocked"],
|
|
125032
|
+
pending: ["sent", "failed", "fallback_visible", "policy_blocked"],
|
|
125033
|
+
sent: [],
|
|
125034
|
+
failed: ["fallback_visible"],
|
|
125035
|
+
fallback_visible: [],
|
|
125036
|
+
policy_blocked: []
|
|
125037
|
+
};
|
|
125038
|
+
function canTransitionPostStatus(from, to) {
|
|
125039
|
+
return from === to || POST_LEDGER_TRANSITIONS[from].includes(to);
|
|
125040
|
+
}
|
|
125041
|
+
function assertPostStatusTransition(from, to) {
|
|
125042
|
+
if (!canTransitionPostStatus(from, to)) {
|
|
125043
|
+
throw new Error(`invalid post ledger transition: ${from} -> ${to}`);
|
|
125044
|
+
}
|
|
125045
|
+
}
|
|
125046
|
+
var PostAttemptSchema = external_exports.object({
|
|
125047
|
+
attemptedAt: external_exports.string(),
|
|
125048
|
+
status: external_exports.enum(["sent", "failed"]),
|
|
125049
|
+
retryable: external_exports.boolean().default(false),
|
|
125050
|
+
error: external_exports.string().optional(),
|
|
125051
|
+
code: external_exports.string().optional()
|
|
125052
|
+
});
|
|
125053
|
+
var PostLedgerEntrySchema = external_exports.object({
|
|
125054
|
+
idempotencyKey: external_exports.string().min(1).max(64),
|
|
125055
|
+
status: PostLedgerStatusSchema,
|
|
125056
|
+
botId: external_exports.string().min(1),
|
|
125057
|
+
chatId: external_exports.string().min(1),
|
|
125058
|
+
threadId: external_exports.string().min(1),
|
|
125059
|
+
replyToMessageId: external_exports.string().min(1),
|
|
125060
|
+
role: external_exports.enum(["primary", "secondary", "fallback"]),
|
|
125061
|
+
logicalIndex: external_exports.number().int().nonnegative(),
|
|
125062
|
+
contentDigest: external_exports.string().min(1),
|
|
125063
|
+
mentionCount: external_exports.number().int().nonnegative().default(0),
|
|
125064
|
+
postMessageId: external_exports.string().optional(),
|
|
125065
|
+
fallbackCardMessageId: external_exports.string().optional(),
|
|
125066
|
+
error: external_exports.string().optional(),
|
|
125067
|
+
attempts: external_exports.array(PostAttemptSchema).default([]),
|
|
125068
|
+
createdAt: external_exports.string(),
|
|
125069
|
+
updatedAt: external_exports.string()
|
|
125070
|
+
});
|
|
125071
|
+
var PostFileSchema = external_exports.object({
|
|
125072
|
+
version: external_exports.literal(1),
|
|
125073
|
+
posts: external_exports.array(PostLedgerEntrySchema).max(50)
|
|
125074
|
+
});
|
|
125075
|
+
function emptyPostFile() {
|
|
125076
|
+
return { version: 1, posts: [] };
|
|
125077
|
+
}
|
|
125078
|
+
function postDirOf(worktreePath) {
|
|
125079
|
+
return path12.join(worktreePath, ".larkway");
|
|
125080
|
+
}
|
|
125081
|
+
function postFilePathOf(worktreePath) {
|
|
125082
|
+
return path12.join(postDirOf(worktreePath), "post.json");
|
|
125083
|
+
}
|
|
125084
|
+
async function readPostFile(worktreePath) {
|
|
125085
|
+
const file = postFilePathOf(worktreePath);
|
|
125086
|
+
let raw;
|
|
125087
|
+
try {
|
|
125088
|
+
raw = await fs8.readFile(file, "utf8");
|
|
125089
|
+
} catch (err) {
|
|
125090
|
+
if (err.code === "ENOENT") return null;
|
|
125091
|
+
console.warn(`[postFile] read ${file} failed:`, err);
|
|
125092
|
+
return null;
|
|
125093
|
+
}
|
|
125094
|
+
let parsed;
|
|
125095
|
+
try {
|
|
125096
|
+
parsed = JSON.parse(raw);
|
|
125097
|
+
} catch (err) {
|
|
125098
|
+
console.warn(`[postFile] ${file} not valid JSON:`, err);
|
|
125099
|
+
return null;
|
|
125100
|
+
}
|
|
125101
|
+
const result = PostFileSchema.safeParse(parsed);
|
|
125102
|
+
if (!result.success) {
|
|
125103
|
+
console.warn(`[postFile] ${file} failed schema validation:`, result.error.issues);
|
|
125104
|
+
return null;
|
|
125105
|
+
}
|
|
125106
|
+
return result.data;
|
|
125107
|
+
}
|
|
125108
|
+
async function writePostFile(worktreePath, data) {
|
|
125109
|
+
const dir = postDirOf(worktreePath);
|
|
125110
|
+
const file = postFilePathOf(worktreePath);
|
|
125111
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
125112
|
+
const parsed = PostFileSchema.parse(data);
|
|
125113
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
125114
|
+
await fs8.writeFile(tmp, JSON.stringify(parsed, null, 2), "utf8");
|
|
125115
|
+
try {
|
|
125116
|
+
await fs8.rename(tmp, file);
|
|
125117
|
+
} catch (err) {
|
|
125118
|
+
await fs8.rm(tmp, { force: true }).catch(() => {
|
|
125119
|
+
});
|
|
125120
|
+
throw err;
|
|
125121
|
+
}
|
|
125122
|
+
}
|
|
125123
|
+
var DEFAULT_POST_RECONCILE_MIN_AGE_MS = 6e4;
|
|
125124
|
+
function timestampAgeMs(iso, nowMs) {
|
|
125125
|
+
if (!Number.isFinite(nowMs)) return null;
|
|
125126
|
+
const then = Date.parse(iso);
|
|
125127
|
+
if (!Number.isFinite(then)) return null;
|
|
125128
|
+
return nowMs - then;
|
|
125129
|
+
}
|
|
125130
|
+
function reconcilePostEntry(entry, opts) {
|
|
125131
|
+
if (entry.botId !== opts.botId) {
|
|
125132
|
+
return { entry, changed: false, sent: false, needsVisibleFallback: false };
|
|
125133
|
+
}
|
|
125134
|
+
if (entry.status === "sent" || entry.status === "fallback_visible" || entry.status === "policy_blocked") {
|
|
125135
|
+
return { entry, changed: false, sent: false, needsVisibleFallback: false };
|
|
125136
|
+
}
|
|
125137
|
+
const now = opts.now();
|
|
125138
|
+
const ageMs = timestampAgeMs(entry.updatedAt, Date.parse(now));
|
|
125139
|
+
if (ageMs == null || ageMs < opts.minAgeMs) {
|
|
125140
|
+
return { entry, changed: false, sent: false, needsVisibleFallback: false };
|
|
125141
|
+
}
|
|
125142
|
+
if (entry.postMessageId) {
|
|
125143
|
+
return {
|
|
125144
|
+
entry: {
|
|
125145
|
+
...entry,
|
|
125146
|
+
status: "sent",
|
|
125147
|
+
error: void 0,
|
|
125148
|
+
updatedAt: now,
|
|
125149
|
+
attempts: [
|
|
125150
|
+
...entry.attempts,
|
|
125151
|
+
{
|
|
125152
|
+
attemptedAt: now,
|
|
125153
|
+
status: "sent",
|
|
125154
|
+
retryable: false
|
|
125155
|
+
}
|
|
125156
|
+
]
|
|
125157
|
+
},
|
|
125158
|
+
changed: true,
|
|
125159
|
+
sent: true,
|
|
125160
|
+
needsVisibleFallback: false
|
|
125161
|
+
};
|
|
125162
|
+
}
|
|
125163
|
+
return {
|
|
125164
|
+
entry,
|
|
125165
|
+
changed: false,
|
|
125166
|
+
sent: false,
|
|
125167
|
+
needsVisibleFallback: true
|
|
125168
|
+
};
|
|
125169
|
+
}
|
|
125170
|
+
function reconcilePostLedgerEntries(data, opts) {
|
|
125171
|
+
const normalizedOpts = {
|
|
125172
|
+
botId: opts.botId,
|
|
125173
|
+
minAgeMs: opts.minAgeMs ?? DEFAULT_POST_RECONCILE_MIN_AGE_MS,
|
|
125174
|
+
now: opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString())
|
|
125175
|
+
};
|
|
125176
|
+
let sent = 0;
|
|
125177
|
+
const fallbackVisible = 0;
|
|
125178
|
+
let needsVisibleFallback = 0;
|
|
125179
|
+
let skippedLive = 0;
|
|
125180
|
+
const visibleFallbackCandidates = [];
|
|
125181
|
+
const posts = data.posts.map((post) => {
|
|
125182
|
+
const reconciled = reconcilePostEntry(post, normalizedOpts);
|
|
125183
|
+
if (reconciled.changed) {
|
|
125184
|
+
if (reconciled.sent) sent += 1;
|
|
125185
|
+
} else if (reconciled.needsVisibleFallback) {
|
|
125186
|
+
needsVisibleFallback += 1;
|
|
125187
|
+
visibleFallbackCandidates.push(reconciled.entry);
|
|
125188
|
+
} else if (post.botId === normalizedOpts.botId && (post.status === "planned" || post.status === "pending" || post.status === "failed")) {
|
|
125189
|
+
skippedLive += 1;
|
|
125190
|
+
}
|
|
125191
|
+
return reconciled.entry;
|
|
125192
|
+
});
|
|
125193
|
+
const changed = sent > 0 || fallbackVisible > 0;
|
|
125194
|
+
return {
|
|
125195
|
+
file: changed ? { version: 1, posts } : data,
|
|
125196
|
+
result: { changed, sent, fallbackVisible, needsVisibleFallback, skippedLive },
|
|
125197
|
+
visibleFallbackCandidates
|
|
125198
|
+
};
|
|
125199
|
+
}
|
|
125200
|
+
async function reconcilePostFileOrphans(worktreePath, opts) {
|
|
125201
|
+
const existing = await readPostFile(worktreePath);
|
|
125202
|
+
if (!existing) {
|
|
125203
|
+
return {
|
|
125204
|
+
changed: false,
|
|
125205
|
+
sent: 0,
|
|
125206
|
+
fallbackVisible: 0,
|
|
125207
|
+
needsVisibleFallback: 0,
|
|
125208
|
+
skippedLive: 0,
|
|
125209
|
+
visibleFallbackCandidates: []
|
|
125210
|
+
};
|
|
125211
|
+
}
|
|
125212
|
+
const { file, result, visibleFallbackCandidates } = reconcilePostLedgerEntries(existing, opts);
|
|
125213
|
+
if (result.changed) {
|
|
125214
|
+
await writePostFile(worktreePath, file);
|
|
125215
|
+
}
|
|
125216
|
+
return { ...result, visibleFallbackCandidates };
|
|
125217
|
+
}
|
|
125218
|
+
async function markPostLedgerFallbackVisible(worktreePath, idempotencyKey2, opts) {
|
|
125219
|
+
const existing = await readPostFile(worktreePath) ?? emptyPostFile();
|
|
125220
|
+
const idx = existing.posts.findIndex((post) => post.idempotencyKey === idempotencyKey2);
|
|
125221
|
+
if (idx < 0) {
|
|
125222
|
+
throw new Error(`post ledger entry not found: ${idempotencyKey2}`);
|
|
125223
|
+
}
|
|
125224
|
+
const current = existing.posts[idx];
|
|
125225
|
+
assertPostStatusTransition(current.status, "fallback_visible");
|
|
125226
|
+
const now = opts.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
125227
|
+
const nextPosts = [...existing.posts];
|
|
125228
|
+
nextPosts[idx] = {
|
|
125229
|
+
...current,
|
|
125230
|
+
status: "fallback_visible",
|
|
125231
|
+
fallbackCardMessageId: opts.fallbackCardMessageId,
|
|
125232
|
+
error: opts.error,
|
|
125233
|
+
updatedAt: now,
|
|
125234
|
+
attempts: [
|
|
125235
|
+
...current.attempts,
|
|
125236
|
+
{
|
|
125237
|
+
attemptedAt: now,
|
|
125238
|
+
status: "failed",
|
|
125239
|
+
retryable: false,
|
|
125240
|
+
code: "orphan_reconcile",
|
|
125241
|
+
error: opts.error
|
|
125242
|
+
}
|
|
125243
|
+
]
|
|
125244
|
+
};
|
|
125245
|
+
const next = { version: 1, posts: nextPosts };
|
|
125246
|
+
await writePostFile(worktreePath, next);
|
|
125247
|
+
return next;
|
|
125248
|
+
}
|
|
125249
|
+
|
|
125250
|
+
// src/bridge/reconcile.ts
|
|
124851
125251
|
var DEFAULT_MIN_AGE_MS = 6e4;
|
|
124852
125252
|
var RETRY_CAP = 3;
|
|
124853
125253
|
function isStateFreshForCard(state, card) {
|
|
@@ -125394,7 +125794,7 @@ async function writeStatusFile(botId, w) {
|
|
|
125394
125794
|
|
|
125395
125795
|
// src/claude/runner.ts
|
|
125396
125796
|
import { spawn as spawn2 } from "node:child_process";
|
|
125397
|
-
import { writeFile as writeFile3, unlink, mkdir as mkdir3 } from "node:fs/promises";
|
|
125797
|
+
import { writeFile as writeFile3, unlink as unlink2, mkdir as mkdir3 } from "node:fs/promises";
|
|
125398
125798
|
import { join as join2 } from "node:path";
|
|
125399
125799
|
import { createInterface } from "node:readline";
|
|
125400
125800
|
var SIGKILL_GRACE_MS = 5e3;
|
|
@@ -125601,7 +126001,7 @@ function runClaude(opts) {
|
|
|
125601
126001
|
clearTimeout(totalTimeoutFallbackHandle);
|
|
125602
126002
|
rlAbortController.abort();
|
|
125603
126003
|
if (pidFilePath !== null) {
|
|
125604
|
-
void
|
|
126004
|
+
void unlink2(pidFilePath).catch(() => {
|
|
125605
126005
|
});
|
|
125606
126006
|
}
|
|
125607
126007
|
if (exitCode !== 0 && !killScheduled) {
|
|
@@ -125631,7 +126031,7 @@ stderr: ${stderr}` : "")
|
|
|
125631
126031
|
clearTimeout(totalTimeoutFallbackHandle);
|
|
125632
126032
|
rlAbortController.abort();
|
|
125633
126033
|
if (pidFilePath !== null) {
|
|
125634
|
-
void
|
|
126034
|
+
void unlink2(pidFilePath).catch(() => {
|
|
125635
126035
|
});
|
|
125636
126036
|
}
|
|
125637
126037
|
if (err.code === "ENOENT") {
|
|
@@ -125696,7 +126096,8 @@ stderr: ${stderr}` : "")
|
|
|
125696
126096
|
return {
|
|
125697
126097
|
events: generateEvents(),
|
|
125698
126098
|
done,
|
|
125699
|
-
kill: doKill
|
|
126099
|
+
kill: doKill,
|
|
126100
|
+
pid: child.pid ?? void 0
|
|
125700
126101
|
};
|
|
125701
126102
|
}
|
|
125702
126103
|
var ClaudeRunner = class {
|
|
@@ -126053,7 +126454,8 @@ stderr: ${stderr}` : "")
|
|
|
126053
126454
|
return {
|
|
126054
126455
|
events: generateEvents(),
|
|
126055
126456
|
done,
|
|
126056
|
-
kill: doKill
|
|
126457
|
+
kill: doKill,
|
|
126458
|
+
pid: child.pid ?? void 0
|
|
126057
126459
|
};
|
|
126058
126460
|
}
|
|
126059
126461
|
var CodexRunner = class {
|
|
@@ -126527,7 +126929,11 @@ async function runV2Mode({
|
|
|
126527
126929
|
await upsertRuntimeEvent(larkwayHome(), bot.id, patch);
|
|
126528
126930
|
}
|
|
126529
126931
|
});
|
|
126530
|
-
const housekeeping = new Housekeeping({
|
|
126932
|
+
const housekeeping = new Housekeeping({
|
|
126933
|
+
sessionStore,
|
|
126934
|
+
botId: bot.id,
|
|
126935
|
+
runtime: bot.runtime
|
|
126936
|
+
});
|
|
126531
126937
|
const inst = {
|
|
126532
126938
|
bot,
|
|
126533
126939
|
client,
|