@adhdev/daemon-core 0.9.82-rc.7 → 0.9.82-rc.70

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 (72) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +2 -0
  2. package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
  3. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  4. package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
  5. package/dist/commands/router.d.ts +24 -0
  6. package/dist/config/mesh-config.d.ts +66 -1
  7. package/dist/git/git-commands.d.ts +1 -0
  8. package/dist/git/git-status.d.ts +5 -0
  9. package/dist/git/git-types.d.ts +10 -0
  10. package/dist/index.d.ts +13 -6
  11. package/dist/index.js +4619 -1143
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +4582 -1128
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/installer.d.ts +1 -4
  16. package/dist/launch.d.ts +1 -1
  17. package/dist/logging/async-batch-writer.d.ts +10 -0
  18. package/dist/mesh/beads-db.d.ts +18 -0
  19. package/dist/mesh/mesh-active-work.d.ts +48 -0
  20. package/dist/mesh/mesh-events.d.ts +28 -5
  21. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  22. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  23. package/dist/mesh/mesh-ledger.d.ts +38 -1
  24. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  25. package/dist/mesh/refine-config.d.ts +119 -0
  26. package/dist/providers/chat-message-normalization.d.ts +1 -0
  27. package/dist/providers/cli-provider-instance.d.ts +1 -0
  28. package/dist/repo-mesh-types.d.ts +160 -0
  29. package/dist/status/reporter.d.ts +2 -0
  30. package/package.json +3 -1
  31. package/src/boot/daemon-lifecycle.ts +4 -0
  32. package/src/cli-adapters/provider-cli-adapter.ts +91 -3
  33. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  34. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  35. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  36. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  37. package/src/cli-adapters/provider-cli-shared.ts +20 -10
  38. package/src/commands/handler.ts +8 -1
  39. package/src/commands/mesh-coordinator.ts +13 -143
  40. package/src/commands/router.ts +2452 -409
  41. package/src/config/chat-history.ts +9 -7
  42. package/src/config/mesh-config.ts +244 -1
  43. package/src/daemon/dev-cli-debug.ts +10 -1
  44. package/src/detection/ide-detector.ts +26 -16
  45. package/src/git/git-commands.ts +3 -3
  46. package/src/git/git-status.ts +97 -6
  47. package/src/git/git-summary.ts +3 -0
  48. package/src/git/git-types.ts +11 -0
  49. package/src/index.ts +39 -5
  50. package/src/installer.d.ts +1 -1
  51. package/src/installer.ts +8 -6
  52. package/src/launch.d.ts +1 -1
  53. package/src/launch.ts +37 -28
  54. package/src/logging/async-batch-writer.ts +55 -0
  55. package/src/logging/logger.ts +2 -1
  56. package/src/mesh/beads-db.ts +176 -0
  57. package/src/mesh/coordinator-prompt.ts +4 -2
  58. package/src/mesh/mesh-active-work.ts +205 -0
  59. package/src/mesh/mesh-events.ts +291 -38
  60. package/src/mesh/mesh-fast-forward.ts +430 -0
  61. package/src/mesh/mesh-host-ownership.ts +73 -0
  62. package/src/mesh/mesh-ledger.ts +138 -1
  63. package/src/mesh/mesh-work-queue.ts +199 -137
  64. package/src/mesh/refine-config.ts +306 -0
  65. package/src/providers/chat-message-normalization.ts +3 -1
  66. package/src/providers/cli-provider-instance.ts +66 -1
  67. package/src/providers/ide-provider-instance.ts +17 -3
  68. package/src/providers/provider-loader.ts +10 -4
  69. package/src/providers/version-archive.ts +38 -20
  70. package/src/repo-mesh-types.ts +174 -0
  71. package/src/status/reporter.ts +15 -0
  72. package/src/system/host-memory.ts +29 -12
@@ -1,63 +1,149 @@
1
+ import { appendFileSync, existsSync, readFileSync, unlinkSync } from 'fs';
2
+ import { join } from 'path';
1
3
  import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
2
4
  import { loadConfig } from '../config/config.js';
3
5
  import { getMesh, getMeshByRepo } from '../config/mesh-config.js';
4
6
  import { detectCLI } from '../detection/cli-detector.js';
5
7
  import { LOG } from '../logging/logger.js';
