@adhdev/daemon-core 0.9.82-rc.515 → 0.9.82-rc.516
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/config/mesh-config.d.ts +6 -4
- package/dist/git/git-commands.d.ts +0 -9
- package/dist/index.js +23 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -50
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-node-slots.d.ts +11 -27
- package/dist/repo-mesh-types.d.ts +20 -31
- package/package.json +3 -3
- package/src/boot/daemon-lifecycle.ts +15 -14
- package/src/config/mesh-config.ts +6 -4
- package/src/git/git-commands.ts +12 -21
- package/src/mesh/mesh-node-identity.ts +26 -25
- package/src/mesh/mesh-node-slots.ts +12 -54
- package/src/repo-mesh-types.ts +20 -31
|
@@ -13,37 +13,21 @@ import { type NodeCapabilitySlot } from '@adhdev/mesh-shared';
|
|
|
13
13
|
/** Ordered, de-duplicated providerPriority from a node policy (defensive). */
|
|
14
14
|
export declare function normalizeProviderPriority(policy: unknown): string[];
|
|
15
15
|
/**
|
|
16
|
-
* Resolve a node's capability slots
|
|
17
|
-
* (REMOTE-NODE-
|
|
16
|
+
* Resolve a node's capability slots — coordinator-owned config is the single source
|
|
17
|
+
* of truth for ALL nodes (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix).
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* — the reason remote slot-cap chips were missing.
|
|
29
|
-
* 3. Legacy-derived from `providerPriority` + machine-global difficultyBrains.
|
|
19
|
+
* The coordinator's local meshes.json owns `policy.slots` for every node in the mesh
|
|
20
|
+
* (self AND remote members alike — a remote member's mesh config lives on the
|
|
21
|
+
* coordinator, its own on-disk meshes.json is empty). So slots resolve directly from
|
|
22
|
+
* the coordinator's locally-owned `node.policy.slots`, with no remote reporter
|
|
23
|
+
* round-trip: there is no per-node "reported slots" mirror to consult.
|
|
24
|
+
*
|
|
25
|
+
* Precedence:
|
|
26
|
+
* 1. `policy.slots` — coordinator-owned, authoritative for self and remote alike.
|
|
27
|
+
* 2. Legacy-derived from `providerPriority` + machine-global difficultyBrains.
|
|
30
28
|
*
|
|
31
|
-
* The mirror only ever fills the gap left by an empty local policy — it can never
|
|
32
|
-
* shadow a populated local policy — so there is no inversion. And a self node never
|
|
33
|
-
* carries a reportedMemberState (the ingest path stamps it ONLY for remote
|
|
34
|
-
* members), so a self node's local policy is always used directly.
|
|
35
29
|
* (The former per-provider `providerRoles` cap has been removed; a persisted
|
|
36
30
|
* meshes.json is migrated to slots on load, so by the time a node reaches routing
|
|
37
31
|
* its cap already lives on `slots[].maxParallel`.)
|
|
38
32
|
*/
|
|
39
33
|
export declare function resolveNodeCapabilitySlots(node: any): NodeCapabilitySlot[];
|
|
40
|
-
/**
|
|
41
|
-
* Reporter-side helper (Direction B): resolve THIS daemon's own capability slots for
|
|
42
|
-
* a given workspace by finding the local meshes.json node whose workspace matches
|
|
43
|
-
* and running the same {@link resolveNodeCapabilitySlots} over its real local policy.
|
|
44
|
-
* Used at the git_status reporter wiring so a member node self-reports its own
|
|
45
|
-
* resolved slots inside the unified reporterMemberState envelope. Returns [] when no
|
|
46
|
-
* local node matches the workspace or slots can't be resolved (best-effort, never
|
|
47
|
-
* throws) so the reporter simply omits the field.
|
|
48
|
-
*/
|
|
49
|
-
export declare function resolveLocalNodeSlotsForWorkspace(workspace: string | undefined): NodeCapabilitySlot[];
|
|
@@ -301,35 +301,28 @@ export interface RepoMeshRelatedRepo {
|
|
|
301
301
|
* (see migrateLoadedMeshConfig).
|
|
302
302
|
*/
|
|
303
303
|
/**
|
|
304
|
-
*
|
|
304
|
+
* Unified mirrored member state — the per-machine RUNTIME facts a remote member
|
|
305
|
+
* self-reports (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix).
|
|
305
306
|
*
|
|
306
307
|
* A single self-reported observability envelope stamped by the daemon that OWNS a
|
|
307
308
|
* node's workspace and carried wholesale on the git_status envelope
|
|
308
|
-
* (reporterMemberState),
|
|
309
|
-
* providerVersions and daemonBuildVersion
|
|
310
|
-
*
|
|
309
|
+
* (reporterMemberState), consolidating the previously per-field self-heal of
|
|
310
|
+
* providerVersions and daemonBuildVersion. The coordinator ingests this whole object
|
|
311
|
+
* onto the mirrored remote node in one place
|
|
311
312
|
* (mesh-node-identity.recordInlineMeshDirectGitTruth).
|
|
312
313
|
*
|
|
313
|
-
* Source-of-truth model:
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
* resolveNodeCapabilitySlots)
|
|
314
|
+
* Source-of-truth model: provider VERSIONS + daemon BUILD are per-machine runtime
|
|
315
|
+
* facts (e.g. a member has claude-cli@2.1.168 while the coordinator has @2.1.170), so
|
|
316
|
+
* they are reported here. SLOTS are NOT reported — they are coordinator-owned config
|
|
317
|
+
* (node.policy.slots for every node, self and remote alike; see
|
|
318
|
+
* resolveNodeCapabilitySlots), a single source of truth on the coordinator with no
|
|
319
|
+
* reporter round-trip.
|
|
318
320
|
*/
|
|
319
321
|
export interface MeshReportedMemberState {
|
|
320
322
|
/** Reporter node's detected provider CLI/ACP versions (keyed by provider id). */
|
|
321
323
|
providerVersions?: Record<string, string>;
|
|
322
324
|
/** Reporter daemon's build version (getDaemonBuildInfo().version). */
|
|
323
325
|
daemonBuildVersion?: string;
|
|
324
|
-
/**
|
|
325
|
-
* Reporter node's OWN resolved capability slots (resolveNodeCapabilitySlots over
|
|
326
|
-
* its LOCAL real policy). This is the field the coordinator's join-time policy
|
|
327
|
-
* copy lacks for a remote member, and the reason remote slot-cap chips were
|
|
328
|
-
* missing. For a remote mirrored node this is authoritative over the (empty)
|
|
329
|
-
* local policy.slots; for the coordinator's own node the local policy is used
|
|
330
|
-
* directly (never mirrored here).
|
|
331
|
-
*/
|
|
332
|
-
slots?: NodeCapabilitySlot[];
|
|
333
326
|
/** Epoch ms the report was stamped; used for mirror staleness reasoning. */
|
|
334
327
|
lastReportedAt?: number;
|
|
335
328
|
}
|
|
@@ -670,19 +663,15 @@ export interface LocalMeshNodeEntry {
|
|
|
670
663
|
/**
|
|
671
664
|
* Unified mirrored "member state" self-reported by the daemon that owns this
|
|
672
665
|
* node's workspace, carried wholesale on the git_status envelope
|
|
673
|
-
* (reporterMemberState) and ingested by the coordinator in one place.
|
|
674
|
-
* the
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
*
|
|
682
|
-
* reporter node's own resolved capability slots (resolveNodeCapabilitySlots over
|
|
683
|
-
* its LOCAL real policy). `lastReportedAt` is the epoch ms the report was
|
|
684
|
-
* stamped, so a partitioned node's mirror does not linger as fresh forever.
|
|
685
|
-
* Absent until the first envelope carrying it is ingested.
|
|
666
|
+
* (reporterMemberState) and ingested by the coordinator in one place. It carries
|
|
667
|
+
* the per-machine RUNTIME facts (reportedProviderVersions, reportedDaemonBuildVersion)
|
|
668
|
+
* consolidated into one envelope — NOT slots, which are coordinator-owned config
|
|
669
|
+
* resolved from node.policy.slots for every node, not reported
|
|
670
|
+
* (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix). The legacy flat fields above stay
|
|
671
|
+
* populated in parallel during rollout for back-compat (see mesh-node-identity
|
|
672
|
+
* ingest). `lastReportedAt` is the epoch ms the report was stamped, so a
|
|
673
|
+
* partitioned node's mirror does not linger as fresh forever. Absent until the
|
|
674
|
+
* first envelope carrying it is ingested.
|
|
686
675
|
*/
|
|
687
676
|
reportedMemberState?: MeshReportedMemberState;
|
|
688
677
|
/**
|
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.516",
|
|
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.
|
|
51
|
-
"@adhdev/session-host-core": "0.9.82-rc.
|
|
50
|
+
"@adhdev/mesh-shared": "0.9.82-rc.516",
|
|
51
|
+
"@adhdev/session-host-core": "0.9.82-rc.516",
|
|
52
52
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
@@ -37,7 +37,6 @@ import { loadConfig } from '../config/config.js';
|
|
|
37
37
|
import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
38
38
|
import type { IdeProviderInstance } from '../providers/ide-provider-instance.js';
|
|
39
39
|
import { createDefaultGitCommandServices } from '../git/git-commands.js';
|
|
40
|
-
import { resolveLocalNodeSlotsForWorkspace } from '../mesh/mesh-node-slots.js';
|
|
41
40
|
import { setupMeshEventForwarding } from '../mesh/mesh-events.js';
|
|
42
41
|
import { setupMeshReconcileLoop } from '../mesh/mesh-reconcile-loop.js';
|
|
43
42
|
import { loadMeshCoordinatorRegistry } from '../mesh/coordinator-registry.js';
|
|
@@ -313,24 +312,26 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
|
|
|
313
312
|
instanceManager,
|
|
314
313
|
sessionRegistry,
|
|
315
314
|
gitCommandServices: createDefaultGitCommandServices({
|
|
316
|
-
// T7
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
|
|
315
|
+
// T7: fold this daemon's cached provider versions + build version onto the
|
|
316
|
+
// git_status envelope so the mesh coordinator self-heals each node's
|
|
317
|
+
// providerVersions/build (the blue version chips) — these are per-machine
|
|
318
|
+
// RUNTIME facts (e.g. this node has claude-cli@2.1.168 while the
|
|
319
|
+
// coordinator has @2.1.170), so they stay reported. Non-blocking: reads a
|
|
320
|
+
// TTL cache, lazily refreshed. (setDefaultProviderLoader is registered
|
|
321
|
+
// above so getCachedProviderVersions is not empty — see the 380556d3
|
|
322
|
+
// version-chip lesson.)
|
|
323
|
+
//
|
|
324
|
+
// Slots are NOT reported: they are coordinator-owned config
|
|
325
|
+
// (node.policy.slots for every node, resolved via
|
|
326
|
+
// resolveNodeCapabilitySlots), not a per-machine runtime fact — the
|
|
327
|
+
// reporter round-trip for slots was removed (REMOTE-NODE-SLOTS-
|
|
328
|
+
// COORDINATOR-LOCAL fix).
|
|
329
|
+
getReporterProviderVersions: () => {
|
|
327
330
|
const providerVersions = getCachedProviderVersions(providerLoader);
|
|
328
331
|
const daemonBuildVersion = getDaemonBuildInfo().version;
|
|
329
|
-
const slots = resolveLocalNodeSlotsForWorkspace(workspace);
|
|
330
332
|
return {
|
|
331
333
|
...(Object.keys(providerVersions).length > 0 ? { providerVersions } : {}),
|
|
332
334
|
...(daemonBuildVersion && daemonBuildVersion !== 'unknown' ? { daemonBuildVersion } : {}),
|
|
333
|
-
...(slots.length > 0 ? { slots } : {}),
|
|
334
335
|
};
|
|
335
336
|
},
|
|
336
337
|
}),
|
|
@@ -644,10 +644,12 @@ export function updateNode(
|
|
|
644
644
|
* is overwritten by the next report. */
|
|
645
645
|
reportedProviderVersions?: Record<string, string>;
|
|
646
646
|
reportedDaemonBuildVersion?: string;
|
|
647
|
-
/**
|
|
648
|
-
*
|
|
649
|
-
* envelope. Persisted wholesale so a remote node's
|
|
650
|
-
* coordinator restart, mirroring the per-field reported* self-heal.
|
|
647
|
+
/** Unified mirrored member state (per-machine runtime facts: provider versions
|
|
648
|
+
* + daemon build + lastReportedAt) self-reported by the owning daemon on the
|
|
649
|
+
* git_status envelope. Persisted wholesale so a remote node's version chips
|
|
650
|
+
* survive a coordinator restart, mirroring the per-field reported* self-heal.
|
|
651
|
+
* Slots are NOT carried — they are coordinator-owned config
|
|
652
|
+
* (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix). */
|
|
651
653
|
reportedMemberState?: MeshReportedMemberState;
|
|
652
654
|
},
|
|
653
655
|
): LocalMeshNodeEntry | undefined {
|
package/src/git/git-commands.ts
CHANGED
|
@@ -112,14 +112,6 @@ export interface GitCommandServices {
|
|
|
112
112
|
getReporterProviderVersions?: (workspace?: string) => {
|
|
113
113
|
providerVersions?: Record<string, string>;
|
|
114
114
|
daemonBuildVersion?: string;
|
|
115
|
-
/**
|
|
116
|
-
* Direction-B: the reporter node's OWN resolved capability slots
|
|
117
|
-
* (resolveNodeCapabilitySlots over its LOCAL policy for this workspace). Carried
|
|
118
|
-
* inside the unified reporterMemberState so a remote node's slot-cap chips
|
|
119
|
-
* self-heal on the coordinator the same way versions do. Best-effort; omitted
|
|
120
|
-
* when the reporter cannot resolve slots for the workspace.
|
|
121
|
-
*/
|
|
122
|
-
slots?: unknown[];
|
|
123
115
|
};
|
|
124
116
|
}
|
|
125
117
|
|
|
@@ -135,11 +127,13 @@ type GitCommandSuccess =
|
|
|
135
127
|
// node's userOverrides.platform/arch (the fields capability-tag routing reads).
|
|
136
128
|
// reporterMachineNickname carries the responding daemon's config.machineNickname
|
|
137
129
|
// so the coordinator can populate node.machineNickname → the friendly display label.
|
|
138
|
-
// reporterMemberState is the
|
|
139
|
-
//
|
|
130
|
+
// reporterMemberState is the unified envelope carrying the reporter node's
|
|
131
|
+
// per-machine RUNTIME facts (provider versions + daemon build + lastReportedAt).
|
|
132
|
+
// Slots are NOT carried here — they are coordinator-owned config, not reported
|
|
133
|
+
// (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix). The legacy flat
|
|
140
134
|
// reporterProviderVersions/reporterDaemonBuildVersion fields are ALSO emitted for
|
|
141
135
|
// back-compat during a mixed-version-mesh rollout.
|
|
142
|
-
| { success: true; status: GitRepoStatus; reporterPlatform?: string; reporterArch?: string; reporterMachineNickname?: string; reporterProviderVersions?: Record<string, string>; reporterDaemonBuildVersion?: string; reporterMemberState?: { providerVersions?: Record<string, string>; daemonBuildVersion?: string;
|
|
136
|
+
| { success: true; status: GitRepoStatus; reporterPlatform?: string; reporterArch?: string; reporterMachineNickname?: string; reporterProviderVersions?: Record<string, string>; reporterDaemonBuildVersion?: string; reporterMemberState?: { providerVersions?: Record<string, string>; daemonBuildVersion?: string; lastReportedAt?: number } }
|
|
143
137
|
| { success: true; diffSummary: GitDiffSummary }
|
|
144
138
|
| { success: true; diff: GitFileDiff }
|
|
145
139
|
| { success: true; snapshot: GitSnapshot }
|
|
@@ -374,20 +368,17 @@ export async function handleGitCommand(
|
|
|
374
368
|
typeof reporterVersions.daemonBuildVersion === 'string' && reporterVersions.daemonBuildVersion.trim()
|
|
375
369
|
? reporterVersions.daemonBuildVersion.trim()
|
|
376
370
|
: undefined;
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
// present so a fully-cold reporter doesn't ship an empty stub. The legacy flat
|
|
384
|
-
// fields above ride alongside for back-compat during rollout.
|
|
371
|
+
// Unified envelope: fold the reporter's per-machine RUNTIME facts
|
|
372
|
+
// (versions/build) into ONE object the coordinator ingests wholesale. Slots are
|
|
373
|
+
// NOT carried — they are coordinator-owned config, not reported
|
|
374
|
+
// (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix). Emitted only when at least one
|
|
375
|
+
// field is present so a fully-cold reporter doesn't ship an empty stub. The
|
|
376
|
+
// legacy flat fields above ride alongside for back-compat during rollout.
|
|
385
377
|
const reporterMemberState =
|
|
386
|
-
reporterProviderVersions || reporterDaemonBuildVersion
|
|
378
|
+
reporterProviderVersions || reporterDaemonBuildVersion
|
|
387
379
|
? {
|
|
388
380
|
...(reporterProviderVersions ? { providerVersions: reporterProviderVersions } : {}),
|
|
389
381
|
...(reporterDaemonBuildVersion ? { daemonBuildVersion: reporterDaemonBuildVersion } : {}),
|
|
390
|
-
...(reporterSlots ? { slots: reporterSlots } : {}),
|
|
391
382
|
lastReportedAt: Date.now(),
|
|
392
383
|
}
|
|
393
384
|
: undefined;
|
|
@@ -15,7 +15,7 @@ import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
|
15
15
|
import { detectCLI, getCachedProviderVersions } from '../detection/cli-detector.js';
|
|
16
16
|
import { getDaemonBuildInfo } from '../build-info.js';
|
|
17
17
|
import { getGitRepoStatus } from '../git/git-status.js';
|
|
18
|
-
import { normalizeGitStatus as sharedNormalizeGitStatus, pickBestTransitGitStatus as sharedPickBestTransitGitStatus, summarizeGitShape as sharedSummarizeGitShape, normalizeMeshNodeId, daemonIdsEquivalent, meshWorkspacesEquivalent, sessionIdsEquivalent, withStatusProbeMarker
|
|
18
|
+
import { normalizeGitStatus as sharedNormalizeGitStatus, pickBestTransitGitStatus as sharedPickBestTransitGitStatus, summarizeGitShape as sharedSummarizeGitShape, normalizeMeshNodeId, daemonIdsEquivalent, meshWorkspacesEquivalent, sessionIdsEquivalent, withStatusProbeMarker } from '@adhdev/mesh-shared';
|
|
19
19
|
import type { MeshReportedMemberState } from '../repo-mesh-types.js';
|
|
20
20
|
import { LOG } from '../logging/logger.js';
|
|
21
21
|
import { getSessionHostSurfaceKind } from '../session-host/runtime-surface.js';
|
|
@@ -408,30 +408,28 @@ export function recordInlineMeshDirectGitTruth(
|
|
|
408
408
|
?? (isLocalSource ? readLocalReporterDaemonBuildVersion() : null))
|
|
409
409
|
?? null;
|
|
410
410
|
if (reporterDaemonBuildVersion) node.reportedDaemonBuildVersion = reporterDaemonBuildVersion;
|
|
411
|
-
// Mirror the unified state onto the node record
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
411
|
+
// Mirror the unified state onto the node record for observability. Only stamped
|
|
412
|
+
// for a REMOTE member (!isLocalSource): the coordinator's own / worktree nodes
|
|
413
|
+
// read their runtime facts from the local warm cache above, so a self node
|
|
414
|
+
// deliberately carries NO reportedMemberState. Carries the per-machine RUNTIME
|
|
415
|
+
// facts (provider versions + daemon build) — NOT slots, which are coordinator-
|
|
416
|
+
// owned config resolved from node.policy.slots, not reported
|
|
417
|
+
// (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix). Synthesizes from the legacy flat
|
|
418
|
+
// fields when a node reports only those (no unified envelope yet).
|
|
418
419
|
if (!isLocalSource) {
|
|
419
|
-
const reportedSlots = normalizeNodeCapabilitySlots(unified?.slots);
|
|
420
420
|
const mirrored: {
|
|
421
421
|
providerVersions?: Record<string, string>;
|
|
422
422
|
daemonBuildVersion?: string;
|
|
423
|
-
slots?: NodeCapabilitySlot[];
|
|
424
423
|
lastReportedAt?: number;
|
|
425
424
|
} = {
|
|
426
425
|
...(reporterProviderVersions ? { providerVersions: reporterProviderVersions } : {}),
|
|
427
426
|
...(reporterDaemonBuildVersion ? { daemonBuildVersion: reporterDaemonBuildVersion } : {}),
|
|
428
|
-
...(reportedSlots.length ? { slots: reportedSlots } : {}),
|
|
429
427
|
lastReportedAt: readNumberValue(unified?.lastReportedAt) ?? checkedAt,
|
|
430
428
|
};
|
|
431
429
|
// Only stamp when there's actual reported content beyond the timestamp — a
|
|
432
430
|
// bare {lastReportedAt} carries no observability and would just churn the
|
|
433
431
|
// record on every probe.
|
|
434
|
-
if (mirrored.providerVersions || mirrored.daemonBuildVersion
|
|
432
|
+
if (mirrored.providerVersions || mirrored.daemonBuildVersion) {
|
|
435
433
|
node.reportedMemberState = mirrored;
|
|
436
434
|
}
|
|
437
435
|
}
|
|
@@ -446,26 +444,26 @@ export function recordInlineMeshDirectGitTruth(
|
|
|
446
444
|
}
|
|
447
445
|
|
|
448
446
|
/**
|
|
449
|
-
*
|
|
447
|
+
* Coerce an unknown git-envelope `reporterMemberState` into a clean
|
|
450
448
|
* {@link MeshReportedMemberState}. Reuses readProviderVersionsRecord for the version
|
|
451
|
-
* map
|
|
452
|
-
*
|
|
453
|
-
*
|
|
449
|
+
* map. Carries the per-machine RUNTIME facts (provider versions + daemon build) only
|
|
450
|
+
* — slots are coordinator-owned config, not reported (REMOTE-NODE-SLOTS-COORDINATOR-
|
|
451
|
+
* LOCAL fix), and any stray `slots` on a legacy envelope is ignored. Returns null
|
|
452
|
+
* when nothing usable is present (bare/absent envelope) so callers fall back to the
|
|
453
|
+
* legacy flat fields. Best-effort, never throws.
|
|
454
454
|
*/
|
|
455
455
|
function normalizeReportedMemberState(value: unknown): MeshReportedMemberState | null {
|
|
456
456
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
457
457
|
const raw = value as Record<string, unknown>;
|
|
458
458
|
const providerVersions = readProviderVersionsRecord(raw.providerVersions);
|
|
459
459
|
const daemonBuildVersion = readStringValue(raw.daemonBuildVersion);
|
|
460
|
-
const slots = normalizeNodeCapabilitySlots(raw.slots);
|
|
461
460
|
const lastReportedAt = readNumberValue(raw.lastReportedAt);
|
|
462
|
-
if (!providerVersions && !daemonBuildVersion &&
|
|
461
|
+
if (!providerVersions && !daemonBuildVersion && lastReportedAt === undefined) {
|
|
463
462
|
return null;
|
|
464
463
|
}
|
|
465
464
|
return {
|
|
466
465
|
...(providerVersions ? { providerVersions } : {}),
|
|
467
466
|
...(daemonBuildVersion ? { daemonBuildVersion } : {}),
|
|
468
|
-
...(slots.length ? { slots } : {}),
|
|
469
467
|
...(lastReportedAt !== undefined ? { lastReportedAt } : {}),
|
|
470
468
|
};
|
|
471
469
|
}
|
|
@@ -567,8 +565,10 @@ export function persistNodeReporterPlatform(
|
|
|
567
565
|
const reportedMachineNickname = reporter.reporterMachineNickname ?? undefined;
|
|
568
566
|
const reportedProviderVersions = reporter.reporterProviderVersions ?? undefined;
|
|
569
567
|
const reportedDaemonBuildVersion = reporter.reporterDaemonBuildVersion ?? undefined;
|
|
570
|
-
//
|
|
571
|
-
//
|
|
568
|
+
// Persist the unified mirrored member state so a remote node's version chips
|
|
569
|
+
// survive a coordinator restart (mirrors the per-field self-heal). Carries
|
|
570
|
+
// per-machine runtime facts only (versions + build) — slots are coordinator-owned
|
|
571
|
+
// config, not reported (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix).
|
|
572
572
|
const reportedMemberState = reporter.reportedMemberState ?? undefined;
|
|
573
573
|
if (
|
|
574
574
|
!reportedPlatform &&
|
|
@@ -1618,10 +1618,11 @@ async function probeRemoteMeshGitStatus(args: {
|
|
|
1618
1618
|
if (reporterProviderVersions) git.reporterProviderVersions = reporterProviderVersions;
|
|
1619
1619
|
const reporterDaemonBuildVersion = readStringValue(remoteResult?.reporterDaemonBuildVersion);
|
|
1620
1620
|
if (reporterDaemonBuildVersion) git.reporterDaemonBuildVersion = reporterDaemonBuildVersion;
|
|
1621
|
-
//
|
|
1622
|
-
//
|
|
1623
|
-
//
|
|
1624
|
-
//
|
|
1621
|
+
// Propagate the member's UNIFIED reportedMemberState (per-machine runtime facts:
|
|
1622
|
+
// versions + build + lastReportedAt) on the same channel so the coordinator can
|
|
1623
|
+
// ingest it wholesale. Slots are NOT carried — coordinator-owned config
|
|
1624
|
+
// (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix). Carried alongside the legacy flat
|
|
1625
|
+
// fields above for a mixed-version-mesh rollout.
|
|
1625
1626
|
const reporterMemberState = normalizeReportedMemberState(remoteResult?.reporterMemberState);
|
|
1626
1627
|
if (reporterMemberState) git.reporterMemberState = reporterMemberState;
|
|
1627
1628
|
return git;
|
|
@@ -12,10 +12,9 @@
|
|
|
12
12
|
import {
|
|
13
13
|
deriveSlotsFromLegacy,
|
|
14
14
|
normalizeNodeCapabilitySlots,
|
|
15
|
-
meshWorkspacesEquivalent,
|
|
16
15
|
type NodeCapabilitySlot,
|
|
17
16
|
} from '@adhdev/mesh-shared';
|
|
18
|
-
import { getDifficultyBrains
|
|
17
|
+
import { getDifficultyBrains } from '../config/mesh-config.js';
|
|
19
18
|
|
|
20
19
|
/** Ordered, de-duplicated providerPriority from a node policy (defensive). */
|
|
21
20
|
export function normalizeProviderPriority(policy: unknown): string[] {
|
|
@@ -35,25 +34,19 @@ export function normalizeProviderPriority(policy: unknown): string[] {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
|
-
* Resolve a node's capability slots
|
|
39
|
-
* (REMOTE-NODE-
|
|
37
|
+
* Resolve a node's capability slots — coordinator-owned config is the single source
|
|
38
|
+
* of truth for ALL nodes (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix).
|
|
40
39
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* — the reason remote slot-cap chips were missing.
|
|
51
|
-
* 3. Legacy-derived from `providerPriority` + machine-global difficultyBrains.
|
|
40
|
+
* The coordinator's local meshes.json owns `policy.slots` for every node in the mesh
|
|
41
|
+
* (self AND remote members alike — a remote member's mesh config lives on the
|
|
42
|
+
* coordinator, its own on-disk meshes.json is empty). So slots resolve directly from
|
|
43
|
+
* the coordinator's locally-owned `node.policy.slots`, with no remote reporter
|
|
44
|
+
* round-trip: there is no per-node "reported slots" mirror to consult.
|
|
45
|
+
*
|
|
46
|
+
* Precedence:
|
|
47
|
+
* 1. `policy.slots` — coordinator-owned, authoritative for self and remote alike.
|
|
48
|
+
* 2. Legacy-derived from `providerPriority` + machine-global difficultyBrains.
|
|
52
49
|
*
|
|
53
|
-
* The mirror only ever fills the gap left by an empty local policy — it can never
|
|
54
|
-
* shadow a populated local policy — so there is no inversion. And a self node never
|
|
55
|
-
* carries a reportedMemberState (the ingest path stamps it ONLY for remote
|
|
56
|
-
* members), so a self node's local policy is always used directly.
|
|
57
50
|
* (The former per-provider `providerRoles` cap has been removed; a persisted
|
|
58
51
|
* meshes.json is migrated to slots on load, so by the time a node reaches routing
|
|
59
52
|
* its cap already lives on `slots[].maxParallel`.)
|
|
@@ -61,13 +54,6 @@ export function normalizeProviderPriority(policy: unknown): string[] {
|
|
|
61
54
|
export function resolveNodeCapabilitySlots(node: any): NodeCapabilitySlot[] {
|
|
62
55
|
const explicit = normalizeNodeCapabilitySlots(node?.policy?.slots);
|
|
63
56
|
if (explicit.length) return explicit;
|
|
64
|
-
// Remote mirror: the member's own resolved slots, carried on the git_status
|
|
65
|
-
// envelope and ingested onto the node record. Preferred over the legacy derive
|
|
66
|
-
// (it is the member's real resolved profile), but only when the local policy is
|
|
67
|
-
// empty — so it fills the coordinator's join-time gap without overriding an
|
|
68
|
-
// explicit local override.
|
|
69
|
-
const mirrored = normalizeNodeCapabilitySlots(node?.reportedMemberState?.slots);
|
|
70
|
-
if (mirrored.length) return mirrored;
|
|
71
57
|
let difficultyBrains: any;
|
|
72
58
|
try { difficultyBrains = getDifficultyBrains(); } catch { difficultyBrains = undefined; }
|
|
73
59
|
return deriveSlotsFromLegacy({
|
|
@@ -75,31 +61,3 @@ export function resolveNodeCapabilitySlots(node: any): NodeCapabilitySlot[] {
|
|
|
75
61
|
difficultyBrains,
|
|
76
62
|
});
|
|
77
63
|
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Reporter-side helper (Direction B): resolve THIS daemon's own capability slots for
|
|
81
|
-
* a given workspace by finding the local meshes.json node whose workspace matches
|
|
82
|
-
* and running the same {@link resolveNodeCapabilitySlots} over its real local policy.
|
|
83
|
-
* Used at the git_status reporter wiring so a member node self-reports its own
|
|
84
|
-
* resolved slots inside the unified reporterMemberState envelope. Returns [] when no
|
|
85
|
-
* local node matches the workspace or slots can't be resolved (best-effort, never
|
|
86
|
-
* throws) so the reporter simply omits the field.
|
|
87
|
-
*/
|
|
88
|
-
export function resolveLocalNodeSlotsForWorkspace(workspace: string | undefined): NodeCapabilitySlot[] {
|
|
89
|
-
const ws = typeof workspace === 'string' ? workspace.trim() : '';
|
|
90
|
-
if (!ws) return [];
|
|
91
|
-
let meshes: any[];
|
|
92
|
-
try { meshes = listMeshes(); } catch { return []; }
|
|
93
|
-
for (const mesh of Array.isArray(meshes) ? meshes : []) {
|
|
94
|
-
for (const node of Array.isArray(mesh?.nodes) ? mesh.nodes : []) {
|
|
95
|
-
const nodeWs = typeof node?.workspace === 'string' ? node.workspace : '';
|
|
96
|
-
if (nodeWs && meshWorkspacesEquivalent(nodeWs, ws)) {
|
|
97
|
-
// Resolve from the LOCAL node's real policy only — never re-read its
|
|
98
|
-
// own reportedMemberState (a member does not mirror itself).
|
|
99
|
-
const slots = resolveNodeCapabilitySlots({ policy: node?.policy });
|
|
100
|
-
if (slots.length) return slots;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return [];
|
|
105
|
-
}
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -399,35 +399,28 @@ export interface RepoMeshRelatedRepo {
|
|
|
399
399
|
*/
|
|
400
400
|
|
|
401
401
|
/**
|
|
402
|
-
*
|
|
402
|
+
* Unified mirrored member state — the per-machine RUNTIME facts a remote member
|
|
403
|
+
* self-reports (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix).
|
|
403
404
|
*
|
|
404
405
|
* A single self-reported observability envelope stamped by the daemon that OWNS a
|
|
405
406
|
* node's workspace and carried wholesale on the git_status envelope
|
|
406
|
-
* (reporterMemberState),
|
|
407
|
-
* providerVersions and daemonBuildVersion
|
|
408
|
-
*
|
|
407
|
+
* (reporterMemberState), consolidating the previously per-field self-heal of
|
|
408
|
+
* providerVersions and daemonBuildVersion. The coordinator ingests this whole object
|
|
409
|
+
* onto the mirrored remote node in one place
|
|
409
410
|
* (mesh-node-identity.recordInlineMeshDirectGitTruth).
|
|
410
411
|
*
|
|
411
|
-
* Source-of-truth model:
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* resolveNodeCapabilitySlots)
|
|
412
|
+
* Source-of-truth model: provider VERSIONS + daemon BUILD are per-machine runtime
|
|
413
|
+
* facts (e.g. a member has claude-cli@2.1.168 while the coordinator has @2.1.170), so
|
|
414
|
+
* they are reported here. SLOTS are NOT reported — they are coordinator-owned config
|
|
415
|
+
* (node.policy.slots for every node, self and remote alike; see
|
|
416
|
+
* resolveNodeCapabilitySlots), a single source of truth on the coordinator with no
|
|
417
|
+
* reporter round-trip.
|
|
416
418
|
*/
|
|
417
419
|
export interface MeshReportedMemberState {
|
|
418
420
|
/** Reporter node's detected provider CLI/ACP versions (keyed by provider id). */
|
|
419
421
|
providerVersions?: Record<string, string>;
|
|
420
422
|
/** Reporter daemon's build version (getDaemonBuildInfo().version). */
|
|
421
423
|
daemonBuildVersion?: string;
|
|
422
|
-
/**
|
|
423
|
-
* Reporter node's OWN resolved capability slots (resolveNodeCapabilitySlots over
|
|
424
|
-
* its LOCAL real policy). This is the field the coordinator's join-time policy
|
|
425
|
-
* copy lacks for a remote member, and the reason remote slot-cap chips were
|
|
426
|
-
* missing. For a remote mirrored node this is authoritative over the (empty)
|
|
427
|
-
* local policy.slots; for the coordinator's own node the local policy is used
|
|
428
|
-
* directly (never mirrored here).
|
|
429
|
-
*/
|
|
430
|
-
slots?: NodeCapabilitySlot[];
|
|
431
424
|
/** Epoch ms the report was stamped; used for mirror staleness reasoning. */
|
|
432
425
|
lastReportedAt?: number;
|
|
433
426
|
}
|
|
@@ -952,19 +945,15 @@ export interface LocalMeshNodeEntry {
|
|
|
952
945
|
/**
|
|
953
946
|
* Unified mirrored "member state" self-reported by the daemon that owns this
|
|
954
947
|
* node's workspace, carried wholesale on the git_status envelope
|
|
955
|
-
* (reporterMemberState) and ingested by the coordinator in one place.
|
|
956
|
-
* the
|
|
957
|
-
*
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
* reporter node's own resolved capability slots (resolveNodeCapabilitySlots over
|
|
965
|
-
* its LOCAL real policy). `lastReportedAt` is the epoch ms the report was
|
|
966
|
-
* stamped, so a partitioned node's mirror does not linger as fresh forever.
|
|
967
|
-
* Absent until the first envelope carrying it is ingested.
|
|
948
|
+
* (reporterMemberState) and ingested by the coordinator in one place. It carries
|
|
949
|
+
* the per-machine RUNTIME facts (reportedProviderVersions, reportedDaemonBuildVersion)
|
|
950
|
+
* consolidated into one envelope — NOT slots, which are coordinator-owned config
|
|
951
|
+
* resolved from node.policy.slots for every node, not reported
|
|
952
|
+
* (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix). The legacy flat fields above stay
|
|
953
|
+
* populated in parallel during rollout for back-compat (see mesh-node-identity
|
|
954
|
+
* ingest). `lastReportedAt` is the epoch ms the report was stamped, so a
|
|
955
|
+
* partitioned node's mirror does not linger as fresh forever. Absent until the
|
|
956
|
+
* first envelope carrying it is ingested.
|
|
968
957
|
*/
|
|
969
958
|
reportedMemberState?: MeshReportedMemberState;
|
|
970
959
|
/**
|