@adhdev/daemon-core 0.9.82-rc.136 → 0.9.82-rc.138

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.
Files changed (63) hide show
  1. package/dist/chat/source-machine.d.ts +166 -0
  2. package/dist/chat/source-resolver.d.ts +104 -0
  3. package/dist/cli-adapters/cli-script-runner.d.ts +45 -0
  4. package/dist/cli-adapters/cli-state-engine.d.ts +169 -0
  5. package/dist/cli-adapters/provider-cli-adapter.d.ts +72 -74
  6. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  7. package/dist/cli-adapters/provider-cli-shared.d.ts +5 -0
  8. package/dist/config/chat-history.d.ts +1 -0
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js +3507 -2288
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +3515 -2301
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/mesh/beads-db.d.ts +54 -0
  15. package/dist/mesh/contracts.d.ts +164 -0
  16. package/dist/mesh/mesh-active-work.d.ts +7 -1
  17. package/dist/mesh/mesh-events.d.ts +10 -4
  18. package/dist/mesh/mesh-ledger.d.ts +21 -1
  19. package/dist/mesh/mesh-refine-status.d.ts +2 -3
  20. package/dist/mesh/mesh-work-queue.d.ts +17 -0
  21. package/dist/mesh/worktree-bootstrap-config.d.ts +2 -4
  22. package/dist/providers/contracts.d.ts +19 -0
  23. package/dist/providers/read-chat-contract.d.ts +29 -0
  24. package/dist/providers/transcript-v2.d.ts +176 -0
  25. package/dist/repo-mesh-types.d.ts +5 -0
  26. package/dist/shared-types.d.ts +7 -0
  27. package/dist/status/snapshot.d.ts +1 -0
  28. package/dist/types.d.ts +5 -0
  29. package/package.json +1 -1
  30. package/src/chat/source-machine.ts +534 -0
  31. package/src/chat/source-resolver.ts +0 -0
  32. package/src/chat/subscription-updates.ts +9 -0
  33. package/src/cli-adapters/cli-script-runner.ts +145 -0
  34. package/src/cli-adapters/cli-state-engine.ts +1054 -0
  35. package/src/cli-adapters/provider-cli-adapter.d.ts +0 -1
  36. package/src/cli-adapters/provider-cli-adapter.ts +413 -1399
  37. package/src/cli-adapters/provider-cli-parse.ts +3 -0
  38. package/src/cli-adapters/provider-cli-shared.ts +17 -1
  39. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
  40. package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
  41. package/src/commands/chat-commands.ts +715 -368
  42. package/src/commands/router.ts +22 -2
  43. package/src/config/chat-history.ts +43 -16
  44. package/src/git/git-worktree.ts +8 -1
  45. package/src/index.ts +3 -2
  46. package/src/mesh/beads-db.ts +305 -2
  47. package/src/mesh/contracts.ts +329 -0
  48. package/src/mesh/coordinator-prompt.ts +12 -17
  49. package/src/mesh/mesh-active-work.ts +162 -59
  50. package/src/mesh/mesh-events.ts +198 -53
  51. package/src/mesh/mesh-ledger.ts +321 -105
  52. package/src/mesh/mesh-refine-status.ts +2 -3
  53. package/src/mesh/mesh-work-queue.ts +116 -120
  54. package/src/mesh/worktree-bootstrap-config.ts +17 -4
  55. package/src/providers/contracts.ts +19 -0
  56. package/src/providers/provider-loader.ts +21 -7
  57. package/src/providers/provider-schema.ts +12 -0
  58. package/src/providers/read-chat-contract.ts +74 -14
  59. package/src/providers/transcript-v2.ts +567 -0
  60. package/src/repo-mesh-types.ts +10 -0
  61. package/src/shared-types.ts +7 -0
  62. package/src/status/snapshot.ts +35 -11
  63. package/src/types.ts +5 -0