6
- import { appendLedgerEntry, buildTaskCompletionEvidence, getSessionRecoveryContext, isIntentionalCleanupStopEntry, readLedgerEntries } from './mesh-ledger.js';
8
+ import { appendLedgerEntry, buildTaskCompletionEvidence, getLedgerDir, getSessionRecoveryContext, isIntentionalCleanupStopEntry, readLedgerEntries } from './mesh-ledger.js';
7
9
  import type { MeshLedgerKind, SessionRecoveryContext } from './mesh-ledger.js';
8
10
  import { claimNextTask, updateSessionTaskStatus, enqueueTask, updateTaskStatus, getQueue, recordTaskAutoLaunch } from './mesh-work-queue.js';
9
11
 
10
12
  // ---------------------------------------------------------------------------
11
13
  // Remote Node Idle Session Tracking
12
14
  // ---------------------------------------------------------------------------
13
- // Tracks remote sessions that emitted 'agent:ready' so triggerMeshQueue
14
- // can assign tasks to them.
15
+ // Tracks remote sessions that emitted 'agent:ready' so triggerMeshQueue
16
+ // can assign tasks to them. Each entry carries an expiresAt timestamp;
17
+ // entries are swept on insertion to prevent unbounded growth.
15
18
  // ---------------------------------------------------------------------------
16
19
  interface RemoteIdleSession {
17
20
  nodeId: string;
18
21
  sessionId: string;
19
22
  providerType: string;
23
+ expiresAt: number;
20
24
  }
25
+ const REMOTE_IDLE_SESSION_TTL_MS = 5 * 60 * 1000; // 5 minutes
21
26
  const remoteIdleSessions = new Map<string, RemoteIdleSession>(); // key: `${nodeId}:${sessionId}`
22
27
 
28
+ function readWorkerResultMetadata(event: Record<string, unknown>): Record<string, unknown> | undefined {
29
+ return readRecord(event.workerResult) || readRecord(event.meshWorkerResult) || readRecord(event.structuredResult);
30
+ }
31
+
32
+ function sweepExpiredRemoteIdleSessions(): void {
33
+ const now = Date.now();
34
+ for (const [key, session] of remoteIdleSessions) {
35
+ if (session.expiresAt <= now) remoteIdleSessions.delete(key);
36
+ }
37
+ }
38
+
23
39
  // ---------------------------------------------------------------------------
24
- // MCP coordinator pending-event queue
40
+ // MCP coordinator pending-event queue — FILE-BASED PERSISTENCE
25
41
  // ---------------------------------------------------------------------------
26
42
  // When a mesh event fires but no CLI coordinator session is registered (e.g.
27
- // the coordinator is Claude Code running via MCP), we buffer the event here.
28
- // The MCP server drains this queue on every mesh_status / mesh_send_task poll.
43
+ // the coordinator is Claude Code running via MCP), we persist the event to a
44
+ // per-mesh JSONL file so it survives daemon restarts. The 50-entry hard cap
45
+ // is removed; the file is drained atomically on each get_pending_mesh_events
46
+ // call and limited to 100 KB to prevent runaway growth.
47
+ //
48
+ // File: <ledgerDir>/<meshId>.pending-events.jsonl
29
49
  // ---------------------------------------------------------------------------
30
50
 
31
51
  export interface PendingMeshCoordinatorEvent {
32
52
  event: string;
33
53
  meshId: string;
34
54
  nodeLabel: string;
55
+ nodeId?: string;
56
+ workspace?: string;
35
57
  metadataEvent: Record<string, unknown>;
36
58
  queuedAt: number;
37
59
  }
38
60
 
