@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/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 ? "ff8ffda8c3d1a14f846ad4d785e4cab4d111a36b" : void 0) ?? "unknown";
315
- const commitShort = readInjected(true ? "ff8ffda8" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
316
- const version = readInjected(true ? "0.9.82-rc.342" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
317
- const builtAt = readInjected(true ? "2026-06-21T05:12:18.017Z" : void 0);
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 existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, statSync as statSync3, renameSync, writeFileSync as writeFileSync3 } from "fs";
2600
- import { join as join7 } from "path";
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 = join7(getConfigDir(), LEDGER_DIR_NAME);
2610
- if (!existsSync6(dir)) {
2611
- mkdirSync3(dir, { recursive: true, mode: 448 });
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 join7(getLedgerDir(), `${safe}.jsonl`);
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 join7(getLedgerDir(), `${safe}.${index}.jsonl`);
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 join7(getLedgerDir(), `${safe}.archive.jsonl`);
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 join7(getLedgerDir(), `${safe}.archive.${index}.jsonl`);
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 join7(getLedgerDir(), `${safe}.archived-counts.json`);
2924
+ return join8(getLedgerDir(), `${safe}.archived-counts.json`);
2634
2925
  }
2635
2926
  function rotateArchiveFile(meshId, archivePath) {
2636
2927
  let index = 1;
2637
- while (existsSync6(getRotatedArchivePath(meshId, index))) {
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
- renameSync(archivePath, getRotatedArchivePath(meshId, index));
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 (!existsSync6(path42)) return { taskCompleted: 0, taskFailed: 0, taskStalled: 0, recoveryAttempted: 0, totalArchived: 0, lastArchivedAt: "" };
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 (!existsSync6(filePath)) return { archivedCount: 0, retainedCount: 0 };
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 (existsSync6(archivePath) && statSync3(archivePath).size > 50 * 1024 * 1024) {
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 (existsSync6(filePath)) {
3148
+ if (existsSync7(filePath)) {
2858
3149
  try {
2859
- const stat2 = statSync3(filePath);
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 (!existsSync6(filePath)) return [];
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 (existsSync6(getRotatedPath(meshId, index))) {
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
- renameSync(currentPath, getRotatedPath(meshId, index));
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
- this.instance = new _MeshRuntimeStore(meshRuntimeStorePath());
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
- if ((meshId || nodeId || coordinatorDaemonId) && settings.launchedByCoordinator !== true) {
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 resolveMeshSurfacedSessionPreview(metadataEvent) {
7887
+ function readMeshCompletionSummary(metadataEvent) {
7861
7888
  const workerResult = readWorkerResultMetadata(metadataEvent);
7862
7889
  const resultRecord = readRecord4(metadataEvent.result);
7863
- const summaryText = readNonEmptyString2(metadataEvent.finalSummary) || readNonEmptyString2(workerResult?.summary) || readNonEmptyString2(workerResult?.finalSummary) || readNonEmptyString2(resultRecord?.summary) || readNonEmptyString2(resultRecord?.finalSummary);
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 reviewNote = args.metadataEvent.reviewRecommended === true ? " 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.";
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,
@@ -9790,7 +9874,16 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
9790
9874
  }).catch((e) => {
9791
9875
  LOG.error("MeshQueue", `Failed to dispatch task locally to node ${nodeId}: ${e?.message}`);
9792
9876
  updateSessionDeliveryStatus(delivery.id, "failed", { lastError: e?.message, incrementAttempt: true });
9793
- updateTaskStatus(meshId, task.id, "failed");
9877
+ updateTaskStatus(meshId, task.id, "pending");
9878
+ try {
9879
+ appendLedgerEntry(meshId, {
9880
+ kind: "dispatch_failed",
9881
+ nodeId,
9882
+ sessionId,
9883
+ payload: { taskId: task.id, deliveryId: delivery.id, error: e?.message, retryable: true }
9884
+ });
9885
+ } catch {
9886
+ }
9794
9887
  });
9795
9888
  return true;
9796
9889
  }
@@ -10419,6 +10512,9 @@ function injectMeshSystemMessage(components, args) {
10419
10512
  const workerCoordinatorDaemonId = readNonEmptyString2(
10420
10513
  sourceSession?.getState()?.settings?.meshCoordinatorDaemonId
10421
10514
  );
10515
+ const workerCoordinatorSessionId = readNonEmptyString2(
10516
+ sourceSession?.getState()?.settings?.meshCoordinatorSessionId
10517
+ ) || readNonEmptyString2(args.metadataEvent.meshCoordinatorSessionId);
10422
10518
  if (components.onMeshCoordinatorEventForwarded) {
10423
10519
  try {
10424
10520
  const surfacedPreview = resolveMeshSurfacedSessionPreview(args.metadataEvent);
@@ -10799,7 +10895,12 @@ function injectMeshSystemMessage(components, args) {
10799
10895
  workspace: readNonEmptyString2(args.metadataEvent.workspace) || readNonEmptyString2(args.metadataEvent.workspaceName),
10800
10896
  metadataEvent: {
10801
10897
  ...args.metadataEvent,
10802
- ...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 } : {}
10803
10904
  },
10804
10905
  // Silent lifecycle events (agent:ready / agent:generating_started) carry no
10805
10906
  // coordinator message; they are queued only so the coordinator re-runs the
@@ -10807,10 +10908,13 @@ function injectMeshSystemMessage(components, args) {
10807
10908
  // entries without a coordinatorMessage, so a live CLI coordinator is not spammed.
10808
10909
  ...messageText ? { coordinatorMessage: messageText } : {},
10809
10910
  queuedAt: Date.now(),
10810
- ...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 } : {}
10811
10915
  };
10812
10916
  if (queuePendingMeshCoordinatorEvent(pendingEvent)) {
10813
- 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}` : ""})`);
10814
10918
  }
10815
10919
  return { success: true, forwarded: 0 };
10816
10920
  }
@@ -10835,6 +10939,13 @@ function handleMeshForwardEvent(components, payload) {
10835
10939
  targetSessionId: readNonEmptyString2(payload.targetSessionId) || readNonEmptyString2(payload.sessionId) || readNonEmptyString2(payload.instanceId),
10836
10940
  providerType: readNonEmptyString2(payload.providerType),
10837
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),
10838
10949
  // Carry the session identity fields the worker provider event emits so the
10839
10950
  // coordinator's mirror (updateMeshOwnedSession) gets a real workspace/title/
10840
10951
  // settings. Without these the remote-relay hop reconstructs metadataEvent with
@@ -11730,7 +11841,8 @@ function findLiveCoordinators(components) {
11730
11841
  if (!meshId) continue;
11731
11842
  const status = readNonEmptyString2(state.status).toLowerCase();
11732
11843
  const modalParked = status === "waiting_choice" || status === "waiting_approval";
11733
- out.push({ meshId, instance: inst, idle: status === "idle", modalParked });
11844
+ const sessionId = readNonEmptyString2(state.instanceId);
11845
+ out.push({ meshId, instance: inst, sessionId, idle: status === "idle", modalParked });
11734
11846
  }
11735
11847
  return out;
11736
11848
  }
@@ -11742,6 +11854,44 @@ function injectPendingIntoCoordinator(coordinator, pending) {
11742
11854
  ...force ? { force: true } : {}
11743
11855
  });
11744
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
+ }
11745
11895
  async function runMeshReconcileTick(components) {
11746
11896
  const localDaemonId = readNonEmptyString2(loadConfig().machineId) || void 0;
11747
11897
  const drainDaemonIds = resolveCoordinatorDaemonIds(components);
@@ -11826,6 +11976,21 @@ async function runMeshReconcileTick(components) {
11826
11976
  if (targetCoordinators.length === 0) {
11827
11977
  if (modalParkedCoordinators.length > 0) {
11828
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
+ }
11829
11994
  }
11830
11995
  continue;
11831
11996
  }
@@ -11850,12 +12015,55 @@ async function runMeshReconcileTick(components) {
11850
12015
  const mode = forceOnly ? "force-drain \u2192 generating" : "inject \u2192 idle";
11851
12016
  LOG.info("MeshReconcile", `Reconcile ${mode}: ${pendingEvents.length} pending event(s) \u2192 ${targetCoordinators.length} coordinator(s) for mesh ${meshId}`);
11852
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
+ }
11853
12028
  for (const c of targetCoordinators) {
11854
12029
  injectPendingIntoCoordinator(c.instance, pending);
11855
12030
  }
11856
12031
  }
11857
12032
  }
11858
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
+ }
11859
12067
  async function retryUnresolvedDelegateForwards(components) {
11860
12068
  const dispatchMeshCommand = components.dispatchMeshCommand;
11861
12069
  if (!dispatchMeshCommand) return;
@@ -12073,6 +12281,11 @@ function buildForwardPayloadFromPending(event) {
12073
12281
  meshId: readNonEmptyString2(event?.meshId),
12074
12282
  nodeId: readNonEmptyString2(event?.nodeId) || readNonEmptyString2(metadata.meshNodeId),
12075
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) } : {},
12076
12289
  ...metadata
12077
12290
  };
12078
12291
  }
