@adhdev/daemon-core 0.9.82-rc.257 → 0.9.82-rc.259

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.
@@ -1,5 +1,6 @@
1
1
  import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
2
2
  export declare function __resetIdleAutoFastForwardForTests(): void;
3
+ export declare function __resetMeshWorkspaceCacheForTests(): void;
3
4
  export declare function tryAssignQueueTask(components: DaemonComponents, meshId: string, nodeId: string, sessionId: string, providerType: string): boolean;
4
5
  export interface MeshQueueTriggerResult {
5
6
  success: true;
@@ -39,6 +40,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
39
40
  duplicateCompletion?: undefined;
40
41
  duplicateStopped?: undefined;
41
42
  error?: undefined;
43
+ remoteForwarded?: undefined;
42
44
  } | {
43
45
  success: boolean;
44
46
  forwarded: number;
@@ -51,6 +53,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
51
53
  duplicateCompletion?: undefined;
52
54
  duplicateStopped?: undefined;
53
55
  error?: undefined;
56
+ remoteForwarded?: undefined;
54
57
  } | {
55
58
  success: boolean;
56
59
  forwarded: number;
@@ -63,6 +66,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
63
66
  duplicateCompletion?: undefined;
64
67
  duplicateStopped?: undefined;
65
68
  error?: undefined;
69
+ remoteForwarded?: undefined;
66
70
  } | {
67
71
  success: boolean;
68
72
  forwarded: number;
@@ -75,6 +79,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
75
79
  duplicateCompletion?: undefined;
76
80
  duplicateStopped?: undefined;
77
81
  error?: undefined;
82
+ remoteForwarded?: undefined;
78
83
  } | {
79
84
  success: boolean;
80
85
  forwarded: number;
@@ -87,6 +92,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
87
92
  duplicateApproval?: undefined;
88
93
  duplicateStopped?: undefined;
89
94
  error?: undefined;
95
+ remoteForwarded?: undefined;
90
96
  } | {
91
97
  success: boolean;
92
98
  forwarded: number;
@@ -99,6 +105,7 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
99
105
  duplicateApproval?: undefined;
100
106
  duplicateStopped?: undefined;
101
107
  error?: undefined;
108
+ remoteForwarded?: undefined;
102
109
  } | {
103
110
  success: boolean;
104
111
  forwarded: number;
@@ -111,9 +118,11 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
111
118
  duplicateApproval?: undefined;
112
119
  duplicateCompletion?: undefined;
113
120
  error?: undefined;
121
+ remoteForwarded?: undefined;
114
122
  } | {
115
123
  success: boolean;
116
124
  forwarded: number;
125
+ remoteForwarded: boolean;
117
126
  suppressed?: undefined;
118
127
  intentionalCleanupStop?: undefined;
119
128
  terminalLedgerEvidence?: undefined;
@@ -123,6 +132,19 @@ export declare function handleMeshForwardEvent(components: DaemonComponents, pay
123
132
  duplicateCompletion?: undefined;
124
133
  duplicateStopped?: undefined;
125
134
  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;
126
148
  } | {
127
149
  success: boolean;
128
150
  error: string;
@@ -18,6 +18,13 @@ 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;
21
28
  export declare function hasPendingCoordinatorEventDuplicate(event: PendingMeshCoordinatorEvent): boolean;
22
29
  export declare function queuePendingMeshCoordinatorEvent(event: PendingMeshCoordinatorEvent): boolean;
23
30
  /** Drain and return all pending coordinator events for meshId, removing them from disk. */
@@ -1,6 +1,8 @@
1
1
  import type { SessionRecoveryContext } from './mesh-ledger.js';
2
2
  export declare function readNonEmptyString(value: unknown): string;
3
3
  export declare function readRecord(value: unknown): Record<string, unknown> | undefined;
4
+ export declare function canonicalDaemonId(value: unknown): string;
5
+ export declare function sameDaemonId(a: unknown, b: unknown): boolean;
4
6
  export declare function resolveEventSessionId(event: Record<string, unknown>, fallback?: unknown): string;
5
7
  export declare function readRefineJobId(event: {
6
8
  metadataEvent?: Record<string, unknown>;
@@ -1,5 +1,5 @@
1
1
  export type { PendingMeshCoordinatorEvent } from './mesh-events-pending.js';
2
- export { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, } from './mesh-events-pending.js';
2
+ export { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, markMeshCoordinatorEventDirectDelivered, } from './mesh-events-pending.js';
3
3
  export { reconcileDirectDispatchCompletionFromTranscript, } from './mesh-events-stale.js';
4
4
  export type { MeshQueueTriggerResult } from './mesh-events-coordinator.js';
5
- export { tryAssignQueueTask, triggerMeshQueue, handleMeshForwardEvent, setupMeshEventForwarding, isMeshCoordinatorEvent, __resetIdleAutoFastForwardForTests, } from './mesh-events-coordinator.js';
5
+ export { tryAssignQueueTask, triggerMeshQueue, handleMeshForwardEvent, setupMeshEventForwarding, isMeshCoordinatorEvent, __resetIdleAutoFastForwardForTests, __resetMeshWorkspaceCacheForTests, } from './mesh-events-coordinator.js';
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { EventEmitter } from 'events';
16
16
  import { MeshRuntimeStore } from './mesh-runtime-store.js';
17
- export type MeshLedgerKind = 'task_dispatched' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward';
17
+ export type MeshLedgerKind = 'task_dispatched' | 'task_completed' | 'task_failed' | 'task_stalled' | 'task_approval_needed' | 'p2p_dispatch_failed' | 'session_launched' | 'session_auto_launch' | 'session_stopped' | 'checkpoint_created' | 'node_cloned' | 'node_joined' | 'node_removed' | 'coordinator_started' | 'recovery_attempted' | 'ledger_replicated' | 'ledger_reconciled' | 'direct_fast_forward' | 'delivery_unroutable';
18
18
  export interface MeshLedgerEntry {
19
19
  id: string;
20
20
  meshId: string;
@@ -0,0 +1,70 @@
1
+ import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
2
+ export type WorkerDelegateRejectionReason = 'not_cli' | 'no_workspace' | 'no_worker_envelope' | 'coordinator_not_dispatch_target' | 'mesh_unresolved';
3
+ export interface WorkerDelegateRouting {
4
+ /** True when the source session is a mesh worker whose events must route to a coordinator. */
5
+ isDelegate: boolean;
6
+ /** Resolved mesh id (runtime stamp, direct-dispatch recovery, or workspace lookup). */
7
+ meshId: string;
8
+ /** Resolved node id within the mesh, or '' when it cannot be determined. */
9
+ nodeId: string;
10
+ /** Human-readable node label for the coordinator system message. */
11
+ nodeLabel: string;
12
+ /** Coordinator daemon anchor (prefixed id) when the worker carries one; '' otherwise. */
13
+ coordinatorDaemonId: string;
14
+ /** Source session workspace (best-effort; '' when the session/workspace is missing). */
15
+ workspace: string;
16
+ /** Source session id echoed back, so diagnostics can name the dropped event's origin. */
17
+ sessionId: string;
18
+ /** Why routing was rejected (isDelegate=false), for fail-loud diagnostics (R4). */
19
+ rejectionReason?: WorkerDelegateRejectionReason;
20
+ }
21
+ interface ResolveDeps {
22
+ /** Resolve a mesh by id (local config + inline cache). */
23
+ getMeshById: (meshId: string) => any | undefined;
24
+ /** Resolve a mesh by workspace path (cached repo lookup). */
25
+ getMeshByWorkspace: (workspace: string) => any | undefined;
26
+ }
27
+ /**
28
+ * Resolve how a source CLI session's mesh events should route to a coordinator.
29
+ *
30
+ * This is the single authoritative interpretation of the worker envelope. It folds
31
+ * together every signal that previously lived in inline branches:
32
+ * - runtime stamps (meshNodeFor / meshNodeId)
33
+ * - the coordinator anchor (meshCoordinatorDaemonId) and launch marker (launchedByCoordinator,
34
+ * meshCoordinatorNodeId) — any one proves delegation even if the node/mesh stamp was dropped
35
+ * - direct-dispatch recovery: a coordinator session that is itself a dispatch target
36
+ * - workspace→mesh fallback when no runtime mesh id survived
37
+ *
38
+ * Returns isDelegate=false (with a rejectionReason) instead of throwing, so the caller
39
+ * can either ignore non-delegate events or surface a diagnostic.
40
+ */
41
+ export declare function resolveWorkerDelegateRouting(components: DaemonComponents, instanceId: string, deps: ResolveDeps): WorkerDelegateRouting;
42
+ /** Ledger stream that collects delivery_unroutable diagnostics (no real mesh id exists for them). */
43
+ export declare const UNROUTABLE_DIAGNOSTIC_STREAM = "__unroutable__";
44
+ export declare function __resetUnroutableDiagnosticsForTests(): void;
45
+ /** True only for rejections that represent a silently-dropped delegate event worth a diagnostic. */
46
+ export declare function isUnroutableDelegateRejection(routing: WorkerDelegateRouting): boolean;
47
+ /**
48
+ * R4: record that a worker event with a valid envelope could not be routed to a coordinator.
49
+ * Writes a `delivery_unroutable` ledger entry (deduped within a short window) so operators see
50
+ * WHY a completion never arrived instead of it vanishing. No-op for non-diagnostic rejections.
51
+ */
52
+ export declare function recordUnroutableDelegateEvent(routing: WorkerDelegateRouting, eventName: string): boolean;
53
+ export interface UnroutableDeliveryDiagnostic {
54
+ timestamp: string;
55
+ event: string;
56
+ sessionId?: string;
57
+ workspace?: string;
58
+ coordinatorDaemonId?: string;
59
+ }
60
+ /**
61
+ * R4 visibility: recent delivery_unroutable diagnostics, newest first. Surfaced in mesh_status
62
+ * so an operator/coordinator can see that completions were dropped (envelope present, mesh
63
+ * unresolved) rather than the drops being invisible in a ledger nobody reads. The diagnostics
64
+ * live in a single shared stream because an unroutable event has no resolvable mesh.
65
+ */
66
+ export declare function getRecentUnroutableDeliveries(opts?: {
67
+ sinceMs?: number;
68
+ limit?: number;
69
+ }): UnroutableDeliveryDiagnostic[];
70
+ export {};
@@ -17,6 +17,9 @@ export declare class MeshRuntimeStore {
17
17
  hasCompletionFingerprint(fingerprint: string): boolean;
18
18
  recordCompletionFingerprint(fingerprint: string, ttlMs: number): void;
19
19
  sweepExpiredFingerprints(): void;
20
+ recordDirectDelivered(coordinatorDaemonId: string, fingerprint: string, ttlMs: number): void;
21
+ wasDirectDelivered(coordinatorDaemonId: string, fingerprint: string): boolean;
22
+ sweepExpiredDirectDelivered(): void;
20
23
  private maybeCheckpointWal;
21
24
  private ensureLegacyQueueMigrated;
22
25
  getQueueEntries(meshId: string, statuses?: MeshTaskStatus[]): MeshWorkQueueEntry[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.257",
3
+ "version": "0.9.82-rc.259",
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",
@@ -92,6 +92,8 @@ export interface DaemonInitConfig {
92
92
  dispatchMeshCommand?: (daemonId: string, command: string, args: Record<string, unknown>) => Promise<any>;
93
93
  /** Returns selected-coordinator mesh peer telemetry for a target daemon when available. */
94
94
  getMeshPeerConnectionStatus?: (daemonId: string) => Record<string, unknown> | null;
95
+ /** Cloud-only: P2P dashboard metadata sync after the core forwarder handles a mesh event. */
96
+ onMeshCoordinatorEventForwarded?: (payload: Record<string, unknown>) => void;
95
97
  }
96
98
 
97
99
  // ─── Result ───
@@ -110,6 +112,11 @@ export interface DaemonComponents {
110
112
  detectedIdes: { value: IDEInfo[] };
111
113
  refreshProviderAvailability: (providerType?: string) => Promise<void>;
112
114
  dispatchMeshCommand?: (daemonId: string, command: string, args: Record<string, unknown>) => Promise<any>;
115
+ // Cloud-only hook: after the single core forwarder handles a mesh coordinator event, cloud
116
+ // uses this to keep its P2P dashboard view in sync (mesh-owned session metadata + flush
117
+ // subscriptions). Injected by daemon-cloud; absent/no-op on standalone. Replaces cloud's
118
+ // former separate instanceManager.onEvent listener so the event path stays single-listener.
119
+ onMeshCoordinatorEventForwarded?: (payload: Record<string, unknown>) => void;
113
120
  }
114
121
 
115
122
  export interface DaemonDevSupportOptions {
@@ -348,6 +355,7 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
348
355
  detectedIdes: detectedIdesRef,
349
356
  refreshProviderAvailability,
350
357
  dispatchMeshCommand: config.dispatchMeshCommand,
358
+ onMeshCoordinatorEventForwarded: config.onMeshCoordinatorEventForwarded,
351
359
  };
352
360
 
353
361
  // 11. Setup Mesh Event Forwarding
@@ -40,6 +40,7 @@ import { createHermesManualMeshCoordinatorSetup, resolveMeshCoordinatorSetup } f
40
40
  import { buildSessionEntries } from '../status/builders.js';
41
41
  import { registerMeshCoordinator, getCoordinatorForSession } from '../mesh/coordinator-registry.js';
42
42
  import { handleMeshForwardEvent, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent, type PendingMeshCoordinatorEvent } from '../mesh/mesh-events.js';
43
+ import { getRecentUnroutableDeliveries } from '../mesh/mesh-routing.js';
43
44
  import { buildMeshHostRequiredFailure, normalizeMeshDaemonRole, resolveMeshHostStatus } from '../mesh/mesh-host-ownership.js';
44
45
  import { fastForwardMeshNode } from '../mesh/mesh-fast-forward.js';
45
46
  import { buildPreviewFreshness } from '../mesh/preview-freshness.js';
@@ -3513,7 +3514,8 @@ export class DaemonCommandRouter {
3513
3514
  private async executeMeshRefineNodeSynchronously(meshId: string, nodeId: string, args: any): Promise<CommandRouterResult> {
3514
3515
  const refineStages: Array<Record<string, unknown>> = [];
3515
3516
  try {
3516
- const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
3517
+ // preferInline: same as startMeshRefineJob — inline-cache-only clone nodes must resolve.
3518
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
3517
3519
  const mesh = meshRecord?.mesh;
3518
3520
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
3519
3521
  if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh`, refineStages };
@@ -4246,7 +4248,10 @@ export class DaemonCommandRouter {
4246
4248
  if (running) return { ...running, duplicate: true };
4247
4249
  const terminal = this.terminalRefineJobs.get(key);
4248
4250
 
4249
- const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
4251
+ // preferInline so inline-cache-only clone worktree nodes resolve — same
4252
+ // membership authority as clone_mesh_node / get_mesh. Without it refine reads
4253
+ // config-first and misses nodes that only live in the inline cache.
4254
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
4250
4255
  const mesh = meshRecord?.mesh;
4251
4256
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
4252
4257
  if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
@@ -5802,7 +5807,8 @@ export class DaemonCommandRouter {
5802
5807
  const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node removal');
5803
5808
  if (ownerFailure) return ownerFailure;
5804
5809
  try {
5805
- const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
5810
+ // preferInline so inline-cache-only clone nodes resolve (matches owner check above).
5811
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
5806
5812
  const mesh = meshRecord?.mesh;
5807
5813
  if (!mesh) return { success: false, error: 'Mesh not found' };
5808
5814
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
@@ -5868,7 +5874,8 @@ export class DaemonCommandRouter {
5868
5874
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
5869
5875
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
5870
5876
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
5871
- const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
5877
+ // preferInline: plan is the dry-run sibling of refine — clone nodes must resolve.
5878
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
5872
5879
  const mesh = meshRecord?.mesh;
5873
5880
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
5874
5881
  if (!node?.workspace) return { success: false, error: `Node '${nodeId}' workspace not found` };
@@ -5892,7 +5899,8 @@ export class DaemonCommandRouter {
5892
5899
  : undefined;
5893
5900
  let nodeDaemonId: string | undefined;
5894
5901
  if (meshId && nodeId) {
5895
- const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
5902
+ // preferInline so fast-forward can resolve inline-cache-only clone worktree nodes.
5903
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
5896
5904
  const mesh = meshRecord?.mesh;
5897
5905
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
5898
5906
  if (!workspace) {
@@ -5941,7 +5949,8 @@ export class DaemonCommandRouter {
5941
5949
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
5942
5950
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
5943
5951
  try {
5944
- const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
5952
+ // preferInline so removal can resolve inline-cache-only clone worktree nodes.
5953
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
5945
5954
  const mesh = meshRecord?.mesh;
5946
5955
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
5947
5956
 
@@ -6326,7 +6335,8 @@ export class DaemonCommandRouter {
6326
6335
  if (ownerFailure) return ownerFailure;
6327
6336
 
6328
6337
  try {
6329
- const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
6338
+ // preferInline so bootstrap-retry can resolve inline-cache-only clone worktree nodes.
6339
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
6330
6340
  const mesh = meshRecord?.mesh;
6331
6341
  if (!mesh) return { success: false, error: 'Mesh not found' };
6332
6342
 
@@ -7364,6 +7374,10 @@ export class DaemonCommandRouter {
7364
7374
  ? args.coordinatorDaemonId.trim()
7365
7375
  : (this.deps.statusInstanceId || undefined);
7366
7376
  const pendingCoordinatorEvents = getPendingMeshCoordinatorEvents(meshId, callerCoordinatorDaemonId);
7377
+ // R4: surface recent fail-loud routing drops so a coordinator/operator can see
7378
+ // that a worker completion was lost (envelope present, mesh unresolved) instead
7379
+ // of it vanishing silently. Diagnostic-only — never cached (see omit below).
7380
+ const unroutableDeliveries = getRecentUnroutableDeliveries();
7367
7381
  const previewFreshness = (() => {
7368
7382
  const localRepoRoot = nodeStatuses
7369
7383
  .map((node: any) => readStringValue(node?.git?.repoRoot, node?.repoRoot, node?.workspace))
@@ -7425,6 +7439,7 @@ export class DaemonCommandRouter {
7425
7439
  ...(asyncRefineJobs.length > 0 ? { asyncRefineJobs } : {}),
7426
7440
  ...(historicalSessions ? { historicalSessions } : {}),
7427
7441
  ...(pendingCoordinatorEvents.length > 0 ? { pendingCoordinatorEvents } : {}),
7442
+ ...(unroutableDeliveries.length > 0 ? { unroutableDeliveries } : {}),
7428
7443
  activeRefineJobs: Array.from(this.runningRefineJobs.values())
7429
7444
  .filter(job => job.meshId === meshId)
7430
7445
  .map(job => ({
@@ -7436,11 +7451,13 @@ export class DaemonCommandRouter {
7436
7451
  targetCoordinatorDaemonId: job.targetCoordinatorDaemonId,
7437
7452
  })),
7438
7453
  };
7439
- const { pendingCoordinatorEvents: _pendingCoordinatorEvents, ...cacheableStatusResult } = statusResult as any;
7454
+ const { pendingCoordinatorEvents: _pendingCoordinatorEvents, unroutableDeliveries: _unroutableDeliveries, ...cacheableStatusResult } = statusResult as any;
7440
7455
  const rememberedStatus = this.rememberAggregateMeshStatus(meshId, cacheableStatusResult, refreshReason);
7441
- const returnedStatus = pendingCoordinatorEvents.length > 0
7442
- ? { ...rememberedStatus, pendingCoordinatorEvents }
7443
- : rememberedStatus;
7456
+ const returnedStatus = {
7457
+ ...rememberedStatus,
7458
+ ...(pendingCoordinatorEvents.length > 0 ? { pendingCoordinatorEvents } : {}),
7459
+ ...(unroutableDeliveries.length > 0 ? { unroutableDeliveries } : {}),
7460
+ };
7444
7461
  logRepoMeshStatusDebug('return_live', {
7445
7462
  meshId,
7446
7463
  command: 'mesh_status',
@@ -10,8 +10,9 @@ 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 } from './mesh-events-pending.js';
13
+ import { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents, markMeshCoordinatorEventDirectDelivered } from './mesh-events-pending.js';
14
14
  import type { PendingMeshCoordinatorEvent } from './mesh-events-pending.js';
15
+ import { resolveWorkerDelegateRouting, recordUnroutableDelegateEvent } from './mesh-routing.js';
15
16
  import {
16
17
  findRecentTerminalLedgerEvidence,
17
18
  hasDispatchAfterTerminal,
@@ -25,6 +26,7 @@ import {
25
26
  resolveEventSessionId,
26
27
  readRefineJobId,
27
28
  readWorkerResultMetadata,
29
+ sameDaemonId,
28
30
  } from './mesh-events-utils.js';
29
31
 
30
32
  // ---------------------------------------------------------------------------
@@ -53,6 +55,10 @@ export function __resetIdleAutoFastForwardForTests(): void {
53
55
  idleAutoFastForwardLastAttempt.clear();
54
56
  }
55
57
 
58
+ export function __resetMeshWorkspaceCacheForTests(): void {
59
+ meshByWorkspaceCache.clear();
60
+ }
61
+
56
62
  function sweepExpiredRemoteIdleSessions(): void {
57
63
  try {
58
64
  MeshRuntimeStore.getInstance().pruneExpiredRemoteIdleSessions();
@@ -980,6 +986,22 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
980
986
  (sourceSession?.getState()?.settings as Record<string, unknown>)?.meshCoordinatorDaemonId,
981
987
  );
982
988
  const localDaemonId = readNonEmptyString(loadConfig().machineId);
989
+
990
+ // R2: cloud P2P dashboard metadata sync. The cloud daemon used to do this from its own
991
+ // relay listener; now the single core forwarder invokes the injected hook (no-op on
992
+ // standalone) so the event path stays single-listener and the local code path is identical
993
+ // across standalone and cloud.
994
+ if (components.onMeshCoordinatorEventForwarded) {
995
+ try {
996
+ components.onMeshCoordinatorEventForwarded({
997
+ event: args.event,
998
+ meshId: args.meshId,
999
+ nodeId: eventNodeId || undefined,
1000
+ ...args.metadataEvent,
1001
+ });
1002
+ } catch { /* dashboard metadata sync is best-effort */ }
1003
+ }
1004
+
983
1005
  const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
984
1006
  event: args.event,
985
1007
  meshId: args.meshId,
@@ -1349,11 +1371,52 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1349
1371
  const instState = inst.getState();
1350
1372
  if (instState.settings?.meshCoordinatorFor !== args.meshId) return false;
1351
1373
  if (args.sourceInstanceId && instState.instanceId === args.sourceInstanceId) return false;
1352
- if (workerCoordinatorDaemonId && localDaemonId && workerCoordinatorDaemonId !== localDaemonId) return false;
1374
+ // canonicalize: the worker stamps the prefixed daemon id (standalone_/daemon_) while
1375
+ // localDaemonId is the raw machineId — a literal !== wrongly excludes the local coordinator.
1376
+ if (workerCoordinatorDaemonId && localDaemonId && !sameDaemonId(workerCoordinatorDaemonId, localDaemonId)) return false;
1353
1377
  return true;
1354
1378
  });
1355
1379
 
1356
1380
  if (coordinatorInstances.length === 0) {
1381
+ // R2: the coordinator is not a live instance on THIS daemon. If it lives on a remote
1382
+ // daemon, forward over the injected transport (dispatchMeshCommand, supplied by cloud as
1383
+ // P2P; absent/no-op on standalone). This replaces cloud's separate
1384
+ // relayRemoteMeshCoordinatorEvent listener — remote delivery is now part of the single
1385
+ // core forwarder path, so local-vs-remote is one code path and standalone == cloud for
1386
+ // the local case. The pending queue remains the fallback when there is no remote target
1387
+ // or the transport send fails (e.g. P2P down), so MCP coordinators can still backfill.
1388
+ const remoteCoordinatorDaemonId = workerCoordinatorDaemonId && localDaemonId && !sameDaemonId(workerCoordinatorDaemonId, localDaemonId)
1389
+ ? workerCoordinatorDaemonId
1390
+ : '';
1391
+ if (remoteCoordinatorDaemonId && components.dispatchMeshCommand) {
1392
+ const forwardPayload: Record<string, unknown> = {
1393
+ event: args.event,
1394
+ meshId: args.meshId,
1395
+ nodeId: args.nodeId || undefined,
1396
+ workspace: readNonEmptyString(args.metadataEvent.workspace),
1397
+ ...args.metadataEvent,
1398
+ ...(recoveryContext ? { recoveryContext } : {}),
1399
+ };
1400
+ components.dispatchMeshCommand(remoteCoordinatorDaemonId, 'mesh_forward_event', forwardPayload)
1401
+ .then(() => {
1402
+ LOG.info('MeshEvents', `Forwarded ${args.event} for mesh ${args.meshId} to remote coordinator daemon ${remoteCoordinatorDaemonId.slice(0, 12)}…`);
1403
+ })
1404
+ .catch((error: any) => {
1405
+ LOG.warn('MeshEvents', `Remote forward of ${args.event} failed (${error?.message || error}); queuing for backfill`);
1406
+ queuePendingMeshCoordinatorEvent({
1407
+ event: args.event,
1408
+ meshId: args.meshId,
1409
+ nodeLabel: args.nodeLabel,
1410
+ nodeId: args.nodeId || undefined,
1411
+ workspace: readNonEmptyString(args.metadataEvent.workspace),
1412
+ metadataEvent: { ...args.metadataEvent, ...(recoveryContext ? { recoveryContext } : {}) },
1413
+ coordinatorMessage: messageText,
1414
+ queuedAt: Date.now(),
1415
+ targetCoordinatorDaemonId: remoteCoordinatorDaemonId,
1416
+ });
1417
+ });
1418
+ return { success: true, forwarded: 0, remoteForwarded: true };
1419
+ }
1357
1420
  if (queuePendingMeshCoordinatorEvent({
1358
1421
  event: args.event,
1359
1422
  meshId: args.meshId,
@@ -1380,23 +1443,35 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
1380
1443
  // Previously, terminal events skipped the queue and were only direct-injected
1381
1444
  // into live CLI coordinators via send_message, which silently dropped them
1382
1445
  // when the coordinator was in a generating state.
1383
- if (queuePendingMeshCoordinatorEvent({
1384
- event: args.event,
1385
- meshId: args.meshId,
1386
- nodeLabel: args.nodeLabel,
1387
- nodeId: args.nodeId || undefined,
1388
- workspace: readNonEmptyString(args.metadataEvent.workspace),
1389
- metadataEvent: {
1390
- ...args.metadataEvent,
1391
- ...(recoveryContext ? { recoveryContext } : {}),
1392
- },
1393
- coordinatorMessage: messageText,
1394
- queuedAt: Date.now(),
1395
- ...(workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {}),
1396
- })) {
1446
+ const pendingEvent = {
1447
+ event: args.event,
1448
+ meshId: args.meshId,
1449
+ nodeLabel: args.nodeLabel,
1450
+ nodeId: args.nodeId || undefined,
1451
+ workspace: readNonEmptyString(args.metadataEvent.workspace),
1452
+ metadataEvent: {
1453
+ ...args.metadataEvent,
1454
+ ...(recoveryContext ? { recoveryContext } : {}),
1455
+ },
1456
+ coordinatorMessage: messageText,
1457
+ queuedAt: Date.now(),
1458
+ ...(workerCoordinatorDaemonId ? { targetCoordinatorDaemonId: workerCoordinatorDaemonId } : {}),
1459
+ };
1460
+ if (queuePendingMeshCoordinatorEvent(pendingEvent)) {
1397
1461
  LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId})`);
1398
1462
  }
1399
1463
 
1464
+ // R3: the live coordinators below receive this event directly in their PTY. They (or their
1465
+ // MCP client) also poll get_pending_mesh_events with coordinatorDaemonId = this daemon, which
1466
+ // would re-deliver the queued copy → the user saw the same completion twice. Mark the event
1467
+ // direct-delivered to THIS daemon so that coordinator's own drain skips it. The queue entry
1468
+ // stays for every OTHER consumer (idle / MCP-only / remote) that did not get the direct inject.
1469
+ // markMeshCoordinatorEventDirectDelivered canonicalizes the id internally so the raw
1470
+ // machineId here matches the prefixed instanceId (standalone_mach_X) the coordinator drains with.
1471
+ if (localDaemonId) {
1472
+ markMeshCoordinatorEventDirectDelivered(localDaemonId, pendingEvent);
1473
+ }
1474
+
1400
1475
  const forceInject = shouldForceInjectMeshEvent(args.event);
1401
1476
  for (const coord of coordinatorInstances) {
1402
1477
  const coordState = coord.getState();
@@ -1520,63 +1595,29 @@ export function setupMeshEventForwarding(components: DaemonComponents) {
1520
1595
  const instanceId = readNonEmptyString(event.instanceId);
1521
1596
  if (!instanceId) return;
1522
1597
 
1523
- const sourceInstance = components.instanceManager.getInstance(instanceId);
1524
- if (!sourceInstance || sourceInstance.category !== 'cli') return;
1525
- const state = sourceInstance.getState();
1526
- const workspace = readNonEmptyString(state.workspace);
1527
- if (!workspace) return;
1528
- const settings = state.settings && typeof state.settings === 'object' ? state.settings as Record<string, unknown> : {};
1529
-
1530
- const coordinatorMeshId = readNonEmptyString(settings.meshCoordinatorFor);
1531
- let meshIdFromDirectDispatch = '';
1532
- if (coordinatorMeshId) {
1533
- try {
1534
- const hasActiveDispatch =
1535
- getActiveDirectDispatches(coordinatorMeshId).some(d => d.sessionId === instanceId)
1536
- || hasUnterminalDirectDispatchLedgerEntry(coordinatorMeshId, instanceId);
1537
- if (hasActiveDispatch) meshIdFromDirectDispatch = coordinatorMeshId;
1538
- } catch { /* best-effort */ }
1539
- if (!meshIdFromDirectDispatch) return;
1598
+ // R1: all session→node→mesh→coordinator interpretation is folded into the single
1599
+ // resolveWorkerDelegateRouting() resolver. No stamp (meshNodeFor / meshNodeId /
1600
+ // meshCoordinatorDaemonId / meshCoordinatorNodeId / launchedByCoordinator) is read
1601
+ // here to make a routing decision — the resolver is the one authority, and the
1602
+ // forwarder consumes its typed result only.
1603
+ const routing = resolveWorkerDelegateRouting(components, instanceId, {
1604
+ getMeshById: (meshId) => getMeshWithCache(components, meshId),
1605
+ getMeshByWorkspace: (workspace) => getCachedMeshByWorkspace(workspace),
1606
+ });
1607
+ if (!routing.isDelegate) {
1608
+ // R4: a worker that presented a valid envelope but resolved to no mesh used to be
1609
+ // dropped silently. Leave a fail-loud diagnostic so the missing completion is
1610
+ // traceable. Benign non-delegate rejections (not_cli / no_workspace / etc.) are
1611
+ // no-ops inside recordUnroutableDelegateEvent.
1612
+ recordUnroutableDelegateEvent(routing, event.event);
1613
+ return;
1540
1614
  }
1541
1615
 
1542
- const meshIdFromRuntime = readNonEmptyString(settings.meshNodeFor) || meshIdFromDirectDispatch;
1543
-
1544
- // A mesh worker must be recognised as a delegate even if its meshNodeFor stamp is
1545
- // missing. Worker launch envelopes are assembled from several settings, and in
1546
- // practice a worker can arrive carrying only meshCoordinatorDaemonId (the routing
1547
- // anchor) without meshNodeFor — e.g. when the node/mesh stamp was dropped on a
1548
- // direct dispatch or a relaunch. Gating delegate routing solely on meshNodeFor then
1549
- // silently drops the completion: setupMeshEventForwarding returns here, the
1550
- // coordinator filter can't resolve args.meshId, and the event only lands in the
1551
- // pending queue that an idle/generating coordinator never drains. Treat any of the
1552
- // worker-envelope markers as proof of delegation and recover the mesh id by
1553
- // workspace when the runtime id is absent.
1554
- const hasWorkerEnvelope = Boolean(
1555
- meshIdFromRuntime
1556
- || settings.launchedByCoordinator
1557
- || readNonEmptyString(settings.meshCoordinatorDaemonId)
1558
- || readNonEmptyString(settings.meshCoordinatorNodeId),
1559
- );
1560
- if (!hasWorkerEnvelope) return;
1561
-
1562
- const mesh = meshIdFromRuntime ? getMeshWithCache(components, meshIdFromRuntime) : getCachedMeshByWorkspace(workspace);
1563
- const meshId = meshIdFromRuntime || readNonEmptyString(mesh?.id);
1564
- if (!meshId) return;
1565
-
1566
- const targetNode = mesh?.nodes?.find((n: any) => n.workspace === workspace);
1567
- const runtimeNodeId = readNonEmptyString(settings.meshNodeId);
1568
- const resolvedNodeId = targetNode?.id || runtimeNodeId;
1569
- const nodeLabel = targetNode
1570
- ? `Node '${targetNode.id}'`
1571
- : runtimeNodeId
1572
- ? `Node '${runtimeNodeId}'`
1573
- : `Agent at ${workspace}`;
1574
-
1575
1616
  injectMeshSystemMessage(components, {
1576
- meshId,
1617
+ meshId: routing.meshId,
1577
1618
  sourceInstanceId: instanceId,
1578
- nodeId: resolvedNodeId,
1579
- nodeLabel,
1619
+ nodeId: routing.nodeId,
1620
+ nodeLabel: routing.nodeLabel,
1580
1621
  event: event.event,
1581
1622
  metadataEvent: event,
1582
1623
  });