39
- const MAX_PENDING_EVENTS = 50;
40
- const pendingMeshCoordinatorEvents: PendingMeshCoordinatorEvent[] = [];
61
+ const REFINE_TERMINAL_EVENTS = new Set(['refine:completed', 'refine:failed']);
62
+
63
+ function readRefineJobId(event: { metadataEvent?: Record<string, unknown> } | Record<string, unknown>): string {
64
+ const metadata = readRecord((event as any).metadataEvent) || event as Record<string, unknown>;
65
+ const result = readRecord(metadata.result);
66
+ const refineJob = readRecord(result?.refineJob);
67
+ return readNonEmptyString(metadata.jobId) || readNonEmptyString(refineJob?.jobId);
68
+ }
69
+
70
+ function buildRefineTerminalEventFingerprint(meshId: string, eventName: string, metadataEvent: Record<string, unknown>): string {
71
+ const jobId = readRefineJobId({ metadataEvent });
72
+ return jobId && REFINE_TERMINAL_EVENTS.has(eventName) ? `${meshId}::${eventName}::${jobId}` : '';
73
+ }
74
+
75
+ function hasPendingRefineTerminalEventDuplicate(event: PendingMeshCoordinatorEvent): boolean {
76
+ if (!REFINE_TERMINAL_EVENTS.has(event.event)) return false;
77
+ const jobId = readRefineJobId(event);
78
+ if (!jobId) return false;
79
+ return getPendingMeshCoordinatorEvents(event.meshId).some((pending) =>
80
+ pending.event === event.event && readRefineJobId(pending) === jobId,
81
+ );
82
+ }
41
83
 
42
- /** Drain and return all pending coordinator events, clearing the queue. */
43
- export function drainPendingMeshCoordinatorEvents(): PendingMeshCoordinatorEvent[] {
44
- return pendingMeshCoordinatorEvents.splice(0);
84
+ function getPendingEventsPath(meshId: string): string {
85
+ const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
86
+ return join(getLedgerDir(), `${safe}.pending-events.jsonl`);
87
+ }
88
+
89
+ export function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEvent): boolean {
90
+ try {
91
+ if (hasPendingRefineTerminalEventDuplicate(event)) {
92
+ LOG.info('MeshEvents', `Suppressed duplicate pending ${event.event} for refine job ${readRefineJobId(event)}`);
93
+ return true;
94
+ }
95
+ appendFileSync(getPendingEventsPath(event.meshId), JSON.stringify(event) + '\n', 'utf-8');
96
+ return true;
97
+ } catch (e: any) {
98
+ LOG.warn('MeshEvents', `Failed to persist pending coordinator event: ${e?.message || e}`);
99
+ return false;
100
+ }
101
+ }
102
+
103
+ /** Drain and return all pending coordinator events for meshId, removing them from disk. */
104
+ export function drainPendingMeshCoordinatorEvents(meshId?: string): PendingMeshCoordinatorEvent[] {
105
+ if (!meshId) return [];
106
+ const path = getPendingEventsPath(meshId);
107
+ if (!existsSync(path)) return [];
108
+ try {
109
+ const raw = readFileSync(path, 'utf-8');
110
+ try { unlinkSync(path); } catch { /* concurrent drain already removed it */ }
111
+ return raw.split('\n').filter(Boolean).flatMap(line => {
112
+ try { return [JSON.parse(line) as PendingMeshCoordinatorEvent]; } catch { return []; }
113
+ });
114
+ } catch { return []; }
45
115
  }
46
116
 
47
117
  /** Peek at pending coordinator events without draining (non-destructive). */
