@adhdev/daemon-core 0.9.82-rc.60 → 0.9.82-rc.62

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.
@@ -26,6 +26,15 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
26
26
  forwarded: number;
27
27
  suppressed: boolean;
28
28
  intentionalCleanupStop: boolean;
29
+ duplicateRefineTerminalEvent?: undefined;
30
+ duplicateCompletion?: undefined;
31
+ error?: undefined;
32
+ } | {
33
+ success: boolean;
34
+ forwarded: number;
35
+ suppressed: boolean;
36
+ duplicateRefineTerminalEvent: boolean;
37
+ intentionalCleanupStop?: undefined;
29
38
  duplicateCompletion?: undefined;
30
39
  error?: undefined;
31
40
  } | {
@@ -34,12 +43,14 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
34
43
  suppressed: boolean;
35
44
  duplicateCompletion: boolean;
36
45
  intentionalCleanupStop?: undefined;
46
+ duplicateRefineTerminalEvent?: undefined;
37
47
  error?: undefined;
38
48
  } | {
39
49
  success: boolean;
40
50
  forwarded: number;
41
51
  suppressed?: undefined;
42
52
  intentionalCleanupStop?: undefined;
53
+ duplicateRefineTerminalEvent?: undefined;
43
54
  duplicateCompletion?: undefined;
44
55
  error?: undefined;
45
56
  } | {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.60",
3
+ "version": "0.9.82-rc.62",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -925,6 +925,7 @@ type MeshRefineJobHandle = {
925
925
  startedAt: string;
926
926
  completedAt?: string;
927
927
  duplicate?: boolean;
928
+ retryOfJobId?: string;
928
929
  eventDelivery: {
929
930
  pendingEvents: true;
930
931
  ledger: true;
@@ -2188,6 +2189,7 @@ export class DaemonCommandRouter {
2188
2189
  completedAt?: string;
2189
2190
  jobId?: string;
2190
2191
  interactionId?: string;
2192
+ retryOfJobId?: string;
2191
2193
  }): MeshRefineJobHandle {
2192
2194
  return {
2193
2195
  success: true,
@@ -2202,6 +2204,7 @@ export class DaemonCommandRouter {
2202
2204
  workspace: readStringValue(args.node?.workspace),
2203
2205
  startedAt: args.startedAt || new Date().toISOString(),
2204
2206
  ...(args.completedAt ? { completedAt: args.completedAt } : {}),
2207
+ ...(args.retryOfJobId ? { retryOfJobId: args.retryOfJobId } : {}),
2205
2208
  eventDelivery: { pendingEvents: true, ledger: true },
2206
2209
  evidence: {
2207
2210
  pendingEventsCommand: 'get_pending_mesh_events',
@@ -2229,6 +2232,7 @@ export class DaemonCommandRouter {
2229
2232
  status: handle.status,
2230
2233
  startedAt: handle.startedAt,
2231
2234
  completedAt: handle.completedAt,
2235
+ retryOfJobId: handle.retryOfJobId,
2232
2236
  ...(result ? { result } : {}),
2233
2237
  },
2234
2238
  queuedAt: Date.now(),
@@ -2253,8 +2257,10 @@ export class DaemonCommandRouter {
2253
2257
  workspace: handle.workspace,
2254
2258
  startedAt: handle.startedAt,
2255
2259
  completedAt: handle.completedAt,
2260
+ retryOfJobId: handle.retryOfJobId,
2256
2261
  },
2257
2262
  async: true,
2263
+ retryOfJobId: handle.retryOfJobId,
2258
2264
  ...(result ? {
2259
2265
  success: result.success === true,
2260
2266
  result,
@@ -2509,6 +2515,7 @@ export class DaemonCommandRouter {
2509
2515
  completedAt,
2510
2516
  jobId: handle.jobId,
2511
2517
  interactionId: handle.interactionId,
2518
+ retryOfJobId: handle.retryOfJobId,
2512
2519
  node: { daemonId: handle.targetDaemonId, workspace: handle.workspace },
2513
2520
  });
2514
2521
  const terminal: MeshRefineTerminalJob = { ...terminalHandle, result };
@@ -2524,7 +2531,6 @@ export class DaemonCommandRouter {
2524
2531
  const running = this.runningRefineJobs.get(key);
2525
2532
  if (running) return { ...running, duplicate: true };
2526
2533
  const terminal = this.terminalRefineJobs.get(key);
2527
- if (terminal) return { ...terminal, duplicate: true };
2528
2534
 
2529
2535
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2530
2536
  const mesh = meshRecord?.mesh;
@@ -2532,7 +2538,7 @@ export class DaemonCommandRouter {
2532
2538
  if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
2533
2539
  if (!node.isLocalWorktree || !node.workspace) return { success: false, error: `Refinery requires a local worktree node` };
2534
2540
 
2535
- const handle = this.buildRefineJobHandle({ meshId, nodeId, node });
2541
+ const handle = this.buildRefineJobHandle({ meshId, nodeId, node, retryOfJobId: terminal?.jobId });
2536
2542
  this.runningRefineJobs.set(key, handle);
2537
2543
  await this.appendRefineJobLedger('task_dispatched', handle);
2538
2544
  this.queueRefineJobEvent('refine:accepted', handle);
@@ -58,6 +58,29 @@ export interface PendingMeshCoordinatorEvent {
58
58
  queuedAt: number;
59
59
  }
60
60
 
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
+ }
83
+
61
84
  function getPendingEventsPath(meshId: string): string {
62
85
  const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
63
86
  return join(getLedgerDir(), `${safe}.pending-events.jsonl`);
@@ -65,6 +88,10 @@ function getPendingEventsPath(meshId: string): string {
65
88
 
66
89
  export function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEvent): boolean {
67
90
  try {
91
+ if (hasPendingRefineTerminalEventDuplicate(event)) {
92
+ LOG.info('MeshEvents', `Suppressed duplicate pending ${event.event} for refine job ${readRefineJobId(event)}`);
93
+ return true;
94
+ }
68
95
  appendFileSync(getPendingEventsPath(event.meshId), JSON.stringify(event) + '\n', 'utf-8');
69
96
  return true;
70
97
  } catch (e: any) {
@@ -131,6 +158,9 @@ const MESH_COORDINATOR_EVENTS = new Set([
131
158
  'agent:stopped',
132
159
  'agent:ready',
133
160
  'monitor:long_generating',
161
+ 'refine:accepted',
162
+ 'refine:completed',
163
+ 'refine:failed',
134
164
  ]);
135
165
 
136
166
  const EVENT_TO_LEDGER_KIND: Record<string, MeshLedgerKind> = {
@@ -266,6 +296,18 @@ function isDuplicateMeshCompletionEvent(args: {
266
296
  return false;
267
297
  }
268
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
+
269
311
 
270
312
  export function tryAssignQueueTask(
271
313
  components: DaemonComponents,
@@ -700,6 +742,32 @@ function buildMeshSystemMessage(args: {
700
742
  if (args.event === 'monitor:long_generating') {
701
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.`;
702
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
+ }
703
771
  return '';
704
772
  }
705
773
 
@@ -728,6 +796,11 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
728
796
  return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
729
797
  }
730
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
+
731
804
  const eventTimestamp = readEventTimestamp(args.metadataEvent.timestamp);
732
805
  if (args.event === 'agent:generating_completed' && eventSessionId) {
733
806
  const duplicateCompletion = isDuplicateMeshCompletionEvent({
@@ -1005,6 +1078,14 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
1005
1078
  providerType: readNonEmptyString(payload.providerType),
1006
1079
  providerSessionId: readNonEmptyString(payload.providerSessionId),
1007
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 } : {}),
1008
1089
  ...(payload.timestamp !== undefined ? { timestamp: payload.timestamp } : {}),
1009
1090
  intentional: payload.intentional === true,
1010
1091
  intentionalStop: payload.intentionalStop === true,