@adhdev/daemon-core 0.9.82-rc.273 → 0.9.82-rc.275

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.
@@ -28,6 +28,8 @@ export interface MeshQueueTriggerResult {
28
28
  }
29
29
  export declare function triggerMeshQueue(components: DaemonComponents, meshId: string): Promise<MeshQueueTriggerResult>;
30
30
  export declare function isMeshCoordinatorEvent(eventName: unknown): eventName is string;
31
+ export declare const MESH_FORCE_INJECT_EVENTS: ReadonlySet<string>;
32
+ export declare function shouldForceInjectMeshEvent(eventName: unknown): boolean;
31
33
  export declare function handleMeshForwardEvent(components: DaemonComponents, payload: Record<string, unknown>): {
32
34
  success: boolean;
33
35
  forwarded: number;
@@ -40,7 +42,6 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
40
42
  duplicateCompletion?: undefined;
41
43
  duplicateStopped?: undefined;
42
44
  error?: undefined;
43
- remoteForwarded?: undefined;
44
45
  } | {
45
46
  success: boolean;
46
47
  forwarded: number;
@@ -53,7 +54,6 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
53
54
  duplicateCompletion?: undefined;
54
55
  duplicateStopped?: undefined;
55
56
  error?: undefined;
56
- remoteForwarded?: undefined;
57
57
  } | {
58
58
  success: boolean;
59
59
  forwarded: number;
@@ -66,7 +66,6 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
66
66
  duplicateCompletion?: undefined;
67
67
  duplicateStopped?: undefined;
68
68
  error?: undefined;
69
- remoteForwarded?: undefined;
70
69
  } | {
71
70
  success: boolean;
72
71
  forwarded: number;
@@ -79,7 +78,6 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
79
78
  duplicateCompletion?: undefined;
80
79
  duplicateStopped?: undefined;
81
80
  error?: undefined;
82
- remoteForwarded?: undefined;
83
81
  } | {
84
82
  success: boolean;
85
83
  forwarded: number;
@@ -92,7 +90,6 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
92
90
  duplicateApproval?: undefined;
93
91
  duplicateStopped?: undefined;
94
92
  error?: undefined;
95
- remoteForwarded?: undefined;
96
93
  } | {
97
94
  success: boolean;
98
95
  forwarded: number;
@@ -105,7 +102,6 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
105
102
  duplicateApproval?: undefined;
106
103
  duplicateStopped?: undefined;
107
104
  error?: undefined;
108
- remoteForwarded?: undefined;
109
105
  } | {
110
106
  success: boolean;
111
107
  forwarded: number;
@@ -118,11 +114,9 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
118
114
  duplicateApproval?: undefined;
119
115
  duplicateCompletion?: undefined;
120
116
  error?: undefined;
121
- remoteForwarded?: undefined;
122
117
  } | {
123
118
  success: boolean;
124
119
  forwarded: number;
125
- remoteForwarded: boolean;
126
120
  suppressed?: undefined;
127
121
  intentionalCleanupStop?: undefined;
128
122
  terminalLedgerEvidence?: undefined;
@@ -132,19 +126,6 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
132
126
  duplicateCompletion?: undefined;
133
127
  duplicateStopped?: undefined;
134
128
  error?: undefined;
135
- } | {
136
- success: boolean;
137
- forwarded: number;
138
- suppressed?: undefined;
139
- intentionalCleanupStop?: undefined;
140
- terminalLedgerEvidence?: undefined;
141
- terminalLedgerKind?: undefined;
142
- duplicateRefineTerminalEvent?: undefined;
143
- duplicateApproval?: undefined;
144
- duplicateCompletion?: undefined;
145
- duplicateStopped?: undefined;
146
- error?: undefined;
147
- remoteForwarded?: undefined;
148
129
  } | {
149
130
  success: boolean;
150
131
  error: string;
@@ -18,17 +18,22 @@ export declare function readRefineJobId(event: {
18
18
  metadataEvent?: Record<string, unknown>;
19
19
  } | Record<string, unknown>): string;
20
20
  export declare function buildPendingEventFingerprint(event: PendingMeshCoordinatorEvent): string;
21
- /**
22
- * R3: record that an event was direct-injected into a live coordinator on `coordinatorDaemonId`.
23
- * That coordinator's own drain (get_pending_mesh_events with the same coordinatorDaemonId) will
24
- * skip the queued copy, so it receives the event exactly once instead of twice (PTY + poll).
25
- * Other consumers (unscoped drainers, other daemons) are unaffected — they did not get the inject.
26
- */
27
- export declare function markMeshCoordinatorEventDirectDelivered(coordinatorDaemonId: string, event: PendingMeshCoordinatorEvent): void;
28
21
  export declare function hasPendingCoordinatorEventDuplicate(event: PendingMeshCoordinatorEvent): boolean;
29
22
  export declare function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEvent): boolean;