48
- export function getPendingMeshCoordinatorEvents(): readonly PendingMeshCoordinatorEvent[] {
49
- return pendingMeshCoordinatorEvents.slice();
118
+ export function getPendingMeshCoordinatorEvents(meshId?: string): readonly PendingMeshCoordinatorEvent[] {
119
+ if (!meshId) return [];
120
+ const path = getPendingEventsPath(meshId);
121
+ if (!existsSync(path)) return [];
122
+ try {
123
+ const raw = readFileSync(path, 'utf-8');
124
+ return raw.split('\n').filter(Boolean).flatMap(line => {
125
+ try { return [JSON.parse(line) as PendingMeshCoordinatorEvent]; } catch { return []; }
126
+ });
127
+ } catch { return []; }
50
128
  }
51
129
 
52
- /** Explicitly clear all pending coordinator events. */
53
- export function clearPendingMeshCoordinatorEvents(): void {
54
- pendingMeshCoordinatorEvents.splice(0);
130
+ /** Explicitly clear all pending coordinator events for a mesh. */
131
+ export function clearPendingMeshCoordinatorEvents(meshId?: string): void {
132
+ if (!meshId) return;
133
+ const path = getPendingEventsPath(meshId);
134
+ if (existsSync(path)) try { unlinkSync(path); } catch { /* already removed */ }
55
135
  }
56
136
 
57
137
  function readNonEmptyString(value: unknown): string {
58
138
  return typeof value === 'string' && value.trim() ? value.trim() : '';
59
139
  }
60
140
 
141
+ function readRecord(value: unknown): Record<string, unknown> | undefined {
142
+ return value && typeof value === 'object' && !Array.isArray(value)
143
+ ? value as Record<string, unknown>
144
+ : undefined;
145
+ }
146
+
61
147
  function resolveEventSessionId(event: Record<string, unknown>, fallback?: unknown): string {
62
148
  return readNonEmptyString(event.targetSessionId)
63
149
  || readNonEmptyString(event.sessionId)
@@ -72,6 +158,9 @@ const MESH_COORDINATOR_EVENTS = new Set([
72
158
  'agent:stopped',
73
159
  'agent:ready',
74
160
  'monitor:long_generating',
161
+ 'refine:accepted',
162
+ 'refine:completed',
163
+ 'refine:failed',
75
164
  ]);
76
165
 
77
166
  const EVENT_TO_LEDGER_KIND: Record<string, MeshLedgerKind> = {
@@ -86,10 +175,21 @@ function isMeshCoordinatorEvent(eventName: unknown): eventName is string {
86
175
  }
87
176
 
88
177
  function formatCompletionMetadata(event: Record<string, unknown>): string {
178
+ const completionDiagnostic = event.completionDiagnostic && typeof event.completionDiagnostic === 'object'
179
+ ? event.completionDiagnostic as Record<string, unknown>
180
+ : null;
181
+ const diagnosticReason = completionDiagnostic
182
+ ? readNonEmptyString(completionDiagnostic.blockReason) || 'present'
183
+ : '';
184
+ const finalAssistantPresent = typeof completionDiagnostic?.finalAssistantPresent === 'boolean'
185
+ ? String(completionDiagnostic.finalAssistantPresent)
186
+ : '';
89
187
  const parts = [
90
188
  readNonEmptyString(event.targetSessionId) ? `session_id=${readNonEmptyString(event.targetSessionId)}` : '',
91
189
  readNonEmptyString(event.providerType) ? `provider=${readNonEmptyString(event.providerType)}` : '',
92
190
  readNonEmptyString(event.providerSessionId) ? `provider_session_id=${readNonEmptyString(event.providerSessionId)}` : '',
191
+ diagnosticReason ? `completion_diagnostic=${diagnosticReason}` : '',
192
+ finalAssistantPresent ? `final_assistant=${finalAssistantPresent}` : '',
93
193
  ].filter(Boolean);
94
194
  return parts.length > 0 ? ` (${parts.join('; ')})` : '';
95
195
  }
@@ -140,6 +240,74 @@ function shouldSuppressIntentionalCleanupStop(args: {
140
240
  return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
141
241
  }
142
242
 
243
+ const RECENT_COMPLETION_FINGERPRINT_TTL_MS = 10 * 60 * 1000;
244
+ const recentCompletionFingerprints = new Map<string, number>();
245
+
246
+ function readEventTimestamp(value: unknown): number | null {
247
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
248
+ if (typeof value === 'string' && value.trim()) {
249
+ const numeric = Number(value);
250
+ if (Number.isFinite(numeric)) return numeric;
251
+ const parsed = Date.parse(value);
252
+ if (Number.isFinite(parsed)) return parsed;
253
+ }
254
+ return null;
255
+ }
256
+
257
+ function buildMeshCompletionFingerprint(args: {
258
+ meshId: string;
259
+ event: string;
260
+ sessionId: string;
261
+ providerType?: string;
262
+ providerSessionId?: string;
263
+ timestamp?: number | null;
264
+ finalSummary?: string;
265
+ }): string {
266
+ const timestampPart = Number.isFinite(args.timestamp)
267
+ ? String(args.timestamp)
268
+ : readNonEmptyString(args.finalSummary).slice(0, 200);
269
+ return [
270
+ args.meshId,
271
+ args.event,
272
+ args.sessionId,
273
+ args.providerType || '',
274
+ args.providerSessionId || '',
275
+ timestampPart,
276
+ ].join('::');
277
+ }
278
+
279
+ function isDuplicateMeshCompletionEvent(args: {
280
+ meshId: string;
281
+ event: string;
282
+ sessionId: string;
283
+ providerType?: string;
284
+ providerSessionId?: string;
285
+ timestamp?: number | null;
286
+ finalSummary?: string;
287
+ }): boolean {
288
+ const fingerprint = buildMeshCompletionFingerprint(args);
289
+ if (!fingerprint) return false;
290
+ const now = Date.now();
291
+ for (const [key, seenAt] of recentCompletionFingerprints.entries()) {
292
+ if (now - seenAt > RECENT_COMPLETION_FINGERPRINT_TTL_MS) recentCompletionFingerprints.delete(key);
293
+ }
294
+ if (recentCompletionFingerprints.has(fingerprint)) return true;
295
+ recentCompletionFingerprints.set(fingerprint, now);
296
+ return false;
297
+ }
298
+
299
+ function isDuplicateRefineTerminalEvent(meshId: string, eventName: string, metadataEvent: Record<string, unknown>): boolean {
300
+ const fingerprint = buildRefineTerminalEventFingerprint(meshId, eventName, metadataEvent);
301
+ if (!fingerprint) return false;
302
+ const now = Date.now();
303
+ for (const [key, seenAt] of recentCompletionFingerprints.entries()) {
304
+ if (now - seenAt > RECENT_COMPLETION_FINGERPRINT_TTL_MS) recentCompletionFingerprints.delete(key);
305
+ }
306
+ if (recentCompletionFingerprints.has(fingerprint)) return true;
307
+ recentCompletionFingerprints.set(fingerprint, now);
308
+ return false;
309
+ }
310
+
143
311
 
144
312
  export function tryAssignQueueTask(
145
313
  components: DaemonComponents,
@@ -170,7 +338,16 @@ export function tryAssignQueueTask(
170
338
  message: task.message,
171
339
  }).catch((e: any) => {
172
340
  LOG.error('MeshQueue', `Failed to dispatch task via P2P to remote node ${nodeId}: ${e?.message}`);
173
- updateTaskStatus(meshId, task.id, 'failed');
341
+ // Revert to pending so the task can be retried rather than permanently failing
342
+ updateTaskStatus(meshId, task.id, 'pending');
343
+ try {
344
+ appendLedgerEntry(meshId, {
345
+ kind: 'dispatch_failed' as any,
346
+ nodeId,
347
+ sessionId,
348
+ payload: { taskId: task.id, error: e?.message, retryable: true },
349
+ });
350
+ } catch { /* ledger write is best-effort */ }
174
351
  });
175
352
  return true;
176
353
  }
@@ -565,6 +742,32 @@ function buildMeshSystemMessage(args: {
565
742
  if (args.event === 'monitor:long_generating') {
566
743
  return `[System] ${args.nodeLabel} has been generating for a long time${metadata}. Use mesh_read_chat once for a status check, but do not poll repeatedly.`;
567
744
  }
745
+ if (args.event === 'refine:accepted') {
746
+ const jobId = readRefineJobId({ metadataEvent: args.metadataEvent });
747
+ return `[System] Refinery accepted async job${jobId ? ` ${jobId}` : ''} for ${args.nodeLabel}. Completion/failure will be delivered as a terminal refine event; do not poll repeatedly.`;
748
+ }
749
+ if (args.event === 'refine:completed') {
750
+ const jobId = readRefineJobId({ metadataEvent: args.metadataEvent });
751
+ const result = readRecord(args.metadataEvent.result);
752
+ const validationSummary = readRecord(result?.validationSummary);
753
+ const validationStatus = readNonEmptyString(validationSummary?.status);
754
+ const into = readNonEmptyString(result?.into);
755
+ const branch = readNonEmptyString(result?.branch);
756
+ const details = [
757
+ jobId ? `job_id=${jobId}` : '',
758
+ branch && into ? `${branch}→${into}` : '',
759
+ validationStatus ? `validation=${validationStatus}` : '',
760
+ ].filter(Boolean).join('; ');
761
+ return `[System] Refinery async job for ${args.nodeLabel} completed successfully${details ? ` (${details})` : ''}. The worktree was merged and cleanup completed; continue from the updated mesh state.`;
762
+ }
763
+ if (args.event === 'refine:failed') {
764
+ const jobId = readRefineJobId({ metadataEvent: args.metadataEvent });
765
+ const result = readRecord(args.metadataEvent.result);
766
+ const code = readNonEmptyString(result?.code);
767
+ const error = readNonEmptyString(result?.error);
768
+ const details = [jobId ? `job_id=${jobId}` : '', code ? `code=${code}` : ''].filter(Boolean).join('; ');
769
+ return `[System] Refinery async job for ${args.nodeLabel} failed${details ? ` (${details})` : ''}${error ? `: ${error}` : '.'} Review the terminal refine event/ledger before retrying.`;
770
+ }
568
771
  return '';
569
772
  }
570
773
 
@@ -593,6 +796,28 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
593
796
  return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
594
797
  }
595
798
 
799
+ if (isDuplicateRefineTerminalEvent(args.meshId, args.event, args.metadataEvent)) {
800
+ LOG.info('MeshEvents', `Suppressed duplicate ${args.event} for refine job ${readRefineJobId({ metadataEvent: args.metadataEvent })}`);
801
+ return { success: true, forwarded: 0, suppressed: true, duplicateRefineTerminalEvent: true };
802
+ }
803
+
804
+ const eventTimestamp = readEventTimestamp(args.metadataEvent.timestamp);
805
+ if (args.event === 'agent:generating_completed' && eventSessionId) {
806
+ const duplicateCompletion = isDuplicateMeshCompletionEvent({
807
+ meshId: args.meshId,
808
+ event: args.event,
809
+ sessionId: eventSessionId,
810
+ providerType: readNonEmptyString(args.metadataEvent.providerType) || undefined,
811
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
812
+ timestamp: eventTimestamp,
813
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
814
+ });
815
+ if (duplicateCompletion) {
816
+ LOG.info('MeshEvents', `Suppressed duplicate completion for mesh ${args.meshId} session ${eventSessionId}`);
817
+ return { success: true, forwarded: 0, suppressed: true, duplicateCompletion: true };
818
+ }
819
+ }
820
+
596
821
  // ── Task Queue & Ledger ──
597
822
  let completedTaskForLedger: { id?: string } | null = null;
598
823
  if (args.event === 'agent:generating_completed') {
@@ -601,19 +826,25 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
601
826
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
602
827
 
603
828
  if (sessionId) {
604
- const completedTask = updateSessionTaskStatus(args.meshId, sessionId, 'completed');
829
+ const completedTask = updateSessionTaskStatus(args.meshId, sessionId, 'completed', {
830
+ occurredAt: eventTimestamp !== null ? new Date(eventTimestamp).toISOString() : undefined,
831
+ });
605
832
  completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
606
833
  if (nodeId && providerType) {
607
- // Short delay to allow completion event to propagate before pulling next
608
- setTimeout(() => {
834
+ // Queue state is already updated above; setImmediate avoids the
835
+ // 500 ms artificial delay while still deferring past this call frame.
836
+ setImmediate(() => {
609
837
  tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
610
- }, 500);
838
+ });
611
839
  }
612
840
  }
613
841
  } else if (args.event === 'agent:ready') {
614
842
  const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
615
843
  const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
616
844
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
845
+ const providerSessionId = readNonEmptyString(args.metadataEvent.providerSessionId) || undefined;
846
+ const finalSummary = readNonEmptyString(args.metadataEvent.finalSummary) || undefined;
847
+ const workerResult = readWorkerResultMetadata(args.metadataEvent);
617
848
  const completedTask = sessionId
618
849
  ? updateSessionTaskStatus(args.meshId, sessionId, 'completed')
619
850
  : null;
@@ -630,15 +861,17 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
630
861
  nodeLabel: args.nodeLabel,
631
862
  taskId: completedTask.id,
632
863
  completedViaReady: true,
633
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
634
- finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
864
+ providerSessionId,
865
+ finalSummary,
866
+ workerResult,
635
867
  evidence: buildTaskCompletionEvidence({
636
868
  event: 'agent:ready',
637
869
  nodeId,
638
870
  sessionId,
639
871
  providerType: providerType || undefined,
640
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
641
- finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
872
+ providerSessionId,
873
+ finalSummary,
874
+ workerResult,
642
875
  }),
643
876
  },
644
877
  });
@@ -648,13 +881,15 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
648
881
  }
649
882
 
650
883
  if (sessionId && nodeId && providerType) {
651
- remoteIdleSessions.set(`${nodeId}:${sessionId}`, { nodeId, sessionId, providerType });
652
- setTimeout(() => {
884
+ sweepExpiredRemoteIdleSessions();
885
+ remoteIdleSessions.set(`${nodeId}:${sessionId}`, {
886
+ nodeId, sessionId, providerType,
887
+ expiresAt: Date.now() + REMOTE_IDLE_SESSION_TTL_MS,
888
+ });
889
+ setImmediate(() => {
653
890
  const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
654
- if (assigned) {
655
- remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
656
- }
657
- }, 500);
891
+ if (assigned) remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
892
+ });
658
893
  }
659
894
  } else if (args.event === 'agent:generating_started') {
660
895
  const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
@@ -679,14 +914,18 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
679
914
  const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || undefined;
680
915
  const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || undefined;
681
916
  const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || undefined;
917
+ const providerSessionId = readNonEmptyString(args.metadataEvent.providerSessionId) || undefined;
918
+ const finalSummary = readNonEmptyString(args.metadataEvent.finalSummary) || undefined;
919
+ const workerResult = readWorkerResultMetadata(args.metadataEvent);
682
920
  const completionEvidence = ledgerKind === 'task_completed' && ledgerNodeId && ledgerSessionId
683
921
  ? buildTaskCompletionEvidence({
684
922
  event: 'agent:generating_completed',
685
923
  nodeId: ledgerNodeId,
686
924
  sessionId: ledgerSessionId,
687
925
  providerType: ledgerProviderType,
688
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
689
- finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
926
+ providerSessionId,
927
+ finalSummary,
928
+ workerResult,
690
929
  })
691
930
  : undefined;
692
931
  appendLedgerEntry(args.meshId, {
@@ -698,8 +937,12 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
698
937
  event: args.event,
699
938
  nodeLabel: args.nodeLabel,
700
939
  taskId: completedTaskForLedger?.id || undefined,
701
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
702
- finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
940
+ providerSessionId,
941
+ finalSummary,
942
+ workerResult,
943
+ completionDiagnostic: args.metadataEvent.completionDiagnostic && typeof args.metadataEvent.completionDiagnostic === 'object'
944
+ ? args.metadataEvent.completionDiagnostic
945
+ : undefined,
703
946
  evidence: completionEvidence,
704
947
  },
705
948
  });
