@adhdev/daemon-core 0.9.82-rc.342 → 0.9.82-rc.344
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/router.d.ts +20 -4
- package/dist/index.js +654 -332
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +674 -352
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events-pending.d.ts +10 -0
- package/dist/mesh/mesh-events-utils.d.ts +10 -0
- package/dist/mesh/mesh-ledger.d.ts +1 -1
- package/dist/mesh/mesh-runtime-store.d.ts +1 -0
- package/dist/mesh/mesh-work-queue.d.ts +10 -0
- package/dist/providers/cli-provider-instance.d.ts +1 -0
- package/dist/providers/provider-instance-manager.d.ts +1 -0
- package/dist/providers/provider-instance.d.ts +2 -0
- package/dist/providers/spec/fsm-driver.d.ts +16 -0
- package/package.json +2 -2
- package/src/commands/cli-manager.ts +18 -0
- package/src/commands/router.ts +86 -8
- package/src/mesh/mesh-events-coordinator.ts +53 -3
- package/src/mesh/mesh-events-pending.ts +54 -4
- package/src/mesh/mesh-events-utils.ts +61 -9
- package/src/mesh/mesh-ledger.ts +1 -0
- package/src/mesh/mesh-reconcile-loop.ts +170 -3
- package/src/mesh/mesh-runtime-store.ts +38 -4
- package/src/mesh/mesh-work-queue.ts +13 -0
- package/src/providers/cli-provider-instance.ts +4 -1
- package/src/providers/provider-instance-manager.ts +1 -1
- package/src/providers/provider-instance.ts +1 -1
- package/src/providers/spec/fsm-driver.ts +64 -24
package/dist/index.js
CHANGED
|
@@ -316,10 +316,10 @@ function readInjected(value) {
|
|
|
316
316
|
}
|
|
317
317
|
function getDaemonBuildInfo() {
|
|
318
318
|
if (cached) return cached;
|
|
319
|
-
const commit = readInjected(true ? "
|
|
320
|
-
const commitShort = readInjected(true ? "
|
|
321
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
322
|
-
const builtAt = readInjected(true ? "2026-06-
|
|
319
|
+
const commit = readInjected(true ? "d04317b7eab7aec6a5557cb1991af603c7e5143c" : void 0) ?? "unknown";
|
|
320
|
+
const commitShort = readInjected(true ? "d04317b7" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
321
|
+
const version = readInjected(true ? "0.9.82-rc.344" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
322
|
+
const builtAt = readInjected(true ? "2026-06-21T13:38:42.110Z" : void 0);
|
|
323
323
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
324
324
|
return cached;
|
|
325
325
|
}
|
|
@@ -2541,6 +2541,297 @@ Follow these recovery rules:
|
|
|
2541
2541
|
}
|
|
2542
2542
|
});
|
|
2543
2543
|
|
|
2544
|
+
// src/logging/async-batch-writer.ts
|
|
2545
|
+
var fs3, AsyncBatchWriter;
|
|
2546
|
+
var init_async_batch_writer = __esm({
|
|
2547
|
+
"src/logging/async-batch-writer.ts"() {
|
|
2548
|
+
"use strict";
|
|
2549
|
+
fs3 = __toESM(require("fs"));
|
|
2550
|
+
AsyncBatchWriter = class {
|
|
2551
|
+
// Maps filePath -> string buffer
|
|
2552
|
+
static buffers = /* @__PURE__ */ new Map();
|
|
2553
|
+
static writePromises = /* @__PURE__ */ new Map();
|
|
2554
|
+
static flushTimer = null;
|
|
2555
|
+
/**
|
|
2556
|
+
* Queues data to be written to a file asynchronously in a batch.
|
|
2557
|
+
*/
|
|
2558
|
+
static write(filePath, data) {
|
|
2559
|
+
let buf = this.buffers.get(filePath);
|
|
2560
|
+
if (!buf) {
|
|
2561
|
+
buf = [];
|
|
2562
|
+
this.buffers.set(filePath, buf);
|
|
2563
|
+
}
|
|
2564
|
+
buf.push(data);
|
|
2565
|
+
if (!this.flushTimer) {
|
|
2566
|
+
this.flushTimer = setTimeout(() => {
|
|
2567
|
+
this.flushTimer = null;
|
|
2568
|
+
this.flushAll();
|
|
2569
|
+
}, 50);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
static async flushAll() {
|
|
2573
|
+
const entries = Array.from(this.buffers.entries());
|
|
2574
|
+
this.buffers.clear();
|
|
2575
|
+
for (const [filePath, buffer] of entries) {
|
|
2576
|
+
const dataToWrite = buffer.join("");
|
|
2577
|
+
const doWrite = async () => {
|
|
2578
|
+
try {
|
|
2579
|
+
const prevPromise = this.writePromises.get(filePath);
|
|
2580
|
+
if (prevPromise) await prevPromise;
|
|
2581
|
+
await fs3.promises.appendFile(filePath, dataToWrite, { encoding: "utf-8", mode: 384 });
|
|
2582
|
+
} catch {
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2585
|
+
const writePromise = doWrite();
|
|
2586
|
+
this.writePromises.set(filePath, writePromise);
|
|
2587
|
+
writePromise.finally(() => {
|
|
2588
|
+
if (this.writePromises.get(filePath) === writePromise) {
|
|
2589
|
+
this.writePromises.delete(filePath);
|
|
2590
|
+
}
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
});
|
|
2597
|
+
|
|
2598
|
+
// src/logging/logger.ts
|
|
2599
|
+
var logger_exports = {};
|
|
2600
|
+
__export(logger_exports, {
|
|
2601
|
+
LOG: () => LOG,
|
|
2602
|
+
LOG_DIR_PATH: () => LOG_DIR_PATH,
|
|
2603
|
+
LOG_PATH: () => LOG_PATH,
|
|
2604
|
+
daemonLog: () => daemonLog,
|
|
2605
|
+
getCurrentDaemonLogPath: () => getCurrentDaemonLogPath,
|
|
2606
|
+
getDaemonLogDir: () => getDaemonLogDir,
|
|
2607
|
+
getLogBufferSize: () => getLogBufferSize,
|
|
2608
|
+
getLogLevel: () => getLogLevel,
|
|
2609
|
+
getLogPath: () => getLogPath,
|
|
2610
|
+
getRecentLogs: () => getRecentLogs,
|
|
2611
|
+
installGlobalInterceptor: () => installGlobalInterceptor,
|
|
2612
|
+
setLogLevel: () => setLogLevel
|
|
2613
|
+
});
|
|
2614
|
+
function setLogLevel(level) {
|
|
2615
|
+
currentLevel = level;
|
|
2616
|
+
daemonLog("Logger", `Log level set to: ${level}`, "info");
|
|
2617
|
+
}
|
|
2618
|
+
function getLogLevel() {
|
|
2619
|
+
return currentLevel;
|
|
2620
|
+
}
|
|
2621
|
+
function getDateStr() {
|
|
2622
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2623
|
+
}
|
|
2624
|
+
function getDaemonLogDir() {
|
|
2625
|
+
return LOG_DIR;
|
|
2626
|
+
}
|
|
2627
|
+
function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
|
|
2628
|
+
return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
|
|
2629
|
+
}
|
|
2630
|
+
function checkDateRotation() {
|
|
2631
|
+
const today = getDateStr();
|
|
2632
|
+
if (today !== currentDate) {
|
|
2633
|
+
currentDate = today;
|
|
2634
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
2635
|
+
cleanOldLogs();
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
function cleanOldLogs() {
|
|
2639
|
+
try {
|
|
2640
|
+
const files = fs4.readdirSync(LOG_DIR).filter((f) => f.startsWith("daemon-") && f.endsWith(".log"));
|
|
2641
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
2642
|
+
cutoff.setDate(cutoff.getDate() - MAX_LOG_DAYS);
|
|
2643
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
2644
|
+
for (const file of files) {
|
|
2645
|
+
const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
|
|
2646
|
+
if (dateMatch && dateMatch[1] < cutoffStr) {
|
|
2647
|
+
try {
|
|
2648
|
+
fs4.unlinkSync(path9.join(LOG_DIR, file));
|
|
2649
|
+
} catch {
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
} catch {
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
function rotateSizeIfNeeded() {
|
|
2657
|
+
try {
|
|
2658
|
+
const stat2 = fs4.statSync(currentLogFile);
|
|
2659
|
+
if (stat2.size > MAX_LOG_SIZE) {
|
|
2660
|
+
const backup = currentLogFile.replace(".log", ".1.log");
|
|
2661
|
+
try {
|
|
2662
|
+
fs4.unlinkSync(backup);
|
|
2663
|
+
} catch {
|
|
2664
|
+
}
|
|
2665
|
+
fs4.renameSync(currentLogFile, backup);
|
|
2666
|
+
}
|
|
2667
|
+
} catch {
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
function writeToFile(line) {
|
|
2671
|
+
try {
|
|
2672
|
+
if (++writeCount % 1e3 === 0) {
|
|
2673
|
+
checkDateRotation();
|
|
2674
|
+
rotateSizeIfNeeded();
|
|
2675
|
+
}
|
|
2676
|
+
AsyncBatchWriter.write(currentLogFile, line + "\n");
|
|
2677
|
+
} catch {
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
function getRecentLogs(count = 50, minLevel = "info") {
|
|
2681
|
+
const minNum = LEVEL_NUM[minLevel];
|
|
2682
|
+
const filtered = ringBuffer.filter((e) => LEVEL_NUM[e.level] >= minNum);
|
|
2683
|
+
return filtered.slice(-count);
|
|
2684
|
+
}
|
|
2685
|
+
function getLogBufferSize() {
|
|
2686
|
+
return ringBuffer.length;
|
|
2687
|
+
}
|
|
2688
|
+
function ts() {
|
|
2689
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
2690
|
+
}
|
|
2691
|
+
function fullTs() {
|
|
2692
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2693
|
+
}
|
|
2694
|
+
function daemonLog(category, msg, level = "info") {
|
|
2695
|
+
const shouldOutput = LEVEL_NUM[level] >= LEVEL_NUM[currentLevel];
|
|
2696
|
+
const label = LEVEL_LABEL[level];
|
|
2697
|
+
const line = `[${ts()}] [${label}] [${category}] ${msg}`;
|
|
2698
|
+
if (!shouldOutput) return;
|
|
2699
|
+
writeToFile(line);
|
|
2700
|
+
ringBuffer.push({ ts: Date.now(), level, category, message: msg });
|
|
2701
|
+
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
2702
|
+
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
2703
|
+
}
|
|
2704
|
+
origConsoleLog(line);
|
|
2705
|
+
}
|
|
2706
|
+
function installGlobalInterceptor() {
|
|
2707
|
+
if (interceptorInstalled) return;
|
|
2708
|
+
interceptorInstalled = true;
|
|
2709
|
+
const stripAnsi4 = (str) => str.replace(/\x1B\[[0-9;]*m/g, "");
|
|
2710
|
+
const isDaemonLogLine = (msg) => /\[(DBG|INF|WRN|ERR)\]/.test(msg);
|
|
2711
|
+
console.log = (...args) => {
|
|
2712
|
+
origConsoleLog(...args);
|
|
2713
|
+
try {
|
|
2714
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
2715
|
+
const clean = stripAnsi4(msg);
|
|
2716
|
+
if (isDaemonLogLine(clean)) return;
|
|
2717
|
+
const line = clean.startsWith("[20") ? clean : `[${fullTs()}] ${clean}`;
|
|
2718
|
+
writeToFile(line);
|
|
2719
|
+
const catMatch = clean.match(/\[([^\]]+)\]/);
|
|
2720
|
+
ringBuffer.push({
|
|
2721
|
+
ts: Date.now(),
|
|
2722
|
+
level: "info",
|
|
2723
|
+
category: catMatch?.[1] || "System",
|
|
2724
|
+
message: clean
|
|
2725
|
+
});
|
|
2726
|
+
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
2727
|
+
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
2728
|
+
}
|
|
2729
|
+
} catch {
|
|
2730
|
+
}
|
|
2731
|
+
};
|
|
2732
|
+
console.error = (...args) => {
|
|
2733
|
+
origConsoleError(...args);
|
|
2734
|
+
try {
|
|
2735
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
2736
|
+
const clean = stripAnsi4(msg);
|
|
2737
|
+
if (isDaemonLogLine(clean)) return;
|
|
2738
|
+
const line = `[${fullTs()}] [ERROR] ${clean}`;
|
|
2739
|
+
writeToFile(line);
|
|
2740
|
+
ringBuffer.push({ ts: Date.now(), level: "error", category: "System", message: clean });
|
|
2741
|
+
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
2742
|
+
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
2743
|
+
}
|
|
2744
|
+
} catch {
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
console.warn = (...args) => {
|
|
2748
|
+
origConsoleWarn(...args);
|
|
2749
|
+
try {
|
|
2750
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
2751
|
+
const clean = stripAnsi4(msg);
|
|
2752
|
+
if (isDaemonLogLine(clean)) return;
|
|
2753
|
+
const line = `[${fullTs()}] [WARN] ${clean}`;
|
|
2754
|
+
writeToFile(line);
|
|
2755
|
+
ringBuffer.push({ ts: Date.now(), level: "warn", category: "System", message: clean });
|
|
2756
|
+
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
2757
|
+
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
2758
|
+
}
|
|
2759
|
+
} catch {
|
|
2760
|
+
}
|
|
2761
|
+
};
|
|
2762
|
+
writeToFile(`
|
|
2763
|
+
=== ADHDev Daemon started at ${fullTs()} ===`);
|
|
2764
|
+
writeToFile(`Log file: ${currentLogFile}`);
|
|
2765
|
+
writeToFile(`Log level: ${currentLevel}`);
|
|
2766
|
+
}
|
|
2767
|
+
function getLogPath() {
|
|
2768
|
+
return currentLogFile;
|
|
2769
|
+
}
|
|
2770
|
+
var fs4, path9, os3, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH, LOG_DIR_PATH;
|
|
2771
|
+
var init_logger = __esm({
|
|
2772
|
+
"src/logging/logger.ts"() {
|
|
2773
|
+
"use strict";
|
|
2774
|
+
fs4 = __toESM(require("fs"));
|
|
2775
|
+
path9 = __toESM(require("path"));
|
|
2776
|
+
os3 = __toESM(require("os"));
|
|
2777
|
+
init_async_batch_writer();
|
|
2778
|
+
LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
2779
|
+
LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
|
|
2780
|
+
currentLevel = "info";
|
|
2781
|
+
LOG_DIR = process.platform === "win32" ? path9.join(process.env.LOCALAPPDATA || process.env.APPDATA || path9.join(os3.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path9.join(os3.homedir(), "Library", "Logs", "adhdev") : path9.join(os3.homedir(), ".local", "share", "adhdev", "logs");
|
|
2782
|
+
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
2783
|
+
MAX_LOG_DAYS = 7;
|
|
2784
|
+
try {
|
|
2785
|
+
fs4.mkdirSync(LOG_DIR, { recursive: true });
|
|
2786
|
+
} catch {
|
|
2787
|
+
}
|
|
2788
|
+
currentDate = getDateStr();
|
|
2789
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
2790
|
+
cleanOldLogs();
|
|
2791
|
+
try {
|
|
2792
|
+
const oldLog = path9.join(LOG_DIR, "daemon.log");
|
|
2793
|
+
if (fs4.existsSync(oldLog)) {
|
|
2794
|
+
const stat2 = fs4.statSync(oldLog);
|
|
2795
|
+
const oldDate = stat2.mtime.toISOString().slice(0, 10);
|
|
2796
|
+
fs4.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
|
|
2797
|
+
}
|
|
2798
|
+
const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
|
|
2799
|
+
if (fs4.existsSync(oldLogBackup)) {
|
|
2800
|
+
fs4.unlinkSync(oldLogBackup);
|
|
2801
|
+
}
|
|
2802
|
+
} catch {
|
|
2803
|
+
}
|
|
2804
|
+
writeCount = 0;
|
|
2805
|
+
RING_BUFFER_SIZE = 200;
|
|
2806
|
+
ringBuffer = [];
|
|
2807
|
+
origConsoleLog = console.log.bind(console);
|
|
2808
|
+
origConsoleError = console.error.bind(console);
|
|
2809
|
+
origConsoleWarn = console.warn.bind(console);
|
|
2810
|
+
LOG = {
|
|
2811
|
+
debug: (category, msg) => daemonLog(category, msg, "debug"),
|
|
2812
|
+
info: (category, msg) => daemonLog(category, msg, "info"),
|
|
2813
|
+
warn: (category, msg) => daemonLog(category, msg, "warn"),
|
|
2814
|
+
error: (category, msg) => daemonLog(category, msg, "error"),
|
|
2815
|
+
/**
|
|
2816
|
+
* Create a scoped logger for a specific component.
|
|
2817
|
+
* Category is baked in so callers only pass the message.
|
|
2818
|
+
*/
|
|
2819
|
+
forComponent(category) {
|
|
2820
|
+
return {
|
|
2821
|
+
debug: (msg) => daemonLog(category, msg, "debug"),
|
|
2822
|
+
info: (msg) => daemonLog(category, msg, "info"),
|
|
2823
|
+
warn: (msg) => daemonLog(category, msg, "warn"),
|
|
2824
|
+
error: (msg) => daemonLog(category, msg, "error"),
|
|
2825
|
+
asLogFn: (level = "info") => (msg) => daemonLog(category, msg, level)
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
};
|
|
2829
|
+
interceptorInstalled = false;
|
|
2830
|
+
LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
|
|
2831
|
+
LOG_DIR_PATH = LOG_DIR;
|
|
2832
|
+
}
|
|
2833
|
+
});
|
|
2834
|
+
|
|
2544
2835
|
// src/system/load-better-sqlite3.ts
|
|
2545
2836
|
function loadBetterSqlite3() {
|
|
2546
2837
|
if (cached2) return cached2;
|
|
@@ -3260,297 +3551,6 @@ var init_mesh_ledger = __esm({
|
|
|
3260
3551
|
}
|
|
3261
3552
|
});
|
|
3262
3553
|
|
|
3263
|
-
// src/logging/async-batch-writer.ts
|
|
3264
|
-
var fs3, AsyncBatchWriter;
|
|
3265
|
-
var init_async_batch_writer = __esm({
|
|
3266
|
-
"src/logging/async-batch-writer.ts"() {
|
|
3267
|
-
"use strict";
|
|
3268
|
-
fs3 = __toESM(require("fs"));
|
|
3269
|
-
AsyncBatchWriter = class {
|
|
3270
|
-
// Maps filePath -> string buffer
|
|
3271
|
-
static buffers = /* @__PURE__ */ new Map();
|
|
3272
|
-
static writePromises = /* @__PURE__ */ new Map();
|
|
3273
|
-
static flushTimer = null;
|
|
3274
|
-
/**
|
|
3275
|
-
* Queues data to be written to a file asynchronously in a batch.
|
|
3276
|
-
*/
|
|
3277
|
-
static write(filePath, data) {
|
|
3278
|
-
let buf = this.buffers.get(filePath);
|
|
3279
|
-
if (!buf) {
|
|
3280
|
-
buf = [];
|
|
3281
|
-
this.buffers.set(filePath, buf);
|
|
3282
|
-
}
|
|
3283
|
-
buf.push(data);
|
|
3284
|
-
if (!this.flushTimer) {
|
|
3285
|
-
this.flushTimer = setTimeout(() => {
|
|
3286
|
-
this.flushTimer = null;
|
|
3287
|
-
this.flushAll();
|
|
3288
|
-
}, 50);
|
|
3289
|
-
}
|
|
3290
|
-
}
|
|
3291
|
-
static async flushAll() {
|
|
3292
|
-
const entries = Array.from(this.buffers.entries());
|
|
3293
|
-
this.buffers.clear();
|
|
3294
|
-
for (const [filePath, buffer] of entries) {
|
|
3295
|
-
const dataToWrite = buffer.join("");
|
|
3296
|
-
const doWrite = async () => {
|
|
3297
|
-
try {
|
|
3298
|
-
const prevPromise = this.writePromises.get(filePath);
|
|
3299
|
-
if (prevPromise) await prevPromise;
|
|
3300
|
-
await fs3.promises.appendFile(filePath, dataToWrite, { encoding: "utf-8", mode: 384 });
|
|
3301
|
-
} catch {
|
|
3302
|
-
}
|
|
3303
|
-
};
|
|
3304
|
-
const writePromise = doWrite();
|
|
3305
|
-
this.writePromises.set(filePath, writePromise);
|
|
3306
|
-
writePromise.finally(() => {
|
|
3307
|
-
if (this.writePromises.get(filePath) === writePromise) {
|
|
3308
|
-
this.writePromises.delete(filePath);
|
|
3309
|
-
}
|
|
3310
|
-
});
|
|
3311
|
-
}
|
|
3312
|
-
}
|
|
3313
|
-
};
|
|
3314
|
-
}
|
|
3315
|
-
});
|
|
3316
|
-
|
|
3317
|
-
// src/logging/logger.ts
|
|
3318
|
-
var logger_exports = {};
|
|
3319
|
-
__export(logger_exports, {
|
|
3320
|
-
LOG: () => LOG,
|
|
3321
|
-
LOG_DIR_PATH: () => LOG_DIR_PATH,
|
|
3322
|
-
LOG_PATH: () => LOG_PATH,
|
|
3323
|
-
daemonLog: () => daemonLog,
|
|
3324
|
-
getCurrentDaemonLogPath: () => getCurrentDaemonLogPath,
|
|
3325
|
-
getDaemonLogDir: () => getDaemonLogDir,
|
|
3326
|
-
getLogBufferSize: () => getLogBufferSize,
|
|
3327
|
-
getLogLevel: () => getLogLevel,
|
|
3328
|
-
getLogPath: () => getLogPath,
|
|
3329
|
-
getRecentLogs: () => getRecentLogs,
|
|
3330
|
-
installGlobalInterceptor: () => installGlobalInterceptor,
|
|
3331
|
-
setLogLevel: () => setLogLevel
|
|
3332
|
-
});
|
|
3333
|
-
function setLogLevel(level) {
|
|
3334
|
-
currentLevel = level;
|
|
3335
|
-
daemonLog("Logger", `Log level set to: ${level}`, "info");
|
|
3336
|
-
}
|
|
3337
|
-
function getLogLevel() {
|
|
3338
|
-
return currentLevel;
|
|
3339
|
-
}
|
|
3340
|
-
function getDateStr() {
|
|
3341
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3342
|
-
}
|
|
3343
|
-
function getDaemonLogDir() {
|
|
3344
|
-
return LOG_DIR;
|
|
3345
|
-
}
|
|
3346
|
-
function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
|
|
3347
|
-
return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
|
|
3348
|
-
}
|
|
3349
|
-
function checkDateRotation() {
|
|
3350
|
-
const today = getDateStr();
|
|
3351
|
-
if (today !== currentDate) {
|
|
3352
|
-
currentDate = today;
|
|
3353
|
-
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
3354
|
-
cleanOldLogs();
|
|
3355
|
-
}
|
|
3356
|
-
}
|
|
3357
|
-
function cleanOldLogs() {
|
|
3358
|
-
try {
|
|
3359
|
-
const files = fs4.readdirSync(LOG_DIR).filter((f) => f.startsWith("daemon-") && f.endsWith(".log"));
|
|
3360
|
-
const cutoff = /* @__PURE__ */ new Date();
|
|
3361
|
-
cutoff.setDate(cutoff.getDate() - MAX_LOG_DAYS);
|
|
3362
|
-
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
3363
|
-
for (const file of files) {
|
|
3364
|
-
const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
|
|
3365
|
-
if (dateMatch && dateMatch[1] < cutoffStr) {
|
|
3366
|
-
try {
|
|
3367
|
-
fs4.unlinkSync(path9.join(LOG_DIR, file));
|
|
3368
|
-
} catch {
|
|
3369
|
-
}
|
|
3370
|
-
}
|
|
3371
|
-
}
|
|
3372
|
-
} catch {
|
|
3373
|
-
}
|
|
3374
|
-
}
|
|
3375
|
-
function rotateSizeIfNeeded() {
|
|
3376
|
-
try {
|
|
3377
|
-
const stat2 = fs4.statSync(currentLogFile);
|
|
3378
|
-
if (stat2.size > MAX_LOG_SIZE) {
|
|
3379
|
-
const backup = currentLogFile.replace(".log", ".1.log");
|
|
3380
|
-
try {
|
|
3381
|
-
fs4.unlinkSync(backup);
|
|
3382
|
-
} catch {
|
|
3383
|
-
}
|
|
3384
|
-
fs4.renameSync(currentLogFile, backup);
|
|
3385
|
-
}
|
|
3386
|
-
} catch {
|
|
3387
|
-
}
|
|
3388
|
-
}
|
|
3389
|
-
function writeToFile(line) {
|
|
3390
|
-
try {
|
|
3391
|
-
if (++writeCount % 1e3 === 0) {
|
|
3392
|
-
checkDateRotation();
|
|
3393
|
-
rotateSizeIfNeeded();
|
|
3394
|
-
}
|
|
3395
|
-
AsyncBatchWriter.write(currentLogFile, line + "\n");
|
|
3396
|
-
} catch {
|
|
3397
|
-
}
|
|
3398
|
-
}
|
|
3399
|
-
function getRecentLogs(count = 50, minLevel = "info") {
|
|
3400
|
-
const minNum = LEVEL_NUM[minLevel];
|
|
3401
|
-
const filtered = ringBuffer.filter((e) => LEVEL_NUM[e.level] >= minNum);
|
|
3402
|
-
return filtered.slice(-count);
|
|
3403
|
-
}
|
|
3404
|
-
function getLogBufferSize() {
|
|
3405
|
-
return ringBuffer.length;
|
|
3406
|
-
}
|
|
3407
|
-
function ts() {
|
|
3408
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
3409
|
-
}
|
|
3410
|
-
function fullTs() {
|
|
3411
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3412
|
-
}
|
|
3413
|
-
function daemonLog(category, msg, level = "info") {
|
|
3414
|
-
const shouldOutput = LEVEL_NUM[level] >= LEVEL_NUM[currentLevel];
|
|
3415
|
-
const label = LEVEL_LABEL[level];
|
|
3416
|
-
const line = `[${ts()}] [${label}] [${category}] ${msg}`;
|
|
3417
|
-
if (!shouldOutput) return;
|
|
3418
|
-
writeToFile(line);
|
|
3419
|
-
ringBuffer.push({ ts: Date.now(), level, category, message: msg });
|
|
3420
|
-
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
3421
|
-
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
3422
|
-
}
|
|
3423
|
-
origConsoleLog(line);
|
|
3424
|
-
}
|
|
3425
|
-
function installGlobalInterceptor() {
|
|
3426
|
-
if (interceptorInstalled) return;
|
|
3427
|
-
interceptorInstalled = true;
|
|
3428
|
-
const stripAnsi4 = (str) => str.replace(/\x1B\[[0-9;]*m/g, "");
|
|
3429
|
-
const isDaemonLogLine = (msg) => /\[(DBG|INF|WRN|ERR)\]/.test(msg);
|
|
3430
|
-
console.log = (...args) => {
|
|
3431
|
-
origConsoleLog(...args);
|
|
3432
|
-
try {
|
|
3433
|
-
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
3434
|
-
const clean = stripAnsi4(msg);
|
|
3435
|
-
if (isDaemonLogLine(clean)) return;
|
|
3436
|
-
const line = clean.startsWith("[20") ? clean : `[${fullTs()}] ${clean}`;
|
|
3437
|
-
writeToFile(line);
|
|
3438
|
-
const catMatch = clean.match(/\[([^\]]+)\]/);
|
|
3439
|
-
ringBuffer.push({
|
|
3440
|
-
ts: Date.now(),
|
|
3441
|
-
level: "info",
|
|
3442
|
-
category: catMatch?.[1] || "System",
|
|
3443
|
-
message: clean
|
|
3444
|
-
});
|
|
3445
|
-
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
3446
|
-
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
3447
|
-
}
|
|
3448
|
-
} catch {
|
|
3449
|
-
}
|
|
3450
|
-
};
|
|
3451
|
-
console.error = (...args) => {
|
|
3452
|
-
origConsoleError(...args);
|
|
3453
|
-
try {
|
|
3454
|
-
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
3455
|
-
const clean = stripAnsi4(msg);
|
|
3456
|
-
if (isDaemonLogLine(clean)) return;
|
|
3457
|
-
const line = `[${fullTs()}] [ERROR] ${clean}`;
|
|
3458
|
-
writeToFile(line);
|
|
3459
|
-
ringBuffer.push({ ts: Date.now(), level: "error", category: "System", message: clean });
|
|
3460
|
-
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
3461
|
-
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
3462
|
-
}
|
|
3463
|
-
} catch {
|
|
3464
|
-
}
|
|
3465
|
-
};
|
|
3466
|
-
console.warn = (...args) => {
|
|
3467
|
-
origConsoleWarn(...args);
|
|
3468
|
-
try {
|
|
3469
|
-
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
3470
|
-
const clean = stripAnsi4(msg);
|
|
3471
|
-
if (isDaemonLogLine(clean)) return;
|
|
3472
|
-
const line = `[${fullTs()}] [WARN] ${clean}`;
|
|
3473
|
-
writeToFile(line);
|
|
3474
|
-
ringBuffer.push({ ts: Date.now(), level: "warn", category: "System", message: clean });
|
|
3475
|
-
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
3476
|
-
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
3477
|
-
}
|
|
3478
|
-
} catch {
|
|
3479
|
-
}
|
|
3480
|
-
};
|
|
3481
|
-
writeToFile(`
|
|
3482
|
-
=== ADHDev Daemon started at ${fullTs()} ===`);
|
|
3483
|
-
writeToFile(`Log file: ${currentLogFile}`);
|
|
3484
|
-
writeToFile(`Log level: ${currentLevel}`);
|
|
3485
|
-
}
|
|
3486
|
-
function getLogPath() {
|
|
3487
|
-
return currentLogFile;
|
|
3488
|
-
}
|
|
3489
|
-
var fs4, path9, os3, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH, LOG_DIR_PATH;
|
|
3490
|
-
var init_logger = __esm({
|
|
3491
|
-
"src/logging/logger.ts"() {
|
|
3492
|
-
"use strict";
|
|
3493
|
-
fs4 = __toESM(require("fs"));
|
|
3494
|
-
path9 = __toESM(require("path"));
|
|
3495
|
-
os3 = __toESM(require("os"));
|
|
3496
|
-
init_async_batch_writer();
|
|
3497
|
-
LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
3498
|
-
LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
|
|
3499
|
-
currentLevel = "info";
|
|
3500
|
-
LOG_DIR = process.platform === "win32" ? path9.join(process.env.LOCALAPPDATA || process.env.APPDATA || path9.join(os3.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path9.join(os3.homedir(), "Library", "Logs", "adhdev") : path9.join(os3.homedir(), ".local", "share", "adhdev", "logs");
|
|
3501
|
-
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
3502
|
-
MAX_LOG_DAYS = 7;
|
|
3503
|
-
try {
|
|
3504
|
-
fs4.mkdirSync(LOG_DIR, { recursive: true });
|
|
3505
|
-
} catch {
|
|
3506
|
-
}
|
|
3507
|
-
currentDate = getDateStr();
|
|
3508
|
-
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
3509
|
-
cleanOldLogs();
|
|
3510
|
-
try {
|
|
3511
|
-
const oldLog = path9.join(LOG_DIR, "daemon.log");
|
|
3512
|
-
if (fs4.existsSync(oldLog)) {
|
|
3513
|
-
const stat2 = fs4.statSync(oldLog);
|
|
3514
|
-
const oldDate = stat2.mtime.toISOString().slice(0, 10);
|
|
3515
|
-
fs4.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
|
|
3516
|
-
}
|
|
3517
|
-
const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
|
|
3518
|
-
if (fs4.existsSync(oldLogBackup)) {
|
|
3519
|
-
fs4.unlinkSync(oldLogBackup);
|
|
3520
|
-
}
|
|
3521
|
-
} catch {
|
|
3522
|
-
}
|
|
3523
|
-
writeCount = 0;
|
|
3524
|
-
RING_BUFFER_SIZE = 200;
|
|
3525
|
-
ringBuffer = [];
|
|
3526
|
-
origConsoleLog = console.log.bind(console);
|
|
3527
|
-
origConsoleError = console.error.bind(console);
|
|
3528
|
-
origConsoleWarn = console.warn.bind(console);
|
|
3529
|
-
LOG = {
|
|
3530
|
-
debug: (category, msg) => daemonLog(category, msg, "debug"),
|
|
3531
|
-
info: (category, msg) => daemonLog(category, msg, "info"),
|
|
3532
|
-
warn: (category, msg) => daemonLog(category, msg, "warn"),
|
|
3533
|
-
error: (category, msg) => daemonLog(category, msg, "error"),
|
|
3534
|
-
/**
|
|
3535
|
-
* Create a scoped logger for a specific component.
|
|
3536
|
-
* Category is baked in so callers only pass the message.
|
|
3537
|
-
*/
|
|
3538
|
-
forComponent(category) {
|
|
3539
|
-
return {
|
|
3540
|
-
debug: (msg) => daemonLog(category, msg, "debug"),
|
|
3541
|
-
info: (msg) => daemonLog(category, msg, "info"),
|
|
3542
|
-
warn: (msg) => daemonLog(category, msg, "warn"),
|
|
3543
|
-
error: (msg) => daemonLog(category, msg, "error"),
|
|
3544
|
-
asLogFn: (level = "info") => (msg) => daemonLog(category, msg, level)
|
|
3545
|
-
};
|
|
3546
|
-
}
|
|
3547
|
-
};
|
|
3548
|
-
interceptorInstalled = false;
|
|
3549
|
-
LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
|
|
3550
|
-
LOG_DIR_PATH = LOG_DIR;
|
|
3551
|
-
}
|
|
3552
|
-
});
|
|
3553
|
-
|
|
3554
3554
|
// src/mesh/mesh-work-queue.ts
|
|
3555
3555
|
var mesh_work_queue_exports = {};
|
|
3556
3556
|
__export(mesh_work_queue_exports, {
|
|
@@ -3867,6 +3867,7 @@ function enqueueTask(meshId, message, opts) {
|
|
|
3867
3867
|
requiredTags: resolvedRequiredTags,
|
|
3868
3868
|
...dependsOn.length > 0 ? { dependsOn } : {},
|
|
3869
3869
|
...typeof opts?.missionId === "string" && opts.missionId.trim() ? { missionId: opts.missionId.trim() } : {},
|
|
3870
|
+
...typeof opts?.sourceCoordinatorSessionId === "string" && opts.sourceCoordinatorSessionId.trim() ? { sourceCoordinatorSessionId: opts.sourceCoordinatorSessionId.trim() } : {},
|
|
3870
3871
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3871
3872
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3872
3873
|
};
|
|
@@ -4217,19 +4218,29 @@ function meshRuntimeStorePath() {
|
|
|
4217
4218
|
(0, import_fs5.renameSync)(legacyCompanion, `${nextPath}${suffix}`);
|
|
4218
4219
|
}
|
|
4219
4220
|
}
|
|
4220
|
-
} catch {
|
|
4221
|
+
} catch (err) {
|
|
4222
|
+
if (!loggedMigrationFailure) {
|
|
4223
|
+
loggedMigrationFailure = true;
|
|
4224
|
+
LOG.warn(
|
|
4225
|
+
"MeshRuntimeStore",
|
|
4226
|
+
`Legacy beads.db\u2192mesh-runtime.db migration failed; using existing DB in-place to avoid data loss: ${err?.message || err}`
|
|
4227
|
+
);
|
|
4228
|
+
}
|
|
4229
|
+
return (0, import_fs5.existsSync)(nextPath) ? nextPath : legacyPath;
|
|
4221
4230
|
}
|
|
4222
4231
|
return nextPath;
|
|
4223
4232
|
}
|
|
4224
|
-
var import_fs5, import_path5, DatabaseCtor, MeshRuntimeStore;
|
|
4233
|
+
var import_fs5, import_path5, DatabaseCtor, loggedMigrationFailure, MeshRuntimeStore;
|
|
4225
4234
|
var init_mesh_runtime_store = __esm({
|
|
4226
4235
|
"src/mesh/mesh-runtime-store.ts"() {
|
|
4227
4236
|
"use strict";
|
|
4228
4237
|
import_fs5 = require("fs");
|
|
4229
4238
|
import_path5 = require("path");
|
|
4239
|
+
init_logger();
|
|
4230
4240
|
init_load_better_sqlite3();
|
|
4231
4241
|
init_mesh_ledger();
|
|
4232
4242
|
init_mesh_work_queue();
|
|
4243
|
+
loggedMigrationFailure = false;
|
|
4233
4244
|
MeshRuntimeStore = class _MeshRuntimeStore {
|
|
4234
4245
|
static instance;
|
|
4235
4246
|
db;
|
|
@@ -4251,9 +4262,21 @@ var init_mesh_runtime_store = __esm({
|
|
|
4251
4262
|
this.db.pragma("busy_timeout = 5000");
|
|
4252
4263
|
this.migrate();
|
|
4253
4264
|
}
|
|
4265
|
+
static loggedGetInstanceFailure = false;
|
|
4254
4266
|
static getInstance() {
|
|
4255
4267
|
if (!this.instance) {
|
|
4256
|
-
|
|
4268
|
+
try {
|
|
4269
|
+
this.instance = new _MeshRuntimeStore(meshRuntimeStorePath());
|
|
4270
|
+
} catch (err) {
|
|
4271
|
+
if (!_MeshRuntimeStore.loggedGetInstanceFailure) {
|
|
4272
|
+
_MeshRuntimeStore.loggedGetInstanceFailure = true;
|
|
4273
|
+
LOG.warn(
|
|
4274
|
+
"MeshRuntimeStore",
|
|
4275
|
+
`getInstance failed; callers will degrade to JSONL-only: ${err?.message || err}`
|
|
4276
|
+
);
|
|
4277
|
+
}
|
|
4278
|
+
throw err;
|
|
4279
|
+
}
|
|
4257
4280
|
}
|
|
4258
4281
|
return this.instance;
|
|
4259
4282
|
}
|
|
@@ -7846,7 +7869,11 @@ function buildMeshWorkerRelayStamp(currentSettings, meshContext) {
|
|
|
7846
7869
|
if (coordinatorDaemonId && !readNonEmptyString2(settings.meshCoordinatorDaemonId)) {
|
|
7847
7870
|
stamp.meshCoordinatorDaemonId = coordinatorDaemonId;
|
|
7848
7871
|
}
|
|
7849
|
-
|
|
7872
|
+
const coordinatorSessionId = readNonEmptyString2(meshContext.coordinatorSessionId);
|
|
7873
|
+
if (coordinatorSessionId && !readNonEmptyString2(settings.meshCoordinatorSessionId)) {
|
|
7874
|
+
stamp.meshCoordinatorSessionId = coordinatorSessionId;
|
|
7875
|
+
}
|
|
7876
|
+
if ((meshId || nodeId || coordinatorDaemonId || coordinatorSessionId) && settings.launchedByCoordinator !== true) {
|
|
7850
7877
|
stamp.launchedByCoordinator = true;
|
|
7851
7878
|
}
|
|
7852
7879
|
return Object.keys(stamp).length > 0 ? stamp : void 0;
|
|
@@ -7863,10 +7890,13 @@ function readRefineJobId(event) {
|
|
|
7863
7890
|
function readWorkerResultMetadata(event) {
|
|
7864
7891
|
return readRecord4(event.workerResult) || readRecord4(event.meshWorkerResult) || readRecord4(event.structuredResult);
|
|
7865
7892
|
}
|
|
7866
|
-
function
|
|
7893
|
+
function readMeshCompletionSummary(metadataEvent) {
|
|
7867
7894
|
const workerResult = readWorkerResultMetadata(metadataEvent);
|
|
7868
7895
|
const resultRecord = readRecord4(metadataEvent.result);
|
|
7869
|
-
|
|
7896
|
+
return readNonEmptyString2(metadataEvent.finalSummary) || readNonEmptyString2(workerResult?.summary) || readNonEmptyString2(workerResult?.finalSummary) || readNonEmptyString2(resultRecord?.summary) || readNonEmptyString2(resultRecord?.finalSummary);
|
|
7897
|
+
}
|
|
7898
|
+
function resolveMeshSurfacedSessionPreview(metadataEvent) {
|
|
7899
|
+
const summaryText = readMeshCompletionSummary(metadataEvent);
|
|
7870
7900
|
if (!summaryText) return void 0;
|
|
7871
7901
|
const truncationSuffix = "...[truncated]";
|
|
7872
7902
|
const preview = summaryText.length > MESH_SURFACED_PREVIEW_MAX_CHARS ? `${summaryText.slice(0, MESH_SURFACED_PREVIEW_MAX_CHARS - truncationSuffix.length)}${truncationSuffix}` : summaryText;
|
|
@@ -7904,7 +7934,18 @@ function buildMeshSystemMessage(args) {
|
|
|
7904
7934
|
if (args.metadataEvent.source === "no_progress_reconciliation") {
|
|
7905
7935
|
return `[System] ${args.nodeLabel} already has completion evidence${metadata}. The no-progress monitor reconciled the terminal handoff and marked the session complete; wait for the queued completion event/status refresh before doing any manual transcript check.`;
|
|
7906
7936
|
}
|
|
7907
|
-
const
|
|
7937
|
+
const reviewRecommended = args.metadataEvent.reviewRecommended === true;
|
|
7938
|
+
const completionSummary = readMeshCompletionSummary(args.metadataEvent);
|
|
7939
|
+
if (completionSummary) {
|
|
7940
|
+
const truncationSuffix = "\n\u2026[truncated \u2014 call mesh_read_chat once for the full transcript]";
|
|
7941
|
+
const surfaced = completionSummary.length > MESH_COMPLETION_SURFACE_MAX_CHARS ? `${completionSummary.slice(0, MESH_COMPLETION_SURFACE_MAX_CHARS - truncationSuffix.length)}${truncationSuffix}` : completionSummary;
|
|
7942
|
+
const verifyNote = reviewRecommended ? " Completion evidence is insufficient \u2014 verify via git status or provider_session_id before assuming the task is done." : "";
|
|
7943
|
+
return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. Its final summary is included below \u2014 read it directly and only call mesh_read_chat if you need the full transcript.${verifyNote}
|
|
7944
|
+
|
|
7945
|
+
--- ${args.nodeLabel} final summary ---
|
|
7946
|
+
${surfaced}`;
|
|
7947
|
+
}
|
|
7948
|
+
const reviewNote = reviewRecommended ? " Completion evidence is insufficient \u2014 verify via git status or provider_session_id before assuming the task is done. Use mesh_read_chat once if needed, but do not poll repeatedly." : " Use mesh_read_chat once to review its final progress, but do not poll repeatedly.";
|
|
7908
7949
|
return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path;${reviewNote}`;
|
|
7909
7950
|
}
|
|
7910
7951
|
if (args.event === "agent:waiting_approval") {
|
|
@@ -8012,11 +8053,12 @@ Next step: ${nextStep}`;
|
|
|
8012
8053
|
}
|
|
8013
8054
|
return "";
|
|
8014
8055
|
}
|
|
8015
|
-
var MESH_SURFACED_PREVIEW_MAX_CHARS;
|
|
8056
|
+
var MESH_SURFACED_PREVIEW_MAX_CHARS, MESH_COMPLETION_SURFACE_MAX_CHARS;
|
|
8016
8057
|
var init_mesh_events_utils = __esm({
|
|
8017
8058
|
"src/mesh/mesh-events-utils.ts"() {
|
|
8018
8059
|
"use strict";
|
|
8019
8060
|
MESH_SURFACED_PREVIEW_MAX_CHARS = 512;
|
|
8061
|
+
MESH_COMPLETION_SURFACE_MAX_CHARS = 4e3;
|
|
8020
8062
|
}
|
|
8021
8063
|
});
|
|
8022
8064
|
|
|
@@ -8174,6 +8216,37 @@ function trimPendingEventsIfNeeded(path42) {
|
|
|
8174
8216
|
if ((0, import_fs10.statSync)(path42).size <= MAX_PENDING_EVENTS_BYTES) return;
|
|
8175
8217
|
const lines = (0, import_fs10.readFileSync)(path42, "utf-8").split("\n").filter(Boolean);
|
|
8176
8218
|
if (lines.length <= MAX_PENDING_EVENTS_KEEP) return;
|
|
8219
|
+
const dropped = lines.slice(0, lines.length - MAX_PENDING_EVENTS_KEEP);
|
|
8220
|
+
for (const line of dropped) {
|
|
8221
|
+
let event;
|
|
8222
|
+
try {
|
|
8223
|
+
event = JSON.parse(line);
|
|
8224
|
+
} catch {
|
|
8225
|
+
continue;
|
|
8226
|
+
}
|
|
8227
|
+
if (!event || !event.meshId) continue;
|
|
8228
|
+
const finalSummary = readMeshCompletionSummary(event.metadataEvent || {});
|
|
8229
|
+
if (!readNonEmptyString2(event.coordinatorMessage) && !finalSummary) continue;
|
|
8230
|
+
try {
|
|
8231
|
+
appendLedgerEntry(event.meshId, {
|
|
8232
|
+
kind: "event_held",
|
|
8233
|
+
...event.nodeId ? { nodeId: event.nodeId } : {},
|
|
8234
|
+
payload: {
|
|
8235
|
+
event: event.event,
|
|
8236
|
+
reason: "pending_trim_dropped",
|
|
8237
|
+
recoverable: true,
|
|
8238
|
+
nodeLabel: event.nodeLabel,
|
|
8239
|
+
...event.workspace ? { workspace: event.workspace } : {},
|
|
8240
|
+
targetCoordinatorDaemonId: event.targetCoordinatorDaemonId ?? null,
|
|
8241
|
+
queuedAt: event.queuedAt,
|
|
8242
|
+
...finalSummary ? { finalSummary } : {}
|
|
8243
|
+
}
|
|
8244
|
+
});
|
|
8245
|
+
LOG.warn("MeshEvents", `Pending-events trim dropping undelivered ${event.event} for mesh ${event.meshId} \u2014 recorded to ledger (recoverable)`);
|
|
8246
|
+
} catch (e) {
|
|
8247
|
+
LOG.warn("MeshEvents", `Failed to ledger-record trim-dropped ${event.event} for mesh ${event.meshId}: ${e?.message || e}`);
|
|
8248
|
+
}
|
|
8249
|
+
}
|
|
8177
8250
|
(0, import_fs10.writeFileSync)(path42, lines.slice(-MAX_PENDING_EVENTS_KEEP).join("\n") + "\n", "utf-8");
|
|
8178
8251
|
} catch {
|
|
8179
8252
|
}
|
|
@@ -8303,7 +8376,8 @@ function drainPendingMeshCoordinatorEvents(meshId, coordinatorDaemonId, opts) {
|
|
|
8303
8376
|
if (event) pushUnique(event);
|
|
8304
8377
|
}
|
|
8305
8378
|
}
|
|
8306
|
-
} catch {
|
|
8379
|
+
} catch (e) {
|
|
8380
|
+
LOG.warn("MeshEvents", `SQLite pending-event drain failed for mesh ${meshId}; JSONL fallback only: ${e?.message || e}`);
|
|
8307
8381
|
}
|
|
8308
8382
|
const paths = primaryDaemonId ? [getPendingEventsPath(meshId, primaryDaemonId), getPendingEventsPath(meshId)] : [getPendingEventsPath(meshId)];
|
|
8309
8383
|
for (const path42 of paths) {
|
|
@@ -9718,6 +9792,8 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9718
9792
|
if (node?.daemonId && components.dispatchMeshCommand) {
|
|
9719
9793
|
const isLocalNode = components.cliManager.adapters.has(sessionId);
|
|
9720
9794
|
if (!isLocalNode) {
|
|
9795
|
+
const localDaemonIdForDispatch = readNonEmptyString2(loadConfig().machineId) || void 0;
|
|
9796
|
+
const sourceCoordinatorSessionId = readNonEmptyString2(task.sourceCoordinatorSessionId) || void 0;
|
|
9721
9797
|
const delivery2 = createSessionDelivery({
|
|
9722
9798
|
meshId,
|
|
9723
9799
|
nodeId,
|
|
@@ -9726,9 +9802,10 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9726
9802
|
taskId: task.id,
|
|
9727
9803
|
kind: "task",
|
|
9728
9804
|
message: task.message,
|
|
9729
|
-
status: "delivering"
|
|
9805
|
+
status: "delivering",
|
|
9806
|
+
...sourceCoordinatorSessionId ? { sourceCoordinatorSessionId } : {},
|
|
9807
|
+
...localDaemonIdForDispatch ? { sourceCoordinatorDaemonId: localDaemonIdForDispatch } : {}
|
|
9730
9808
|
});
|
|
9731
|
-
const localDaemonIdForDispatch = readNonEmptyString2(loadConfig().machineId) || void 0;
|
|
9732
9809
|
components.dispatchMeshCommand(node.daemonId, "agent_command", {
|
|
9733
9810
|
targetSessionId: sessionId,
|
|
9734
9811
|
cliType: providerType,
|
|
@@ -9738,7 +9815,8 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9738
9815
|
meshId,
|
|
9739
9816
|
nodeId,
|
|
9740
9817
|
taskId: task.id,
|
|
9741
|
-
...localDaemonIdForDispatch ? { coordinatorDaemonId: localDaemonIdForDispatch } : {}
|
|
9818
|
+
...localDaemonIdForDispatch ? { coordinatorDaemonId: localDaemonIdForDispatch } : {},
|
|
9819
|
+
...sourceCoordinatorSessionId ? { coordinatorSessionId: sourceCoordinatorSessionId } : {}
|
|
9742
9820
|
}
|
|
9743
9821
|
}).then(() => {
|
|
9744
9822
|
updateSessionDeliveryStatus(delivery2.id, "delivered");
|
|
@@ -9763,12 +9841,16 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9763
9841
|
const inst = components.instanceManager.getInstance(sessionId);
|
|
9764
9842
|
if (inst && typeof inst.updateSettings === "function") {
|
|
9765
9843
|
const localDaemonId = readNonEmptyString2(loadConfig().machineId);
|
|
9844
|
+
const localSourceCoordinatorSessionId = readNonEmptyString2(task.sourceCoordinatorSessionId);
|
|
9766
9845
|
inst.updateSettings({
|
|
9767
9846
|
meshNodeFor: meshId,
|
|
9768
9847
|
meshNodeId: nodeId,
|
|
9769
9848
|
launchedByCoordinator: true,
|
|
9770
9849
|
autoApprove: resolveDelegatedWorkerAutoApprove(mesh?.policy, node?.policy),
|
|
9771
|
-
...localDaemonId ? { meshCoordinatorDaemonId: localDaemonId } : {}
|
|
9850
|
+
...localDaemonId ? { meshCoordinatorDaemonId: localDaemonId } : {},
|
|
9851
|
+
// (3) Stamp the originating coordinator session for session-anchored routing
|
|
9852
|
+
// of this co-located worker's completion. Absent → daemon-level fallback.
|
|
9853
|
+
...localSourceCoordinatorSessionId ? { meshCoordinatorSessionId: localSourceCoordinatorSessionId } : {}
|
|
9772
9854
|
});
|
|
9773
9855
|
}
|
|
9774
9856
|
} catch {
|
|
@@ -9781,7 +9863,9 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9781
9863
|
taskId: task.id,
|
|
9782
9864
|
kind: "task",
|
|
9783
9865
|
message: task.message,
|
|
9784
|
-
status: "delivering"
|
|
9866
|
+
status: "delivering",
|
|
9867
|
+
...readNonEmptyString2(task.sourceCoordinatorSessionId) ? { sourceCoordinatorSessionId: readNonEmptyString2(task.sourceCoordinatorSessionId) } : {},
|
|
9868
|
+
...readNonEmptyString2(loadConfig().machineId) ? { sourceCoordinatorDaemonId: readNonEmptyString2(loadConfig().machineId) } : {}
|
|
9785
9869
|
});
|
|
9786
9870
|
components.cliManager.handleCliCommand("agent_command", {
|
|
9787
9871
|
targetSessionId: sessionId,
|
|
@@ -9793,7 +9877,16 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9793
9877
|
}).catch((e) => {
|
|
9794
9878
|
LOG.error("MeshQueue", `Failed to dispatch task locally to node ${nodeId}: ${e?.message}`);
|
|
9795
9879
|
updateSessionDeliveryStatus(delivery.id, "failed", { lastError: e?.message, incrementAttempt: true });
|
|
9796
|
-
updateTaskStatus(meshId, task.id, "
|
|
9880
|
+
updateTaskStatus(meshId, task.id, "pending");
|
|
9881
|
+
try {
|
|
9882
|
+
appendLedgerEntry(meshId, {
|
|
9883
|
+
kind: "dispatch_failed",
|
|
9884
|
+
nodeId,
|
|
9885
|
+
sessionId,
|
|
9886
|
+
payload: { taskId: task.id, deliveryId: delivery.id, error: e?.message, retryable: true }
|
|
9887
|
+
});
|
|
9888
|
+
} catch {
|
|
9889
|
+
}
|
|
9797
9890
|
});
|
|
9798
9891
|
return true;
|
|
9799
9892
|
}
|
|
@@ -10422,6 +10515,9 @@ function injectMeshSystemMessage(components, args) {
|
|
|
10422
10515
|
const workerCoordinatorDaemonId = readNonEmptyString2(
|
|
10423
10516
|
sourceSession?.getState()?.settings?.meshCoordinatorDaemonId
|
|
10424
10517
|
);
|
|
10518
|
+
const workerCoordinatorSessionId = readNonEmptyString2(
|
|
10519
|
+
sourceSession?.getState()?.settings?.meshCoordinatorSessionId
|
|
10520
|
+
) || readNonEmptyString2(args.metadataEvent.meshCoordinatorSessionId);
|
|
10425
10521
|
if (components.onMeshCoordinatorEventForwarded) {
|
|
10426
10522
|
try {
|
|
10427
10523
|
const surfacedPreview = resolveMeshSurfacedSessionPreview(args.metadataEvent);
|
|
@@ -10802,7 +10898,12 @@ function injectMeshSystemMessage(components, args) {
|
|
|
10802
10898
|
workspace: readNonEmptyString2(args.metadataEvent.workspace) || readNonEmptyString2(args.metadataEvent.workspaceName),
|
|
10803
10899
|
metadataEvent: {
|
|
10804
10900
|
...args.metadataEvent,
|
|
10805
|
-
...recoveryContext ? { recoveryContext } : {}
|
|
10901
|
+
...recoveryContext ? { recoveryContext } : {},
|
|
10902
|
+
// Stash the coordinator session id INSIDE metadataEvent too, so it survives the
|
|
10903
|
+
// P2P relay serialization (buildForwardPayloadFromPending spreads metadata; the
|
|
10904
|
+
// handleMeshForwardEvent whitelist reads it back) — a top-level field alone would
|
|
10905
|
+
// be dropped when the event crosses a machine boundary.
|
|
10906
|
+
...workerCoordinatorSessionId ? { meshCoordinatorSessionId: workerCoordinatorSessionId } : {}
|
|
10806
10907
|
},
|
|
10807
10908
|
// Silent lifecycle events (agent:ready / agent:generating_started) carry no
|
|
10808
10909
|
// coordinator message; they are queued only so the coordinator re-runs the
|
|
@@ -10810,10 +10911,13 @@ function injectMeshSystemMessage(components, args) {
|
|
|
10810
10911
|
// entries without a coordinatorMessage, so a live CLI coordinator is not spammed.
|
|
10811
10912
|
...messageText ? { coordinatorMessage: messageText } : {},
|
|
10812
10913
|
queuedAt: Date.now(),
|
|
10813
|
-
...workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {}
|
|
10914
|
+
...workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {},
|
|
10915
|
+
// Top-level session anchor for the local PHASE 2 strict-match on the coordinator
|
|
10916
|
+
// daemon. Absent → daemon-level broadcast (legacy / single-coordinator path).
|
|
10917
|
+
...workerCoordinatorSessionId ? { targetCoordinatorSessionId: workerCoordinatorSessionId } : {}
|
|
10814
10918
|
};
|
|
10815
10919
|
if (queuePendingMeshCoordinatorEvent(pendingEvent)) {
|
|
10816
|
-
LOG.info("MeshEvents", `Queued ${args.event} for coordinator (mesh ${args.meshId}${workerCoordinatorDaemonId ? `, coordinator daemon ${workerCoordinatorDaemonId}` : ""})`);
|
|
10920
|
+
LOG.info("MeshEvents", `Queued ${args.event} for coordinator (mesh ${args.meshId}${workerCoordinatorDaemonId ? `, coordinator daemon ${workerCoordinatorDaemonId}` : ""}${workerCoordinatorSessionId ? `, coordinator session ${workerCoordinatorSessionId}` : ""})`);
|
|
10817
10921
|
}
|
|
10818
10922
|
return { success: true, forwarded: 0 };
|
|
10819
10923
|
}
|
|
@@ -10838,6 +10942,13 @@ function handleMeshForwardEvent(components, payload) {
|
|
|
10838
10942
|
targetSessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId) || readNonEmptyString2(payload.instanceId),
|
|
10839
10943
|
providerType: readNonEmptyString2(payload.providerType),
|
|
10840
10944
|
providerSessionId: readNonEmptyString2(payload.providerSessionId),
|
|
10945
|
+
// Preserve the originating coordinator SESSION id across the machine boundary so
|
|
10946
|
+
// the completion routes back to the exact coordinator session (multi-coordinator).
|
|
10947
|
+
// buildForwardPayloadFromPending spreads the worker event's metadata, so the id
|
|
10948
|
+
// arrives as payload.meshCoordinatorSessionId; the top-level targetCoordinatorSessionId
|
|
10949
|
+
// is also accepted as a fallback. injectMeshSystemMessage re-derives the routing
|
|
10950
|
+
// anchors from this. Absent → daemon-level fallback (version-skew safe).
|
|
10951
|
+
meshCoordinatorSessionId: readNonEmptyString2(payload.meshCoordinatorSessionId) || readNonEmptyString2(payload.targetCoordinatorSessionId),
|
|
10841
10952
|
// Carry the session identity fields the worker provider event emits so the
|
|
10842
10953
|
// coordinator's mirror (updateMeshOwnedSession) gets a real workspace/title/
|
|
10843
10954
|
// settings. Without these the remote-relay hop reconstructs metadataEvent with
|
|
@@ -11734,7 +11845,8 @@ function findLiveCoordinators(components) {
|
|
|
11734
11845
|
if (!meshId) continue;
|
|
11735
11846
|
const status = readNonEmptyString2(state.status).toLowerCase();
|
|
11736
11847
|
const modalParked = status === "waiting_choice" || status === "waiting_approval";
|
|
11737
|
-
|
|
11848
|
+
const sessionId = readNonEmptyString2(state.instanceId);
|
|
11849
|
+
out.push({ meshId, instance: inst, sessionId, idle: status === "idle", modalParked });
|
|
11738
11850
|
}
|
|
11739
11851
|
return out;
|
|
11740
11852
|
}
|
|
@@ -11746,6 +11858,44 @@ function injectPendingIntoCoordinator(coordinator, pending) {
|
|
|
11746
11858
|
...force ? { force: true } : {}
|
|
11747
11859
|
});
|
|
11748
11860
|
}
|
|
11861
|
+
function recordHeldTerminalEventsToLedger(meshId, drainDaemonIds, reason, heldForCoordinatorCount) {
|
|
11862
|
+
let pending;
|
|
11863
|
+
try {
|
|
11864
|
+
pending = getPendingMeshCoordinatorEvents(meshId, drainDaemonIds.length > 0 ? drainDaemonIds : void 0);
|
|
11865
|
+
} catch {
|
|
11866
|
+
return;
|
|
11867
|
+
}
|
|
11868
|
+
for (const event of pending) {
|
|
11869
|
+
if (!shouldForceInjectMeshEvent(event.event)) continue;
|
|
11870
|
+
const fingerprint = buildPendingEventFingerprint(event);
|
|
11871
|
+
const key = `${meshId}::${fingerprint || `${event.event}::${event.nodeId || ""}::${event.queuedAt}`}`;
|
|
11872
|
+
if (heldEventLedgerRecorded.has(key)) continue;
|
|
11873
|
+
heldEventLedgerRecorded.add(key);
|
|
11874
|
+
const finalSummary = readMeshCompletionSummary(event.metadataEvent);
|
|
11875
|
+
try {
|
|
11876
|
+
appendLedgerEntry(meshId, {
|
|
11877
|
+
kind: "event_held",
|
|
11878
|
+
...event.nodeId ? { nodeId: event.nodeId } : {},
|
|
11879
|
+
payload: {
|
|
11880
|
+
event: event.event,
|
|
11881
|
+
reason,
|
|
11882
|
+
recoverable: true,
|
|
11883
|
+
heldForCoordinators: heldForCoordinatorCount,
|
|
11884
|
+
nodeLabel: event.nodeLabel,
|
|
11885
|
+
...event.workspace ? { workspace: event.workspace } : {},
|
|
11886
|
+
targetCoordinatorDaemonId: event.targetCoordinatorDaemonId ?? null,
|
|
11887
|
+
queuedAt: event.queuedAt,
|
|
11888
|
+
...fingerprint ? { fingerprint } : {},
|
|
11889
|
+
...finalSummary ? { finalSummary } : {}
|
|
11890
|
+
}
|
|
11891
|
+
});
|
|
11892
|
+
LOG.info("MeshReconcile", `Ledger-recorded held ${event.event} for mesh ${meshId} (reason ${reason}) \u2014 recoverable from ledger`);
|
|
11893
|
+
} catch (e) {
|
|
11894
|
+
heldEventLedgerRecorded.delete(key);
|
|
11895
|
+
LOG.warn("MeshReconcile", `Failed to ledger-record held ${event.event} for mesh ${meshId}: ${e?.message || e}`);
|
|
11896
|
+
}
|
|
11897
|
+
}
|
|
11898
|
+
}
|
|
11749
11899
|
async function runMeshReconcileTick(components) {
|
|
11750
11900
|
const localDaemonId = readNonEmptyString2(loadConfig().machineId) || void 0;
|
|
11751
11901
|
const drainDaemonIds = resolveCoordinatorDaemonIds(components);
|
|
@@ -11830,6 +11980,21 @@ async function runMeshReconcileTick(components) {
|
|
|
11830
11980
|
if (targetCoordinators.length === 0) {
|
|
11831
11981
|
if (modalParkedCoordinators.length > 0) {
|
|
11832
11982
|
LOG.info("MeshReconcile", `Reconcile skip \u2192 modal-parked: holding pending event(s) for mesh ${meshId} (${modalParkedCoordinators.length} coordinator(s) awaiting a modal answer; events left queued)`);
|
|
11983
|
+
let hasPending = true;
|
|
11984
|
+
if (store) {
|
|
11985
|
+
try {
|
|
11986
|
+
hasPending = store.pendingEventCount(meshId) > 0;
|
|
11987
|
+
} catch {
|
|
11988
|
+
}
|
|
11989
|
+
}
|
|
11990
|
+
if (hasPending) {
|
|
11991
|
+
recordHeldTerminalEventsToLedger(
|
|
11992
|
+
meshId,
|
|
11993
|
+
drainDaemonIds.length > 0 ? drainDaemonIds : localDaemonId ? [localDaemonId] : [],
|
|
11994
|
+
"modal_parked",
|
|
11995
|
+
modalParkedCoordinators.length
|
|
11996
|
+
);
|
|
11997
|
+
}
|
|
11833
11998
|
}
|
|
11834
11999
|
continue;
|
|
11835
12000
|
}
|
|
@@ -11854,12 +12019,55 @@ async function runMeshReconcileTick(components) {
|
|
|
11854
12019
|
const mode = forceOnly ? "force-drain \u2192 generating" : "inject \u2192 idle";
|
|
11855
12020
|
LOG.info("MeshReconcile", `Reconcile ${mode}: ${pendingEvents.length} pending event(s) \u2192 ${targetCoordinators.length} coordinator(s) for mesh ${meshId}`);
|
|
11856
12021
|
for (const pending of pendingEvents) {
|
|
12022
|
+
const wantSession = readNonEmptyString2(pending.targetCoordinatorSessionId);
|
|
12023
|
+
if (wantSession) {
|
|
12024
|
+
const matched = targetCoordinators.filter((c) => c.sessionId === wantSession);
|
|
12025
|
+
if (matched.length === 0) {
|
|
12026
|
+
holdOrExpireStrictUnmatchedEvent(pending, wantSession, meshId);
|
|
12027
|
+
continue;
|
|
12028
|
+
}
|
|
12029
|
+
for (const c of matched) injectPendingIntoCoordinator(c.instance, pending);
|
|
12030
|
+
continue;
|
|
12031
|
+
}
|
|
11857
12032
|
for (const c of targetCoordinators) {
|
|
11858
12033
|
injectPendingIntoCoordinator(c.instance, pending);
|
|
11859
12034
|
}
|
|
11860
12035
|
}
|
|
11861
12036
|
}
|
|
11862
12037
|
}
|
|
12038
|
+
function holdOrExpireStrictUnmatchedEvent(pending, wantSession, meshId) {
|
|
12039
|
+
const queuedAt = typeof pending.queuedAt === "number" ? pending.queuedAt : Date.now();
|
|
12040
|
+
if (Date.now() - queuedAt <= STRICT_SESSION_MATCH_TTL_MS) {
|
|
12041
|
+
try {
|
|
12042
|
+
queuePendingMeshCoordinatorEvent(pending);
|
|
12043
|
+
LOG.info("MeshReconcile", `Strict route hold: coordinator session ${wantSession} not live on mesh ${meshId} \u2014 re-queued (${pending.event})`);
|
|
12044
|
+
} catch (e) {
|
|
12045
|
+
LOG.warn("MeshReconcile", `Strict route re-queue failed for ${pending.event} on mesh ${meshId}: ${e?.message || e}`);
|
|
12046
|
+
}
|
|
12047
|
+
return;
|
|
12048
|
+
}
|
|
12049
|
+
const finalSummary = readMeshCompletionSummary(pending.metadataEvent || {});
|
|
12050
|
+
try {
|
|
12051
|
+
appendLedgerEntry(meshId, {
|
|
12052
|
+
kind: "event_held",
|
|
12053
|
+
...pending.nodeId ? { nodeId: pending.nodeId } : {},
|
|
12054
|
+
payload: {
|
|
12055
|
+
event: pending.event,
|
|
12056
|
+
reason: "strict_route_expired",
|
|
12057
|
+
recoverable: true,
|
|
12058
|
+
targetCoordinatorSessionId: wantSession,
|
|
12059
|
+
targetCoordinatorDaemonId: pending.targetCoordinatorDaemonId ?? null,
|
|
12060
|
+
nodeLabel: pending.nodeLabel,
|
|
12061
|
+
...pending.workspace ? { workspace: pending.workspace } : {},
|
|
12062
|
+
queuedAt,
|
|
12063
|
+
...finalSummary ? { finalSummary } : {}
|
|
12064
|
+
}
|
|
12065
|
+
});
|
|
12066
|
+
LOG.warn("MeshReconcile", `Strict route expire: coordinator session ${wantSession} never returned for mesh ${meshId} \u2014 recorded to ledger (recoverable), dropped (${pending.event})`);
|
|
12067
|
+
} catch (e) {
|
|
12068
|
+
LOG.warn("MeshReconcile", `Failed to ledger-expire strict-unmatched ${pending.event} for mesh ${meshId}: ${e?.message || e}`);
|
|
12069
|
+
}
|
|
12070
|
+
}
|
|
11863
12071
|
async function retryUnresolvedDelegateForwards(components) {
|
|
11864
12072
|
const dispatchMeshCommand = components.dispatchMeshCommand;
|
|
11865
12073
|
if (!dispatchMeshCommand) return;
|
|
@@ -12077,6 +12285,11 @@ function buildForwardPayloadFromPending(event) {
|
|
|
12077
12285
|
meshId: readNonEmptyString2(event?.meshId),
|
|
12078
12286
|
nodeId: readNonEmptyString2(event?.nodeId) || readNonEmptyString2(metadata.meshNodeId),
|
|
12079
12287
|
workspace: readNonEmptyString2(event?.workspace) || readNonEmptyString2(metadata.workspace),
|
|
12288
|
+
// Preserve the originating coordinator session id across the relay. It is normally
|
|
12289
|
+
// carried inside metadataEvent.meshCoordinatorSessionId (spread below), but pass the
|
|
12290
|
+
// top-level field through explicitly too so the handleMeshForwardEvent whitelist
|
|
12291
|
+
// recovers it regardless of which carrier the producing daemon used.
|
|
12292
|
+
...readNonEmptyString2(event?.targetCoordinatorSessionId) ? { targetCoordinatorSessionId: readNonEmptyString2(event.targetCoordinatorSessionId) } : {},
|
|
12080
12293
|
...metadata
|
|
12081
12294
|
};
|
|
12082
12295
|
}
|
|
@@ -12099,7 +12312,7 @@ function setupMeshReconcileLoop(components) {
|
|
|
12099
12312
|
}
|
|
12100
12313
|
};
|
|
12101
12314
|
}
|
|
12102
|
-
var DEFAULT_RECONCILE_INTERVAL_MS, DEFAULT_AUTO_PRUNE_MIN_AGE_MS;
|
|
12315
|
+
var DEFAULT_RECONCILE_INTERVAL_MS, DEFAULT_AUTO_PRUNE_MIN_AGE_MS, heldEventLedgerRecorded, STRICT_SESSION_MATCH_TTL_MS;
|
|
12103
12316
|
var init_mesh_reconcile_loop = __esm({
|
|
12104
12317
|
"src/mesh/mesh-reconcile-loop.ts"() {
|
|
12105
12318
|
"use strict";
|
|
@@ -12107,6 +12320,7 @@ var init_mesh_reconcile_loop = __esm({
|
|
|
12107
12320
|
init_mesh_config();
|
|
12108
12321
|
init_logger();
|
|
12109
12322
|
init_mesh_events_pending();
|
|
12323
|
+
init_mesh_ledger();
|
|
12110
12324
|
init_mesh_runtime_store();
|
|
12111
12325
|
init_mesh_events_coordinator();
|
|
12112
12326
|
init_mesh_unresolved_forward_outbox();
|
|
@@ -12118,6 +12332,8 @@ var init_mesh_reconcile_loop = __esm({
|
|
|
12118
12332
|
init_chat_message_normalization();
|
|
12119
12333
|
DEFAULT_RECONCILE_INTERVAL_MS = 4e3;
|
|
12120
12334
|
DEFAULT_AUTO_PRUNE_MIN_AGE_MS = 24 * 60 * 6e4;
|
|
12335
|
+
heldEventLedgerRecorded = /* @__PURE__ */ new Set();
|
|
12336
|
+
STRICT_SESSION_MATCH_TTL_MS = 6e4;
|
|
12121
12337
|
}
|
|
12122
12338
|
});
|
|
12123
12339
|
|
|
@@ -31983,7 +32199,8 @@ function countNewlines(s) {
|
|
|
31983
32199
|
return n;
|
|
31984
32200
|
}
|
|
31985
32201
|
var SUBMIT_DELAY_FLOOR_MS = 200;
|
|
31986
|
-
var
|
|
32202
|
+
var WIN32_SUBMIT_RESEND_GAP_MS = 350;
|
|
32203
|
+
var WIN32_SUBMIT_MAX_RESENDS = 14;
|
|
31987
32204
|
function resolveSubmitDelayMs(specBeforeSubmit, text) {
|
|
31988
32205
|
const lines = countNewlines(text);
|
|
31989
32206
|
const linesBonus = Math.min(800, lines * 80);
|
|
@@ -32035,6 +32252,10 @@ var FsmDriver = class {
|
|
|
32035
32252
|
* after a re-prime we don't re-inject until the screen changes (which
|
|
32036
32253
|
* resets the stall reference) or another full stall window lapses. */
|
|
32037
32254
|
lastRefocusAt = 0;
|
|
32255
|
+
/** Timer driving the win32 verification-based submit resend loop (see
|
|
32256
|
+
* WIN32_SUBMIT_* and scheduleWin32Submit). Re-arms itself until the FSM
|
|
32257
|
+
* leaves idle (submitted) or the resend budget is spent. */
|
|
32258
|
+
win32SubmitTimer = null;
|
|
32038
32259
|
currentEval = null;
|
|
32039
32260
|
stateHistory = [];
|
|
32040
32261
|
prevStateAt = 0;
|
|
@@ -32151,6 +32372,10 @@ var FsmDriver = class {
|
|
|
32151
32372
|
clearTimeout(this.stallTimer);
|
|
32152
32373
|
this.stallTimer = null;
|
|
32153
32374
|
}
|
|
32375
|
+
if (this.win32SubmitTimer) {
|
|
32376
|
+
clearTimeout(this.win32SubmitTimer);
|
|
32377
|
+
this.win32SubmitTimer = null;
|
|
32378
|
+
}
|
|
32154
32379
|
this.specWatcher?.close();
|
|
32155
32380
|
this.adapter.kill();
|
|
32156
32381
|
}
|
|
@@ -32563,13 +32788,8 @@ var FsmDriver = class {
|
|
|
32563
32788
|
const perChar = sm.delay_ms_per_char ?? 0;
|
|
32564
32789
|
const beforeSubmit = resolveSubmitDelayMs(sm.delay_ms_before_submit, text);
|
|
32565
32790
|
if (process.platform === "win32") {
|
|
32566
|
-
const submitTwice = () => {
|
|
32567
|
-
this.adapter.send_keys(sm.submit_key);
|
|
32568
|
-
setTimeout(() => this.adapter.send_keys(sm.submit_key), WIN32_SUBMIT_REPEAT_GAP_MS);
|
|
32569
|
-
};
|
|
32570
32791
|
this.adapter.send_keys(text);
|
|
32571
|
-
|
|
32572
|
-
else submitTwice();
|
|
32792
|
+
this.scheduleWin32Submit(sm.submit_key, beforeSubmit);
|
|
32573
32793
|
return;
|
|
32574
32794
|
}
|
|
32575
32795
|
if (perChar === 0) {
|
|
@@ -32589,6 +32809,40 @@ var FsmDriver = class {
|
|
|
32589
32809
|
i += 1;
|
|
32590
32810
|
}, perChar);
|
|
32591
32811
|
}
|
|
32812
|
+
/** The agent's current coarse status, derived from the FSM node we're in. */
|
|
32813
|
+
currentStatus() {
|
|
32814
|
+
const st = stateById(this.spec, this.currentStateId);
|
|
32815
|
+
return st ? statusForState(st) : "idle";
|
|
32816
|
+
}
|
|
32817
|
+
/**
|
|
32818
|
+
* win32 verification-based submit. Sends the submit key, waits a gap, and if
|
|
32819
|
+
* the FSM is still 'idle' (the prompt did not submit — the CR was absorbed as
|
|
32820
|
+
* a multiline-paste newline) resends, up to WIN32_SUBMIT_MAX_RESENDS. The
|
|
32821
|
+
* first CR always fires (so a stale/edge status never suppresses the submit);
|
|
32822
|
+
* subsequent resends are gated on still being idle, and stop the instant the
|
|
32823
|
+
* agent leaves idle (submitted → generating / approval). This converges the
|
|
32824
|
+
* nondeterministic multiline window without spamming Enter into the next turn.
|
|
32825
|
+
*/
|
|
32826
|
+
scheduleWin32Submit(submitKey, initialDelayMs) {
|
|
32827
|
+
if (this.win32SubmitTimer) {
|
|
32828
|
+
clearTimeout(this.win32SubmitTimer);
|
|
32829
|
+
this.win32SubmitTimer = null;
|
|
32830
|
+
}
|
|
32831
|
+
const fire = (attempt) => {
|
|
32832
|
+
this.win32SubmitTimer = null;
|
|
32833
|
+
this.adapter.send_keys(submitKey);
|
|
32834
|
+
if (attempt + 1 >= WIN32_SUBMIT_MAX_RESENDS) return;
|
|
32835
|
+
this.win32SubmitTimer = setTimeout(() => {
|
|
32836
|
+
if (this.currentStatus() !== "idle") {
|
|
32837
|
+
this.win32SubmitTimer = null;
|
|
32838
|
+
return;
|
|
32839
|
+
}
|
|
32840
|
+
fire(attempt + 1);
|
|
32841
|
+
}, WIN32_SUBMIT_RESEND_GAP_MS);
|
|
32842
|
+
};
|
|
32843
|
+
if (initialDelayMs > 0) this.win32SubmitTimer = setTimeout(() => fire(0), initialDelayMs);
|
|
32844
|
+
else fire(0);
|
|
32845
|
+
}
|
|
32592
32846
|
handleClickControl(controlId, payload) {
|
|
32593
32847
|
const ctl = (this.spec.control_bar ?? []).find((c) => c.id === controlId);
|
|
32594
32848
|
if (!ctl) return;
|
|
@@ -35012,7 +35266,10 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
35012
35266
|
meshNodeFor: assignment.meshId,
|
|
35013
35267
|
...assignment.nodeId ? { meshNodeId: assignment.nodeId } : {},
|
|
35014
35268
|
...assignment.taskId ? { meshActiveTaskId: assignment.taskId } : {},
|
|
35015
|
-
...assignment.coordinatorDaemonId ? { meshCoordinatorDaemonId: assignment.coordinatorDaemonId } : {}
|
|
35269
|
+
...assignment.coordinatorDaemonId ? { meshCoordinatorDaemonId: assignment.coordinatorDaemonId } : {},
|
|
35270
|
+
// Session-level routing anchor: the originating coordinator session, so this
|
|
35271
|
+
// worker's completion events route back to the exact session that dispatched it.
|
|
35272
|
+
...assignment.coordinatorSessionId ? { meshCoordinatorSessionId: assignment.coordinatorSessionId } : {}
|
|
35016
35273
|
};
|
|
35017
35274
|
this.adapter.updateRuntimeSettings?.(this.settings);
|
|
35018
35275
|
}
|
|
@@ -37996,6 +38253,12 @@ Enable and detect this provider from the Machine Providers page before starting
|
|
|
37996
38253
|
);
|
|
37997
38254
|
}
|
|
37998
38255
|
const key = crypto5.randomUUID();
|
|
38256
|
+
{
|
|
38257
|
+
const coordinatorMeshId = options?.settingsOverride?.meshCoordinatorFor;
|
|
38258
|
+
if (typeof coordinatorMeshId === "string" && coordinatorMeshId.trim()) {
|
|
38259
|
+
options = { ...options, extraEnv: { ...options?.extraEnv || {}, ADHDEV_COORDINATOR_SESSION_ID: key } };
|
|
38260
|
+
}
|
|
38261
|
+
}
|
|
37999
38262
|
const sessionRegistry = this.deps.getSessionRegistry?.() || null;
|
|
38000
38263
|
if (provider && provider.category === "acp") {
|
|
38001
38264
|
const instanceManager2 = this.deps.getInstanceManager();
|
|
@@ -44410,6 +44673,40 @@ function readCachedInlineMeshActiveSessions(node) {
|
|
|
44410
44673
|
const sessionId = readStringValue(fallbackSession.id, fallbackSession.sessionId, fallbackSession.session_id, node?.activeSessionId, node?.active_session_id, node?.sessionId, node?.session_id);
|
|
44411
44674
|
return sessionId ? [sessionId] : [];
|
|
44412
44675
|
}
|
|
44676
|
+
function collectMeshNodeHostedSessionIds(node) {
|
|
44677
|
+
const ids = /* @__PURE__ */ new Set();
|
|
44678
|
+
for (const id of readCachedInlineMeshActiveSessions(node)) ids.add(id);
|
|
44679
|
+
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
44680
|
+
for (const value of [
|
|
44681
|
+
node?.activeSessions,
|
|
44682
|
+
node?.active_sessions,
|
|
44683
|
+
node?.activeSessionDetails,
|
|
44684
|
+
node?.active_session_details,
|
|
44685
|
+
node?.sessions,
|
|
44686
|
+
node?.sessionDetails,
|
|
44687
|
+
node?.session_details,
|
|
44688
|
+
readObjectRecord(node?.lastProbe).sessions,
|
|
44689
|
+
readObjectRecord(node?.last_probe).sessions,
|
|
44690
|
+
cachedStatus.activeSessions,
|
|
44691
|
+
cachedStatus.active_sessions,
|
|
44692
|
+
cachedStatus.activeSessionDetails,
|
|
44693
|
+
cachedStatus.active_session_details,
|
|
44694
|
+
cachedStatus.sessions
|
|
44695
|
+
]) {
|
|
44696
|
+
if (!Array.isArray(value)) continue;
|
|
44697
|
+
for (const item of value) {
|
|
44698
|
+
if (typeof item === "string") {
|
|
44699
|
+
const id2 = readStringValue(item);
|
|
44700
|
+
if (id2) ids.add(id2);
|
|
44701
|
+
continue;
|
|
44702
|
+
}
|
|
44703
|
+
const record = readObjectRecord(item);
|
|
44704
|
+
const id = readStringValue(record.id, record.sessionId, record.session_id, record.runtimeSessionId, record.instanceId);
|
|
44705
|
+
if (id) ids.add(id);
|
|
44706
|
+
}
|
|
44707
|
+
}
|
|
44708
|
+
return ids;
|
|
44709
|
+
}
|
|
44413
44710
|
function resolveMeshNodeAttribution(node) {
|
|
44414
44711
|
const record = readObjectRecord(node);
|
|
44415
44712
|
return {
|
|
@@ -46288,25 +46585,47 @@ var DaemonCommandRouter = class {
|
|
|
46288
46585
|
* controlbar commands do not — so the controlbar buttons appear to do nothing.
|
|
46289
46586
|
*
|
|
46290
46587
|
* Mirror the existing node-level remote-forward pattern (fast_forward_mesh_node etc.):
|
|
46291
|
-
* scan the
|
|
46292
|
-
*
|
|
46293
|
-
*
|
|
46294
|
-
*
|
|
46588
|
+
* scan the candidate mesh nodes for the one hosting the targetSessionId, and return its
|
|
46589
|
+
* daemonId when that daemonId is a remote daemon (i.e. not this coordinator's own
|
|
46590
|
+
* statusInstanceId). Returns undefined for a locally-hosted session (no forward — execute
|
|
46591
|
+
* locally as before) or when ownership can't be resolved.
|
|
46592
|
+
*
|
|
46593
|
+
* The candidate set spans BOTH the cached inline-mesh nodes and the live aggregate
|
|
46594
|
+
* mesh-status snapshots. The inline cache reliably carries only each node's single primary
|
|
46595
|
+
* session (cachedStatus.activeSession), so a worker hosting more than one session exposes its
|
|
46596
|
+
* non-primary sessions only on the aggregate snapshot nodes (status.activeSessions /
|
|
46597
|
+
* activeSessionDetails, built from live session records). collectMeshNodeHostedSessionIds does
|
|
46598
|
+
* the wider plural-shape scan so a controlbar/modal command targeting a non-primary remote
|
|
46599
|
+
* session still resolves its owner — the singular readCachedInlineMeshActiveSessions semantics
|
|
46600
|
+
* other consumers depend on stay untouched.
|
|
46295
46601
|
*/
|
|
46296
46602
|
resolveRemoteMeshSessionOwnerDaemonId(sessionId) {
|
|
46297
46603
|
const trimmed = typeof sessionId === "string" ? sessionId.trim() : "";
|
|
46298
46604
|
if (!trimmed) return void 0;
|
|
46299
46605
|
const selfDaemonId = this.deps.statusInstanceId;
|
|
46300
|
-
for (const node of this.
|
|
46301
|
-
|
|
46302
|
-
if (!nodeSessions.includes(trimmed)) continue;
|
|
46606
|
+
for (const node of this.collectMeshSessionOwnerCandidateNodes()) {
|
|
46607
|
+
if (!collectMeshNodeHostedSessionIds(node).has(trimmed)) continue;
|
|
46303
46608
|
const nodeDaemonId = readMeshNodeDaemonId(readObjectRecord(node));
|
|
46304
|
-
if (!nodeDaemonId)
|
|
46609
|
+
if (!nodeDaemonId) continue;
|
|
46305
46610
|
if (selfDaemonId && nodeDaemonId === selfDaemonId) return void 0;
|
|
46306
46611
|
return nodeDaemonId;
|
|
46307
46612
|
}
|
|
46308
46613
|
return void 0;
|
|
46309
46614
|
}
|
|
46615
|
+
/**
|
|
46616
|
+
* Candidate nodes for remote-session owner resolution: the cached inline-mesh nodes (which
|
|
46617
|
+
* carry each node's primary session) plus the nodes from every cached aggregate mesh-status
|
|
46618
|
+
* snapshot (which carry each node's full live session list). getCachedInlineMeshNodes()
|
|
46619
|
+
* returns a fresh array, so appending the aggregate nodes never mutates cached state.
|
|
46620
|
+
*/
|
|
46621
|
+
collectMeshSessionOwnerCandidateNodes() {
|
|
46622
|
+
const nodes = this.getCachedInlineMeshNodes();
|
|
46623
|
+
for (const cached3 of this.aggregateMeshStatusCache.values()) {
|
|
46624
|
+
const snapshotNodes = cached3?.snapshot?.nodes;
|
|
46625
|
+
if (Array.isArray(snapshotNodes)) nodes.push(...snapshotNodes);
|
|
46626
|
+
}
|
|
46627
|
+
return nodes;
|
|
46628
|
+
}
|
|
46310
46629
|
getCachedInlineMesh(meshId, inlineMesh) {
|
|
46311
46630
|
if (inlineMesh && typeof inlineMesh === "object") {
|
|
46312
46631
|
return this.warmInlineMeshCache(meshId, inlineMesh);
|
|
@@ -48326,7 +48645,10 @@ ${hintLines.join("\n")}` : "",
|
|
|
48326
48645
|
{
|
|
48327
48646
|
meshId: dispatchMeshContext.meshId,
|
|
48328
48647
|
nodeId: dispatchMeshContext.nodeId,
|
|
48329
|
-
coordinatorDaemonId: dispatchMeshContext.coordinatorDaemonId
|
|
48648
|
+
coordinatorDaemonId: dispatchMeshContext.coordinatorDaemonId,
|
|
48649
|
+
// Session-level anchor: preserved across the P2P dispatch to a
|
|
48650
|
+
// remote worker so its completion echoes back to the right session.
|
|
48651
|
+
coordinatorSessionId: dispatchMeshContext.coordinatorSessionId
|
|
48330
48652
|
}
|
|
48331
48653
|
);
|
|
48332
48654
|
if (stamp) inst.updateSettings(stamp);
|