30
- /** Drain and return all pending coordinator events for meshId, removing them from disk. */
31
- export declare function drainPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): PendingMeshCoordinatorEvent[];
23
+ /**
24
+ * Drain and return pending coordinator events for meshId, removing the drained
25
+ * ones from both the SQLite inbox and the JSONL legacy file.
26
+ *
27
+ * When `opts.onlyEvents` is supplied, ONLY events whose name is in that set are
28
+ * drained; every other event stays queued (undrained in SQLite, rewritten back to
29
+ * the JSONL file). The reconcile loop uses this to force-drain terminal/force-inject
30
+ * events into a *generating* coordinator while leaving non-force progress events for
31
+ * the coordinator's next idle transition. The atomic SQLite drained=1 marking and the
32
+ * atomic JSONL rename keep force-drain and a concurrent full drain from double-consuming.
33
+ */
34
+ export declare function drainPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string, opts?: {
35
+ onlyEvents?: ReadonlySet<string>;
36
+ }): PendingMeshCoordinatorEvent[];
32
37
  /** Peek at pending coordinator events without draining (non-destructive). */
33
38
  export declare function getPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): readonly PendingMeshCoordinatorEvent[];
34
39
  /**
@@ -1,5 +1,6 @@
1
1
  export type { PendingMeshCoordinatorEvent } from './mesh-events-pending.js';
2
- export { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, markMeshCoordinatorEventDirectDelivered, } from './mesh-events-pending.js';
2
+ export { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, } from './mesh-events-pending.js';
3
3
  export { reconcileDirectDispatchCompletionFromTranscript, } from './mesh-events-stale.js';
4
+ export { setupMeshReconcileLoop, runMeshReconcileTick, } from './mesh-reconcile-loop.js';
4
5
  export type { MeshQueueTriggerResult } from './mesh-events-coordinator.js';
5
6
  export { tryAssignQueueTask, triggerMeshQueue, handleMeshForwardEvent, setupMeshEventForwarding, isMeshCoordinatorEvent, __resetIdleAutoFastForwardForTests, __resetMeshWorkspaceCacheForTests, } from './mesh-events-coordinator.js';
@@ -0,0 +1,7 @@
1
+ import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
2
+ export declare function runMeshReconcileTick(components: DaemonComponents): Promise<void>;
3
+ interface ReconcileLoopHandle {
4
+ stop(): void;
5
+ }
6
+ export declare function setupMeshReconcileLoop(components: DaemonComponents): ReconcileLoopHandle;
7
+ export {};
@@ -288,7 +288,19 @@ export declare class MeshRuntimeStore {
288
288
  fingerprint?: string | null;
289
289
  queuedAt: number;
290
290
  }): boolean;
291
- drainPendingEvents(meshId: string, coordinatorDaemonId?: string | null): Array<{
291
+ /**
292
+ * Drain undrained pending events for a mesh, atomically marking them drained.
293
+ * When `opts.onlyEvents` is supplied, ONLY rows whose `event` is in that set are
294
+ * drained — the rest stay queued (drained=0) for a later drain. This is how the
295
+ * reconcile loop force-drains terminal/force-inject events into a *generating*
296
+ * coordinator while leaving non-force progress events for the coordinator's next
297
+ * idle transition. Filtering happens inside the same transaction as the
298
+ * drained=1 marking, so force-drain + a concurrent full drain can never both
299
+ * consume the same row.
300
+ */
301
+ drainPendingEvents(meshId: string, coordinatorDaemonId?: string | null, opts?: {
302
+ onlyEvents?: ReadonlySet<string>;
303
+ }): Array<{
292
304
  id: string;
293
305
  event: string;
294
306
  payload: unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.273",
3
+ "version": "0.9.82-rc.275",
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",
@@ -46,7 +46,7 @@
46
46
  "author": "vilmire",
47
47
  "license": "AGPL-3.0-or-later",
48
48
  "dependencies": {
49
- "@adhdev/mesh-shared": "0.9.82-rc.273",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.275",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -37,6 +37,7 @@ import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
37
37
  import type { IdeProviderInstance } from '../providers/ide-provider-instance.js';
38
38
  import { createDefaultGitCommandServices } from '../git/git-commands.js';
39
39
  import { setupMeshEventForwarding } from '../mesh/mesh-events.js';
40
+ import { setupMeshReconcileLoop } from '../mesh/mesh-reconcile-loop.js';
40
41
  import { loadMeshCoordinatorRegistry } from '../mesh/coordinator-registry.js';
41
42
  import { applyProcessHardening } from './process-hardening.js';
42
43
  import { installProviderProcessShim } from '../providers/sdk/v1/sandbox/require-whitelist.js';
@@ -117,6 +118,11 @@ export interface DaemonComponents {
117
118
  // subscriptions). Injected by daemon-cloud; absent/no-op on standalone. Replaces cloud's
118
119
  // former separate instanceManager.onEvent listener so the event path stays single-listener.
119
120
  onMeshCoordinatorEventForwarded?: (payload: Record<string, unknown>) => void;
121
+ // Periodic queue → live-coordinator reconcile loop handle. Set during
122
+ // initDaemonComponents, stopped during shutdownDaemonComponents. Drives the
123
+ // single-model (queue + polling) delivery: drains the pending-events queue
124
+ // on a fixed interval and injects into live CLI coordinators when idle.
125
+ meshReconcileLoop?: { stop(): void };
120
126
  }
121
127
 
122
128
  export interface DaemonDevSupportOptions {
@@ -341,7 +347,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
341
347
  // 10. Start instance ticking
342
348
  instanceManager.startTicking(config.tickIntervalMs ?? 5_000);
343
349
 
344
- const components = {
350
+ const components: DaemonComponents = {
345
351
  providerLoader,
346
352
  instanceManager,
347
353
  cliManager,
@@ -358,8 +364,14 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
358
364
  onMeshCoordinatorEventForwarded: config.onMeshCoordinatorEventForwarded,
359
365
  };
360
366
 
361
- // 11. Setup Mesh Event Forwarding
367
+ // 11. Setup Mesh Event Forwarding (queue persistence) + periodic reconcile loop.
368
+ // injectMeshSystemMessage now ONLY persists events to the pending-events queue;
369
+ // the reconcile loop drains that queue on a fixed interval and injects into live
370
+ // CLI coordinators when idle (and, in cloud mode, pulls remote worker daemons'
371
+ // queues over P2P). This is the single-model (queue + polling) replacement for the
372
+ // old spontaneous-forward push paths.
362
373
  setupMeshEventForwarding(components);
374
+ components.meshReconcileLoop = setupMeshReconcileLoop(components);
363
375
 
364
376
  // 12. Resume any refine jobs that were interrupted by a previous daemon restart.
365
377
  setImmediate(() => void router.resumePendingRefineJobsOnStartup());
@@ -404,11 +416,13 @@ export async function shutdownDaemonComponents(components: DaemonComponents): Pr
404
416
  const {
405
417
  poller, cdpInitializer, agentStreamManager,
406
418
  cliManager, instanceManager, cdpManagers,
419
+ meshReconcileLoop,
407
420
  } = components;
408
421
 
409
422
  // 1. Stop timers
410
423
  poller.stop();
411
424
  cdpInitializer.stop();
425
+ try { meshReconcileLoop?.stop(); } catch { /* noop */ }
412
426
 
413
427
  // 2. Dispose agent stream
414
428
  try {
@@ -10,7 +10,7 @@ import { buildMeshNodeCapabilityTags, nodeSatisfiesRequiredTags, claimNextTask,
10
10
  import { fastForwardMeshNode } from './mesh-fast-forward.js';
11
11
  import { createSessionDelivery, markSessionDeliveriesTerminal, updateSessionDeliveryStatus, recordCompletionConflict } from './mesh-delivery-policy.js';
12
12
  import { MeshRuntimeStore } from './mesh-runtime-store.js';
13
- import { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents, markMeshCoordinatorEventDirectDelivered } from './mesh-events-pending.js';
13
+ import { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents } from './mesh-events-pending.js';
14
14
  import type { PendingMeshCoordinatorEvent } from './mesh-events-pending.js';
15
15
  import { resolveWorkerDelegateRouting, recordUnroutableDelegateEvent } from './mesh-routing.js';
16
16
  import { resolveDelegatedWorkerAutoApprove } from '../repo-mesh-types.js';
@@ -27,7 +27,6 @@ import {
27
27
  resolveEventSessionId,
28
28
  readRefineJobId,
29
29
  readWorkerResultMetadata,
30
- sameDaemonId,
31
30
  } from './mesh-events-utils.js';
32
31
 
33
32
  // ---------------------------------------------------------------------------
@@ -976,7 +975,7 @@ export function isMeshCoordinatorEvent(eventName: unknown): eventName is string
976
975
  // only flushed on the coordinator's OWN idle transition. That transition can't
977
976
  // happen until it receives this very event → deadlock. We force-inject these so
978
977
  // they bypass the busy send-guard and land in the PTY while generating.
979
- const MESH_FORCE_INJECT_EVENTS = new Set([
978
+ export const MESH_FORCE_INJECT_EVENTS: ReadonlySet<string> = new Set([
980
979
  'agent:generating_completed',
981
980
  'agent:stopped',
982
981
  'agent:waiting_approval',
@@ -986,7 +985,7 @@ const MESH_FORCE_INJECT_EVENTS = new Set([
986
985
  'worktree_bootstrap_failed',
987
986
  ]);
988
987
 
989
- function shouldForceInjectMeshEvent(eventName: unknown): boolean {
988
+ export function shouldForceInjectMeshEvent(eventName: unknown): boolean {
990
989
  return typeof eventName === 'string' && MESH_FORCE_INJECT_EVENTS.has(eventName);
991
990
  }
992
991
 
@@ -1007,7 +1006,6 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1007
1006
  const workerCoordinatorDaemonId = readNonEmptyString(
1008
1007
  (sourceSession?.getState()?.settings as Record<string, unknown>)?.meshCoordinatorDaemonId,
1009
1008
  );
1010
- const localDaemonId = readNonEmptyString(loadConfig().machineId);
1011
1009
 
1012
1010
  // R2: cloud P2P dashboard metadata sync. The cloud daemon used to do this from its own
1013
1011
  // relay listener; now the single core forwarder invokes the injected hook (no-op on
@@ -1392,82 +1390,24 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1392
1390
  });
1393
1391
  if (!messageText) return { success: false, error: 'unsupported mesh event' };
1394
1392
 
1395
- const coordinatorInstances = components.instanceManager.getByCategory('cli').filter((inst) => {
1396
- const instState = inst.getState();
1397
- if (instState.settings?.meshCoordinatorFor !== args.meshId) return false;
1398
- if (args.sourceInstanceId && instState.instanceId === args.sourceInstanceId) return false;
1399
- // canonicalize: the worker stamps the prefixed daemon id (standalone_/daemon_) while
1400
- // localDaemonId is the raw machineId a literal !== wrongly excludes the local coordinator.
1401
- if (workerCoordinatorDaemonId && localDaemonId && !sameDaemonId(workerCoordinatorDaemonId, localDaemonId)) return false;
1402
- return true;
1403
- });
1404
-
1405
- if (coordinatorInstances.length === 0) {
1406
- // R2: the coordinator is not a live instance on THIS daemon. If it lives on a remote
1407
- // daemon, forward over the injected transport (dispatchMeshCommand, supplied by cloud as
1408
- // P2P; absent/no-op on standalone). This replaces cloud's separate
1409
- // relayRemoteMeshCoordinatorEvent listener — remote delivery is now part of the single
1410
- // core forwarder path, so local-vs-remote is one code path and standalone == cloud for
1411
- // the local case. The pending queue remains the fallback when there is no remote target
1412
- // or the transport send fails (e.g. P2P down), so MCP coordinators can still backfill.
1413
- const remoteCoordinatorDaemonId = workerCoordinatorDaemonId && localDaemonId && !sameDaemonId(workerCoordinatorDaemonId, localDaemonId)
1414
- ? workerCoordinatorDaemonId
1415
- : '';
1416
- if (remoteCoordinatorDaemonId && components.dispatchMeshCommand) {
1417
- const forwardPayload: Record<string, unknown> = {
1418
- event: args.event,
1419
- meshId: args.meshId,
1420
- nodeId: args.nodeId || undefined,
1421
- workspace: readNonEmptyString(args.metadataEvent.workspace),
1422
- ...args.metadataEvent,
1423
- ...(recoveryContext ? { recoveryContext } : {}),
1424
- };
1425
- components.dispatchMeshCommand(remoteCoordinatorDaemonId, 'mesh_forward_event', forwardPayload)
1426
- .then(() => {
1427
- LOG.info('MeshEvents', `Forwarded ${args.event} for mesh ${args.meshId} to remote coordinator daemon ${remoteCoordinatorDaemonId.slice(0, 12)}…`);
1428
- })
1429
- .catch((error: any) => {
1430
- LOG.warn('MeshEvents', `Remote forward of ${args.event} failed (${error?.message || error}); queuing for backfill`);
1431
- queuePendingMeshCoordinatorEvent({
1432
- event: args.event,
1433
- meshId: args.meshId,
1434
- nodeLabel: args.nodeLabel,
1435
- nodeId: args.nodeId || undefined,
1436
- workspace: readNonEmptyString(args.metadataEvent.workspace),
1437
- metadataEvent: { ...args.metadataEvent, ...(recoveryContext ? { recoveryContext } : {}) },
1438
- coordinatorMessage: messageText,
1439
- queuedAt: Date.now(),
1440
- targetCoordinatorDaemonId: remoteCoordinatorDaemonId,
1441
- });
1442
- });
1443
- return { success: true, forwarded: 0, remoteForwarded: true };
1444
- }
1445
- if (queuePendingMeshCoordinatorEvent({
1446
- event: args.event,
1447
- meshId: args.meshId,
1448
- nodeLabel: args.nodeLabel,
1449
- nodeId: args.nodeId || undefined,
1450
- workspace: readNonEmptyString(args.metadataEvent.workspace),
1451
- metadataEvent: {
1452
- ...args.metadataEvent,
1453
- ...(recoveryContext ? { recoveryContext } : {}),
1454
- },
1455
- coordinatorMessage: messageText,
1456
- queuedAt: Date.now(),
1457
- ...(workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {}),
1458
- })) {
1459
- LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId}${workerCoordinatorDaemonId ? `, coordinator daemon ${workerCoordinatorDaemonId}` : ''})`);
1460
- }
1461
- return { success: true, forwarded: 0 };
1462
- }
1463
-
1464
- // All events — terminal and non-terminal — are queued for MCP coordinator
1465
- // delivery via the pending-events file/SQLite path. This ensures that MCP
1466
- // coordinators (which poll via get_pending_mesh_events / mesh_status) always
1467
- // receive terminal events even when a live CLI coordinator session is present.
1468
- // Previously, terminal events skipped the queue and were only direct-injected
1469
- // into live CLI coordinators via send_message, which silently dropped them
1470
- // when the coordinator was in a generating state.
1393
+ // ── Queue-only delivery (single-model: queue + periodic poll) ──────────────
1394
+ // Every mesh coordinator event — terminal or not, local-coordinator or
1395
+ // remote — is persisted to the pending-events queue (SQLite + JSONL) and
1396
+ // NOTHING is pushed here. The old spontaneous-forward paths were removed:
1397
+ // - F1 remote P2P `mesh_forward_event` dispatch (network/stamp-dependent,
1398
+ // silently dropped on P2P failure or missing meshCoordinatorDaemonId)
1399
+ // - F3 live-CLI PTY `send_message` fire-and-forget inject (silently
1400
+ // dropped when the coordinator was generating)
1401
+ // Delivery to a live CLI coordinator now happens via setupMeshReconcileLoop,
1402
+ // which drains this queue on a fixed interval and injects into the coordinator
1403
+ // only when it is idle. A pure stdio MCP (LLM) coordinator — which has no live
1404
+ // CLI session to inject into drains the queue itself when it calls a mesh
1405
+ // tool (mesh_status / mesh_read_chat). Either way the queue is the single
1406
+ // source of truth and the only thing this function writes to.
1407
+ //
1408
+ // targetCoordinatorDaemonId scopes the event to a specific coordinator daemon
1409
+ // (unicast) when the worker carries one, so the reconcile loop on the right
1410
+ // daemon drains it and other daemons skip it. Absent broadcast/backfill.
1471
1411
  const pendingEvent = {
1472
1412
  event: args.event,
1473
1413
  meshId: args.meshId,
@@ -1483,30 +1423,9 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1483
1423
  ...(workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {}),
1484
1424
  };
1485
1425
  if (queuePendingMeshCoordinatorEvent(pendingEvent)) {
1486
- LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId})`);
1487
- }
1488
-
1489
- // R3: the live coordinators below receive this event directly in their PTY. They (or their
1490
- // MCP client) also poll get_pending_mesh_events with coordinatorDaemonId = this daemon, which
1491
- // would re-deliver the queued copy → the user saw the same completion twice. Mark the event
1492
- // direct-delivered to THIS daemon so that coordinator's own drain skips it. The queue entry
1493
- // stays for every OTHER consumer (idle / MCP-only / remote) that did not get the direct inject.
1494
- // markMeshCoordinatorEventDirectDelivered canonicalizes the id internally so the raw
1495
- // machineId here matches the prefixed instanceId (standalone_mach_X) the coordinator drains with.
1496
- if (localDaemonId) {
1497
- markMeshCoordinatorEventDirectDelivered(localDaemonId, pendingEvent);
1498
- }
1499
-
1500
- const forceInject = shouldForceInjectMeshEvent(args.event);
1501
- for (const coord of coordinatorInstances) {
1502
- const coordState = coord.getState();
1503
- LOG.info('MeshEvents', `Forwarding mesh event to coordinator ${coordState.instanceId}${forceInject ? ' (force)' : ''}`);
1504
- coord.onEvent('send_message', {
1505
- input: { text: messageText, textFallback: messageText },
1506
- ...(forceInject ? { force: true } : {}),
1507
- });
1426
+ LOG.info('MeshEvents', `Queued ${args.event} for coordinator (mesh ${args.meshId}${workerCoordinatorDaemonId ? `, coordinator daemon ${workerCoordinatorDaemonId}` : ''})`);
1508
1427
  }
1509
- return { success: true, forwarded: coordinatorInstances.length };
1428
+ return { success: true, forwarded: 0 };
1510
1429
  }
1511
1430
 
1512
1431
  export function handleMeshForwardEvent(components: DaemonComponents, payload: Record<string, unknown>) {
@@ -1563,10 +1482,15 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
1563
1482
 
1564
1483
  export function setupMeshEventForwarding(components: DaemonComponents) {
1565
1484
  components.instanceManager.onEvent((event) => {
1566
- // --- Coordinator idle auto-flush ---
1567
- // When a coordinator session becomes idle, flush any pending coordinator events
1568
- // that accumulated while it was generating. This runs before the delegate routing
1569
- // below so that coordinator-own idle transitions are handled first.
1485
+ // --- Coordinator idle auto-flush (fast path) ---
1486
+ // When a coordinator session becomes idle, immediately flush any pending
1487
+ // coordinator events that accumulated while it was generating, rather than
1488
+ // waiting up to one reconcile interval for setupMeshReconcileLoop to do it.
1489
+ // Both paths drain the SAME queue via drainPendingMeshCoordinatorEvents,
1490
+ // whose SQLite drained=1 marking is atomic — whichever fires first consumes
1491
+ // the events and the other gets nothing, so there is no double-delivery.
1492
+ // This runs before the delegate routing below so that coordinator-own idle
1493
+ // transitions are handled first.
1570
1494
  // Exception: a coordinator that is itself a direct-dispatch target still needs
1571
1495
  // to go through delegate routing so that the dispatching coordinator receives a
1572
1496
  // pendingCoordinatorEvents entry for the completion.