@adhdev/daemon-core 0.9.82-rc.303 → 0.9.82-rc.305

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.303",
3
+ "version": "0.9.82-rc.305",
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.303",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.305",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -966,6 +966,26 @@ function finalizeMeshNodeStatus(args: {
966
966
  );
967
967
  }
968
968
 
969
+ // Reads a positive integer timeout (ms) from an env var, clamped to [1s, 120s];
970
+ // falls back to the default when unset or out of range. Lets slow cross-machine
971
+ // peers (e.g. a TURN-relayed Windows daemon whose git_status RTT is 10-18s) be
972
+ // tuned without a rebuild.
973
+ function readMeshTimeoutEnvMs(name: string, defaultMs: number): number {
974
+ const raw = process.env[name]?.trim();
975
+ if (!raw) return defaultMs;
976
+ const parsed = Number.parseInt(raw, 10);
977
+ if (Number.isFinite(parsed) && parsed >= 1_000 && parsed <= 120_000) return parsed;
978
+ return defaultMs;
979
+ }
980
+
981
+ // Direct-peer git_status probe timeout for the dashboard's requireDirectPeerTruth
982
+ // bootstrap. The previous hard-coded 8s/12s were shorter than the real P2P
983
+ // round-trip to slow (often TURN-relayed) peers, so such a node was permanently
984
+ // marked unavailable and blocked the whole mesh graph. Default raised to 25s
985
+ // (still under the P2P REQUEST_TIMEOUT of 30s) and made env-overridable.
986
+ const MESH_DIRECT_PROBE_TIMEOUT_MS = readMeshTimeoutEnvMs('MESH_DIRECT_PROBE_TIMEOUT_MS', 25_000);
987
+ const MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS = readMeshTimeoutEnvMs('MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS', 25_000);
988
+
969
989
  async function probeRemoteMeshGitStatus(args: {
970
990
  dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
971
991
  daemonId: string;
@@ -989,11 +1009,21 @@ async function hydrateInlineMeshDirectTruth(args: {
989
1009
  dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
990
1010
  statusInstanceId?: string;
991
1011
  localMachineId?: string;
1012
+ // Standing-state model: the default (non-refresh) bootstrap load must NOT
1013
+ // fan out a blocking git_status probe to every peer — a single slow
1014
+ // (TURN-relayed) peer would time out and mark the whole mesh unavailable,
1015
+ // blocking the graph. When false, a non-local node is satisfied from its
1016
+ // held standing git truth (lastGit / cachedStatus reflected via mesh
1017
+ // events) and is never pushed to unavailableNodeIds merely because no live
1018
+ // probe was attempted. Only an explicit refresh (probeRemotePeers=true)
1019
+ // performs the fan-out and classifies an unreachable peer as unavailable.
1020
+ probeRemotePeers: boolean;
992
1021
  }): Promise<{
993
1022
  directEvidenceCount: number;
994
1023
  localConfirmedCount: number;
995
1024
  peerAttemptedCount: number;
996
1025
  peerConfirmedCount: number;
1026
+ standingEvidenceCount: number;
997
1027
  unavailableNodeIds: string[];
998
1028
  }> {
999
1029
  const nodes = Array.isArray(args.mesh?.nodes) ? args.mesh.nodes : [];
@@ -1003,6 +1033,7 @@ async function hydrateInlineMeshDirectTruth(args: {
1003
1033
  localConfirmedCount: 0,
1004
1034
  peerAttemptedCount: 0,
1005
1035
  peerConfirmedCount: 0,
1036
+ standingEvidenceCount: 0,
1006
1037
  unavailableNodeIds: [],
1007
1038
  };
1008
1039
  }
@@ -1016,6 +1047,7 @@ async function hydrateInlineMeshDirectTruth(args: {
1016
1047
  let localConfirmedCount = 0;
1017
1048
  let peerAttemptedCount = 0;
1018
1049
  let peerConfirmedCount = 0;
1050
+ let standingEvidenceCount = 0;
1019
1051
  const unavailableNodeIds: string[] = [];
1020
1052
 
1021
1053
  for (const [nodeIndex, node] of nodes.entries()) {
@@ -1046,6 +1078,25 @@ async function hydrateInlineMeshDirectTruth(args: {
1046
1078
  }
1047
1079
  }
1048
1080
 
1081
+ // Standing-state first: a non-local peer's held git truth (reflected
1082
+ // from its self-emitted mesh events into node.lastGit / cachedStatus)
1083
+ // counts as direct evidence without any probe. On the default load this
1084
+ // is the ONLY thing we consult — no fan-out, so one slow peer can't
1085
+ // block the bootstrap.
1086
+ const standingGit = buildInlineMeshTransitGitStatus(node);
1087
+ if (standingGit) {
1088
+ standingEvidenceCount += 1;
1089
+ continue;
1090
+ }
1091
+
1092
+ if (!args.probeRemotePeers) {
1093
+ // Default (non-refresh) load: a peer with no held truth yet is left
1094
+ // pending (the per-node loop marks it gitProbePending and the graph
1095
+ // shows setup inventory for it). It is NOT unavailable — the graph
1096
+ // must still render. An explicit refresh will fan out and freshen it.
1097
+ continue;
1098
+ }
1099
+
1049
1100
  if (!daemonId || !args.dispatchMeshCommand) {
1050
1101
  if (!isSelfNode) unavailableNodeIds.push(nodeId);
1051
1102
  continue;
@@ -1057,7 +1108,7 @@ async function hydrateInlineMeshDirectTruth(args: {
1057
1108
  dispatchMeshCommand: args.dispatchMeshCommand,
1058
1109
  daemonId,
1059
1110
  workspace,
1060
- timeoutMs: 8_000,
1111
+ timeoutMs: MESH_DIRECT_PROBE_TIMEOUT_MS,
1061
1112
  });
1062
1113
  if (remoteGit) {
1063
1114
  recordInlineMeshDirectGitTruth(node, remoteGit, 'selected_coordinator_mesh_p2p_git');
@@ -1072,10 +1123,11 @@ async function hydrateInlineMeshDirectTruth(args: {
1072
1123
  }
1073
1124
 
1074
1125
  return {
1075
- directEvidenceCount: localConfirmedCount + peerConfirmedCount,
1126
+ directEvidenceCount: localConfirmedCount + peerConfirmedCount + standingEvidenceCount,
1076
1127
  localConfirmedCount,
1077
1128
  peerAttemptedCount,
1078
1129
  peerConfirmedCount,
1130
+ standingEvidenceCount,
1079
1131
  unavailableNodeIds,
1080
1132
  };
1081
1133
  }
@@ -6484,12 +6536,16 @@ export class DaemonCommandRouter {
6484
6536
  if (!meshRecord?.mesh) return { success: false, error: 'Mesh not found' };
6485
6537
 
6486
6538
  const requireDirectPeerTruth = args?.requireDirectPeerTruth === true;
6539
+ // Only an explicit refresh fans out a blocking peer probe.
6540
+ // Default loads are satisfied from held standing-state git truth.
6541
+ const probeRemotePeers = args?.refresh === true || args?.forceRefresh === true;
6487
6542
  const directTruth = await hydrateInlineMeshDirectTruth({
6488
6543
  mesh: meshRecord.mesh,
6489
6544
  meshSource: meshRecord.source,
6490
6545
  dispatchMeshCommand: this.deps.dispatchMeshCommand,
6491
6546
  statusInstanceId: this.deps.statusInstanceId,
6492
6547
  localMachineId: loadConfig().machineId || '',
6548
+ probeRemotePeers,
6493
6549
  });
6494
6550
  const directTruthSatisfied = meshRecord.source !== 'inline_bootstrap' || directTruth.directEvidenceCount > 0;
6495
6551
  const sourceOfTruth = {
@@ -8378,12 +8434,17 @@ export class DaemonCommandRouter {
8378
8434
  dispatchMeshCommand: this.deps.dispatchMeshCommand,
8379
8435
  statusInstanceId: this.deps.statusInstanceId,
8380
8436
  localMachineId,
8437
+ // Standing-state model: only an explicit refresh fans
8438
+ // out a blocking peer git probe. Default loads return
8439
+ // held truth so one slow peer can't block the graph.
8440
+ probeRemotePeers: refreshRequested,
8381
8441
  })
8382
8442
  : {
8383
8443
  directEvidenceCount: 0,
8384
8444
  localConfirmedCount: 0,
8385
8445
  peerAttemptedCount: 0,
8386
8446
  peerConfirmedCount: 0,
8447
+ standingEvidenceCount: 0,
8387
8448
  unavailableNodeIds: [] as string[],
8388
8449
  };
8389
8450
  // Default/cached loads may not attempt a remote peer probe yet; do not surface that as
@@ -8401,9 +8462,15 @@ export class DaemonCommandRouter {
8401
8462
  && mesh.nodes
8402
8463
  .filter((node: any) => unavailableDirectTruthNodeIds.has(normalizeMeshNodeId(node) ?? ''))
8403
8464
  .every((node: any) => node?.isLocalWorktree === true);
8465
+ // Default (non-refresh) loads never hard-fail: held
8466
+ // standing-state truth is returned and the graph renders
8467
+ // immediately. The hard mesh_direct_peer_truth_unavailable
8468
+ // failure is reserved for an explicit refresh that actually
8469
+ // attempted a peer probe and could not confirm any evidence.
8404
8470
  const directTruthSatisfied = !requireDirectPeerTruth
8471
+ || !refreshRequested
8405
8472
  || (effectiveDirectTruth.directEvidenceCount > 0 && (effectiveDirectTruth.unavailableNodeIds.length === 0 || unavailableNodesAreOnlyRemovedWorktrees));
8406
- if (requireDirectPeerTruth && !directTruthSatisfied) {
8473
+ if (requireDirectPeerTruth && refreshRequested && !directTruthSatisfied) {
8407
8474
  const failureResult = {
8408
8475
  success: false,
8409
8476
  code: 'mesh_direct_peer_truth_unavailable',
@@ -8568,13 +8635,17 @@ export class DaemonCommandRouter {
8568
8635
  status.connection = buildLivePeerGitConnection(connection, refreshedAt);
8569
8636
  }
8570
8637
  remoteProbeApplied = true;
8571
- } else if (!isSelfNode && daemonId && this.deps.dispatchMeshCommand && !directTruthUnavailableNodeIds.has(nodeId)) {
8638
+ } else if (refreshRequested && !isSelfNode && daemonId && this.deps.dispatchMeshCommand && !directTruthUnavailableNodeIds.has(nodeId)) {
8639
+ // Only an explicit refresh fans out a blocking
8640
+ // per-node git probe. On the default load a peer
8641
+ // with no held truth falls through to
8642
+ // gitProbePending below — the graph still renders.
8572
8643
  try {
8573
8644
  const remoteGit = await probeRemoteMeshGitStatus({
8574
8645
  dispatchMeshCommand: this.deps.dispatchMeshCommand,
8575
8646
  daemonId,
8576
8647
  workspace,
8577
- timeoutMs: 8000,
8648
+ timeoutMs: MESH_DIRECT_PROBE_TIMEOUT_MS,
8578
8649
  });
8579
8650
  if (remoteGit) {
8580
8651
  status.git = remoteGit;
@@ -8600,7 +8671,7 @@ export class DaemonCommandRouter {
8600
8671
  dispatchMeshCommand: this.deps.dispatchMeshCommand,
8601
8672
  daemonId,
8602
8673
  workspace,
8603
- timeoutMs: 12000,
8674
+ timeoutMs: MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS,
8604
8675
  });
8605
8676
  if (remoteGit) {
8606
8677
  status.git = remoteGit;