@@ -12095,7 +12308,7 @@ function setupMeshReconcileLoop(components) {
12095
12308
  }
12096
12309
  };
12097
12310
  }
12098
- 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;
12099
12312
  var init_mesh_reconcile_loop = __esm({
12100
12313
  "src/mesh/mesh-reconcile-loop.ts"() {
12101
12314
  "use strict";
@@ -12103,6 +12316,7 @@ var init_mesh_reconcile_loop = __esm({
12103
12316
  init_mesh_config();
12104
12317
  init_logger();
12105
12318
  init_mesh_events_pending();
12319
+ init_mesh_ledger();
12106
12320
  init_mesh_runtime_store();
12107
12321
  init_mesh_events_coordinator();
12108
12322
  init_mesh_unresolved_forward_outbox();
@@ -12114,6 +12328,8 @@ var init_mesh_reconcile_loop = __esm({
12114
12328
  init_chat_message_normalization();
12115
12329
  DEFAULT_RECONCILE_INTERVAL_MS = 4e3;
12116
12330
  DEFAULT_AUTO_PRUNE_MIN_AGE_MS = 24 * 60 * 6e4;
12331
+ heldEventLedgerRecorded = /* @__PURE__ */ new Set();
12332
+ STRICT_SESSION_MATCH_TTL_MS = 6e4;
12117
12333
  }
12118
12334
  });
12119
12335
 
@@ -31622,7 +31838,8 @@ function countNewlines(s) {
31622
31838
  return n;
31623
31839
  }
31624
31840
  var SUBMIT_DELAY_FLOOR_MS = 200;
31625
- var WIN32_SUBMIT_REPEAT_GAP_MS = 300;
31841
+ var WIN32_SUBMIT_RESEND_GAP_MS = 350;
31842
+ var WIN32_SUBMIT_MAX_RESENDS = 14;
31626
31843
  function resolveSubmitDelayMs(specBeforeSubmit, text) {
31627
31844
  const lines = countNewlines(text);
31628
31845
  const linesBonus = Math.min(800, lines * 80);
@@ -31674,6 +31891,10 @@ var FsmDriver = class {
31674
31891
  * after a re-prime we don't re-inject until the screen changes (which
31675
31892
  * resets the stall reference) or another full stall window lapses. */
31676
31893
  lastRefocusAt = 0;
31894
+ /** Timer driving the win32 verification-based submit resend loop (see
31895
+ * WIN32_SUBMIT_* and scheduleWin32Submit). Re-arms itself until the FSM
31896
+ * leaves idle (submitted) or the resend budget is spent. */
31897
+ win32SubmitTimer = null;
31677
31898
  currentEval = null;
31678
31899
  stateHistory = [];
31679
31900
  prevStateAt = 0;
@@ -31790,6 +32011,10 @@ var FsmDriver = class {
31790
32011
  clearTimeout(this.stallTimer);
31791
32012
  this.stallTimer = null;
31792
32013
  }
32014
+ if (this.win32SubmitTimer) {
32015
+ clearTimeout(this.win32SubmitTimer);
32016
+ this.win32SubmitTimer = null;
32017
+ }
31793
32018
  this.specWatcher?.close();
31794
32019
  this.adapter.kill();
31795
32020
  }
@@ -32202,13 +32427,8 @@ var FsmDriver = class {
32202
32427
  const perChar = sm.delay_ms_per_char ?? 0;
32203
32428
  const beforeSubmit = resolveSubmitDelayMs(sm.delay_ms_before_submit, text);
32204
32429
  if (process.platform === "win32") {
32205
- const submitTwice = () => {
32206
- this.adapter.send_keys(sm.submit_key);
32207
- setTimeout(() => this.adapter.send_keys(sm.submit_key), WIN32_SUBMIT_REPEAT_GAP_MS);
32208
- };
32209
32430
  this.adapter.send_keys(text);
32210
- if (beforeSubmit > 0) setTimeout(submitTwice, beforeSubmit);
32211
- else submitTwice();
32431
+ this.scheduleWin32Submit(sm.submit_key, beforeSubmit);
32212
32432
  return;
32213
32433
  }
32214
32434
  if (perChar === 0) {
@@ -32228,6 +32448,40 @@ var FsmDriver = class {
32228
32448
  i += 1;
32229
32449
  }, perChar);
32230
32450
  }
32451
+ /** The agent's current coarse status, derived from the FSM node we're in. */
32452
+ currentStatus() {
32453
+ const st = stateById(this.spec, this.currentStateId);
32454
+ return st ? statusForState(st) : "idle";
32455
+ }
32456
+ /**
32457
+ * win32 verification-based submit. Sends the submit key, waits a gap, and if
32458
+ * the FSM is still 'idle' (the prompt did not submit — the CR was absorbed as
32459
+ * a multiline-paste newline) resends, up to WIN32_SUBMIT_MAX_RESENDS. The
32460
+ * first CR always fires (so a stale/edge status never suppresses the submit);
32461
+ * subsequent resends are gated on still being idle, and stop the instant the
32462
+ * agent leaves idle (submitted → generating / approval). This converges the
32463
+ * nondeterministic multiline window without spamming Enter into the next turn.
32464
+ */
32465
+ scheduleWin32Submit(submitKey, initialDelayMs) {
32466
+ if (this.win32SubmitTimer) {
32467
+ clearTimeout(this.win32SubmitTimer);
32468
+ this.win32SubmitTimer = null;
32469
+ }
32470
+ const fire = (attempt) => {
32471
+ this.win32SubmitTimer = null;
32472
+ this.adapter.send_keys(submitKey);
32473
+ if (attempt + 1 >= WIN32_SUBMIT_MAX_RESENDS) return;
32474
+ this.win32SubmitTimer = setTimeout(() => {
32475
+ if (this.currentStatus() !== "idle") {
32476
+ this.win32SubmitTimer = null;
32477
+ return;
32478
+ }
32479
+ fire(attempt + 1);
32480
+ }, WIN32_SUBMIT_RESEND_GAP_MS);
32481
+ };
32482
+ if (initialDelayMs > 0) this.win32SubmitTimer = setTimeout(() => fire(0), initialDelayMs);
32483
+ else fire(0);
32484
+ }
32231
32485
  handleClickControl(controlId, payload) {
32232
32486
  const ctl = (this.spec.control_bar ?? []).find((c) => c.id === controlId);
32233
32487
  if (!ctl) return;
@@ -34651,7 +34905,10 @@ var CliProviderInstance = class _CliProviderInstance {
34651
34905
  meshNodeFor: assignment.meshId,
34652
34906
  ...assignment.nodeId ? { meshNodeId: assignment.nodeId } : {},
34653
34907
  ...assignment.taskId ? { meshActiveTaskId: assignment.taskId } : {},
34654
- ...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 } : {}
34655
34912
  };
34656
34913
  this.adapter.updateRuntimeSettings?.(this.settings);
34657
34914
  }
@@ -37640,6 +37897,12 @@ Enable and detect this provider from the Machine Providers page before starting
37640
37897
  );
37641
37898
  }
37642
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
+ }
37643
37906
  const sessionRegistry = this.deps.getSessionRegistry?.() || null;
