@adhdev/daemon-core 0.9.82-rc.287 → 0.9.82-rc.288

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.287",
3
+ "version": "0.9.82-rc.288",
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.287",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.288",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -12,7 +12,7 @@ import { createSessionDelivery, markSessionDeliveriesTerminal, updateSessionDeli
12
12
  import { MeshRuntimeStore } from './mesh-runtime-store.js';
13
13
  import { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents } from './mesh-events-pending.js';
14
14
  import type { PendingMeshCoordinatorEvent } from './mesh-events-pending.js';
15
- import { resolveWorkerDelegateRouting, recordUnroutableDelegateEvent } from './mesh-routing.js';
15
+ import { resolveWorkerDelegateRouting, recordUnroutableDelegateEvent, isUnroutableDelegateRejection } from './mesh-routing.js';
16
16
  import { resolveDelegatedWorkerAutoApprove } from '../repo-mesh-types.js';
17
17
  import {
18
18
  findRecentTerminalLedgerEvidence,
@@ -1446,11 +1446,16 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
1446
1446
  if (!isMeshCoordinatorEvent(eventName)) {
1447
1447
  return { success: false, error: 'unsupported mesh event' };
1448
1448
  }
1449
- const meshId = readNonEmptyString(payload.meshId);
1450
- if (!meshId) return { success: false, error: 'meshId required' };
1451
-
1452
1449
  const nodeId = readNonEmptyString(payload.nodeId);
1453
1450
  const workspace = readNonEmptyString(payload.workspace);
1451
+
1452
+ // The fallback worker-forward path (forwardUnresolvedDelegateEvent) cannot resolve a
1453
+ // mesh id locally on the remote worker, so it forwards the event with workspace only.
1454
+ // The coordinator hosting the mesh CAN resolve it: recover the mesh id by workspace
1455
+ // when the payload doesn't carry one.
1456
+ const meshId = readNonEmptyString(payload.meshId)
1457
+ || (workspace ? readNonEmptyString(getCachedMeshByWorkspace(workspace)?.id) : '');
1458
+ if (!meshId) return { success: false, error: 'meshId required' };
1454
1459
  const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : 'Remote agent';
1455
1460
  const relayModalMessage = readNonEmptyString(payload.modalMessage);
1456
1461
  const relayModalButtons = Array.isArray(payload.modalButtons)
@@ -1493,6 +1498,67 @@ export function handleMeshForwardEvent(components: DaemonComponents, payload: Re
1493
1498
  });
1494
1499
  }
1495
1500
 
