@adhdev/daemon-core 0.9.82-rc.519 → 0.9.82-rc.520

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.
@@ -81,6 +81,32 @@ export declare function normalizeInlineMeshNodeIdentity(inlineMesh: any): any;
81
81
  export declare function sanitizeInlineMesh(inlineMesh: any): any;
82
82
  export declare function reconcileInlineMeshCache(cached: any, incoming: any): any;
83
83
  export declare function deriveMeshNodeHealthFromGit(git: Record<string, unknown> | null | undefined): 'online' | 'dirty' | 'degraded';
84
+ /**
85
+ * Resolve a node's EFFECTIVE health from whatever telemetry the node object carries,
86
+ * in the same precedence the coordinator surfaces use (applyCachedInlineMeshNodeStatus):
87
+ * 1. an explicit `node.health` scalar (set by a fresh mesh_status probe / status report),
88
+ * 2. else the cached inline status health (`node.cachedStatus.health`),
89
+ * 3. else derived from the node's git telemetry (`node.git` / cachedStatus.git) via
90
+ * deriveMeshNodeHealthFromGit,
91
+ * 4. else 'unknown' (no telemetry — cannot prove unhealthy).
92
+ *
93
+ * This is the SINGLE source of truth for "what is this node's health right now" shared by
94
+ * the auto-launch gate (isMeshNodeHealthLaunchable → isLaunchableNode) and the MAGI fan-out
95
+ * planner, so the two never disagree about whether a degraded node is a viable target.
96
+ * Returns a lowercased string (empty string is normalized to 'unknown').
97
+ */
98
+ export declare function resolveEffectiveMeshNodeHealth(node: any): string;
99
+ /**
100
+ * Whether a node's health permits launching / assigning a fresh worker session onto it.
101
+ * Mirrors the auto-launch gate in mesh-queue-assignment.isLaunchableNode: 'online' and
102
+ * 'unknown' (and an absent/empty health, treated as unknown) pass — we never block on
103
+ * missing telemetry; every other resolved health ('degraded', 'offline', 'dirty',
104
+ * 'wrong_branch') is NOT launchable. A task assigned to a non-launchable node parks in
105
+ * `pending` forever (isLaunchableNode skips it → node_not_launch_ready) with no
106
+ * re-assignment, so the MAGI planner must exclude such nodes UP FRONT rather than emit a
107
+ * replica that can never run.
108
+ */
109
+ export declare function isMeshNodeHealthLaunchable(node: any): boolean;
84
110
  export declare function applyInlineMeshBranchConvergence(mesh: any, node: any, status: Record<string, unknown>): void;
85
111
  export declare function summarizeInlineMeshBranchConvergence(nodes: Array<Record<string, unknown>>): Record<string, unknown>;
86
112
  export declare function readCachedInlineMeshActiveSessions(node: any): string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.519",
3
+ "version": "0.9.82-rc.520",
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",
@@ -47,8 +47,8 @@
47
47
  "author": "vilmire",
48
48
  "license": "AGPL-3.0-or-later",