@@ -1,4 +1,4 @@
1
- import { appendFileSync, existsSync, readFileSync, unlinkSync } from 'fs';
1
+ import { appendFileSync, existsSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
4
4
  import { loadConfig } from '../config/config.js';
@@ -7,7 +7,8 @@ import { detectCLI } from '../detection/cli-detector.js';
7
7
  import { LOG } from '../logging/logger.js';
8
8
  import { appendLedgerEntry, buildTaskCompletionEvidence, getLedgerDir, getSessionRecoveryContext, isIntentionalCleanupStopEntry, readLedgerEntries } from './mesh-ledger.js';
9
9
  import type { MeshLedgerKind, SessionRecoveryContext } from './mesh-ledger.js';
10
- import { claimNextTask, updateSessionTaskStatus, enqueueTask, updateTaskStatus, getQueue, recordTaskAutoLaunch } from './mesh-work-queue.js';
10
+ import { claimNextTask, updateSessionTaskStatus, enqueueTask, updateTaskStatus, getQueue, recordTaskAutoLaunch, updateDirectDispatchStatus, cleanupTerminalDirectDispatches } from './mesh-work-queue.js';
11
+ import { BeadsDB } from './beads-db.js';
11
12
 
12
13
  // ---------------------------------------------------------------------------
13
14
  // Remote Node Idle Session Tracking
@@ -25,6 +26,23 @@ interface RemoteIdleSession {
25
26
  const REMOTE_IDLE_SESSION_TTL_MS = 5 * 60 * 1000; // 5 minutes
26
27
  const remoteIdleSessions = new Map<string, RemoteIdleSession>(); // key: `${nodeId}:${sessionId}`
27
28
 
29
+ // ---------------------------------------------------------------------------
30
+ // Workspace-to-mesh lookup cache
31
+ // ---------------------------------------------------------------------------
32
+ // getMeshByRepo is called on every coordinator event for sessions without
33
+ // meshNodeFor. Cache results for 5 seconds to avoid repeated config reads.
34
+ const meshByWorkspaceCache = new Map<string, { mesh: any; cachedAt: number }>();
35
+ const MESH_WORKSPACE_CACHE_TTL_MS = 5_000;
36
+
37
+ function getCachedMeshByWorkspace(workspace: string): any {
38
+ const now = Date.now();
39
+ const cached = meshByWorkspaceCache.get(workspace);
40
+ if (cached && now - cached.cachedAt < MESH_WORKSPACE_CACHE_TTL_MS) return cached.mesh;
41
+ const mesh = getMeshByRepo(workspace);
42
+ meshByWorkspaceCache.set(workspace, { mesh, cachedAt: now });
43
+ return mesh;
44
+ }
45
+
28
46
  function readWorkerResultMetadata(event: Record<string, unknown>): Record<string, unknown> | undefined {
29
47
  return readRecord(event.workerResult) || readRecord(event.meshWorkerResult) || readRecord(event.structuredResult);
30
48
  }
@@ -57,6 +75,12 @@ export interface PendingMeshCoordinatorEvent {
57
75
  metadataEvent: Record<string, unknown>;
58
76
  coordinatorMessage?: string;
59
77
  queuedAt: number;
78
+ /**
79
+ * When set, this event is intended for a specific coordinator daemon.
80
+ * Coordinators on other daemons should ignore it during drain.
81
+ * Absent on legacy events — treated as broadcast to any coordinator.
82
+ */
83
+ targetCoordinatorDaemonId?: string;
60
84
  }
61
85
 
62
86
  const REFINE_TERMINAL_EVENTS = new Set(['refine:completed', 'refine:failed']);
@@ -107,21 +131,37 @@ function hasPendingCoordinatorEventDuplicate(event: PendingMeshCoordinatorEvent)
107
131
  return readPendingMeshCoordinatorEventsFromDisk(event.meshId).some((pending) => buildPendingEventFingerprint(pending) === fingerprint);
108
132
  }
109
133
 
110
- function getPendingEventsPath(meshId: string): string {
134
+ function getPendingEventsPath(meshId: string, coordinatorDaemonId?: string): string {
111
135
  const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
136
+ if (coordinatorDaemonId) {
137
+ const safeDaemon = coordinatorDaemonId.replace(/[^a-zA-Z0-9_-]/g, '_');
138
+ return join(getLedgerDir(), `${safe}-${safeDaemon}.pending-events.jsonl`);
139
+ }
112
140
  return join(getLedgerDir(), `${safe}.pending-events.jsonl`);
113
141
  }
114
142
 
115
- function readPendingMeshCoordinatorEventsFromDisk(meshId?: string): PendingMeshCoordinatorEvent[] {
143
+ function readPendingMeshCoordinatorEventsFromDisk(meshId?: string, coordinatorDaemonId?: string): PendingMeshCoordinatorEvent[] {
116
144
  if (!meshId) return [];
117
- const path = getPendingEventsPath(meshId);
118
- if (!existsSync(path)) return [];
119
- try {
120
- const raw = readFileSync(path, 'utf-8');
121
- return raw.split('\n').filter(Boolean).flatMap(line => {
122
- try { return [JSON.parse(line) as PendingMeshCoordinatorEvent]; } catch { return []; }
123
- });
124
- } catch { return []; }
145
+ // Read coordinator-scoped file first; fall back to legacy shared file.
146
+ const paths = coordinatorDaemonId
147
+ ? [getPendingEventsPath(meshId, coordinatorDaemonId), getPendingEventsPath(meshId)]
148
+ : [getPendingEventsPath(meshId)];
149
+ const events: PendingMeshCoordinatorEvent[] = [];
150
+ for (const path of paths) {
151
+ if (!existsSync(path)) continue;
152
+ try {
153
+ const raw = readFileSync(path, 'utf-8');
154
+ const parsed = raw.split('\n').filter(Boolean).flatMap(line => {
155
+ try { return [JSON.parse(line) as PendingMeshCoordinatorEvent]; } catch { return []; }
156
+ });
157
+ // If reading the shared file, filter to events that target this coordinator or are unscoped.
158
+ const filtered = (coordinatorDaemonId && path === getPendingEventsPath(meshId))
159
+ ? parsed.filter(e => !e.targetCoordinatorDaemonId || e.targetCoordinatorDaemonId === coordinatorDaemonId)
160
+ : parsed;
161
+ events.push(...filtered);
162
+ } catch { /* skip unreadable files */ }
163
+ }
164
+ return events;
125
165
  }
126
166
 
127
167
  function refineTerminalEventFromLedger(meshId: string, pending: readonly PendingMeshCoordinatorEvent[]): PendingMeshCoordinatorEvent[] {
@@ -139,7 +179,7 @@ function refineTerminalEventFromLedger(meshId: string, pending: readonly Pending
139
179
  .filter(value => !value.endsWith(':')),
140
180
  );
141
181
  const backfilled: PendingMeshCoordinatorEvent[] = [];
142
- const entries = readLedgerEntries(meshId);
182
+ const entries = readLedgerEntries(meshId, { tail: 200 });
143
183
  for (let i = entries.length - 1; i >= 0; i--) {
144
184
  const entry = entries[i];
145
185
  if (entry.kind !== 'task_completed' && entry.kind !== 'task_failed') continue;
@@ -194,6 +234,19 @@ function reconcilePendingMeshCoordinatorEvents(meshId: string, events: PendingMe
194
234
  ];
195
235
  }
196
236
 
237
+ const MAX_PENDING_EVENTS_BYTES = 100 * 1024; // 100 KB — keep the pending file small
238
+ const MAX_PENDING_EVENTS_KEEP = 50; // keep the last 50 events when trimming
239
+
240
+ function trimPendingEventsIfNeeded(path: string): void {
241
+ try {
242
+ if (!existsSync(path)) return;
243
+ if (statSync(path).size <= MAX_PENDING_EVENTS_BYTES) return;
244
+ const lines = readFileSync(path, 'utf-8').split('\n').filter(Boolean);
245
+ if (lines.length <= MAX_PENDING_EVENTS_KEEP) return;
246
+ writeFileSync(path, lines.slice(-MAX_PENDING_EVENTS_KEEP).join('\n') + '\n', 'utf-8');
247
+ } catch { /* best-effort; if trim fails, append still proceeds */ }
248
+ }
249
+
197
250
  export function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEvent): boolean {
198
251
  try {
199
252
  if (hasPendingRefineTerminalEventDuplicate(event)) {
@@ -204,7 +257,11 @@ export function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEv
204
257
  LOG.info('MeshEvents', `Suppressed duplicate pending ${event.event} for mesh ${event.meshId}`);
205
258
  return true;
206
259
  }
207
- appendFileSync(getPendingEventsPath(event.meshId), JSON.stringify(event) + '\n', 'utf-8');
260
+ // Write to the coordinator-scoped file when the target coordinator is known;
261
+ // fall back to the shared file for legacy/unscoped events.
262
+ const path = getPendingEventsPath(event.meshId, event.targetCoordinatorDaemonId);
263
+ trimPendingEventsIfNeeded(path);
264
+ appendFileSync(path, JSON.stringify(event) + '\n', 'utf-8');
208
265
  return true;
209
266
  } catch (e: any) {
210
267
  LOG.warn('MeshEvents', `Failed to persist pending coordinator event: ${e?.message || e}`);
@@ -212,29 +269,64 @@ export function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEv
212
269
  }
213
270
  }
214
271
 
272
+ // Atomically rename the file before reading so concurrent drains can't both consume
273
+ // the same events. renameSync is atomic on POSIX (same filesystem); only one caller
274
+ // wins the rename — the other gets ENOENT and returns null, preventing duplicate delivery.
275
+ function atomicDrainFile(path: string): string | null {
276
+ const tmpPath = `${path}.draining`;
277
+ try {
278
+ renameSync(path, tmpPath);
279
+ } catch {
280
+ return null; // another drain already renamed it, or file doesn't exist
281
+ }
282
+ try {
283
+ const content = readFileSync(tmpPath, 'utf-8');
284
+ try { unlinkSync(tmpPath); } catch { /* already cleaned up */ }
285
+ return content;
286
+ } catch {
287
+ try { unlinkSync(tmpPath); } catch { /* best-effort cleanup */ }
288
+ return null;
289
+ }
290
+ }
291
+
215
292
  /** Drain and return all pending coordinator events for meshId, removing them from disk. */
216
- export function drainPendingMeshCoordinatorEvents(meshId?: string): PendingMeshCoordinatorEvent[] {
293
+ export function drainPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): PendingMeshCoordinatorEvent[] {
217
294
  if (!meshId) return [];
218
- const path = getPendingEventsPath(meshId);
219
- if (!existsSync(path)) return [];
220
- try {
221
- const parsed = readPendingMeshCoordinatorEventsFromDisk(meshId);
222
- try { unlinkSync(path); } catch { /* concurrent drain already removed it */ }
223
- return reconcilePendingMeshCoordinatorEvents(meshId, parsed);
224
- } catch { return []; }
295
+ const paths = coordinatorDaemonId
296
+ ? [getPendingEventsPath(meshId, coordinatorDaemonId), getPendingEventsPath(meshId)]
297
+ : [getPendingEventsPath(meshId)];
298
+ const all: PendingMeshCoordinatorEvent[] = [];
299
+ for (const path of paths) {
300
+ const content = atomicDrainFile(path);
301
+ if (!content) continue;
302
+ const parsed = content.split('\n').filter(Boolean).flatMap(line => {
303
+ try { return [JSON.parse(line) as PendingMeshCoordinatorEvent]; } catch { return []; }
304
+ });
305
+ // If reading the shared file, filter to events that target this coordinator or are unscoped.
306
+ const filtered = (coordinatorDaemonId && path === getPendingEventsPath(meshId))
307
+ ? parsed.filter(e => !e.targetCoordinatorDaemonId || e.targetCoordinatorDaemonId === coordinatorDaemonId)
308
+ : parsed;
309
+ all.push(...filtered);
310
+ }
311
+ if (all.length === 0) return [];
312
+ return reconcilePendingMeshCoordinatorEvents(meshId, all);
225
313
  }
226
314
 
227
315
  /** Peek at pending coordinator events without draining (non-destructive). */
228
- export function getPendingMeshCoordinatorEvents(meshId?: string): readonly PendingMeshCoordinatorEvent[] {
316
+ export function getPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): readonly PendingMeshCoordinatorEvent[] {
229
317
  if (!meshId) return [];
230
- return reconcilePendingMeshCoordinatorEvents(meshId, readPendingMeshCoordinatorEventsFromDisk(meshId));
318
+ return reconcilePendingMeshCoordinatorEvents(meshId, readPendingMeshCoordinatorEventsFromDisk(meshId, coordinatorDaemonId));
231
319
  }
232
320
 
233
- /** Explicitly clear all pending coordinator events for a mesh. */
234
- export function clearPendingMeshCoordinatorEvents(meshId?: string): void {
321
+ /** Explicitly clear all pending coordinator events for a mesh (and coordinator if scoped). */
322
+ export function clearPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): void {
235
323
  if (!meshId) return;
236
- const path = getPendingEventsPath(meshId);
237
- if (existsSync(path)) try { unlinkSync(path); } catch { /* already removed */ }
324
+ const paths = coordinatorDaemonId
325
+ ? [getPendingEventsPath(meshId, coordinatorDaemonId), getPendingEventsPath(meshId)]
326
+ : [getPendingEventsPath(meshId)];
327
+ for (const path of paths) {
328
+ if (existsSync(path)) try { unlinkSync(path); } catch { /* already removed */ }
329
+ }
238
330
  }
239
331
 
240
332
  function readNonEmptyString(value: unknown): string {
@@ -319,7 +411,7 @@ function isIntentionalCleanupStopMetadata(event: Record<string, unknown>): boole
319
411
  function hasRecentIntentionalCleanupStop(meshId: string, sessionId?: string, nodeId?: string): boolean {
320
412
  if (!sessionId && !nodeId) return false;
321
413
  const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
322
- const entries = readLedgerEntries(meshId);
414
+ const entries = readLedgerEntries(meshId, { tail: 200 });
323
415
  for (let i = entries.length - 1; i >= 0; i--) {
324
416
  const entry = entries[i];
325
417
  const timestamp = new Date(entry.timestamp).getTime();
@@ -344,7 +436,22 @@ function shouldSuppressIntentionalCleanupStop(args: {
344
436
  }
345
437
 
346
438
  const RECENT_COMPLETION_FINGERPRINT_TTL_MS = 10 * 60 * 1000;
347
- const recentCompletionFingerprints = new Map<string, number>();
439
+
440
+ function hasFingerprintSeen(fingerprint: string): boolean {
441
+ try {
442
+ return BeadsDB.getInstance().hasCompletionFingerprint(fingerprint);
443
+ } catch {
444
+ return false;
445
+ }
446
+ }
447
+
448
+ function recordFingerprintSeen(fingerprint: string): void {
449
+ try {
450
+ const db = BeadsDB.getInstance();
451
+ db.recordCompletionFingerprint(fingerprint, RECENT_COMPLETION_FINGERPRINT_TTL_MS);
452
+ db.sweepExpiredFingerprints();
453
+ } catch { /* best-effort; duplicate events are preferable to a crash */ }
454
+ }
348
455
 
349
456
  function readEventTimestamp(value: unknown): number | null {
350
457
  if (typeof value === 'number' && Number.isFinite(value)) return value;
@@ -365,6 +472,10 @@ function buildMeshCompletionFingerprint(args: {
365
472
  providerSessionId?: string;
366
473
  timestamp?: number | null;
367
474
  finalSummary?: string;
475
+ /** When set, scopes the fingerprint to a specific coordinator daemon so
476
+ * two coordinators processing events from their respective workers don't
477
+ * suppress each other's completion events. */
478
+ coordinatorDaemonId?: string;
368
479
  }): string {
369
480
  const timestampPart = Number.isFinite(args.timestamp)
370
481
  ? String(args.timestamp)
@@ -376,6 +487,7 @@ function buildMeshCompletionFingerprint(args: {
376
487
  args.providerType || '',
377
488
  args.providerSessionId || '',
378
489
  timestampPart,
490
+ args.coordinatorDaemonId || '',
379
491
  ].join('::');
380
492
  }
381
493
 
@@ -387,27 +499,20 @@ function isDuplicateMeshCompletionEvent(args: {
387
499
  providerSessionId?: string;
388
500
  timestamp?: number | null;
389
501
  finalSummary?: string;
502
+ coordinatorDaemonId?: string;
390
503
  }): boolean {
391
504
  const fingerprint = buildMeshCompletionFingerprint(args);
392
505
  if (!fingerprint) return false;
393
- const now = Date.now();
394
- for (const [key, seenAt] of recentCompletionFingerprints.entries()) {
395
- if (now - seenAt > RECENT_COMPLETION_FINGERPRINT_TTL_MS) recentCompletionFingerprints.delete(key);
396
- }
397
- if (recentCompletionFingerprints.has(fingerprint)) return true;
398
- recentCompletionFingerprints.set(fingerprint, now);
506
+ if (hasFingerprintSeen(fingerprint)) return true;
507
+ recordFingerprintSeen(fingerprint);
399
508
  return false;
400
509
  }
401
510
 
402
511
  function isDuplicateRefineTerminalEvent(meshId: string, eventName: string, metadataEvent: Record<string, unknown>): boolean {
403
512
  const fingerprint = buildRefineTerminalEventFingerprint(meshId, eventName, metadataEvent);
404
513
  if (!fingerprint) return false;
405
- const now = Date.now();
406
- for (const [key, seenAt] of recentCompletionFingerprints.entries()) {
407
- if (now - seenAt > RECENT_COMPLETION_FINGERPRINT_TTL_MS) recentCompletionFingerprints.delete(key);
408
- }
409
- if (recentCompletionFingerprints.has(fingerprint)) return true;
410
- recentCompletionFingerprints.set(fingerprint, now);
514
+ if (hasFingerprintSeen(fingerprint)) return true;
515
+ recordFingerprintSeen(fingerprint);
411
516
  return false;
412
517
  }
413
518
 
@@ -417,7 +522,10 @@ function findRecentTerminalLedgerEvidence(args: {
417
522
  nodeId?: string;
418
523
  }): { id: string; kind: MeshLedgerKind; payload: Record<string, unknown>; timestamp: string } | null {
419
524
  if (!args.sessionId && !args.nodeId) return null;
420
- const entries = readLedgerEntries(args.meshId);
525
+ // Tail-limit: 200 entries gives a wide enough window to catch terminal events for active
526
+ // sessions while avoiding a full O(n) scan. If a terminal is older than 200 entries,
527
+ // the BeadsDB fingerprint dedup will still block duplicate processing downstream.
528
+ const entries = readLedgerEntries(args.meshId, { tail: 200 });
421
529
  for (let i = entries.length - 1; i >= 0; i--) {
422
530
  const entry = entries[i];
423
531
  if (entry.kind !== 'task_completed' && entry.kind !== 'task_failed' && entry.kind !== 'task_stalled') continue;
@@ -435,7 +543,8 @@ function findRecentTerminalLedgerEvidence(args: {
435
543
  // entry (identified by terminalId) in ledger order. Positional (append) order is used rather
436
544
  // than timestamp comparison because both entries may share the same millisecond.
437
545
  function hasDispatchAfterTerminal(meshId: string, sessionId: string, terminalId: string): boolean {
438
- const entries = readLedgerEntries(meshId);
546
+ // 200-entry window matches findRecentTerminalLedgerEvidence for consistency.
547
+ const entries = readLedgerEntries(meshId, { tail: 200 });
439
548
  let pastTerminal = false;
440
549
  for (const entry of entries) {
441
550
  if (!pastTerminal) {
@@ -564,6 +673,13 @@ const autoLaunchInProgress = new Set<string>();
564
673
  const autoLaunchCooldownUntil = new Map<string, number>();
565
674
  const AUTO_LAUNCH_COOLDOWN_MS = 5_000;
566
675
 
676
+ function sweepExpiredCooldowns(): void {
677
+ const now = Date.now();
678
+ for (const [key, until] of autoLaunchCooldownUntil) {
679
+ if (now >= until) autoLaunchCooldownUntil.delete(key);
680
+ }
681
+ }
682
+
567
683
  function normalizeProviderPriority(policy: unknown): string[] {
568
684
  const raw = policy && typeof policy === 'object' && !Array.isArray(policy)
569
685
  ? (policy as Record<string, unknown>).providerPriority
@@ -770,12 +886,14 @@ async function maybeAutoLaunchOneQueueSession(components: DaemonComponents, mesh
770
886
  const nodeId = readNonEmptyString(node?.id);
771
887
  if (!nodeId) continue;
772
888
  const launchKey = `${meshId}:${nodeId}`;
889
+ const now = Date.now();
773
890
  const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
891
+ if (cooldownUntil > 0 && now >= cooldownUntil) autoLaunchCooldownUntil.delete(launchKey);
774
892
  if (autoLaunchInProgress.has(launchKey)) {
775
893
  markAutoLaunch(meshId, task.id, { status: 'skipped', reason: 'auto_launch_in_progress', nodeId });
776
894
  continue;
777
895
  }
778
- if (Date.now() < cooldownUntil) {
896
+ if (now < cooldownUntil) {
779
897
  markAutoLaunch(meshId, task.id, { status: 'skipped', reason: 'auto_launch_cooldown', nodeId });
780
898
  continue;
781
899
  }
@@ -825,13 +943,13 @@ async function maybeAutoLaunchOneQueueSession(components: DaemonComponents, mesh
825
943
  if (!launchResult?.success) {
826
944
  const reason = launchResult?.error || 'launch_cli_failed';
827
945
  markAutoLaunch(meshId, task.id, { status: 'failed', reason, nodeId, providerType: resolved.providerType });
828
- autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
946
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS); sweepExpiredCooldowns();
829
947
  return false;
830
948
  }
831
949
  const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
832
950
  if (!sessionId) {
833
951
  markAutoLaunch(meshId, task.id, { status: 'failed', reason: 'launch_missing_session_id', nodeId, providerType: resolved.providerType });
834
- autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
952
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS); sweepExpiredCooldowns();
835
953
  return false;
836
954
  }
837
955
  markAutoLaunch(meshId, task.id, { status: 'completed', nodeId, providerType: resolved.providerType, sessionId });
@@ -1020,6 +1138,15 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1020
1138
  }) {
1021
1139
  const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
1022
1140
  const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
1141
+
1142
+ // Resolve coordinator ownership early — used in fingerprinting and coordinator routing.
1143
+ const sourceSession = args.sourceInstanceId
1144
+ ? components.instanceManager.getInstance(args.sourceInstanceId)
1145
+ : undefined;
1146
+ const workerCoordinatorDaemonId = readNonEmptyString(
1147
+ (sourceSession?.getState()?.settings as Record<string, unknown>)?.meshCoordinatorDaemonId,
1148
+ );
1149
+ const localDaemonId = readNonEmptyString(loadConfig().machineId);
1023
1150
  const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
1024
1151
  event: args.event,
1025
1152
  meshId: args.meshId,
@@ -1102,6 +1229,9 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1102
1229
  providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
1103
1230
  timestamp: eventTimestamp,
1104
1231
  finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
1232
+ // Scope dedup to the coordinator daemon so two coordinators for the same mesh
1233
+ // don't suppress each other's completion events via shared fingerprint table.
1234
+ coordinatorDaemonId: workerCoordinatorDaemonId || undefined,
1105
1235
  });
1106
1236
  if (duplicateCompletion) {
1107
1237
  LOG.info('MeshEvents', `Suppressed duplicate completion for mesh ${args.meshId} session ${eventSessionId}`);
@@ -1121,9 +1251,9 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1121
1251
  occurredAt: eventTimestamp !== null ? new Date(eventTimestamp).toISOString() : undefined,
1122
1252
  });
1123
1253
  completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
1254
+ updateDirectDispatchStatus(args.meshId, sessionId, 'completed');
1255
+ setImmediate(() => cleanupTerminalDirectDispatches());
1124
1256
  if (nodeId && providerType) {
1125
- // Queue state is already updated above; setImmediate avoids the
1126
- // 500 ms artificial delay while still deferring past this call frame.
1127
1257
  setImmediate(() => {
1128
1258
  tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
1129
1259
  });
@@ -1140,8 +1270,10 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1140
1270
  const completedTask = sessionId && hasCompletionEvidence
1141
1271
  ? updateSessionTaskStatus(args.meshId, sessionId, 'completed')
1142
1272
  : null;
1143
- if (completedTask) {
1273
+ if (completedTask && sessionId) {
1144
1274
  completedTaskForLedger = { id: completedTask.id };
1275
+ updateDirectDispatchStatus(args.meshId, sessionId, 'completed');
1276
+ setImmediate(() => cleanupTerminalDirectDispatches());
1145
1277
  try {
1146
1278
  appendLedgerEntry(args.meshId, {
1147
1279
  kind: 'task_completed',
@@ -1189,6 +1321,10 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1189
1321
  if (sessionId && nodeId) {
1190
1322
  remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
1191
1323
  }
1324
+ if (sessionId) {
1325
+ // Mark direct dispatch as acknowledged — the session started generating.
1326
+ updateDirectDispatchStatus(args.meshId, sessionId, 'acked');
1327
+ }
1192
1328
  } else if (args.event === 'agent:stopped') {
1193
1329
  const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
1194
1330
  const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
@@ -1198,6 +1334,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1198
1334
  if (sessionId) {
1199
1335
  const failedTask = updateSessionTaskStatus(args.meshId, sessionId, 'failed');
1200
1336
  completedTaskForLedger = failedTask ? { id: failedTask.id } : null;
1337
+ updateDirectDispatchStatus(args.meshId, sessionId, 'failed');
1201
1338
  }
1202
1339
  }
1203
1340
 
@@ -1320,6 +1457,10 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1320
1457
  const instState = inst.getState();
1321
1458
  if (instState.settings?.meshCoordinatorFor !== args.meshId) return false;
1322
1459
  if (args.sourceInstanceId && instState.instanceId === args.sourceInstanceId) return false;
1460
+ // If the worker knows which coordinator daemon launched it, only route to coordinators
1461
+ // on that specific daemon. This prevents cross-contamination when multiple coordinator
1462
+ // sessions run simultaneously for the same mesh on different daemons.
1463
+ if (workerCoordinatorDaemonId && localDaemonId && workerCoordinatorDaemonId !== localDaemonId) return false;
1323
1464
  return true;
1324
1465
  });
1325
1466
 
@@ -1330,7 +1471,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1330
1471
  const isRefineTerminalEvent = REFINE_TERMINAL_EVENTS.has(args.event);
1331
1472
 
1332
1473
  if (coordinatorInstances.length === 0) {
1333
- // No CLI coordinator session found — buffer for MCP-based coordinators.
1474
+ // No local CLI coordinator — buffer for MCP-based coordinator on the target daemon.
1334
1475
  if (queuePendingMeshCoordinatorEvent({
1335
1476
  event: args.event,
1336
1477
  meshId: args.meshId,
@@ -1343,8 +1484,11 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1343
1484
  },
1344
1485
  coordinatorMessage: messageText,
1345
1486
  queuedAt: Date.now(),
1487
+ // Scope to the coordinator daemon that launched this worker so drain
1488
+ // by other coordinators on the same daemon doesn't consume this event.
1489
+ ...(workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {}),
1346
1490
  })) {
1347
- LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId})`);
1491
+ LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId}${workerCoordinatorDaemonId ? `, coordinator daemon ${workerCoordinatorDaemonId}` : ''})`);
1348
1492
  }
1349
1493
  return { success: true, forwarded: 0 };
1350
1494
  }
@@ -1383,6 +1527,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1383
1527
  },
1384
1528
  coordinatorMessage: messageText,
1385
1529
  queuedAt: Date.now(),
1530
+ ...(workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {}),
1386
1531
  })) {
1387
1532
  if (allCoordinatorsGenerating) {
1388
1533
  LOG.info('MeshEvents', `Queued ${args.event} for generating CLI coordinator (mesh ${args.meshId}) — will be delivered via get_pending_mesh_events when coordinator returns to idle`);
@@ -1484,7 +1629,7 @@ export function setupMeshEventForwarding(components: DaemonComponents) {
1484
1629
  const isMeshDelegate = Boolean(meshIdFromRuntime || settings.launchedByCoordinator);
1485
1630
  if (!isMeshDelegate) return;
1486
1631
 
1487
- const mesh = meshIdFromRuntime ? getMeshWithCache(components, meshIdFromRuntime) : getMeshByRepo(workspace);
1632
+ const mesh = meshIdFromRuntime ? getMeshWithCache(components, meshIdFromRuntime) : getCachedMeshByWorkspace(workspace);
1488
1633
  const meshId = meshIdFromRuntime || readNonEmptyString(mesh?.id);
1489
1634
  if (!meshId) return;
1490
1635