@adhdev/daemon-core 0.9.82-rc.12 → 0.9.82-rc.121

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 (80) hide show
  1. package/dist/chat/subscription-updates.d.ts +1 -0
  2. package/dist/cli-adapters/provider-cli-adapter.d.ts +21 -0
  3. package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
  4. package/dist/cli-adapters/provider-cli-shared.d.ts +14 -0
  5. package/dist/commands/router.d.ts +22 -0
  6. package/dist/config/chat-history.d.ts +4 -0
  7. package/dist/config/mesh-config.d.ts +68 -1
  8. package/dist/git/git-commands.d.ts +5 -1
  9. package/dist/index.d.ts +15 -5
  10. package/dist/index.js +7029 -1368
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +6984 -1351
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/installer.d.ts +1 -4
  15. package/dist/launch.d.ts +1 -1
  16. package/dist/logging/async-batch-writer.d.ts +10 -0
  17. package/dist/mesh/beads-db.d.ts +18 -0
  18. package/dist/mesh/mesh-active-work.d.ts +73 -0
  19. package/dist/mesh/mesh-events.d.ts +26 -5
  20. package/dist/mesh/mesh-fast-forward.d.ts +39 -0
  21. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  22. package/dist/mesh/mesh-ledger.d.ts +38 -1
  23. package/dist/mesh/mesh-refine-status.d.ts +27 -0
  24. package/dist/mesh/mesh-work-queue.d.ts +27 -5
  25. package/dist/mesh/preview-freshness.d.ts +18 -0
  26. package/dist/mesh/refine-config.d.ts +193 -0
  27. package/dist/mesh/worktree-bootstrap-config.d.ts +115 -0
  28. package/dist/providers/chat-message-normalization.d.ts +1 -0
  29. package/dist/providers/cli-provider-instance.d.ts +4 -1
  30. package/dist/repo-mesh-types.d.ts +62 -0
  31. package/dist/status/reporter.d.ts +2 -0
  32. package/package.json +3 -1
  33. package/src/boot/daemon-lifecycle.ts +1 -0
  34. package/src/chat/subscription-updates.ts +5 -1
  35. package/src/cli-adapters/provider-cli-adapter.ts +453 -17
  36. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  37. package/src/cli-adapters/provider-cli-parse.ts +4 -0
  38. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  39. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  40. package/src/cli-adapters/provider-cli-shared.ts +32 -10
  41. package/src/commands/chat-commands.ts +730 -30
  42. package/src/commands/cli-manager.ts +129 -1
  43. package/src/commands/handler.ts +8 -1
  44. package/src/commands/mesh-coordinator.ts +13 -143
  45. package/src/commands/router.ts +3239 -430
  46. package/src/config/chat-history.ts +37 -9
  47. package/src/config/mesh-config.ts +249 -2
  48. package/src/config/recent-activity.ts +8 -2
  49. package/src/daemon/dev-cli-debug.ts +10 -1
  50. package/src/detection/ide-detector.ts +26 -16
  51. package/src/git/git-commands.ts +17 -5
  52. package/src/index.ts +41 -4
  53. package/src/installer.d.ts +1 -1
  54. package/src/installer.ts +8 -6
  55. package/src/launch.d.ts +1 -1
  56. package/src/launch.ts +37 -28
  57. package/src/logging/async-batch-writer.ts +55 -0
  58. package/src/logging/logger.ts +2 -1
  59. package/src/mesh/beads-db.ts +176 -0
  60. package/src/mesh/coordinator-prompt.ts +31 -8
  61. package/src/mesh/mesh-active-work.ts +292 -0
  62. package/src/mesh/mesh-events.ts +389 -47
  63. package/src/mesh/mesh-fast-forward.ts +430 -0
  64. package/src/mesh/mesh-host-ownership.ts +73 -0
  65. package/src/mesh/mesh-ledger.ts +138 -1
  66. package/src/mesh/mesh-refine-status.ts +145 -0
  67. package/src/mesh/mesh-work-queue.ts +199 -137
  68. package/src/mesh/preview-freshness.ts +118 -0
  69. package/src/mesh/refine-config.ts +366 -0
  70. package/src/mesh/worktree-bootstrap-config.ts +234 -0
  71. package/src/providers/approval-utils.ts +12 -5
  72. package/src/providers/chat-message-normalization.ts +7 -12
  73. package/src/providers/cli-provider-instance.ts +201 -28
  74. package/src/providers/ide-provider-instance.ts +17 -3
  75. package/src/providers/provider-loader.ts +10 -4
  76. package/src/providers/read-chat-contract.ts +1 -1
  77. package/src/providers/version-archive.ts +38 -20
  78. package/src/repo-mesh-types.ts +67 -0
  79. package/src/status/reporter.ts +15 -0
  80. package/src/system/host-memory.ts +29 -12