49
49
  "dependencies": {
50
- "@adhdev/mesh-shared": "0.9.82-rc.519",
51
- "@adhdev/session-host-core": "0.9.82-rc.519",
50
+ "@adhdev/mesh-shared": "0.9.82-rc.520",
51
+ "@adhdev/session-host-core": "0.9.82-rc.520",
52
52
  "@agentclientprotocol/sdk": "^0.16.1",
53
53
  "ajv": "^8.20.0",
54
54
  "ajv-formats": "^3.0.1",
package/src/index.ts CHANGED
@@ -277,6 +277,9 @@ export type { AnyLedgerSlice, MeshLedgerReconciliationEvidence, MeshLedgerReplic
277
277
  // ── Mesh Work Queue (GUPP) ──
278
278
  export { enqueueTask, recordDirectDispatchTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats, getMeshQueueRevision, normalizeMeshTaskMode, validateMeshTaskModeRequest, isTaskReadonly, buildMeshNodeCapabilityTags, nodeSatisfiesRequiredTags, normalizeMeshCapabilityTags, resolveConvergeRequiredTags, insertDirectDispatch, getActiveDirectDispatches, updateDirectDispatchStatus, cleanupTerminalDirectDispatches, markStaleDirectDispatches, deleteDirectDispatchesByTaskId, recordMeshToolCall, assertNoDependencyCycle, hasPendingDependents, describeTaskDependencyState, taskDependenciesSatisfied, normalizeMeshTaskPriority, meshTaskPriorityRank, resolveNotBefore, meshTaskNotBeforeReady, MESH_TASK_PRIORITIES, NOT_BEFORE_RELATIVE_THRESHOLD_MS } from './mesh/mesh-work-queue.js';
279
279
  export type { MeshWorkQueueEntry, MeshTaskStatus, MeshTaskMode, MeshTaskPriority, MeshWorkQueueStats, MeshQueueMutationOptions, MeshTaskModeValidationResult, DirectDispatchRecord, MeshToolCallRateResult } from './mesh/mesh-work-queue.js';
280
+ // Shared node-health resolver + launch gate (single source of truth for the auto-launch
281
+ // gate AND the MAGI fan-out planner — they must agree on what "launchable health" means).
282
+ export { deriveMeshNodeHealthFromGit, resolveEffectiveMeshNodeHealth, isMeshNodeHealthLaunchable } from './mesh/mesh-node-identity.js';
280
283
  export { buildCompactStaleDirectWorkSummary, buildMeshActiveWork, buildMeshActiveWorkSummary, collectPendingApprovals, classifyStaleDirectForPrune, pruneStaleDirectDispatches, PRUNABLE_ORPHAN_STALE_REASONS } from './mesh/mesh-active-work.js';
281
284
  export type { StaleDirectPruneClassification, StaleDirectPruneResult, PruneStaleDirectDispatchesOptions } from './mesh/mesh-active-work.js';
282
285
  export type { MeshActiveWorkRecord, MeshActiveWorkStatus, MeshActiveWorkSummary, MeshActiveWorkSource, MeshStaleDirectWorkSummary, MeshPendingApproval } from './mesh/mesh-active-work.js';
@@ -865,6 +865,48 @@ export function deriveMeshNodeHealthFromGit(git: Record<string, unknown> | null
865
865
  return 'online';
866
866
  }
867
867
 
868
+ /**
869
+ * Resolve a node's EFFECTIVE health from whatever telemetry the node object carries,
870
+ * in the same precedence the coordinator surfaces use (applyCachedInlineMeshNodeStatus):
871
+ * 1. an explicit `node.health` scalar (set by a fresh mesh_status probe / status report),
872
+ * 2. else the cached inline status health (`node.cachedStatus.health`),
873
+ * 3. else derived from the node's git telemetry (`node.git` / cachedStatus.git) via
874
+ * deriveMeshNodeHealthFromGit,
875
+ * 4. else 'unknown' (no telemetry — cannot prove unhealthy).
876
+ *
877
+ * This is the SINGLE source of truth for "what is this node's health right now" shared by
878
+ * the auto-launch gate (isMeshNodeHealthLaunchable → isLaunchableNode) and the MAGI fan-out
879
+ * planner, so the two never disagree about whether a degraded node is a viable target.
880
+ * Returns a lowercased string (empty string is normalized to 'unknown').
881
+ */
882
+ export function resolveEffectiveMeshNodeHealth(node: any): string {
883
+ const explicit = (readStringValue(node?.health) ?? '').toLowerCase();
884
+ if (explicit) return explicit;
885
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
886
+ const cachedHealth = (readStringValue(cachedStatus.health) ?? '').toLowerCase();
887
+ if (cachedHealth) return cachedHealth;
888
+ const git = readObjectRecord(node?.git);
889
+ if (Object.keys(git).length > 0) return deriveMeshNodeHealthFromGit(git).toLowerCase();
890
+ const cachedGit = readObjectRecord(cachedStatus.git);
891
+ if (Object.keys(cachedGit).length > 0) return deriveMeshNodeHealthFromGit(cachedGit).toLowerCase();
892
+ return 'unknown';
893
+ }
894
+
895
+ /**
896
+ * Whether a node's health permits launching / assigning a fresh worker session onto it.
897
+ * Mirrors the auto-launch gate in mesh-queue-assignment.isLaunchableNode: 'online' and
898
+ * 'unknown' (and an absent/empty health, treated as unknown) pass — we never block on
899
+ * missing telemetry; every other resolved health ('degraded', 'offline', 'dirty',
900
+ * 'wrong_branch') is NOT launchable. A task assigned to a non-launchable node parks in
901
+ * `pending` forever (isLaunchableNode skips it → node_not_launch_ready) with no
902
+ * re-assignment, so the MAGI planner must exclude such nodes UP FRONT rather than emit a
903
+ * replica that can never run.
904
+ */
905
+ export function isMeshNodeHealthLaunchable(node: any): boolean {
906
+ const health = resolveEffectiveMeshNodeHealth(node);
907
+ return health === 'online' || health === 'unknown';
908
+ }
909
+
868
910
  function readMeshNodeLabel(status: Record<string, unknown>, node: any): string {
869
911
  return readStringValue(status.nodeId, normalizeMeshNodeId(node)) ?? 'unknown';
870
912
  }
@@ -19,7 +19,7 @@ import { normalizeMeshNodeId, meshNodeIdMatches, daemonIdsEquivalent, canonicalD
19
19
  import { resolveNodeCapabilitySlots } from './mesh-node-slots.js';
20
20
  import { findTerminalLedgerEvidenceForTask, hasUnterminalDirectDispatchLedgerEntry } from './mesh-events-stale.js';
21
21
  import { readNonEmptyString } from './mesh-events-utils.js';
22
- import { readMeshNodeDaemonId } from './mesh-node-identity.js';
22
+ import { readMeshNodeDaemonId, isMeshNodeHealthLaunchable } from './mesh-node-identity.js';
23
23
  import { queuePendingMeshCoordinatorEvent, retractPendingDispatchBlockedEvent } from './mesh-events-pending.js';
24
24
  import { isWorktreeBootstrapStaleRunning, shouldDeferDispatchForBootstrap } from './worktree-bootstrap-config.js';
25
25
  import { isWithinCloneBootstrapGrace } from './mesh-clone-grace.js';
@@ -1047,9 +1047,10 @@ function nodeHasActiveMeshWork(components: DaemonComponents, meshId: string, nod
1047
1047
 
1048
1048
  function isLaunchableNode(node: any): boolean {
1049
1049
  if (!node || node.status === 'disabled' || node.status === 'removed') return false;
1050
- const health = readNonEmptyString(node.health).toLowerCase();
1051
- if (!health) return true;
1052
- return health === 'online' || health === 'unknown';
1050
+ // Delegate the health gate to the shared resolver so the auto-launch gate and the
1051
+ // MAGI fan-out planner agree on exactly what "launchable health" means (online /
1052
+ // unknown / absent pass; degraded / offline / dirty / wrong_branch are blocked).
1053
+ return isMeshNodeHealthLaunchable(node);
1053
1054
  }
1054
1055
 
1055
1056
  /** Whether a mesh node's daemon/machine identity resolves to THIS coordinator daemon