@adhdev/daemon-core 0.9.82-rc.343 → 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 +599 -324
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +619 -344
- 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/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 +38 -2
- 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/dist/index.mjs
CHANGED
|
@@ -311,10 +311,10 @@ function readInjected(value) {
|
|
|
311
311
|
}
|
|
312
312
|
function getDaemonBuildInfo() {
|
|
313
313
|
if (cached) return cached;
|
|
314
|
-
const commit = readInjected(true ? "
|
|
315
|
-
const commitShort = readInjected(true ? "
|
|
316
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
317
|
-
const builtAt = readInjected(true ? "2026-06-
|
|
314
|
+
const commit = readInjected(true ? "d04317b7eab7aec6a5557cb1991af603c7e5143c" : void 0) ?? "unknown";
|
|
315
|
+
const commitShort = readInjected(true ? "d04317b7" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
316
|
+
const version = readInjected(true ? "0.9.82-rc.344" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
317
|
+
const builtAt = readInjected(true ? "2026-06-21T13:38:42.110Z" : void 0);
|
|
318
318
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
319
319
|
return cached;
|
|
320
320
|
}
|
|
@@ -2536,6 +2536,297 @@ Follow these recovery rules:
|
|
|
2536
2536
|
}
|
|
2537
2537
|
});
|
|
2538
2538
|
|
|
2539
|
+
// src/logging/async-batch-writer.ts
|
|
2540
|
+
import * as fs3 from "fs";
|
|
2541
|
+
var AsyncBatchWriter;
|
|
2542
|
+
var init_async_batch_writer = __esm({
|
|
2543
|
+
"src/logging/async-batch-writer.ts"() {
|
|
2544
|
+
"use strict";
|
|
2545
|
+
AsyncBatchWriter = class {
|
|
2546
|
+
// Maps filePath -> string buffer
|
|
2547
|
+
static buffers = /* @__PURE__ */ new Map();
|
|
2548
|
+
static writePromises = /* @__PURE__ */ new Map();
|
|
2549
|
+
static flushTimer = null;
|
|
2550
|
+
/**
|
|
2551
|
+
* Queues data to be written to a file asynchronously in a batch.
|
|
2552
|
+
*/
|
|
2553
|
+
static write(filePath, data) {
|
|
2554
|
+
let buf = this.buffers.get(filePath);
|
|
2555
|
+
if (!buf) {
|
|
2556
|
+
buf = [];
|
|
2557
|
+
this.buffers.set(filePath, buf);
|
|
2558
|
+
}
|
|
2559
|
+
buf.push(data);
|
|
2560
|
+
if (!this.flushTimer) {
|
|
2561
|
+
this.flushTimer = setTimeout(() => {
|
|
2562
|
+
this.flushTimer = null;
|
|
2563
|
+
this.flushAll();
|
|
2564
|
+
}, 50);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
static async flushAll() {
|
|
2568
|
+
const entries = Array.from(this.buffers.entries());
|
|
2569
|
+
this.buffers.clear();
|
|
2570
|
+
for (const [filePath, buffer] of entries) {
|
|
2571
|
+
const dataToWrite = buffer.join("");
|
|
2572
|
+
const doWrite = async () => {
|
|
2573
|
+
try {
|
|
2574
|
+
const prevPromise = this.writePromises.get(filePath);
|
|
2575
|
+
if (prevPromise) await prevPromise;
|
|
2576
|
+
await fs3.promises.appendFile(filePath, dataToWrite, { encoding: "utf-8", mode: 384 });
|
|
2577
|
+
} catch {
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2580
|
+
const writePromise = doWrite();
|
|
2581
|
+
this.writePromises.set(filePath, writePromise);
|
|
2582
|
+
writePromise.finally(() => {
|
|
2583
|
+
if (this.writePromises.get(filePath) === writePromise) {
|
|
2584
|
+
this.writePromises.delete(filePath);
|
|
2585
|
+
}
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
});
|
|
2592
|
+
|
|
2593
|
+
// src/logging/logger.ts
|
|
2594
|
+
var logger_exports = {};
|
|
2595
|
+
__export(logger_exports, {
|
|
2596
|
+
LOG: () => LOG,
|
|
2597
|
+
LOG_DIR_PATH: () => LOG_DIR_PATH,
|
|
2598
|
+
LOG_PATH: () => LOG_PATH,
|
|
2599
|
+
daemonLog: () => daemonLog,
|
|
2600
|
+
getCurrentDaemonLogPath: () => getCurrentDaemonLogPath,
|
|
2601
|
+
getDaemonLogDir: () => getDaemonLogDir,
|
|
2602
|
+
getLogBufferSize: () => getLogBufferSize,
|
|
2603
|
+
getLogLevel: () => getLogLevel,
|
|
2604
|
+
getLogPath: () => getLogPath,
|
|
2605
|
+
getRecentLogs: () => getRecentLogs,
|
|
2606
|
+
installGlobalInterceptor: () => installGlobalInterceptor,
|
|
2607
|
+
setLogLevel: () => setLogLevel
|
|
2608
|
+
});
|
|
2609
|
+
import * as fs4 from "fs";
|
|
2610
|
+
import * as path9 from "path";
|
|
2611
|
+
import * as os3 from "os";
|
|
2612
|
+
function setLogLevel(level) {
|
|
2613
|
+
currentLevel = level;
|
|
2614
|
+
daemonLog("Logger", `Log level set to: ${level}`, "info");
|
|
2615
|
+
}
|
|
2616
|
+
function getLogLevel() {
|
|
2617
|
+
return currentLevel;
|
|
2618
|
+
}
|
|
2619
|
+
function getDateStr() {
|
|
2620
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2621
|
+
}
|
|
2622
|
+
function getDaemonLogDir() {
|
|
2623
|
+
return LOG_DIR;
|
|
2624
|
+
}
|
|
2625
|
+
function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
|
|
2626
|
+
return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
|
|
2627
|
+
}
|
|
2628
|
+
function checkDateRotation() {
|
|
2629
|
+
const today = getDateStr();
|
|
2630
|
+
if (today !== currentDate) {
|
|
2631
|
+
currentDate = today;
|
|
2632
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
2633
|
+
cleanOldLogs();
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
function cleanOldLogs() {
|
|
2637
|
+
try {
|
|
2638
|
+
const files = fs4.readdirSync(LOG_DIR).filter((f) => f.startsWith("daemon-") && f.endsWith(".log"));
|
|
2639
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
2640
|
+
cutoff.setDate(cutoff.getDate() - MAX_LOG_DAYS);
|
|
2641
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
2642
|
+
for (const file of files) {
|
|
2643
|
+
const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
|
|
2644
|
+
if (dateMatch && dateMatch[1] < cutoffStr) {
|
|
2645
|
+
try {
|
|
2646
|
+
fs4.unlinkSync(path9.join(LOG_DIR, file));
|
|
2647
|
+
} catch {
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
} catch {
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
function rotateSizeIfNeeded() {
|
|
2655
|
+
try {
|
|
2656
|
+
const stat2 = fs4.statSync(currentLogFile);
|
|
2657
|
+
if (stat2.size > MAX_LOG_SIZE) {
|
|
2658
|
+
const backup = currentLogFile.replace(".log", ".1.log");
|
|
2659
|
+
try {
|
|
2660
|
+
fs4.unlinkSync(backup);
|
|
2661
|
+
} catch {
|
|
2662
|
+
}
|
|
2663
|
+
fs4.renameSync(currentLogFile, backup);
|
|
2664
|
+
}
|
|
2665
|
+
} catch {
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
function writeToFile(line) {
|
|
2669
|
+
try {
|
|
2670
|
+
if (++writeCount % 1e3 === 0) {
|
|
2671
|
+
checkDateRotation();
|
|
2672
|
+
rotateSizeIfNeeded();
|
|
2673
|
+
}
|
|
2674
|
+
AsyncBatchWriter.write(currentLogFile, line + "\n");
|
|
2675
|
+
} catch {
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
function getRecentLogs(count = 50, minLevel = "info") {
|
|
2679
|
+
const minNum = LEVEL_NUM[minLevel];
|
|
2680
|
+
const filtered = ringBuffer.filter((e) => LEVEL_NUM[e.level] >= minNum);
|
|
2681
|
+
return filtered.slice(-count);
|
|
2682
|
+
}
|
|
2683
|
+
function getLogBufferSize() {
|
|
2684
|
+
return ringBuffer.length;
|
|
2685
|
+
}
|
|
2686
|
+
function ts() {
|
|
2687
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
2688
|
+
}
|
|
2689
|
+
function fullTs() {
|
|
2690
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2691
|
+
}
|
|
2692
|
+
function daemonLog(category, msg, level = "info") {
|
|
2693
|
+
const shouldOutput = LEVEL_NUM[level] >= LEVEL_NUM[currentLevel];
|
|
2694
|
+
const label = LEVEL_LABEL[level];
|
|
2695
|
+
const line = `[${ts()}] [${label}] [${category}] ${msg}`;
|
|
2696
|
+
if (!shouldOutput) return;
|
|
2697
|
+
writeToFile(line);
|
|
2698
|
+
ringBuffer.push({ ts: Date.now(), level, category, message: msg });
|
|
2699
|
+
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
2700
|
+
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
2701
|
+
}
|
|
2702
|
+
origConsoleLog(line);
|
|
2703
|
+
}
|
|
2704
|
+
function installGlobalInterceptor() {
|
|
2705
|
+
if (interceptorInstalled) return;
|
|
2706
|
+
interceptorInstalled = true;
|
|
2707
|
+
const stripAnsi4 = (str) => str.replace(/\x1B\[[0-9;]*m/g, "");
|
|
2708
|
+
const isDaemonLogLine = (msg) => /\[(DBG|INF|WRN|ERR)\]/.test(msg);
|
|
2709
|
+
console.log = (...args) => {
|
|
2710
|
+
origConsoleLog(...args);
|
|
2711
|
+
try {
|
|
2712
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
2713
|
+
const clean = stripAnsi4(msg);
|
|
2714
|
+
if (isDaemonLogLine(clean)) return;
|
|
2715
|
+
const line = clean.startsWith("[20") ? clean : `[${fullTs()}] ${clean}`;
|
|
2716
|
+
writeToFile(line);
|
|
2717
|
+
const catMatch = clean.match(/\[([^\]]+)\]/);
|
|
2718
|
+
ringBuffer.push({
|
|
2719
|
+
ts: Date.now(),
|
|
2720
|
+
level: "info",
|
|
2721
|
+
category: catMatch?.[1] || "System",
|
|
2722
|
+
message: clean
|
|
2723
|
+
});
|
|
2724
|
+
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
2725
|
+
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
2726
|
+
}
|
|
2727
|
+
} catch {
|
|
2728
|
+
}
|
|
2729
|
+
};
|
|
2730
|
+
console.error = (...args) => {
|
|
2731
|
+
origConsoleError(...args);
|
|
2732
|
+
try {
|
|
2733
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
2734
|
+
const clean = stripAnsi4(msg);
|
|
2735
|
+
if (isDaemonLogLine(clean)) return;
|
|
2736
|
+
const line = `[${fullTs()}] [ERROR] ${clean}`;
|
|
2737
|
+
writeToFile(line);
|
|
2738
|
+
ringBuffer.push({ ts: Date.now(), level: "error", category: "System", message: clean });
|
|
2739
|
+
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
2740
|
+
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
2741
|
+
}
|
|
2742
|
+
} catch {
|
|
2743
|
+
}
|
|
2744
|
+
};
|
|
2745
|
+
console.warn = (...args) => {
|
|
2746
|
+
origConsoleWarn(...args);
|
|
2747
|
+
try {
|
|
2748
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
2749
|
+
const clean = stripAnsi4(msg);
|
|
2750
|
+
if (isDaemonLogLine(clean)) return;
|
|
2751
|
+
const line = `[${fullTs()}] [WARN] ${clean}`;
|
|
2752
|
+
writeToFile(line);
|
|
2753
|
+
ringBuffer.push({ ts: Date.now(), level: "warn", category: "System", message: clean });
|
|
2754
|
+
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
2755
|
+
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
2756
|
+
}
|
|
2757
|
+
} catch {
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
writeToFile(`
|
|
2761
|
+
=== ADHDev Daemon started at ${fullTs()} ===`);
|
|
2762
|
+
writeToFile(`Log file: ${currentLogFile}`);
|
|
2763
|
+
writeToFile(`Log level: ${currentLevel}`);
|
|
2764
|
+
}
|
|
2765
|
+
function getLogPath() {
|
|
2766
|
+
return currentLogFile;
|
|
2767
|
+
}
|
|
2768
|
+
var 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;
|
|
2769
|
+
var init_logger = __esm({
|
|
2770
|
+
"src/logging/logger.ts"() {
|
|
2771
|
+
"use strict";
|
|
2772
|
+
init_async_batch_writer();
|
|
2773
|
+
LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
2774
|
+
LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
|
|
2775
|
+
currentLevel = "info";
|
|
2776
|
+
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");
|
|
2777
|
+
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
2778
|
+
MAX_LOG_DAYS = 7;
|
|
2779
|
+
try {
|
|
2780
|
+
fs4.mkdirSync(LOG_DIR, { recursive: true });
|
|
2781
|
+
} catch {
|
|
2782
|
+
}
|
|
2783
|
+
currentDate = getDateStr();
|
|
2784
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
2785
|
+
cleanOldLogs();
|
|
2786
|
+
try {
|
|
2787
|
+
const oldLog = path9.join(LOG_DIR, "daemon.log");
|
|
2788
|
+
if (fs4.existsSync(oldLog)) {
|
|
2789
|
+
const stat2 = fs4.statSync(oldLog);
|
|
2790
|
+
const oldDate = stat2.mtime.toISOString().slice(0, 10);
|
|
2791
|
+
fs4.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
|
|
2792
|
+
}
|
|
2793
|
+
const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
|
|
2794
|
+
if (fs4.existsSync(oldLogBackup)) {
|
|
2795
|
+
fs4.unlinkSync(oldLogBackup);
|
|
2796
|
+
}
|
|
2797
|
+
} catch {
|
|
2798
|
+
}
|
|
2799
|
+
writeCount = 0;
|
|
2800
|
+
RING_BUFFER_SIZE = 200;
|
|
2801
|
+
ringBuffer = [];
|
|
2802
|
+
origConsoleLog = console.log.bind(console);
|
|
2803
|
+
origConsoleError = console.error.bind(console);
|
|
2804
|
+
origConsoleWarn = console.warn.bind(console);
|
|
2805
|
+
LOG = {
|
|
2806
|
+
debug: (category, msg) => daemonLog(category, msg, "debug"),
|
|
2807
|
+
info: (category, msg) => daemonLog(category, msg, "info"),
|
|
2808
|
+
warn: (category, msg) => daemonLog(category, msg, "warn"),
|
|
2809
|
+
error: (category, msg) => daemonLog(category, msg, "error"),
|
|
2810
|
+
/**
|
|
2811
|
+
* Create a scoped logger for a specific component.
|
|
2812
|
+
* Category is baked in so callers only pass the message.
|
|
2813
|
+
*/
|
|
2814
|
+
forComponent(category) {
|
|
2815
|
+
return {
|
|
2816
|
+
debug: (msg) => daemonLog(category, msg, "debug"),
|
|
2817
|
+
info: (msg) => daemonLog(category, msg, "info"),
|
|
2818
|
+
warn: (msg) => daemonLog(category, msg, "warn"),
|
|
2819
|
+
error: (msg) => daemonLog(category, msg, "error"),
|
|
2820
|
+
asLogFn: (level = "info") => (msg) => daemonLog(category, msg, level)
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
interceptorInstalled = false;
|
|
2825
|
+
LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
|
|
2826
|
+
LOG_DIR_PATH = LOG_DIR;
|
|
2827
|
+
}
|
|
2828
|
+
});
|
|
2829
|
+
|
|
2539
2830
|
// src/system/load-better-sqlite3.ts
|
|
2540
2831
|
import { createRequire } from "module";
|
|
2541
2832
|
function loadBetterSqlite3() {
|
|
@@ -2596,8 +2887,8 @@ __export(mesh_ledger_exports, {
|
|
|
2596
2887
|
readLedgerSlice: () => readLedgerSlice,
|
|
2597
2888
|
readLedgerSliceFromStore: () => readLedgerSliceFromStore
|
|
2598
2889
|
});
|
|
2599
|
-
import { appendFileSync, existsSync as
|
|
2600
|
-
import { join as
|
|
2890
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync as statSync4, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2891
|
+
import { join as join8 } from "path";
|
|
2601
2892
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
2602
2893
|
import { EventEmitter } from "events";
|
|
2603
2894
|
function isIntentionalCleanupStopEntry(entry) {
|
|
@@ -2606,41 +2897,41 @@ function isIntentionalCleanupStopEntry(entry) {
|
|
|
2606
2897
|
return payload.intentional === true && (payload.reason === "operator_cleanup" || payload.intentionalStopReason === "operator_cleanup" || payload.source === "mesh_cleanup_sessions" || payload.source === "mesh_remove_node");
|
|
2607
2898
|
}
|
|
2608
2899
|
function getLedgerDir() {
|
|
2609
|
-
const dir =
|
|
2610
|
-
if (!
|
|
2611
|
-
|
|
2900
|
+
const dir = join8(getConfigDir(), LEDGER_DIR_NAME);
|
|
2901
|
+
if (!existsSync7(dir)) {
|
|
2902
|
+
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
2612
2903
|
}
|
|
2613
2904
|
return dir;
|
|
2614
2905
|
}
|
|
2615
2906
|
function getLedgerPath(meshId) {
|
|
2616
2907
|
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2617
|
-
return
|
|
2908
|
+
return join8(getLedgerDir(), `${safe}.jsonl`);
|
|
2618
2909
|
}
|
|
2619
2910
|
function getRotatedPath(meshId, index) {
|
|
2620
2911
|
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2621
|
-
return
|
|
2912
|
+
return join8(getLedgerDir(), `${safe}.${index}.jsonl`);
|
|
2622
2913
|
}
|
|
2623
2914
|
function getArchivePath(meshId) {
|
|
2624
2915
|
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2625
|
-
return
|
|
2916
|
+
return join8(getLedgerDir(), `${safe}.archive.jsonl`);
|
|
2626
2917
|
}
|
|
2627
2918
|
function getRotatedArchivePath(meshId, index) {
|
|
2628
2919
|
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2629
|
-
return
|
|
2920
|
+
return join8(getLedgerDir(), `${safe}.archive.${index}.jsonl`);
|
|
2630
2921
|
}
|
|
2631
2922
|
function getArchivedCountsPath(meshId) {
|
|
2632
2923
|
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2633
|
-
return
|
|
2924
|
+
return join8(getLedgerDir(), `${safe}.archived-counts.json`);
|
|
2634
2925
|
}
|
|
2635
2926
|
function rotateArchiveFile(meshId, archivePath) {
|
|
2636
2927
|
let index = 1;
|
|
2637
|
-
while (
|
|
2928
|
+
while (existsSync7(getRotatedArchivePath(meshId, index))) {
|
|
2638
2929
|
index++;
|
|
2639
2930
|
if (index > 5) break;
|
|
2640
2931
|
}
|
|
2641
2932
|
if (index > 5) index = 5;
|
|
2642
2933
|
try {
|
|
2643
|
-
|
|
2934
|
+
renameSync2(archivePath, getRotatedArchivePath(meshId, index));
|
|
2644
2935
|
} catch (e) {
|
|
2645
2936
|
process.stderr.write(`[adhdev-mesh] Archive rotation failed for mesh ${meshId}: ${e?.message || e}
|
|
2646
2937
|
`);
|
|
@@ -2648,7 +2939,7 @@ function rotateArchiveFile(meshId, archivePath) {
|
|
|
2648
2939
|
}
|
|
2649
2940
|
function readArchivedCounts(meshId) {
|
|
2650
2941
|
const path42 = getArchivedCountsPath(meshId);
|
|
2651
|
-
if (!
|
|
2942
|
+
if (!existsSync7(path42)) return { taskCompleted: 0, taskFailed: 0, taskStalled: 0, recoveryAttempted: 0, totalArchived: 0, lastArchivedAt: "" };
|
|
2652
2943
|
try {
|
|
2653
2944
|
return JSON.parse(readFileSync5(path42, "utf-8"));
|
|
2654
2945
|
} catch {
|
|
@@ -2689,7 +2980,7 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
|
|
|
2689
2980
|
}
|
|
2690
2981
|
function compactLedger(meshId) {
|
|
2691
2982
|
const filePath = getLedgerPath(meshId);
|
|
2692
|
-
if (!
|
|
2983
|
+
if (!existsSync7(filePath)) return { archivedCount: 0, retainedCount: 0 };
|
|
2693
2984
|
const cutoff = Date.now() - ARCHIVE_TERMINAL_OLDER_THAN_MS;
|
|
2694
2985
|
const entries = readLedgerEntries(meshId);
|
|
2695
2986
|
const keep = [];
|
|
@@ -2704,7 +2995,7 @@ function compactLedger(meshId) {
|
|
|
2704
2995
|
if (archive.length === 0) return { archivedCount: 0, retainedCount: keep.length };
|
|
2705
2996
|
const archivePath = getArchivePath(meshId);
|
|
2706
2997
|
try {
|
|
2707
|
-
if (
|
|
2998
|
+
if (existsSync7(archivePath) && statSync4(archivePath).size > 50 * 1024 * 1024) {
|
|
2708
2999
|
rotateArchiveFile(meshId, archivePath);
|
|
2709
3000
|
}
|
|
2710
3001
|
const archiveLines = archive.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
@@ -2854,9 +3145,9 @@ function appendLedgerEntry(meshId, partial) {
|
|
|
2854
3145
|
...partial
|
|
2855
3146
|
};
|
|
2856
3147
|
const filePath = getLedgerPath(meshId);
|
|
2857
|
-
if (
|
|
3148
|
+
if (existsSync7(filePath)) {
|
|
2858
3149
|
try {
|
|
2859
|
-
const stat2 =
|
|
3150
|
+
const stat2 = statSync4(filePath);
|
|
2860
3151
|
if (stat2.size >= MAX_FILE_SIZE_BYTES) {
|
|
2861
3152
|
rotateLedgerFile(meshId, filePath);
|
|
2862
3153
|
} else if (stat2.size >= COMPACT_THRESHOLD_BYTES) {
|
|
@@ -2952,7 +3243,7 @@ function appendRemoteLedgerEntries(meshId, entries) {
|
|
|
2952
3243
|
}
|
|
2953
3244
|
function readLedgerFile(meshId) {
|
|
2954
3245
|
const filePath = getLedgerPath(meshId);
|
|
2955
|
-
if (!
|
|
3246
|
+
if (!existsSync7(filePath)) return [];
|
|
2956
3247
|
let content;
|
|
2957
3248
|
try {
|
|
2958
3249
|
content = readFileSync5(filePath, "utf-8");
|
|
@@ -3216,13 +3507,13 @@ function getSessionRecoveryContext(meshId, opts) {
|
|
|
3216
3507
|
}
|
|
3217
3508
|
function rotateLedgerFile(meshId, currentPath) {
|
|
3218
3509
|
let index = 1;
|
|
3219
|
-
while (
|
|
3510
|
+
while (existsSync7(getRotatedPath(meshId, index))) {
|
|
3220
3511
|
index++;
|
|
3221
3512
|
if (index > 10) break;
|
|
3222
3513
|
}
|
|
3223
3514
|
if (index > 10) index = 10;
|
|
3224
3515
|
try {
|
|
3225
|
-
|
|
3516
|
+
renameSync2(currentPath, getRotatedPath(meshId, index));
|
|
3226
3517
|
} catch (e) {
|
|
3227
3518
|
process.stderr.write(`[adhdev-mesh] Ledger rotation failed for mesh ${meshId}: ${e?.message || e}. File will continue to grow.
|
|
3228
3519
|
`);
|
|
@@ -3254,297 +3545,6 @@ var init_mesh_ledger = __esm({
|
|
|
3254
3545
|
}
|
|
3255
3546
|
});
|
|
3256
3547
|
|
|
3257
|
-
// src/logging/async-batch-writer.ts
|
|
3258
|
-
import * as fs3 from "fs";
|
|
3259
|
-
var AsyncBatchWriter;
|
|
3260
|
-
var init_async_batch_writer = __esm({
|
|
3261
|
-
"src/logging/async-batch-writer.ts"() {
|
|
3262
|
-
"use strict";
|
|
3263
|
-
AsyncBatchWriter = class {
|
|
3264
|
-
// Maps filePath -> string buffer
|
|
3265
|
-
static buffers = /* @__PURE__ */ new Map();
|
|
3266
|
-
static writePromises = /* @__PURE__ */ new Map();
|
|
3267
|
-
static flushTimer = null;
|
|
3268
|
-
/**
|
|
3269
|
-
* Queues data to be written to a file asynchronously in a batch.
|
|
3270
|
-
*/
|
|
3271
|
-
static write(filePath, data) {
|
|
3272
|
-
let buf = this.buffers.get(filePath);
|
|
3273
|
-
if (!buf) {
|
|
3274
|
-
buf = [];
|
|
3275
|
-
this.buffers.set(filePath, buf);
|
|
3276
|
-
}
|
|
3277
|
-
buf.push(data);
|
|
3278
|
-
if (!this.flushTimer) {
|
|
3279
|
-
this.flushTimer = setTimeout(() => {
|
|
3280
|
-
this.flushTimer = null;
|
|
3281
|
-
this.flushAll();
|
|
3282
|
-
}, 50);
|
|
3283
|
-
}
|
|
3284
|
-
}
|
|
3285
|
-
static async flushAll() {
|
|
3286
|
-
const entries = Array.from(this.buffers.entries());
|
|
3287
|
-
this.buffers.clear();
|
|
3288
|
-
for (const [filePath, buffer] of entries) {
|
|
3289
|
-
const dataToWrite = buffer.join("");
|
|
3290
|
-
const doWrite = async () => {
|
|
3291
|
-
try {
|
|
3292
|
-
const prevPromise = this.writePromises.get(filePath);
|
|
3293
|
-
if (prevPromise) await prevPromise;
|
|
3294
|
-
await fs3.promises.appendFile(filePath, dataToWrite, { encoding: "utf-8", mode: 384 });
|
|
3295
|
-
} catch {
|
|
3296
|
-
}
|
|
3297
|
-
};
|
|
3298
|
-
const writePromise = doWrite();
|
|
3299
|
-
this.writePromises.set(filePath, writePromise);
|
|
3300
|
-
writePromise.finally(() => {
|
|
3301
|
-
if (this.writePromises.get(filePath) === writePromise) {
|
|
3302
|
-
this.writePromises.delete(filePath);
|
|
3303
|
-
}
|
|
3304
|
-
});
|
|
3305
|
-
}
|
|
3306
|
-
}
|
|
3307
|
-
};
|
|
3308
|
-
}
|
|
3309
|
-
});
|
|
3310
|
-
|
|
3311
|
-
// src/logging/logger.ts
|
|
3312
|
-
var logger_exports = {};
|
|
3313
|
-
__export(logger_exports, {
|
|
3314
|
-
LOG: () => LOG,
|
|
3315
|
-
LOG_DIR_PATH: () => LOG_DIR_PATH,
|
|
3316
|
-
LOG_PATH: () => LOG_PATH,
|
|
3317
|
-
daemonLog: () => daemonLog,
|
|
3318
|
-
getCurrentDaemonLogPath: () => getCurrentDaemonLogPath,
|
|
3319
|
-
getDaemonLogDir: () => getDaemonLogDir,
|
|
3320
|
-
getLogBufferSize: () => getLogBufferSize,
|
|
3321
|
-
getLogLevel: () => getLogLevel,
|
|
3322
|
-
getLogPath: () => getLogPath,
|
|
3323
|
-
getRecentLogs: () => getRecentLogs,
|
|
3324
|
-
installGlobalInterceptor: () => installGlobalInterceptor,
|
|
3325
|
-
setLogLevel: () => setLogLevel
|
|
3326
|
-
});
|
|
3327
|
-
import * as fs4 from "fs";
|
|
3328
|
-
import * as path9 from "path";
|
|
3329
|
-
import * as os3 from "os";
|
|
3330
|
-
function setLogLevel(level) {
|
|
3331
|
-
currentLevel = level;
|
|
3332
|
-
daemonLog("Logger", `Log level set to: ${level}`, "info");
|
|
3333
|
-
}
|
|
3334
|
-
function getLogLevel() {
|
|
3335
|
-
return currentLevel;
|
|
3336
|
-
}
|
|
3337
|
-
function getDateStr() {
|
|
3338
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3339
|
-
}
|
|
3340
|
-
function getDaemonLogDir() {
|
|
3341
|
-
return LOG_DIR;
|
|
3342
|
-
}
|
|
3343
|
-
function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
|
|
3344
|
-
return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
|
|
3345
|
-
}
|
|
3346
|
-
function checkDateRotation() {
|
|
3347
|
-
const today = getDateStr();
|
|
3348
|
-
if (today !== currentDate) {
|
|
3349
|
-
currentDate = today;
|
|
3350
|
-
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
3351
|
-
cleanOldLogs();
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3354
|
-
function cleanOldLogs() {
|
|
3355
|
-
try {
|
|
3356
|
-
const files = fs4.readdirSync(LOG_DIR).filter((f) => f.startsWith("daemon-") && f.endsWith(".log"));
|
|
3357
|
-
const cutoff = /* @__PURE__ */ new Date();
|
|
3358
|
-
cutoff.setDate(cutoff.getDate() - MAX_LOG_DAYS);
|
|
3359
|
-
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
3360
|
-
for (const file of files) {
|
|
3361
|
-
const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
|
|
3362
|
-
if (dateMatch && dateMatch[1] < cutoffStr) {
|
|
3363
|
-
try {
|
|
3364
|
-
fs4.unlinkSync(path9.join(LOG_DIR, file));
|
|
3365
|
-
} catch {
|
|
3366
|
-
}
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
} catch {
|
|
3370
|
-
}
|
|
3371
|
-
}
|
|
3372
|
-
function rotateSizeIfNeeded() {
|
|
3373
|
-
try {
|
|
3374
|
-
const stat2 = fs4.statSync(currentLogFile);
|
|
3375
|
-
if (stat2.size > MAX_LOG_SIZE) {
|
|
3376
|
-
const backup = currentLogFile.replace(".log", ".1.log");
|
|
3377
|
-
try {
|
|
3378
|
-
fs4.unlinkSync(backup);
|
|
3379
|
-
} catch {
|
|
3380
|
-
}
|
|
3381
|
-
fs4.renameSync(currentLogFile, backup);
|
|
3382
|
-
}
|
|
3383
|
-
} catch {
|
|
3384
|
-
}
|
|
3385
|
-
}
|
|
3386
|
-
function writeToFile(line) {
|
|
3387
|
-
try {
|
|
3388
|
-
if (++writeCount % 1e3 === 0) {
|
|
3389
|
-
checkDateRotation();
|
|
3390
|
-
rotateSizeIfNeeded();
|
|
3391
|
-
}
|
|
3392
|
-
AsyncBatchWriter.write(currentLogFile, line + "\n");
|
|
3393
|
-
} catch {
|
|
3394
|
-
}
|
|
3395
|
-
}
|
|
3396
|
-
function getRecentLogs(count = 50, minLevel = "info") {
|
|
3397
|
-
const minNum = LEVEL_NUM[minLevel];
|
|
3398
|
-
const filtered = ringBuffer.filter((e) => LEVEL_NUM[e.level] >= minNum);
|
|
3399
|
-
return filtered.slice(-count);
|
|
3400
|
-
}
|
|
3401
|
-
function getLogBufferSize() {
|
|
3402
|
-
return ringBuffer.length;
|
|
3403
|
-
}
|
|
3404
|
-
function ts() {
|
|
3405
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
3406
|
-
}
|
|
3407
|
-
function fullTs() {
|
|
3408
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3409
|
-
}
|
|
3410
|
-
function daemonLog(category, msg, level = "info") {
|
|
3411
|
-
const shouldOutput = LEVEL_NUM[level] >= LEVEL_NUM[currentLevel];
|
|
3412
|
-
const label = LEVEL_LABEL[level];
|
|
3413
|
-
const line = `[${ts()}] [${label}] [${category}] ${msg}`;
|
|
3414
|
-
if (!shouldOutput) return;
|
|
3415
|
-
writeToFile(line);
|
|
3416
|
-
ringBuffer.push({ ts: Date.now(), level, category, message: msg });
|
|
3417
|
-
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
3418
|
-
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
3419
|
-
}
|
|
3420
|
-
origConsoleLog(line);
|
|
3421
|
-
}
|
|
3422
|
-
function installGlobalInterceptor() {
|
|
3423
|
-
if (interceptorInstalled) return;
|
|
3424
|
-
interceptorInstalled = true;
|
|
3425
|
-
const stripAnsi4 = (str) => str.replace(/\x1B\[[0-9;]*m/g, "");
|
|
3426
|
-
const isDaemonLogLine = (msg) => /\[(DBG|INF|WRN|ERR)\]/.test(msg);
|
|
3427
|
-
console.log = (...args) => {
|
|
3428
|
-
origConsoleLog(...args);
|
|
3429
|
-
try {
|
|
3430
|
-
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
3431
|
-
const clean = stripAnsi4(msg);
|
|
3432
|
-
if (isDaemonLogLine(clean)) return;
|
|
3433
|
-
const line = clean.startsWith("[20") ? clean : `[${fullTs()}] ${clean}`;
|
|
3434
|
-
writeToFile(line);
|
|
3435
|
-
const catMatch = clean.match(/\[([^\]]+)\]/);
|
|
3436
|
-
ringBuffer.push({
|
|
3437
|
-
ts: Date.now(),
|
|
3438
|
-
level: "info",
|
|
3439
|
-
category: catMatch?.[1] || "System",
|
|
3440
|
-
message: clean
|
|
3441
|
-
});
|
|
3442
|
-
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
3443
|
-
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
3444
|
-
}
|
|
3445
|
-
} catch {
|
|
3446
|
-
}
|
|
3447
|
-
};
|
|
3448
|
-
console.error = (...args) => {
|
|
3449
|
-
origConsoleError(...args);
|
|
3450
|
-
try {
|
|
3451
|
-
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
3452
|
-
const clean = stripAnsi4(msg);
|
|
3453
|
-
if (isDaemonLogLine(clean)) return;
|
|
3454
|
-
const line = `[${fullTs()}] [ERROR] ${clean}`;
|
|
3455
|
-
writeToFile(line);
|
|
3456
|
-
ringBuffer.push({ ts: Date.now(), level: "error", category: "System", message: clean });
|
|
3457
|
-
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
3458
|
-
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
3459
|
-
}
|
|
3460
|
-
} catch {
|
|
3461
|
-
}
|
|
3462
|
-
};
|
|
3463
|
-
console.warn = (...args) => {
|
|
3464
|
-
origConsoleWarn(...args);
|
|
3465
|
-
try {
|
|
3466
|
-
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
3467
|
-
const clean = stripAnsi4(msg);
|
|
3468
|
-
if (isDaemonLogLine(clean)) return;
|
|
3469
|
-
const line = `[${fullTs()}] [WARN] ${clean}`;
|
|
3470
|
-
writeToFile(line);
|
|
3471
|
-
ringBuffer.push({ ts: Date.now(), level: "warn", category: "System", message: clean });
|
|
3472
|
-
if (ringBuffer.length > RING_BUFFER_SIZE) {
|
|
3473
|
-
ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
|
|
3474
|
-
}
|
|
3475
|
-
} catch {
|
|
3476
|
-
}
|
|
3477
|
-
};
|
|
3478
|
-
writeToFile(`
|
|
3479
|
-
=== ADHDev Daemon started at ${fullTs()} ===`);
|
|
3480
|
-
writeToFile(`Log file: ${currentLogFile}`);
|
|
3481
|
-
writeToFile(`Log level: ${currentLevel}`);
|
|
3482
|
-
}
|
|
3483
|
-
function getLogPath() {
|
|
3484
|
-
return currentLogFile;
|
|
3485
|
-
}
|
|
3486
|
-
var 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;
|
|
3487
|
-
var init_logger = __esm({
|
|
3488
|
-
"src/logging/logger.ts"() {
|
|
3489
|
-
"use strict";
|
|
3490
|
-
init_async_batch_writer();
|
|
3491
|
-
LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
3492
|
-
LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
|
|
3493
|
-
currentLevel = "info";
|
|
3494
|
-
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");
|
|
3495
|
-
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
3496
|
-
MAX_LOG_DAYS = 7;
|
|
3497
|
-
try {
|
|
3498
|
-
fs4.mkdirSync(LOG_DIR, { recursive: true });
|
|
3499
|
-
} catch {
|
|
3500
|
-
}
|
|
3501
|
-
currentDate = getDateStr();
|
|
3502
|
-
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
3503
|
-
cleanOldLogs();
|
|
3504
|
-
try {
|
|
3505
|
-
const oldLog = path9.join(LOG_DIR, "daemon.log");
|
|
3506
|
-
if (fs4.existsSync(oldLog)) {
|
|
3507
|
-
const stat2 = fs4.statSync(oldLog);
|
|
3508
|
-
const oldDate = stat2.mtime.toISOString().slice(0, 10);
|
|
3509
|
-
fs4.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
|
|
3510
|
-
}
|
|
3511
|
-
const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
|
|
3512
|
-
if (fs4.existsSync(oldLogBackup)) {
|
|
3513
|
-
fs4.unlinkSync(oldLogBackup);
|
|
3514
|
-
}
|
|
3515
|
-
} catch {
|
|
3516
|
-
}
|
|
3517
|
-
writeCount = 0;
|
|
3518
|
-
RING_BUFFER_SIZE = 200;
|
|
3519
|
-
ringBuffer = [];
|
|
3520
|
-
origConsoleLog = console.log.bind(console);
|
|
3521
|
-
origConsoleError = console.error.bind(console);
|
|
3522
|
-
origConsoleWarn = console.warn.bind(console);
|
|
3523
|
-
LOG = {
|
|
3524
|
-
debug: (category, msg) => daemonLog(category, msg, "debug"),
|
|
3525
|
-
info: (category, msg) => daemonLog(category, msg, "info"),
|
|
3526
|
-
warn: (category, msg) => daemonLog(category, msg, "warn"),
|
|
3527
|
-
error: (category, msg) => daemonLog(category, msg, "error"),
|
|
3528
|
-
/**
|
|
3529
|
-
* Create a scoped logger for a specific component.
|
|
3530
|
-
* Category is baked in so callers only pass the message.
|
|
3531
|
-
*/
|
|
3532
|
-
forComponent(category) {
|
|
3533
|
-
return {
|
|
3534
|
-
debug: (msg) => daemonLog(category, msg, "debug"),
|
|
3535
|
-
info: (msg) => daemonLog(category, msg, "info"),
|
|
3536
|
-
warn: (msg) => daemonLog(category, msg, "warn"),
|
|
3537
|
-
error: (msg) => daemonLog(category, msg, "error"),
|
|
3538
|
-
asLogFn: (level = "info") => (msg) => daemonLog(category, msg, level)
|
|
3539
|
-
};
|
|
3540
|
-
}
|
|
3541
|
-
};
|
|
3542
|
-
interceptorInstalled = false;
|
|
3543
|
-
LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
|
|
3544
|
-
LOG_DIR_PATH = LOG_DIR;
|
|
3545
|
-
}
|
|
3546
|
-
});
|
|
3547
|
-
|
|
3548
3548
|
// src/mesh/mesh-work-queue.ts
|
|
3549
3549
|
var mesh_work_queue_exports = {};
|
|
3550
3550
|
__export(mesh_work_queue_exports, {
|
|
@@ -3862,6 +3862,7 @@ function enqueueTask(meshId, message, opts) {
|
|
|
3862
3862
|
requiredTags: resolvedRequiredTags,
|
|
3863
3863
|
...dependsOn.length > 0 ? { dependsOn } : {},
|
|
3864
3864
|
...typeof opts?.missionId === "string" && opts.missionId.trim() ? { missionId: opts.missionId.trim() } : {},
|
|
3865
|
+
...typeof opts?.sourceCoordinatorSessionId === "string" && opts.sourceCoordinatorSessionId.trim() ? { sourceCoordinatorSessionId: opts.sourceCoordinatorSessionId.trim() } : {},
|
|
3865
3866
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3866
3867
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3867
3868
|
};
|
|
@@ -4213,17 +4214,27 @@ function meshRuntimeStorePath() {
|
|
|
4213
4214
|
renameSync3(legacyCompanion, `${nextPath}${suffix}`);
|
|
4214
4215
|
}
|
|
4215
4216
|
}
|
|
4216
|
-
} catch {
|
|
4217
|
+
} catch (err) {
|
|
4218
|
+
if (!loggedMigrationFailure) {
|
|
4219
|
+
loggedMigrationFailure = true;
|
|
4220
|
+
LOG.warn(
|
|
4221
|
+
"MeshRuntimeStore",
|
|
4222
|
+
`Legacy beads.db\u2192mesh-runtime.db migration failed; using existing DB in-place to avoid data loss: ${err?.message || err}`
|
|
4223
|
+
);
|
|
4224
|
+
}
|
|
4225
|
+
return existsSync8(nextPath) ? nextPath : legacyPath;
|
|
4217
4226
|
}
|
|
4218
4227
|
return nextPath;
|
|
4219
4228
|
}
|
|
4220
|
-
var DatabaseCtor, MeshRuntimeStore;
|
|
4229
|
+
var DatabaseCtor, loggedMigrationFailure, MeshRuntimeStore;
|
|
4221
4230
|
var init_mesh_runtime_store = __esm({
|
|
4222
4231
|
"src/mesh/mesh-runtime-store.ts"() {
|
|
4223
4232
|
"use strict";
|
|
4233
|
+
init_logger();
|
|
4224
4234
|
init_load_better_sqlite3();
|
|
4225
4235
|
init_mesh_ledger();
|
|
4226
4236
|
init_mesh_work_queue();
|
|
4237
|
+
loggedMigrationFailure = false;
|
|
4227
4238
|
MeshRuntimeStore = class _MeshRuntimeStore {
|
|
4228
4239
|
static instance;
|
|
4229
4240
|
db;
|
|
@@ -4245,9 +4256,21 @@ var init_mesh_runtime_store = __esm({
|
|
|
4245
4256
|
this.db.pragma("busy_timeout = 5000");
|
|
4246
4257
|
this.migrate();
|
|
4247
4258
|
}
|
|
4259
|
+
static loggedGetInstanceFailure = false;
|
|
4248
4260
|
static getInstance() {
|
|
4249
4261
|
if (!this.instance) {
|
|
4250
|
-
|
|
4262
|
+
try {
|
|
4263
|
+
this.instance = new _MeshRuntimeStore(meshRuntimeStorePath());
|
|
4264
|
+
} catch (err) {
|
|
4265
|
+
if (!_MeshRuntimeStore.loggedGetInstanceFailure) {
|
|
4266
|
+
_MeshRuntimeStore.loggedGetInstanceFailure = true;
|
|
4267
|
+
LOG.warn(
|
|
4268
|
+
"MeshRuntimeStore",
|
|
4269
|
+
`getInstance failed; callers will degrade to JSONL-only: ${err?.message || err}`
|
|
4270
|
+
);
|
|
4271
|
+
}
|
|
4272
|
+
throw err;
|
|
4273
|
+
}
|
|
4251
4274
|
}
|
|
4252
4275
|
return this.instance;
|
|
4253
4276
|
}
|
|
@@ -7840,7 +7863,11 @@ function buildMeshWorkerRelayStamp(currentSettings, meshContext) {
|
|
|
7840
7863
|
if (coordinatorDaemonId && !readNonEmptyString2(settings.meshCoordinatorDaemonId)) {
|
|
7841
7864
|
stamp.meshCoordinatorDaemonId = coordinatorDaemonId;
|
|
7842
7865
|
}
|
|
7843
|
-
|
|
7866
|
+
const coordinatorSessionId = readNonEmptyString2(meshContext.coordinatorSessionId);
|
|
7867
|
+
if (coordinatorSessionId && !readNonEmptyString2(settings.meshCoordinatorSessionId)) {
|
|
7868
|
+
stamp.meshCoordinatorSessionId = coordinatorSessionId;
|
|
7869
|
+
}
|
|
7870
|
+
if ((meshId || nodeId || coordinatorDaemonId || coordinatorSessionId) && settings.launchedByCoordinator !== true) {
|
|
7844
7871
|
stamp.launchedByCoordinator = true;
|
|
7845
7872
|
}
|
|
7846
7873
|
return Object.keys(stamp).length > 0 ? stamp : void 0;
|
|
@@ -7857,10 +7884,13 @@ function readRefineJobId(event) {
|
|
|
7857
7884
|
function readWorkerResultMetadata(event) {
|
|
7858
7885
|
return readRecord4(event.workerResult) || readRecord4(event.meshWorkerResult) || readRecord4(event.structuredResult);
|
|
7859
7886
|
}
|
|
7860
|
-
function
|
|
7887
|
+
function readMeshCompletionSummary(metadataEvent) {
|
|
7861
7888
|
const workerResult = readWorkerResultMetadata(metadataEvent);
|
|
7862
7889
|
const resultRecord = readRecord4(metadataEvent.result);
|
|
7863
|
-
|
|
7890
|
+
return readNonEmptyString2(metadataEvent.finalSummary) || readNonEmptyString2(workerResult?.summary) || readNonEmptyString2(workerResult?.finalSummary) || readNonEmptyString2(resultRecord?.summary) || readNonEmptyString2(resultRecord?.finalSummary);
|
|
7891
|
+
}
|
|
7892
|
+
function resolveMeshSurfacedSessionPreview(metadataEvent) {
|
|
7893
|
+
const summaryText = readMeshCompletionSummary(metadataEvent);
|
|
7864
7894
|
if (!summaryText) return void 0;
|
|
7865
7895
|
const truncationSuffix = "...[truncated]";
|
|
7866
7896
|
const preview = summaryText.length > MESH_SURFACED_PREVIEW_MAX_CHARS ? `${summaryText.slice(0, MESH_SURFACED_PREVIEW_MAX_CHARS - truncationSuffix.length)}${truncationSuffix}` : summaryText;
|
|
@@ -7898,7 +7928,18 @@ function buildMeshSystemMessage(args) {
|
|
|
7898
7928
|
if (args.metadataEvent.source === "no_progress_reconciliation") {
|
|
7899
7929
|
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.`;
|
|
7900
7930
|
}
|
|
7901
|
-
const
|
|
7931
|
+
const reviewRecommended = args.metadataEvent.reviewRecommended === true;
|
|
7932
|
+
const completionSummary = readMeshCompletionSummary(args.metadataEvent);
|
|
7933
|
+
if (completionSummary) {
|
|
7934
|
+
const truncationSuffix = "\n\u2026[truncated \u2014 call mesh_read_chat once for the full transcript]";
|
|
7935
|
+
const surfaced = completionSummary.length > MESH_COMPLETION_SURFACE_MAX_CHARS ? `${completionSummary.slice(0, MESH_COMPLETION_SURFACE_MAX_CHARS - truncationSuffix.length)}${truncationSuffix}` : completionSummary;
|
|
7936
|
+
const verifyNote = reviewRecommended ? " Completion evidence is insufficient \u2014 verify via git status or provider_session_id before assuming the task is done." : "";
|
|
7937
|
+
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}
|
|
7938
|
+
|
|
7939
|
+
--- ${args.nodeLabel} final summary ---
|
|
7940
|
+
${surfaced}`;
|
|
7941
|
+
}
|
|
7942
|
+
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.";
|
|
7902
7943
|
return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path;${reviewNote}`;
|
|
7903
7944
|
}
|
|
7904
7945
|
if (args.event === "agent:waiting_approval") {
|
|
@@ -8006,11 +8047,12 @@ Next step: ${nextStep}`;
|
|
|
8006
8047
|
}
|
|
8007
8048
|
return "";
|
|
8008
8049
|
}
|
|
8009
|
-
var MESH_SURFACED_PREVIEW_MAX_CHARS;
|
|
8050
|
+
var MESH_SURFACED_PREVIEW_MAX_CHARS, MESH_COMPLETION_SURFACE_MAX_CHARS;
|
|
8010
8051
|
var init_mesh_events_utils = __esm({
|
|
8011
8052
|
"src/mesh/mesh-events-utils.ts"() {
|
|
8012
8053
|
"use strict";
|
|
8013
8054
|
MESH_SURFACED_PREVIEW_MAX_CHARS = 512;
|
|
8055
|
+
MESH_COMPLETION_SURFACE_MAX_CHARS = 4e3;
|
|
8014
8056
|
}
|
|
8015
8057
|
});
|
|
8016
8058
|
|
|
@@ -8171,6 +8213,37 @@ function trimPendingEventsIfNeeded(path42) {
|
|
|
8171
8213
|
if (statSync6(path42).size <= MAX_PENDING_EVENTS_BYTES) return;
|
|
8172
8214
|
const lines = readFileSync11(path42, "utf-8").split("\n").filter(Boolean);
|
|
8173
8215
|
if (lines.length <= MAX_PENDING_EVENTS_KEEP) return;
|
|
8216
|
+
const dropped = lines.slice(0, lines.length - MAX_PENDING_EVENTS_KEEP);
|
|
8217
|
+
for (const line of dropped) {
|
|
8218
|
+
let event;
|
|
8219
|
+
try {
|
|
8220
|
+
event = JSON.parse(line);
|
|
8221
|
+
} catch {
|
|
8222
|
+
continue;
|
|
8223
|
+
}
|
|
8224
|
+
if (!event || !event.meshId) continue;
|
|
8225
|
+
const finalSummary = readMeshCompletionSummary(event.metadataEvent || {});
|
|
8226
|
+
if (!readNonEmptyString2(event.coordinatorMessage) && !finalSummary) continue;
|
|
8227
|
+
try {
|
|
8228
|
+
appendLedgerEntry(event.meshId, {
|
|
8229
|
+
kind: "event_held",
|
|
8230
|
+
...event.nodeId ? { nodeId: event.nodeId } : {},
|
|
8231
|
+
payload: {
|
|
8232
|
+
event: event.event,
|
|
8233
|
+
reason: "pending_trim_dropped",
|
|
8234
|
+
recoverable: true,
|
|
8235
|
+
nodeLabel: event.nodeLabel,
|
|
8236
|
+
...event.workspace ? { workspace: event.workspace } : {},
|
|
8237
|
+
targetCoordinatorDaemonId: event.targetCoordinatorDaemonId ?? null,
|
|
8238
|
+
queuedAt: event.queuedAt,
|
|
8239
|
+
...finalSummary ? { finalSummary } : {}
|
|
8240
|
+
}
|
|
8241
|
+
});
|
|
8242
|
+
LOG.warn("MeshEvents", `Pending-events trim dropping undelivered ${event.event} for mesh ${event.meshId} \u2014 recorded to ledger (recoverable)`);
|
|
8243
|
+
} catch (e) {
|
|
8244
|
+
LOG.warn("MeshEvents", `Failed to ledger-record trim-dropped ${event.event} for mesh ${event.meshId}: ${e?.message || e}`);
|
|
8245
|
+
}
|
|
8246
|
+
}
|
|
8174
8247
|
writeFileSync6(path42, lines.slice(-MAX_PENDING_EVENTS_KEEP).join("\n") + "\n", "utf-8");
|
|
8175
8248
|
} catch {
|
|
8176
8249
|
}
|
|
@@ -8300,7 +8373,8 @@ function drainPendingMeshCoordinatorEvents(meshId, coordinatorDaemonId, opts) {
|
|
|
8300
8373
|
if (event) pushUnique(event);
|
|
8301
8374
|
}
|
|
8302
8375
|
}
|
|
8303
|
-
} catch {
|
|
8376
|
+
} catch (e) {
|
|
8377
|
+
LOG.warn("MeshEvents", `SQLite pending-event drain failed for mesh ${meshId}; JSONL fallback only: ${e?.message || e}`);
|
|
8304
8378
|
}
|
|
8305
8379
|
const paths = primaryDaemonId ? [getPendingEventsPath(meshId, primaryDaemonId), getPendingEventsPath(meshId)] : [getPendingEventsPath(meshId)];
|
|
8306
8380
|
for (const path42 of paths) {
|
|
@@ -9715,6 +9789,8 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9715
9789
|
if (node?.daemonId && components.dispatchMeshCommand) {
|
|
9716
9790
|
const isLocalNode = components.cliManager.adapters.has(sessionId);
|
|
9717
9791
|
if (!isLocalNode) {
|
|
9792
|
+
const localDaemonIdForDispatch = readNonEmptyString2(loadConfig().machineId) || void 0;
|
|
9793
|
+
const sourceCoordinatorSessionId = readNonEmptyString2(task.sourceCoordinatorSessionId) || void 0;
|
|
9718
9794
|
const delivery2 = createSessionDelivery({
|
|
9719
9795
|
meshId,
|
|
9720
9796
|
nodeId,
|
|
@@ -9723,9 +9799,10 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9723
9799
|
taskId: task.id,
|
|
9724
9800
|
kind: "task",
|
|
9725
9801
|
message: task.message,
|
|
9726
|
-
status: "delivering"
|
|
9802
|
+
status: "delivering",
|
|
9803
|
+
...sourceCoordinatorSessionId ? { sourceCoordinatorSessionId } : {},
|
|
9804
|
+
...localDaemonIdForDispatch ? { sourceCoordinatorDaemonId: localDaemonIdForDispatch } : {}
|
|
9727
9805
|
});
|
|
9728
|
-
const localDaemonIdForDispatch = readNonEmptyString2(loadConfig().machineId) || void 0;
|
|
9729
9806
|
components.dispatchMeshCommand(node.daemonId, "agent_command", {
|
|
9730
9807
|
targetSessionId: sessionId,
|
|
9731
9808
|
cliType: providerType,
|
|
@@ -9735,7 +9812,8 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9735
9812
|
meshId,
|
|
9736
9813
|
nodeId,
|
|
9737
9814
|
taskId: task.id,
|
|
9738
|
-
...localDaemonIdForDispatch ? { coordinatorDaemonId: localDaemonIdForDispatch } : {}
|
|
9815
|
+
...localDaemonIdForDispatch ? { coordinatorDaemonId: localDaemonIdForDispatch } : {},
|
|
9816
|
+
...sourceCoordinatorSessionId ? { coordinatorSessionId: sourceCoordinatorSessionId } : {}
|
|
9739
9817
|
}
|
|
9740
9818
|
}).then(() => {
|
|
9741
9819
|
updateSessionDeliveryStatus(delivery2.id, "delivered");
|
|
@@ -9760,12 +9838,16 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9760
9838
|
const inst = components.instanceManager.getInstance(sessionId);
|
|
9761
9839
|
if (inst && typeof inst.updateSettings === "function") {
|
|
9762
9840
|
const localDaemonId = readNonEmptyString2(loadConfig().machineId);
|
|
9841
|
+
const localSourceCoordinatorSessionId = readNonEmptyString2(task.sourceCoordinatorSessionId);
|
|
9763
9842
|
inst.updateSettings({
|
|
9764
9843
|
meshNodeFor: meshId,
|
|
9765
9844
|
meshNodeId: nodeId,
|
|
9766
9845
|
launchedByCoordinator: true,
|
|
9767
9846
|
autoApprove: resolveDelegatedWorkerAutoApprove(mesh?.policy, node?.policy),
|
|
9768
|
-
...localDaemonId ? { meshCoordinatorDaemonId: localDaemonId } : {}
|
|
9847
|
+
...localDaemonId ? { meshCoordinatorDaemonId: localDaemonId } : {},
|
|
9848
|
+
// (3) Stamp the originating coordinator session for session-anchored routing
|
|
9849
|
+
// of this co-located worker's completion. Absent → daemon-level fallback.
|
|
9850
|
+
...localSourceCoordinatorSessionId ? { meshCoordinatorSessionId: localSourceCoordinatorSessionId } : {}
|
|
9769
9851
|
});
|
|
9770
9852
|
}
|
|
9771
9853
|
} catch {
|
|
@@ -9778,7 +9860,9 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
9778
9860
|
taskId: task.id,
|
|
9779
9861
|
kind: "task",
|
|
9780
9862
|
message: task.message,
|
|
9781
|
-
status: "delivering"
|
|
9863
|
+
status: "delivering",
|
|
9864
|
+
...readNonEmptyString2(task.sourceCoordinatorSessionId) ? { sourceCoordinatorSessionId: readNonEmptyString2(task.sourceCoordinatorSessionId) } : {},
|
|
9865
|
+
...readNonEmptyString2(loadConfig().machineId) ? { sourceCoordinatorDaemonId: readNonEmptyString2(loadConfig().machineId) } : {}
|
|
9782
9866
|
});
|
|
9783
9867
|
components.cliManager.handleCliCommand("agent_command", {
|
|
9784
9868
|
targetSessionId: sessionId,
|
|
@@ -10428,6 +10512,9 @@ function injectMeshSystemMessage(components, args) {
|
|
|
10428
10512
|
const workerCoordinatorDaemonId = readNonEmptyString2(
|
|
10429
10513
|
sourceSession?.getState()?.settings?.meshCoordinatorDaemonId
|
|
10430
10514
|
);
|
|
10515
|
+
const workerCoordinatorSessionId = readNonEmptyString2(
|
|
10516
|
+
sourceSession?.getState()?.settings?.meshCoordinatorSessionId
|
|
10517
|
+
) || readNonEmptyString2(args.metadataEvent.meshCoordinatorSessionId);
|
|
10431
10518
|
if (components.onMeshCoordinatorEventForwarded) {
|
|
10432
10519
|
try {
|
|
10433
10520
|
const surfacedPreview = resolveMeshSurfacedSessionPreview(args.metadataEvent);
|
|
@@ -10808,7 +10895,12 @@ function injectMeshSystemMessage(components, args) {
|
|
|
10808
10895
|
workspace: readNonEmptyString2(args.metadataEvent.workspace) || readNonEmptyString2(args.metadataEvent.workspaceName),
|
|
10809
10896
|
metadataEvent: {
|
|
10810
10897
|
...args.metadataEvent,
|
|
10811
|
-
...recoveryContext ? { recoveryContext } : {}
|
|
10898
|
+
...recoveryContext ? { recoveryContext } : {},
|
|
10899
|
+
// Stash the coordinator session id INSIDE metadataEvent too, so it survives the
|
|
10900
|
+
// P2P relay serialization (buildForwardPayloadFromPending spreads metadata; the
|
|
10901
|
+
// handleMeshForwardEvent whitelist reads it back) — a top-level field alone would
|
|
10902
|
+
// be dropped when the event crosses a machine boundary.
|
|
10903
|
+
...workerCoordinatorSessionId ? { meshCoordinatorSessionId: workerCoordinatorSessionId } : {}
|
|
10812
10904
|
},
|
|
10813
10905
|
// Silent lifecycle events (agent:ready / agent:generating_started) carry no
|
|
10814
10906
|
// coordinator message; they are queued only so the coordinator re-runs the
|
|
@@ -10816,10 +10908,13 @@ function injectMeshSystemMessage(components, args) {
|
|
|
10816
10908
|
// entries without a coordinatorMessage, so a live CLI coordinator is not spammed.
|
|
10817
10909
|
...messageText ? { coordinatorMessage: messageText } : {},
|
|
10818
10910
|
queuedAt: Date.now(),
|
|
10819
|
-
...workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {}
|
|
10911
|
+
...workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {},
|
|
10912
|
+
// Top-level session anchor for the local PHASE 2 strict-match on the coordinator
|
|
10913
|
+
// daemon. Absent → daemon-level broadcast (legacy / single-coordinator path).
|
|
10914
|
+
...workerCoordinatorSessionId ? { targetCoordinatorSessionId: workerCoordinatorSessionId } : {}
|
|
10820
10915
|
};
|
|
10821
10916
|
if (queuePendingMeshCoordinatorEvent(pendingEvent)) {
|
|
10822
|
-
LOG.info("MeshEvents", `Queued ${args.event} for coordinator (mesh ${args.meshId}${workerCoordinatorDaemonId ? `, coordinator daemon ${workerCoordinatorDaemonId}` : ""})`);
|
|
10917
|
+
LOG.info("MeshEvents", `Queued ${args.event} for coordinator (mesh ${args.meshId}${workerCoordinatorDaemonId ? `, coordinator daemon ${workerCoordinatorDaemonId}` : ""}${workerCoordinatorSessionId ? `, coordinator session ${workerCoordinatorSessionId}` : ""})`);
|
|
10823
10918
|
}
|
|
10824
10919
|
return { success: true, forwarded: 0 };
|
|
10825
10920
|
}
|
|
@@ -10844,6 +10939,13 @@ function handleMeshForwardEvent(components, payload) {
|
|
|
10844
10939
|
targetSessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId) || readNonEmptyString2(payload.instanceId),
|
|
10845
10940
|
providerType: readNonEmptyString2(payload.providerType),
|
|
10846
10941
|
providerSessionId: readNonEmptyString2(payload.providerSessionId),
|
|
10942
|
+
// Preserve the originating coordinator SESSION id across the machine boundary so
|
|
10943
|
+
// the completion routes back to the exact coordinator session (multi-coordinator).
|
|
10944
|
+
// buildForwardPayloadFromPending spreads the worker event's metadata, so the id
|
|
10945
|
+
// arrives as payload.meshCoordinatorSessionId; the top-level targetCoordinatorSessionId
|
|
10946
|
+
// is also accepted as a fallback. injectMeshSystemMessage re-derives the routing
|
|
10947
|
+
// anchors from this. Absent → daemon-level fallback (version-skew safe).
|
|
10948
|
+
meshCoordinatorSessionId: readNonEmptyString2(payload.meshCoordinatorSessionId) || readNonEmptyString2(payload.targetCoordinatorSessionId),
|
|
10847
10949
|
// Carry the session identity fields the worker provider event emits so the
|
|
10848
10950
|
// coordinator's mirror (updateMeshOwnedSession) gets a real workspace/title/
|
|
10849
10951
|
// settings. Without these the remote-relay hop reconstructs metadataEvent with
|
|
@@ -11739,7 +11841,8 @@ function findLiveCoordinators(components) {
|
|
|
11739
11841
|
if (!meshId) continue;
|
|
11740
11842
|
const status = readNonEmptyString2(state.status).toLowerCase();
|
|
11741
11843
|
const modalParked = status === "waiting_choice" || status === "waiting_approval";
|
|
11742
|
-
|
|
11844
|
+
const sessionId = readNonEmptyString2(state.instanceId);
|
|
11845
|
+
out.push({ meshId, instance: inst, sessionId, idle: status === "idle", modalParked });
|
|
11743
11846
|
}
|
|
11744
11847
|
return out;
|
|
11745
11848
|
}
|
|
@@ -11751,6 +11854,44 @@ function injectPendingIntoCoordinator(coordinator, pending) {
|
|
|
11751
11854
|
...force ? { force: true } : {}
|
|
11752
11855
|
});
|
|
11753
11856
|
}
|
|
11857
|
+
function recordHeldTerminalEventsToLedger(meshId, drainDaemonIds, reason, heldForCoordinatorCount) {
|
|
11858
|
+
let pending;
|
|
11859
|
+
try {
|
|
11860
|
+
pending = getPendingMeshCoordinatorEvents(meshId, drainDaemonIds.length > 0 ? drainDaemonIds : void 0);
|
|
11861
|
+
} catch {
|
|
11862
|
+
return;
|
|
11863
|
+
}
|
|
11864
|
+
for (const event of pending) {
|
|
11865
|
+
if (!shouldForceInjectMeshEvent(event.event)) continue;
|
|
11866
|
+
const fingerprint = buildPendingEventFingerprint(event);
|
|
11867
|
+
const key = `${meshId}::${fingerprint || `${event.event}::${event.nodeId || ""}::${event.queuedAt}`}`;
|
|
11868
|
+
if (heldEventLedgerRecorded.has(key)) continue;
|
|
11869
|
+
heldEventLedgerRecorded.add(key);
|
|
11870
|
+
const finalSummary = readMeshCompletionSummary(event.metadataEvent);
|
|
11871
|
+
try {
|
|
11872
|
+
appendLedgerEntry(meshId, {
|
|
11873
|
+
kind: "event_held",
|
|
11874
|
+
...event.nodeId ? { nodeId: event.nodeId } : {},
|
|
11875
|
+
payload: {
|
|
11876
|
+
event: event.event,
|
|
11877
|
+
reason,
|
|
11878
|
+
recoverable: true,
|
|
11879
|
+
heldForCoordinators: heldForCoordinatorCount,
|
|
11880
|
+
nodeLabel: event.nodeLabel,
|
|
11881
|
+
...event.workspace ? { workspace: event.workspace } : {},
|
|
11882
|
+
targetCoordinatorDaemonId: event.targetCoordinatorDaemonId ?? null,
|
|
11883
|
+
queuedAt: event.queuedAt,
|
|
11884
|
+
...fingerprint ? { fingerprint } : {},
|
|
11885
|
+
...finalSummary ? { finalSummary } : {}
|
|
11886
|
+
}
|
|
11887
|
+
});
|
|
11888
|
+
LOG.info("MeshReconcile", `Ledger-recorded held ${event.event} for mesh ${meshId} (reason ${reason}) \u2014 recoverable from ledger`);
|
|
11889
|
+
} catch (e) {
|
|
11890
|
+
heldEventLedgerRecorded.delete(key);
|
|
11891
|
+
LOG.warn("MeshReconcile", `Failed to ledger-record held ${event.event} for mesh ${meshId}: ${e?.message || e}`);
|
|
11892
|
+
}
|
|
11893
|
+
}
|
|
11894
|
+
}
|
|
11754
11895
|
async function runMeshReconcileTick(components) {
|
|
11755
11896
|
const localDaemonId = readNonEmptyString2(loadConfig().machineId) || void 0;
|
|
11756
11897
|
const drainDaemonIds = resolveCoordinatorDaemonIds(components);
|
|
@@ -11835,6 +11976,21 @@ async function runMeshReconcileTick(components) {
|
|
|
11835
11976
|
if (targetCoordinators.length === 0) {
|
|
11836
11977
|
if (modalParkedCoordinators.length > 0) {
|
|
11837
11978
|
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)`);
|
|
11979
|
+
let hasPending = true;
|
|
11980
|
+
if (store) {
|
|
11981
|
+
try {
|
|
11982
|
+
hasPending = store.pendingEventCount(meshId) > 0;
|
|
11983
|
+
} catch {
|
|
11984
|
+
}
|
|
11985
|
+
}
|
|
11986
|
+
if (hasPending) {
|
|
11987
|
+
recordHeldTerminalEventsToLedger(
|
|
11988
|
+
meshId,
|
|
11989
|
+
drainDaemonIds.length > 0 ? drainDaemonIds : localDaemonId ? [localDaemonId] : [],
|
|
11990
|
+
"modal_parked",
|
|
11991
|
+
modalParkedCoordinators.length
|
|
11992
|
+
);
|
|
11993
|
+
}
|
|
11838
11994
|
}
|
|
11839
11995
|
continue;
|
|
11840
11996
|
}
|
|
@@ -11859,12 +12015,55 @@ async function runMeshReconcileTick(components) {
|
|
|
11859
12015
|
const mode = forceOnly ? "force-drain \u2192 generating" : "inject \u2192 idle";
|
|
11860
12016
|
LOG.info("MeshReconcile", `Reconcile ${mode}: ${pendingEvents.length} pending event(s) \u2192 ${targetCoordinators.length} coordinator(s) for mesh ${meshId}`);
|
|
11861
12017
|
for (const pending of pendingEvents) {
|
|
12018
|
+
const wantSession = readNonEmptyString2(pending.targetCoordinatorSessionId);
|
|
12019
|
+
if (wantSession) {
|
|
12020
|
+
const matched = targetCoordinators.filter((c) => c.sessionId === wantSession);
|
|
12021
|
+
if (matched.length === 0) {
|
|
12022
|
+
holdOrExpireStrictUnmatchedEvent(pending, wantSession, meshId);
|
|
12023
|
+
continue;
|
|
12024
|
+
}
|
|
12025
|
+
for (const c of matched) injectPendingIntoCoordinator(c.instance, pending);
|
|
12026
|
+
continue;
|
|
12027
|
+
}
|
|
11862
12028
|
for (const c of targetCoordinators) {
|
|
11863
12029
|
injectPendingIntoCoordinator(c.instance, pending);
|
|
11864
12030
|
}
|
|
11865
12031
|
}
|
|
11866
12032
|
}
|
|
11867
12033
|
}
|
|
12034
|
+
function holdOrExpireStrictUnmatchedEvent(pending, wantSession, meshId) {
|
|
12035
|
+
const queuedAt = typeof pending.queuedAt === "number" ? pending.queuedAt : Date.now();
|
|
12036
|
+
if (Date.now() - queuedAt <= STRICT_SESSION_MATCH_TTL_MS) {
|
|
12037
|
+
try {
|
|
12038
|
+
queuePendingMeshCoordinatorEvent(pending);
|
|
12039
|
+
LOG.info("MeshReconcile", `Strict route hold: coordinator session ${wantSession} not live on mesh ${meshId} \u2014 re-queued (${pending.event})`);
|
|
12040
|
+
} catch (e) {
|
|
12041
|
+
LOG.warn("MeshReconcile", `Strict route re-queue failed for ${pending.event} on mesh ${meshId}: ${e?.message || e}`);
|
|
12042
|
+
}
|
|
12043
|
+
return;
|
|
12044
|
+
}
|
|
12045
|
+
const finalSummary = readMeshCompletionSummary(pending.metadataEvent || {});
|
|
12046
|
+
try {
|
|
12047
|
+
appendLedgerEntry(meshId, {
|
|
12048
|
+
kind: "event_held",
|
|
12049
|
+
...pending.nodeId ? { nodeId: pending.nodeId } : {},
|
|
12050
|
+
payload: {
|
|
12051
|
+
event: pending.event,
|
|
12052
|
+
reason: "strict_route_expired",
|
|
12053
|
+
recoverable: true,
|
|
12054
|
+
targetCoordinatorSessionId: wantSession,
|
|
12055
|
+
targetCoordinatorDaemonId: pending.targetCoordinatorDaemonId ?? null,
|
|
12056
|
+
nodeLabel: pending.nodeLabel,
|
|
12057
|
+
...pending.workspace ? { workspace: pending.workspace } : {},
|
|
12058
|
+
queuedAt,
|
|
12059
|
+
...finalSummary ? { finalSummary } : {}
|
|
12060
|
+
}
|
|
12061
|
+
});
|
|
12062
|
+
LOG.warn("MeshReconcile", `Strict route expire: coordinator session ${wantSession} never returned for mesh ${meshId} \u2014 recorded to ledger (recoverable), dropped (${pending.event})`);
|
|
12063
|
+
} catch (e) {
|
|
12064
|
+
LOG.warn("MeshReconcile", `Failed to ledger-expire strict-unmatched ${pending.event} for mesh ${meshId}: ${e?.message || e}`);
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
11868
12067
|
async function retryUnresolvedDelegateForwards(components) {
|
|
11869
12068
|
const dispatchMeshCommand = components.dispatchMeshCommand;
|
|
11870
12069
|
if (!dispatchMeshCommand) return;
|
|
@@ -12082,6 +12281,11 @@ function buildForwardPayloadFromPending(event) {
|
|
|
12082
12281
|
meshId: readNonEmptyString2(event?.meshId),
|
|
12083
12282
|
nodeId: readNonEmptyString2(event?.nodeId) || readNonEmptyString2(metadata.meshNodeId),
|
|
12084
12283
|
workspace: readNonEmptyString2(event?.workspace) || readNonEmptyString2(metadata.workspace),
|
|
12284
|
+
// Preserve the originating coordinator session id across the relay. It is normally
|
|
12285
|
+
// carried inside metadataEvent.meshCoordinatorSessionId (spread below), but pass the
|
|
12286
|
+
// top-level field through explicitly too so the handleMeshForwardEvent whitelist
|
|
12287
|
+
// recovers it regardless of which carrier the producing daemon used.
|
|
12288
|
+
...readNonEmptyString2(event?.targetCoordinatorSessionId) ? { targetCoordinatorSessionId: readNonEmptyString2(event.targetCoordinatorSessionId) } : {},
|
|
12085
12289
|
...metadata
|
|
12086
12290
|
};
|
|
12087
12291
|
}
|
|
@@ -12104,7 +12308,7 @@ function setupMeshReconcileLoop(components) {
|
|
|
12104
12308
|
}
|
|
12105
12309
|
};
|
|
12106
12310
|
}
|
|
12107
|
-
var DEFAULT_RECONCILE_INTERVAL_MS, DEFAULT_AUTO_PRUNE_MIN_AGE_MS;
|
|
12311
|
+
var DEFAULT_RECONCILE_INTERVAL_MS, DEFAULT_AUTO_PRUNE_MIN_AGE_MS, heldEventLedgerRecorded, STRICT_SESSION_MATCH_TTL_MS;
|
|
12108
12312
|
var init_mesh_reconcile_loop = __esm({
|
|
12109
12313
|
"src/mesh/mesh-reconcile-loop.ts"() {
|
|
12110
12314
|
"use strict";
|
|
@@ -12112,6 +12316,7 @@ var init_mesh_reconcile_loop = __esm({
|
|
|
12112
12316
|
init_mesh_config();
|
|
12113
12317
|
init_logger();
|
|
12114
12318
|
init_mesh_events_pending();
|
|
12319
|
+
init_mesh_ledger();
|
|
12115
12320
|
init_mesh_runtime_store();
|
|
12116
12321
|
init_mesh_events_coordinator();
|
|
12117
12322
|
init_mesh_unresolved_forward_outbox();
|
|
@@ -12123,6 +12328,8 @@ var init_mesh_reconcile_loop = __esm({
|
|
|
12123
12328
|
init_chat_message_normalization();
|
|
12124
12329
|
DEFAULT_RECONCILE_INTERVAL_MS = 4e3;
|
|
12125
12330
|
DEFAULT_AUTO_PRUNE_MIN_AGE_MS = 24 * 60 * 6e4;
|
|
12331
|
+
heldEventLedgerRecorded = /* @__PURE__ */ new Set();
|
|
12332
|
+
STRICT_SESSION_MATCH_TTL_MS = 6e4;
|
|
12126
12333
|
}
|
|
12127
12334
|
});
|
|
12128
12335
|
|
|
@@ -34698,7 +34905,10 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
34698
34905
|
meshNodeFor: assignment.meshId,
|
|
34699
34906
|
...assignment.nodeId ? { meshNodeId: assignment.nodeId } : {},
|
|
34700
34907
|
...assignment.taskId ? { meshActiveTaskId: assignment.taskId } : {},
|
|
34701
|
-
...assignment.coordinatorDaemonId ? { meshCoordinatorDaemonId: assignment.coordinatorDaemonId } : {}
|
|
34908
|
+
...assignment.coordinatorDaemonId ? { meshCoordinatorDaemonId: assignment.coordinatorDaemonId } : {},
|
|
34909
|
+
// Session-level routing anchor: the originating coordinator session, so this
|
|
34910
|
+
// worker's completion events route back to the exact session that dispatched it.
|
|
34911
|
+
...assignment.coordinatorSessionId ? { meshCoordinatorSessionId: assignment.coordinatorSessionId } : {}
|
|
34702
34912
|
};
|
|
34703
34913
|
this.adapter.updateRuntimeSettings?.(this.settings);
|
|
34704
34914
|
}
|
|
@@ -37687,6 +37897,12 @@ Enable and detect this provider from the Machine Providers page before starting
|
|
|
37687
37897
|
);
|
|
37688
37898
|
}
|
|
37689
37899
|
const key = crypto5.randomUUID();
|
|
37900
|
+
{
|
|
37901
|
+
const coordinatorMeshId = options?.settingsOverride?.meshCoordinatorFor;
|
|
37902
|
+
if (typeof coordinatorMeshId === "string" && coordinatorMeshId.trim()) {
|
|
37903
|
+
options = { ...options, extraEnv: { ...options?.extraEnv || {}, ADHDEV_COORDINATOR_SESSION_ID: key } };
|
|
37904
|
+
}
|
|
37905
|
+
}
|
|
37690
37906
|
const sessionRegistry = this.deps.getSessionRegistry?.() || null;
|
|
37691
37907
|
if (provider && provider.category === "acp") {
|
|
37692
37908
|
const instanceManager2 = this.deps.getInstanceManager();
|
|
@@ -44101,6 +44317,40 @@ function readCachedInlineMeshActiveSessions(node) {
|
|
|
44101
44317
|
const sessionId = readStringValue(fallbackSession.id, fallbackSession.sessionId, fallbackSession.session_id, node?.activeSessionId, node?.active_session_id, node?.sessionId, node?.session_id);
|
|
44102
44318
|
return sessionId ? [sessionId] : [];
|
|
44103
44319
|
}
|
|
44320
|
+
function collectMeshNodeHostedSessionIds(node) {
|
|
44321
|
+
const ids = /* @__PURE__ */ new Set();
|
|
44322
|
+
for (const id of readCachedInlineMeshActiveSessions(node)) ids.add(id);
|
|
44323
|
+
const cachedStatus = readObjectRecord(node?.cachedStatus);
|
|
44324
|
+
for (const value of [
|
|
44325
|
+
node?.activeSessions,
|
|
44326
|
+
node?.active_sessions,
|
|
44327
|
+
node?.activeSessionDetails,
|
|
44328
|
+
node?.active_session_details,
|
|
44329
|
+
node?.sessions,
|
|
44330
|
+
node?.sessionDetails,
|
|
44331
|
+
node?.session_details,
|
|
44332
|
+
readObjectRecord(node?.lastProbe).sessions,
|
|
44333
|
+
readObjectRecord(node?.last_probe).sessions,
|
|
44334
|
+
cachedStatus.activeSessions,
|
|
44335
|
+
cachedStatus.active_sessions,
|
|
44336
|
+
cachedStatus.activeSessionDetails,
|
|
44337
|
+
cachedStatus.active_session_details,
|
|
44338
|
+
cachedStatus.sessions
|
|
44339
|
+
]) {
|
|
44340
|
+
if (!Array.isArray(value)) continue;
|
|
44341
|
+
for (const item of value) {
|
|
44342
|
+
if (typeof item === "string") {
|
|
44343
|
+
const id2 = readStringValue(item);
|
|
44344
|
+
if (id2) ids.add(id2);
|
|
44345
|
+
continue;
|
|
44346
|
+
}
|
|
44347
|
+
const record = readObjectRecord(item);
|
|
44348
|
+
const id = readStringValue(record.id, record.sessionId, record.session_id, record.runtimeSessionId, record.instanceId);
|
|
44349
|
+
if (id) ids.add(id);
|
|
44350
|
+
}
|
|
44351
|
+
}
|
|
44352
|
+
return ids;
|
|
44353
|
+
}
|
|
44104
44354
|
function resolveMeshNodeAttribution(node) {
|
|
44105
44355
|
const record = readObjectRecord(node);
|
|
44106
44356
|
return {
|
|
@@ -45979,25 +46229,47 @@ var DaemonCommandRouter = class {
|
|
|
45979
46229
|
* controlbar commands do not — so the controlbar buttons appear to do nothing.
|
|
45980
46230
|
*
|
|
45981
46231
|
* Mirror the existing node-level remote-forward pattern (fast_forward_mesh_node etc.):
|
|
45982
|
-
* scan the
|
|
45983
|
-
*
|
|
45984
|
-
*
|
|
45985
|
-
*
|
|
46232
|
+
* scan the candidate mesh nodes for the one hosting the targetSessionId, and return its
|
|
46233
|
+
* daemonId when that daemonId is a remote daemon (i.e. not this coordinator's own
|
|
46234
|
+
* statusInstanceId). Returns undefined for a locally-hosted session (no forward — execute
|
|
46235
|
+
* locally as before) or when ownership can't be resolved.
|
|
46236
|
+
*
|
|
46237
|
+
* The candidate set spans BOTH the cached inline-mesh nodes and the live aggregate
|
|
46238
|
+
* mesh-status snapshots. The inline cache reliably carries only each node's single primary
|
|
46239
|
+
* session (cachedStatus.activeSession), so a worker hosting more than one session exposes its
|
|
46240
|
+
* non-primary sessions only on the aggregate snapshot nodes (status.activeSessions /
|
|
46241
|
+
* activeSessionDetails, built from live session records). collectMeshNodeHostedSessionIds does
|
|
46242
|
+
* the wider plural-shape scan so a controlbar/modal command targeting a non-primary remote
|
|
46243
|
+
* session still resolves its owner — the singular readCachedInlineMeshActiveSessions semantics
|
|
46244
|
+
* other consumers depend on stay untouched.
|
|
45986
46245
|
*/
|
|
45987
46246
|
resolveRemoteMeshSessionOwnerDaemonId(sessionId) {
|
|
45988
46247
|
const trimmed = typeof sessionId === "string" ? sessionId.trim() : "";
|
|
45989
46248
|
if (!trimmed) return void 0;
|
|
45990
46249
|
const selfDaemonId = this.deps.statusInstanceId;
|
|
45991
|
-
for (const node of this.
|
|
45992
|
-
|
|
45993
|
-
if (!nodeSessions.includes(trimmed)) continue;
|
|
46250
|
+
for (const node of this.collectMeshSessionOwnerCandidateNodes()) {
|
|
46251
|
+
if (!collectMeshNodeHostedSessionIds(node).has(trimmed)) continue;
|
|
45994
46252
|
const nodeDaemonId = readMeshNodeDaemonId(readObjectRecord(node));
|
|
45995
|
-
if (!nodeDaemonId)
|
|
46253
|
+
if (!nodeDaemonId) continue;
|
|
45996
46254
|
if (selfDaemonId && nodeDaemonId === selfDaemonId) return void 0;
|
|
45997
46255
|
return nodeDaemonId;
|
|
45998
46256
|
}
|
|
45999
46257
|
return void 0;
|
|
46000
46258
|
}
|
|
46259
|
+
/**
|
|
46260
|
+
* Candidate nodes for remote-session owner resolution: the cached inline-mesh nodes (which
|
|
46261
|
+
* carry each node's primary session) plus the nodes from every cached aggregate mesh-status
|
|
46262
|
+
* snapshot (which carry each node's full live session list). getCachedInlineMeshNodes()
|
|
46263
|
+
* returns a fresh array, so appending the aggregate nodes never mutates cached state.
|
|
46264
|
+
*/
|
|
46265
|
+
collectMeshSessionOwnerCandidateNodes() {
|
|
46266
|
+
const nodes = this.getCachedInlineMeshNodes();
|
|
46267
|
+
for (const cached3 of this.aggregateMeshStatusCache.values()) {
|
|
46268
|
+
const snapshotNodes = cached3?.snapshot?.nodes;
|
|
46269
|
+
if (Array.isArray(snapshotNodes)) nodes.push(...snapshotNodes);
|
|
46270
|
+
}
|
|
46271
|
+
return nodes;
|
|
46272
|
+
}
|
|
46001
46273
|
getCachedInlineMesh(meshId, inlineMesh) {
|
|
46002
46274
|
if (inlineMesh && typeof inlineMesh === "object") {
|
|
46003
46275
|
return this.warmInlineMeshCache(meshId, inlineMesh);
|
|
@@ -48017,7 +48289,10 @@ ${hintLines.join("\n")}` : "",
|
|
|
48017
48289
|
{
|
|
48018
48290
|
meshId: dispatchMeshContext.meshId,
|
|
48019
48291
|
nodeId: dispatchMeshContext.nodeId,
|
|
48020
|
-
coordinatorDaemonId: dispatchMeshContext.coordinatorDaemonId
|
|
48292
|
+
coordinatorDaemonId: dispatchMeshContext.coordinatorDaemonId,
|
|
48293
|
+
// Session-level anchor: preserved across the P2P dispatch to a
|
|
48294
|
+
// remote worker so its completion echoes back to the right session.
|
|
48295
|
+
coordinatorSessionId: dispatchMeshContext.coordinatorSessionId
|
|
48021
48296
|
}
|
|
48022
48297
|
);
|
|
48023
48298
|
if (stamp) inst.updateSettings(stamp);
|