@@ -1,31 +1,51 @@
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 {
@@ -35,39 +55,125 @@ export interface PendingMeshCoordinatorEvent {
35
55
  nodeId?: string;
36
56
  workspace?: string;
37
57
  metadataEvent: Record<string, unknown>;
58
+ coordinatorMessage?: string;
38
59
  queuedAt: number;
39
60
  }
40
61
 
41
- const MAX_PENDING_EVENTS = 50;
42
- const pendingMeshCoordinatorEvents: PendingMeshCoordinatorEvent[] = [];
62
+ const REFINE_TERMINAL_EVENTS = new Set(['refine:completed', 'refine:failed']);
63
+
64
+ function readRefineJobId(event: { metadataEvent?: Record<string, unknown> } | Record<string, unknown>): string {
65
+ const metadata = readRecord((event as any).metadataEvent) || event as Record<string, unknown>;
66
+ const result = readRecord(metadata.result);
67
+ const refineJob = readRecord(result?.refineJob);
68
+ return readNonEmptyString(metadata.jobId) || readNonEmptyString(refineJob?.jobId);
69
+ }
70
+
71
+ function buildRefineTerminalEventFingerprint(meshId: string, eventName: string, metadataEvent: Record<string, unknown>): string {
72
+ const jobId = readRefineJobId({ metadataEvent });
73
+ return jobId && REFINE_TERMINAL_EVENTS.has(eventName) ? `${meshId}::${eventName}::${jobId}` : '';
74
+ }
75
+
76
+ function hasPendingRefineTerminalEventDuplicate(event: PendingMeshCoordinatorEvent): boolean {
77
+ if (!REFINE_TERMINAL_EVENTS.has(event.event)) return false;
78
+ const jobId = readRefineJobId(event);
79
+ if (!jobId) return false;
80
+ return getPendingMeshCoordinatorEvents(event.meshId).some((pending) =>
81
+ pending.event === event.event && readRefineJobId(pending) === jobId,
82
+ );
83
+ }
84
+
85
+ function buildPendingEventFingerprint(event: PendingMeshCoordinatorEvent): string {
86
+ const metadata = readRecord(event.metadataEvent) || {};
87
+ const sessionId = resolveEventSessionId(metadata);
88
+ const providerSessionId = readNonEmptyString(metadata.providerSessionId);
89
+ const taskId = readNonEmptyString(metadata.taskId) || readNonEmptyString(readRecord(metadata.payload)?.taskId);
90
+ const jobId = readRefineJobId(event);
91
+ const timestamp = metadata.timestamp !== undefined && metadata.timestamp !== null ? String(metadata.timestamp) : '';
92
+ return [
93
+ event.meshId,
94
+ event.event,
95
+ event.nodeId || '',
96
+ sessionId || '',
97
+ providerSessionId || '',
98
+ taskId || '',
99
+ jobId || '',
100
+ timestamp || '',
101
+ ].join('::');
102
+ }
103
+
104
+ function hasPendingCoordinatorEventDuplicate(event: PendingMeshCoordinatorEvent): boolean {
105
+ const fingerprint = buildPendingEventFingerprint(event);
106
+ if (!fingerprint.trim()) return false;
107
+ return getPendingMeshCoordinatorEvents(event.meshId).some((pending) => buildPendingEventFingerprint(pending) === fingerprint);
108
+ }
109
+
110
+ function getPendingEventsPath(meshId: string): string {
111
+ const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
112
+ return join(getLedgerDir(), `${safe}.pending-events.jsonl`);
113
+ }
43
114
 
44
115
  export function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEvent): boolean {
45
- if (pendingMeshCoordinatorEvents.length >= MAX_PENDING_EVENTS) {
116
+ try {
117
+ if (hasPendingRefineTerminalEventDuplicate(event)) {
118
+ LOG.info('MeshEvents', `Suppressed duplicate pending ${event.event} for refine job ${readRefineJobId(event)}`);
119
+ return true;
120
+ }
121
+ if (hasPendingCoordinatorEventDuplicate(event)) {
122
+ LOG.info('MeshEvents', `Suppressed duplicate pending ${event.event} for mesh ${event.meshId}`);
123
+ return true;
124
+ }
125
+ appendFileSync(getPendingEventsPath(event.meshId), JSON.stringify(event) + '\n', 'utf-8');
126
+ return true;
127
+ } catch (e: any) {
128
+ LOG.warn('MeshEvents', `Failed to persist pending coordinator event: ${e?.message || e}`);
46
129
  return false;
47
130
  }
48
- pendingMeshCoordinatorEvents.push(event);
49
- return true;
50
131
  }
51
132
 
52
- /** Drain and return all pending coordinator events, clearing the queue. */
53
- export function drainPendingMeshCoordinatorEvents(): PendingMeshCoordinatorEvent[] {
54
- return pendingMeshCoordinatorEvents.splice(0);
133
+ /** Drain and return all pending coordinator events for meshId, removing them from disk. */
134
+ export function drainPendingMeshCoordinatorEvents(meshId?: string): PendingMeshCoordinatorEvent[] {
135
+ if (!meshId) return [];
136
+ const path = getPendingEventsPath(meshId);
137
+ if (!existsSync(path)) return [];
138
+ try {
139
+ const raw = readFileSync(path, 'utf-8');
140
+ try { unlinkSync(path); } catch { /* concurrent drain already removed it */ }
141
+ return raw.split('\n').filter(Boolean).flatMap(line => {
142
+ try { return [JSON.parse(line) as PendingMeshCoordinatorEvent]; } catch { return []; }
143
+ });
144
+ } catch { return []; }
55
145
  }
56
146
 
57
147
  /** Peek at pending coordinator events without draining (non-destructive). */
58
- export function getPendingMeshCoordinatorEvents(): readonly PendingMeshCoordinatorEvent[] {
59
- return pendingMeshCoordinatorEvents.slice();
148
+ export function getPendingMeshCoordinatorEvents(meshId?: string): readonly PendingMeshCoordinatorEvent[] {
149
+ if (!meshId) return [];
150
+ const path = getPendingEventsPath(meshId);
151
+ if (!existsSync(path)) return [];
152
+ try {
153
+ const raw = readFileSync(path, 'utf-8');
154
+ return raw.split('\n').filter(Boolean).flatMap(line => {
155
+ try { return [JSON.parse(line) as PendingMeshCoordinatorEvent]; } catch { return []; }
156
+ });
157
+ } catch { return []; }
60
158
  }
61
159
 
62
- /** Explicitly clear all pending coordinator events. */
63
- export function clearPendingMeshCoordinatorEvents(): void {
64
- pendingMeshCoordinatorEvents.splice(0);
160
+ /** Explicitly clear all pending coordinator events for a mesh. */
161
+ export function clearPendingMeshCoordinatorEvents(meshId?: string): void {
162
+ if (!meshId) return;
163
+ const path = getPendingEventsPath(meshId);
164
+ if (existsSync(path)) try { unlinkSync(path); } catch { /* already removed */ }
65
165
  }
66
166
 
67
167
  function readNonEmptyString(value: unknown): string {
68
168
  return typeof value === 'string' && value.trim() ? value.trim() : '';
69
169
  }
70
170
 
171
+ function readRecord(value: unknown): Record<string, unknown> | undefined {
172
+ return value && typeof value === 'object' && !Array.isArray(value)
173
+ ? value as Record<string, unknown>
174
+ : undefined;
175
+ }
176
+
71
177
  function resolveEventSessionId(event: Record<string, unknown>, fallback?: unknown): string {
72
178
  return readNonEmptyString(event.targetSessionId)
73
179
  || readNonEmptyString(event.sessionId)
@@ -82,6 +188,9 @@ const MESH_COORDINATOR_EVENTS = new Set([
82
188
  'agent:stopped',
83
189
  'agent:ready',
84
190
  'monitor:long_generating',
191
+ 'refine:accepted',
192
+ 'refine:completed',
193
+ 'refine:failed',
85
194
  ]);
86
195
 
87
196
  const EVENT_TO_LEDGER_KIND: Record<string, MeshLedgerKind> = {
@@ -96,10 +205,21 @@ function isMeshCoordinatorEvent(eventName: unknown): eventName is string {
96
205
  }
97
206
 
98
207
  function formatCompletionMetadata(event: Record<string, unknown>): string {
208
+ const completionDiagnostic = event.completionDiagnostic && typeof event.completionDiagnostic === 'object'
209
+ ? event.completionDiagnostic as Record<string, unknown>
210
+ : null;
211
+ const diagnosticReason = completionDiagnostic
212
+ ? readNonEmptyString(completionDiagnostic.blockReason) || 'present'
213
+ : '';
214
+ const finalAssistantPresent = typeof completionDiagnostic?.finalAssistantPresent === 'boolean'
215
+ ? String(completionDiagnostic.finalAssistantPresent)
216
+ : '';
99
217
  const parts = [
100
218
  readNonEmptyString(event.targetSessionId) ? `session_id=${readNonEmptyString(event.targetSessionId)}` : '',
101
219
  readNonEmptyString(event.providerType) ? `provider=${readNonEmptyString(event.providerType)}` : '',
102
220
  readNonEmptyString(event.providerSessionId) ? `provider_session_id=${readNonEmptyString(event.providerSessionId)}` : '',
221
+ diagnosticReason ? `completion_diagnostic=${diagnosticReason}` : '',
222
+ finalAssistantPresent ? `final_assistant=${finalAssistantPresent}` : '',
103
223
  ].filter(Boolean);
104
224
  return parts.length > 0 ? ` (${parts.join('; ')})` : '';
105
225
  }
@@ -150,6 +270,74 @@ function shouldSuppressIntentionalCleanupStop(args: {
150
270
  return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
151
271
  }
152
272
 
273
+ const RECENT_COMPLETION_FINGERPRINT_TTL_MS = 10 * 60 * 1000;
274
+ const recentCompletionFingerprints = new Map<string, number>();
275
+
276
+ function readEventTimestamp(value: unknown): number | null {
277
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
278
+ if (typeof value === 'string' && value.trim()) {
279
+ const numeric = Number(value);
280
+ if (Number.isFinite(numeric)) return numeric;
281
+ const parsed = Date.parse(value);
282
+ if (Number.isFinite(parsed)) return parsed;
283
+ }
284
+ return null;
285
+ }
286
+
287
+ function buildMeshCompletionFingerprint(args: {
288
+ meshId: string;
289
+ event: string;
290
+ sessionId: string;
291
+ providerType?: string;
292
+ providerSessionId?: string;
293
+ timestamp?: number | null;
294
+ finalSummary?: string;
295
+ }): string {
296
+ const timestampPart = Number.isFinite(args.timestamp)
297
+ ? String(args.timestamp)
298
+ : readNonEmptyString(args.finalSummary).slice(0, 200);
299
+ return [
300
+ args.meshId,
301
+ args.event,
302
+ args.sessionId,
303
+ args.providerType || '',
304
+ args.providerSessionId || '',
305
+ timestampPart,
306
+ ].join('::');
307
+ }
308
+
309
+ function isDuplicateMeshCompletionEvent(args: {
310
+ meshId: string;
311
+ event: string;
312
+ sessionId: string;
313
+ providerType?: string;
314
+ providerSessionId?: string;
315
+ timestamp?: number | null;
316
+ finalSummary?: string;
317
+ }): boolean {
318
+ const fingerprint = buildMeshCompletionFingerprint(args);
319
+ if (!fingerprint) return false;
320
+ const now = Date.now();
321
+ for (const [key, seenAt] of recentCompletionFingerprints.entries()) {
322
+ if (now - seenAt > RECENT_COMPLETION_FINGERPRINT_TTL_MS) recentCompletionFingerprints.delete(key);
323
+ }
324
+ if (recentCompletionFingerprints.has(fingerprint)) return true;
325
+ recentCompletionFingerprints.set(fingerprint, now);
326
+ return false;
327
+ }
328
+
329
+ function isDuplicateRefineTerminalEvent(meshId: string, eventName: string, metadataEvent: Record<string, unknown>): boolean {
330
+ const fingerprint = buildRefineTerminalEventFingerprint(meshId, eventName, metadataEvent);
331
+ if (!fingerprint) return false;
332
+ const now = Date.now();
333
+ for (const [key, seenAt] of recentCompletionFingerprints.entries()) {
334
+ if (now - seenAt > RECENT_COMPLETION_FINGERPRINT_TTL_MS) recentCompletionFingerprints.delete(key);
335
+ }
336
+ if (recentCompletionFingerprints.has(fingerprint)) return true;
337
+ recentCompletionFingerprints.set(fingerprint, now);
338
+ return false;
339
+ }
340
+
153
341
 
154
342
  export function tryAssignQueueTask(
155
343
  components: DaemonComponents,
@@ -180,7 +368,16 @@ export function tryAssignQueueTask(
180
368
  message: task.message,
181
369
  }).catch((e: any) => {
182
370
  LOG.error('MeshQueue', `Failed to dispatch task via P2P to remote node ${nodeId}: ${e?.message}`);
183
- updateTaskStatus(meshId, task.id, 'failed');
371
+ // Revert to pending so the task can be retried rather than permanently failing
372
+ updateTaskStatus(meshId, task.id, 'pending');
373
+ try {
374
+ appendLedgerEntry(meshId, {
375
+ kind: 'dispatch_failed' as any,
376
+ nodeId,
377
+ sessionId,
378
+ payload: { taskId: task.id, error: e?.message, retryable: true },
379
+ });
380
+ } catch { /* ledger write is best-effort */ }
184
381
  });
185
382
  return true;
186
383
  }
@@ -575,6 +772,71 @@ function buildMeshSystemMessage(args: {
575
772
  if (args.event === 'monitor:long_generating') {
576
773
  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.`;
577
774
  }
775
+ if (args.event === 'refine:accepted') {
776
+ const jobId = readRefineJobId({ metadataEvent: args.metadataEvent });
777
+ 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.`;
778
+ }
779
+ if (args.event === 'refine:completed') {
780
+ const jobId = readRefineJobId({ metadataEvent: args.metadataEvent });
781
+ const result = readRecord(args.metadataEvent.result);
782
+ const validationSummary = readRecord(result?.validationSummary);
783
+ const patchEquivalence = readRecord(result?.patchEquivalence);
784
+ const finalConvergence = readRecord(result?.finalBranchConvergenceState);
785
+ const validationStatus = readNonEmptyString(validationSummary?.status);
786
+ const patchStatus = readNonEmptyString(patchEquivalence?.status)
787
+ || (patchEquivalence?.equivalent === true ? 'passed' : '');
788
+ const into = readNonEmptyString(result?.into);
789
+ const branch = readNonEmptyString(result?.branch);
790
+ const mergeStatus = result?.merged === true ? 'merged' : readNonEmptyString(finalConvergence?.status);
791
+ const convergenceStatus = readNonEmptyString(finalConvergence?.status);
792
+ const nextStep = readNonEmptyString(result?.nextStep)
793
+ || readNonEmptyString(finalConvergence?.nextStep)
794
+ || 'Continue from the updated mesh state.';
795
+ const details = [
796
+ jobId ? `job_id=${jobId}` : '',
797
+ branch && into ? `${branch}→${into}` : '',
798
+ validationStatus ? `validation=${validationStatus}` : '',
799
+ patchStatus ? `patch_equivalence=${patchStatus}` : '',
800
+ mergeStatus ? `merge=${mergeStatus}` : '',
801
+ convergenceStatus ? `final_convergence=${convergenceStatus}` : '',
802
+ ].filter(Boolean).join('; ');
803
+ return `[System] Refinery async job for ${args.nodeLabel} completed successfully${details ? ` (${details})` : ''}.\nNext step: ${nextStep}`;
804
+ }
805
+ if (args.event === 'refine:failed') {
806
+ const jobId = readRefineJobId({ metadataEvent: args.metadataEvent });
807
+ const result = readRecord(args.metadataEvent.result);
808
+ const validationSummary = readRecord(result?.validationSummary);
809
+ const patchEquivalence = readRecord(result?.patchEquivalence);
810
+ const finalConvergence = readRecord(result?.finalBranchConvergenceState);
811
+ const code = readNonEmptyString(result?.code);
812
+ const error = readNonEmptyString(result?.error);
813
+ const validationStatus = readNonEmptyString(validationSummary?.status);
814
+ const patchStatus = readNonEmptyString(patchEquivalence?.status)
815
+ || (patchEquivalence?.equivalent === true ? 'passed' : '');
816
+ const mergeStatus = result?.merged === true
817
+ ? 'merged'
818
+ : finalConvergence?.merged === false
819
+ ? 'not_merged'
820
+ : '';
821
+ const convergenceStatus = readNonEmptyString(result?.convergenceStatus)
822
+ || readNonEmptyString(finalConvergence?.status);
823
+ const blockedReason = readNonEmptyString(result?.blockedReason);
824
+ const nextStep = readNonEmptyString(result?.nextStep) || readNonEmptyString(finalConvergence?.nextStep);
825
+ const details = [
826
+ jobId ? `job_id=${jobId}` : '',
827
+ code ? `code=${code}` : '',
828
+ validationStatus ? `validation=${validationStatus}` : '',
829
+ patchStatus ? `patch_equivalence=${patchStatus}` : '',
830
+ mergeStatus ? `merge=${mergeStatus}` : '',
831
+ convergenceStatus ? `convergence=${convergenceStatus}` : '',
832
+ blockedReason ? `reason=${blockedReason}` : '',
833
+ ].filter(Boolean).join('; ');
834
+ const parts = [
835
+ `[System] Refinery async job for ${args.nodeLabel} failed${details ? ` (${details})` : ''}${error ? `: ${error}` : '.'}`,
836
+ nextStep ? `Next step: ${nextStep}` : 'Review the terminal refine event/ledger before retrying.',
837
+ ];
838
+ return parts.join('\n');
839
+ }
578
840
  return '';
579
841
  }
580
842
 
@@ -603,6 +865,28 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
603
865
  return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
604
866
  }
605
867
 
868
+ if (isDuplicateRefineTerminalEvent(args.meshId, args.event, args.metadataEvent)) {
869
+ LOG.info('MeshEvents', `Suppressed duplicate ${args.event} for refine job ${readRefineJobId({ metadataEvent: args.metadataEvent })}`);
870
+ return { success: true, forwarded: 0, suppressed: true, duplicateRefineTerminalEvent: true };
871
+ }
872
+
873
+ const eventTimestamp = readEventTimestamp(args.metadataEvent.timestamp);
874
+ if (args.event === 'agent:generating_completed' && eventSessionId) {
875
+ const duplicateCompletion = isDuplicateMeshCompletionEvent({
876
+ meshId: args.meshId,
877
+ event: args.event,
878
+ sessionId: eventSessionId,
879
+ providerType: readNonEmptyString(args.metadataEvent.providerType) || undefined,
880
+ providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
881
+ timestamp: eventTimestamp,
882
+ finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
883
+ });
884
+ if (duplicateCompletion) {
885
+ LOG.info('MeshEvents', `Suppressed duplicate completion for mesh ${args.meshId} session ${eventSessionId}`);
886
+ return { success: true, forwarded: 0, suppressed: true, duplicateCompletion: true };
887
+ }
888
+ }
889
+
606
890
  // ── Task Queue & Ledger ──
607
891
  let completedTaskForLedger: { id?: string } | null = null;
608
892
  if (args.event === 'agent:generating_completed') {
@@ -611,20 +895,27 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
611
895
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
612
896
 
613
897
  if (sessionId) {
614
- const completedTask = updateSessionTaskStatus(args.meshId, sessionId, 'completed');
898
+ const completedTask = updateSessionTaskStatus(args.meshId, sessionId, 'completed', {
899
+ occurredAt: eventTimestamp !== null ? new Date(eventTimestamp).toISOString() : undefined,
900
+ });
615
901
  completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
616
902
  if (nodeId && providerType) {
617
- // Short delay to allow completion event to propagate before pulling next
618
- setTimeout(() => {
903
+ // Queue state is already updated above; setImmediate avoids the
904
+ // 500 ms artificial delay while still deferring past this call frame.
905
+ setImmediate(() => {
619
906
  tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
620
- }, 500);
907
+ });
621
908
  }
622
909
  }
623
910
  } else if (args.event === 'agent:ready') {
624
911
  const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
625
912
  const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
626
913
  const providerType = readNonEmptyString(args.metadataEvent.providerType);
627
- const completedTask = sessionId
914
+ const providerSessionId = readNonEmptyString(args.metadataEvent.providerSessionId) || undefined;
915
+ const finalSummary = readNonEmptyString(args.metadataEvent.finalSummary) || undefined;
916
+ const workerResult = readWorkerResultMetadata(args.metadataEvent);
917
+ const hasCompletionEvidence = !!finalSummary || !!workerResult;
918
+ const completedTask = sessionId && hasCompletionEvidence
628
919
  ? updateSessionTaskStatus(args.meshId, sessionId, 'completed')
629
920
  : null;
630
921
  if (completedTask) {
@@ -640,15 +931,17 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
640
931
  nodeLabel: args.nodeLabel,
641
932
  taskId: completedTask.id,
642
933
  completedViaReady: true,
643
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
644
- finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
934
+ providerSessionId,
935
+ finalSummary,
936
+ workerResult,
645
937
  evidence: buildTaskCompletionEvidence({
646
938
  event: 'agent:ready',
647
939
  nodeId,
648
940
  sessionId,
649
941
  providerType: providerType || undefined,
650
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
651
- finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
942
+ providerSessionId,
943
+ finalSummary,
944
+ workerResult,
652
945
  }),
653
946
  },
654
947
  });
@@ -658,13 +951,15 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
658
951
  }
659
952
 
660
953
  if (sessionId && nodeId && providerType) {
661
- remoteIdleSessions.set(`${nodeId}:${sessionId}`, { nodeId, sessionId, providerType });
662
- setTimeout(() => {
954
+ sweepExpiredRemoteIdleSessions();
955
+ remoteIdleSessions.set(`${nodeId}:${sessionId}`, {
956
+ nodeId, sessionId, providerType,
957
+ expiresAt: Date.now() + REMOTE_IDLE_SESSION_TTL_MS,
958
+ });
959
+ setImmediate(() => {
663
960
  const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
664
- if (assigned) {
665
- remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
666
- }
667
- }, 500);
961
+ if (assigned) remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
962
+ });
668
963
  }
669
964
  } else if (args.event === 'agent:generating_started') {
670
965
  const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
@@ -679,7 +974,8 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
679
974
  remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
680
975
  }
681
976
  if (sessionId) {
682
- updateSessionTaskStatus(args.meshId, sessionId, 'failed');
977
+ const failedTask = updateSessionTaskStatus(args.meshId, sessionId, 'failed');
978
+ completedTaskForLedger = failedTask ? { id: failedTask.id } : null;
683
979
  }
684
980
  }
685
981
 
@@ -689,14 +985,18 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
689
985
  const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || undefined;
690
986
  const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || undefined;
691
987
  const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || undefined;
988
+ const providerSessionId = readNonEmptyString(args.metadataEvent.providerSessionId) || undefined;
989
+ const finalSummary = readNonEmptyString(args.metadataEvent.finalSummary) || undefined;
990
+ const workerResult = readWorkerResultMetadata(args.metadataEvent);
692
991
  const completionEvidence = ledgerKind === 'task_completed' && ledgerNodeId && ledgerSessionId
693
992
  ? buildTaskCompletionEvidence({
694
993
  event: 'agent:generating_completed',
695
994
  nodeId: ledgerNodeId,
696
995
  sessionId: ledgerSessionId,
697
996
  providerType: ledgerProviderType,
698
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
699
- finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
997
+ providerSessionId,
998
+ finalSummary,
999
+ workerResult,
700
1000
  })
701
1001
  : undefined;
702
1002
  appendLedgerEntry(args.meshId, {
@@ -708,8 +1008,12 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
708
1008
  event: args.event,
709
1009
  nodeLabel: args.nodeLabel,
710
1010
  taskId: completedTaskForLedger?.id || undefined,
711
- providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || undefined,
712
- finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || undefined,
1011
+ providerSessionId,
1012
+ finalSummary,
1013
+ workerResult,
1014
+ completionDiagnostic: args.metadataEvent.completionDiagnostic && typeof args.metadataEvent.completionDiagnostic === 'object'
1015
+ ? args.metadataEvent.completionDiagnostic
1016
+ : undefined,
713
1017
  evidence: completionEvidence,
714
1018
  },
715
1019
  });
@@ -782,6 +1086,14 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
782
1086
  }