@@ -781,17 +1024,18 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
781
1024
 
782
1025
  if (coordinatorInstances.length === 0) {
783
1026
  // No CLI coordinator session found — buffer for MCP-based coordinators.
784
- if (pendingMeshCoordinatorEvents.length < MAX_PENDING_EVENTS) {
785
- pendingMeshCoordinatorEvents.push({
1027
+ if (queuePendingMeshCoordinatorEvent({
786
1028
  event: args.event,
787
1029
  meshId: args.meshId,
788
1030
  nodeLabel: args.nodeLabel,
1031
+ nodeId: args.nodeId || undefined,
1032
+ workspace: readNonEmptyString(args.metadataEvent.workspace),
789
1033
  metadataEvent: {
790
1034
  ...args.metadataEvent,
791
1035
  ...(recoveryContext ? { recoveryContext } : {}),
792
1036
  },
793
1037
  queuedAt: Date.now(),
794
- });
1038
+ })) {
795
1039
  LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId})`);
796
1040
  }
797
1041
  return { success: true, forwarded: 0 };
@@ -834,6 +1078,15 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
834
1078
  providerType: readNonEmptyString(payload.providerType),
835
1079
  providerSessionId: readNonEmptyString(payload.providerSessionId),
836
1080
  finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
1081
+ jobId: readNonEmptyString(payload.jobId),
1082
+ interactionId: readNonEmptyString(payload.interactionId),
1083
+ status: readNonEmptyString(payload.status),
1084
+ targetDaemonId: readNonEmptyString(payload.targetDaemonId),
1085
+ startedAt: readNonEmptyString(payload.startedAt),
1086
+ completedAt: readNonEmptyString(payload.completedAt),
1087
+ retryOfJobId: readNonEmptyString(payload.retryOfJobId),
1088
+ ...(payload.result && typeof payload.result === 'object' && !Array.isArray(payload.result) ? { result: payload.result } : {}),
1089
+ ...(payload.timestamp !== undefined ? { timestamp: payload.timestamp } : {}),
837
1090
  intentional: payload.intentional === true,
838
1091
  intentionalStop: payload.intentionalStop === true,
839
1092
  operatorCleanup: payload.operatorCleanup === true,