37644
37907
  if (provider && provider.category === "acp") {
37645
37908
  const instanceManager2 = this.deps.getInstanceManager();
@@ -44054,6 +44317,40 @@ function readCachedInlineMeshActiveSessions(node) {
44054
44317
  const sessionId = readStringValue(fallbackSession.id, fallbackSession.sessionId, fallbackSession.session_id, node?.activeSessionId, node?.active_session_id, node?.sessionId, node?.session_id);
44055
44318
  return sessionId ? [sessionId] : [];
44056
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
+ }
44057
44354
  function resolveMeshNodeAttribution(node) {
44058
44355
  const record = readObjectRecord(node);
44059
44356
  return {
@@ -45932,25 +46229,47 @@ var DaemonCommandRouter = class {
45932
46229
  * controlbar commands do not — so the controlbar buttons appear to do nothing.
45933
46230
  *
45934
46231
  * Mirror the existing node-level remote-forward pattern (fast_forward_mesh_node etc.):
45935
- * scan the cached inline-mesh nodes for the one whose active session matches the
45936
- * targetSessionId, and return its daemonId when that daemonId is a remote daemon (i.e.
45937
- * not this coordinator's own statusInstanceId). Returns undefined for a locally-hosted
45938
- * session (no forward — execute locally as before) or when ownership can't be resolved.
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.
45939
46245
  */
45940
46246
  resolveRemoteMeshSessionOwnerDaemonId(sessionId) {
45941
46247
  const trimmed = typeof sessionId === "string" ? sessionId.trim() : "";
45942
46248
  if (!trimmed) return void 0;
45943
46249
  const selfDaemonId = this.deps.statusInstanceId;
45944
- for (const node of this.getCachedInlineMeshNodes()) {
45945
- const nodeSessions = readCachedInlineMeshActiveSessions(node);
45946
- if (!nodeSessions.includes(trimmed)) continue;
46250
+ for (const node of this.collectMeshSessionOwnerCandidateNodes()) {
46251
+ if (!collectMeshNodeHostedSessionIds(node).has(trimmed)) continue;
45947
46252
  const nodeDaemonId = readMeshNodeDaemonId(readObjectRecord(node));
45948
- if (!nodeDaemonId) return void 0;
46253
+ if (!nodeDaemonId) continue;
45949
46254
  if (selfDaemonId && nodeDaemonId === selfDaemonId) return void 0;
45950
46255
  return nodeDaemonId;
45951
46256
  }
45952
46257
  return void 0;
45953
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
+ }
45954
46273
  getCachedInlineMesh(meshId, inlineMesh) {
45955
46274
  if (inlineMesh && typeof inlineMesh === "object") {
45956
46275
  return this.warmInlineMeshCache(meshId, inlineMesh);
@@ -47970,7 +48289,10 @@ ${hintLines.join("\n")}` : "",
47970
48289
  {
47971
48290
  meshId: dispatchMeshContext.meshId,
47972
48291
  nodeId: dispatchMeshContext.nodeId,
47973
- 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
47974
48296
  }
47975
48297
  );
47976
48298
  if (stamp) inst.updateSettings(stamp);