783
1087
  }
784
1088
 
1089
+ const messageText = buildMeshSystemMessage({
1090
+ event: args.event,
1091
+ nodeLabel: args.nodeLabel,
1092
+ metadataEvent: args.metadataEvent,
1093
+ recoveryContext,
1094
+ });
1095
+ if (!messageText) return { success: false, error: 'unsupported mesh event' };
1096
+
785
1097
  const coordinatorInstances = components.instanceManager.getByCategory('cli').filter((inst) => {
786
1098
  const instState = inst.getState();
787
1099
  if (instState.settings?.meshCoordinatorFor !== args.meshId) return false;
@@ -789,6 +1101,12 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
789
1101
  return true;
790
1102
  });
791
1103
 
1104
+ // Refine terminal events (refine:completed, refine:failed) are coordinator-delivered
1105
+ // synchronously; only buffer them for MCP when no CLI coordinator is present.
1106
+ // Agent runtime events (agent:*) use dual delivery so both CLI and MCP coordinators
1107
+ // receive them regardless of whether a live CLI coordinator session is active.
1108
+ const isRefineTerminalEvent = REFINE_TERMINAL_EVENTS.has(args.event);
1109
+
792
1110
  if (coordinatorInstances.length === 0) {
793
1111
  // No CLI coordinator session found — buffer for MCP-based coordinators.
794
1112
  if (queuePendingMeshCoordinatorEvent({
@@ -801,6 +1119,7 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
801
1119
  ...args.metadataEvent,
802
1120
  ...(recoveryContext ? { recoveryContext } : {}),
803
1121
  },
1122
+ coordinatorMessage: messageText,
804
1123
  queuedAt: Date.now(),
805
1124
  })) {
806
1125
  LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId})`);
@@ -808,13 +1127,27 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
808
1127
  return { success: true, forwarded: 0 };
809
1128
  }
810
1129
 
811
- const messageText = buildMeshSystemMessage({
812
- event: args.event,
813
- nodeLabel: args.nodeLabel,
814
- metadataEvent: args.metadataEvent,
815
- recoveryContext,
816
- });
817
- if (!messageText) return { success: false, error: 'unsupported mesh event' };
1130
+ // CLI coordinator is present. For non-refine events, also buffer for MCP coordinators
1131
+ // that poll via get_pending_mesh_events (dual delivery). Refine terminal events are
1132
+ // forwarded directly only — they must not accumulate in the pending queue when a live
1133
+ // coordinator already received them.
1134
+ if (!isRefineTerminalEvent) {
1135
+ if (queuePendingMeshCoordinatorEvent({
1136
+ event: args.event,
1137
+ meshId: args.meshId,
1138
+ nodeLabel: args.nodeLabel,
1139
+ nodeId: args.nodeId || undefined,
1140
+ workspace: readNonEmptyString(args.metadataEvent.workspace),
1141
+ metadataEvent: {
1142
+ ...args.metadataEvent,
1143
+ ...(recoveryContext ? { recoveryContext } : {}),
1144
+ },
1145
+ coordinatorMessage: messageText,
1146
+ queuedAt: Date.now(),
1147
+ })) {
1148
+ LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId})`);
1149
+ }
1150
+ }
818
1151
 
819
1152
  for (const coord of coordinatorInstances) {
820
1153
  const coordState = coord.getState();
@@ -845,6 +1178,15 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
845
1178
  providerType: readNonEmptyString(payload.providerType),
846
1179
  providerSessionId: readNonEmptyString(payload.providerSessionId),
847
1180
  finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
1181
+ jobId: readNonEmptyString(payload.jobId),
1182
+ interactionId: readNonEmptyString(payload.interactionId),
1183
+ status: readNonEmptyString(payload.status),
1184
+ targetDaemonId: readNonEmptyString(payload.targetDaemonId),
1185
+ startedAt: readNonEmptyString(payload.startedAt),
1186
+ completedAt: readNonEmptyString(payload.completedAt),
1187
+ retryOfJobId: readNonEmptyString(payload.retryOfJobId),
1188
+ ...(payload.result && typeof payload.result === 'object' && !Array.isArray(payload.result) ? { result: payload.result } : {}),
1189
+ ...(payload.timestamp !== undefined ? { timestamp: payload.timestamp } : {}),
848
1190
  intentional: payload.intentional === true,
849
1191
  intentionalStop: payload.intentionalStop === true,
850
1192
  operatorCleanup: payload.operatorCleanup === true,