@adhdev/daemon-core 0.9.82-rc.250 → 0.9.82-rc.252

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.250",
3
+ "version": "0.9.82-rc.252",
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",
@@ -7301,19 +7301,15 @@ export class DaemonCommandRouter {
7301
7301
  nodeStatuses.push(status);
7302
7302
  }
7303
7303
 
7304
- // (B3) Resolve the coordinator daemon scope for the drain.
7305
- // Emit-time always tags events with the worker's
7306
- // targetCoordinatorDaemonId and writes them to the
7307
- // scoped pending-events file. If the caller (MCP tool,
7308
- // dashboard, etc.) doesn't tell us which coordinator
7309
- // they're running under, fall back to *this daemon's*
7310
- // ID — the caller reached us over our IPC/WS, so they
7311
- // are by definition under this daemon. Falling back to
7312
- // undefined would silently miss every scoped event.
7304
+ // (B3) Resolve the coordinator daemon scope for the peek.
7305
+ // mesh_status is a read-only status query it must not consume
7306
+ // (drain) pending events as a side effect. Coordinators that see
7307
+ // pendingCoordinatorEvents in the response are expected to call
7308
+ // get_pending_mesh_events to explicitly drain them after processing.
7313
7309
  const callerCoordinatorDaemonId = typeof args?.coordinatorDaemonId === 'string' && args.coordinatorDaemonId.trim()
7314
7310
  ? args.coordinatorDaemonId.trim()
7315
7311
  : (this.deps.statusInstanceId || undefined);
7316
- const pendingCoordinatorEvents = drainPendingMeshCoordinatorEvents(meshId, callerCoordinatorDaemonId);
7312
+ const pendingCoordinatorEvents = getPendingMeshCoordinatorEvents(meshId, callerCoordinatorDaemonId);
7317
7313
  const previewFreshness = (() => {
7318
7314
  const localRepoRoot = nodeStatuses
7319
7315
  .map((node: any) => readStringValue(node?.git?.repoRoot, node?.repoRoot, node?.workspace))
@@ -7323,7 +7319,7 @@ export class DaemonCommandRouter {
7323
7319
  const asyncRefineJobs = buildMeshAsyncRefineJobs({
7324
7320
  meshId,
7325
7321
  ledgerEntries: asyncRefineLedgerEntries,
7326
- pendingEvents: pendingCoordinatorEvents,
7322
+ pendingEvents: [...pendingCoordinatorEvents],
7327
7323
  });
7328
7324
  const historicalSessions = buildHistoricalMeshSessions({
7329
7325
  meshId,
@@ -1338,28 +1338,28 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1338
1338
  return { success: true, forwarded: 0 };
1339
1339
  }
1340
1340
 
1341
- // Non-terminal events are also queued for MCP coordinator dual delivery.
1342
- // Terminal events are forwarded directly without buffering — previously
1343
- // they were buffered when coordinators were generating and auto-flushed on
1344
- // idle, but auto-flush was unreliable and events were silently lost.
1345
- const isTerminalEvent = new Set(['refine:completed', 'refine:failed', 'agent:generating_completed', 'agent:stopped']).has(args.event);
1346
- if (!isTerminalEvent) {
1347
- if (queuePendingMeshCoordinatorEvent({
1348
- event: args.event,
1349
- meshId: args.meshId,
1350
- nodeLabel: args.nodeLabel,
1351
- nodeId: args.nodeId || undefined,
1352
- workspace: readNonEmptyString(args.metadataEvent.workspace),
1353
- metadataEvent: {
1354
- ...args.metadataEvent,
1355
- ...(recoveryContext ? { recoveryContext } : {}),
1356
- },
1357
- coordinatorMessage: messageText,
1358
- queuedAt: Date.now(),
1359
- ...(workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {}),
1360
- })) {
1361
- LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId})`);
1362
- }
1341
+ // All events — terminal and non-terminal are queued for MCP coordinator
1342
+ // delivery via the pending-events file/SQLite path. This ensures that MCP
1343
+ // coordinators (which poll via get_pending_mesh_events / mesh_status) always
1344
+ // receive terminal events even when a live CLI coordinator session is present.
1345
+ // Previously, terminal events skipped the queue and were only direct-injected
1346
+ // into live CLI coordinators via send_message, which silently dropped them
1347
+ // when the coordinator was in a generating state.
1348
+ if (queuePendingMeshCoordinatorEvent({
1349
+ event: args.event,
1350
+ meshId: args.meshId,
1351
+ nodeLabel: args.nodeLabel,
1352
+ nodeId: args.nodeId || undefined,
1353
+ workspace: readNonEmptyString(args.metadataEvent.workspace),
1354
+ metadataEvent: {
1355
+ ...args.metadataEvent,
1356
+ ...(recoveryContext ? { recoveryContext } : {}),
1357
+ },
1358
+ coordinatorMessage: messageText,
1359
+ queuedAt: Date.now(),
1360
+ ...(workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {}),
1361
+ })) {
1362
+ LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId})`);
1363
1363
  }
1364
1364
 
1365
1365
  for (const coord of coordinatorInstances) {
@@ -60,6 +60,12 @@ function hasPendingRefineTerminalEventDuplicate(event: PendingMeshCoordinatorEve
60
60
 
61
61
  export function buildPendingEventFingerprint(event: PendingMeshCoordinatorEvent): string {
62
62
  const metadata = readRecord(event.metadataEvent) || {};
63
+ // Bootstrap events are node-scoped: dedup by meshId+event+nodeId only.
64
+ // They carry no sessionId/taskId/timestamp — using those fields would produce
65
+ // an empty fingerprint that defeats dedup entirely.
66
+ if (event.event === 'worktree_bootstrap_complete' || event.event === 'worktree_bootstrap_failed') {
67
+ return [event.meshId, event.event, event.nodeId || ''].join('::');
68
+ }
63
69
  const sessionId = resolveEventSessionId(metadata);
64
70
  const providerSessionId = readNonEmptyString(metadata.providerSessionId);
65
71
  const taskId = readNonEmptyString(metadata.taskId) || readNonEmptyString(readRecord(metadata.payload)?.taskId);