1501
+ // ---------------------------------------------------------------------------
1502
+ // Worker-side fallback forward for unresolved-mesh delegates.
1503
+ //
1504
+ // A REMOTE worker daemon that is being P2P-remote-controlled by a coordinator is
1505
+ // NOT a member of the coordinator's mesh — it has no local mesh record. So when its
1506
+ // completion event reaches the forwarder, resolveWorkerDelegateRouting() resolves the
1507
+ // coordinator anchor (meshCoordinatorDaemonId) from the worker envelope but cannot
1508
+ // resolve the mesh id (neither meshNodeFor nor a workspace→mesh lookup yields one) and
1509
+ // returns isDelegate=false / mesh_unresolved. Before this fallback the event was dropped
1510
+ // (delivery_unroutable) and only recovered later when the coordinator happened to pull
1511
+ // the worker's queue — which it can't, because the worker never queued an unroutable
1512
+ // event. Live symptom: `WARN [MeshEvents] delivery_unroutable: ... mesh unresolved`.
1513
+ //
1514
+ // The fix: the routing object still carries coordinatorDaemonId. Forward the raw event
1515
+ // straight to that coordinator daemon over P2P (mesh_forward_event). The coordinator
1516
+ // hosts the mesh, so it recovers the mesh id by workspace in handleMeshForwardEvent and
1517
+ // injects/queues it normally. meshId is intentionally omitted from the payload (the
1518
+ // worker has none); workspace is the routing anchor the coordinator resolves from.
1519
+ //
1520
+ // No loop / no double-delivery:
1521
+ // - This only fires on the WORKER (the coordinator-own session is rejected by the
1522
+ // resolver before reaching here), and the coordinator merely injects — it does not
1523
+ // re-enter this forwarder for the relayed event.
1524
+ // - It fires only when the normal queue path did NOT run (isDelegate=false), so the
1525
+ // event is never both queued locally and forwarded.
1526
+ //
1527
+ // Returns true when the event was handed off to the coordinator daemon (so the caller
1528
+ // skips the delivery_unroutable diagnostic); false when no fallback was possible.
1529
+ function forwardUnresolvedDelegateEvent(
1530
+ components: DaemonComponents,
1531
+ routing: ReturnType<typeof resolveWorkerDelegateRouting>,
1532
+ event: Record<string, unknown>,
1533
+ ): boolean {
1534
+ const coordinatorDaemonId = readNonEmptyString(routing.coordinatorDaemonId);
1535
+ if (!coordinatorDaemonId) return false;
1536
+ if (!components.dispatchMeshCommand) return false;
1537
+
1538
+ const eventName = readNonEmptyString(event.event);
1539
+ if (!eventName) return false;
1540
+
1541
+ // Flat payload mirroring buildForwardPayloadFromPending / what handleMeshForwardEvent
1542
+ // reads. meshId is omitted on purpose — the worker can't resolve it; the coordinator
1543
+ // recovers it from workspace. nodeId/workspace come from the worker envelope so the
1544
+ // coordinator can name and locate the node.
1545
+ const payload: Record<string, unknown> = {
1546
+ ...event,
1547
+ event: eventName,
1548
+ nodeId: readNonEmptyString(routing.nodeId) || readNonEmptyString(event.meshNodeId) || undefined,
1549
+ workspace: readNonEmptyString(routing.workspace) || readNonEmptyString(event.workspace) || undefined,
1550
+ };
1551
+
1552
+ Promise.resolve(components.dispatchMeshCommand(coordinatorDaemonId, 'mesh_forward_event', payload))
1553
+ .catch((e: any) => {
1554
+ // The coordinator may be momentarily unreachable; the diagnostic was already
1555
+ // skipped, so leave a trace here so an operator can see the relay attempt failed.
1556
+ LOG.warn('MeshEvents', `Fallback forward of ${eventName} to coordinator ${coordinatorDaemonId} failed: ${e?.message || e}`);
1557
+ });
1558
+ LOG.info('MeshEvents', `Fallback-forwarded ${eventName} for unresolved-mesh worker at ${routing.workspace || '(no workspace)'} to coordinator daemon ${coordinatorDaemonId}`);
1559
+ return true;
1560
+ }
1561
+
1496
1562
  export function setupMeshEventForwarding(components: DaemonComponents) {
1497
1563
  components.instanceManager.onEvent((event) => {
1498
1564
  // --- Coordinator idle auto-flush (fast path) ---
@@ -1572,10 +1638,19 @@ export function setupMeshEventForwarding(components: DaemonComponents) {
1572
1638
  getMeshByWorkspace: (workspace) => getCachedMeshByWorkspace(workspace),
1573
1639
  });
1574
1640
  if (!routing.isDelegate) {
1575
- // R4: a worker that presented a valid envelope but resolved to no mesh used to be
1576
- // dropped silently. Leave a fail-loud diagnostic so the missing completion is
1577
- // traceable. Benign non-delegate rejections (not_cli / no_workspace / etc.) are
1578
- // no-ops inside recordUnroutableDelegateEvent.
1641
+ // Fallback: a REMOTE worker that isn't a member of the coordinator's mesh can't
1642
+ // resolve a mesh id locally (mesh_unresolved), but it still carries the coordinator
1643
+ // daemon anchor. Forward the event straight to that coordinator over P2P instead of
1644
+ // dropping it — the coordinator hosts the mesh and recovers the id by workspace.
1645
+ if (isUnroutableDelegateRejection(routing)
1646
+ && forwardUnresolvedDelegateEvent(components, routing, event)) {
1647
+ return;
1648
+ }
1649
+ // R4: a worker that presented a valid envelope but resolved to no mesh (and could
1650
+ // not be fallback-forwarded — e.g. no coordinator anchor) used to be dropped
1651
+ // silently. Leave a fail-loud diagnostic so the missing completion is traceable.
1652
+ // Benign non-delegate rejections (not_cli / no_workspace / etc.) are no-ops inside
1653
+ // recordUnroutableDelegateEvent.
1579
1654
  recordUnroutableDelegateEvent(routing, event.event);
1580
1655
  return;
1581
1656
  }
@@ -82,10 +82,13 @@ export function resolveWorkerDelegateRouting(
82
82
  const sessionId = readNonEmptyString(instanceId);
83
83
  let workspace = '';
84
84
  let coordinatorDaemonId = '';
85
+ // Runtime node-id stamp, surfaced even on rejection so the unresolved-mesh fallback
86
+ // forward can name the worker node for the coordinator.
87
+ let runtimeNodeId = '';
85
88
  const reject = (rejectionReason: WorkerDelegateRejectionReason): WorkerDelegateRouting => ({
86
89
  isDelegate: false,
87
90
  meshId: '',
88
- nodeId: '',
91
+ nodeId: runtimeNodeId,
89
92
  nodeLabel: '',
90
93
  coordinatorDaemonId,
91
94
  workspace,
@@ -102,6 +105,7 @@ export function resolveWorkerDelegateRouting(
102
105
 
103
106
  const settings = readSettings(state);
104
107
  coordinatorDaemonId = readNonEmptyString(settings.meshCoordinatorDaemonId);
108
+ runtimeNodeId = readNonEmptyString(settings.meshNodeId);
105
109
 
106
110
  // A coordinator session (meshCoordinatorFor set) is only treated as a worker delegate
107
111
  // when it is itself the target of an active direct dispatch — otherwise its own events
@@ -138,7 +142,6 @@ export function resolveWorkerDelegateRouting(
138
142
  if (!meshId) return reject('mesh_unresolved');
139
143
 
140
144
  const targetNode = mesh?.nodes?.find((n: any) => n.workspace === workspace);
141
- const runtimeNodeId = readNonEmptyString(settings.meshNodeId);
142
145
  const nodeId = readNonEmptyString(targetNode?.id) || runtimeNodeId;
143
146
  const nodeLabel = targetNode
144
147
  ? `Node '${targetNode.id}'`