@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/dist/index.js +40 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +40 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/commands/router.ts +77 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
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.
|
|
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",
|
package/src/commands/router.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
8674
|
+
timeoutMs: MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS,
|
|
8604
8675
|
});
|
|
8605
8676
|
if (remoteGit) {
|
|
8606
8677
|
status.git = remoteGit;
|