@adhdev/daemon-core 0.9.82-rc.514 → 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 +8 -1
- package/dist/git/git-commands.d.ts +6 -1
- package/dist/index.js +65 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +65 -13
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-node-identity.d.ts +3 -0
- package/dist/mesh/mesh-node-slots.d.ts +13 -2
- package/dist/repo-mesh-types.d.ts +40 -0
- package/package.json +3 -3
- package/src/boot/daemon-lifecycle.ts +12 -1
- package/src/config/mesh-config.ts +14 -0
- package/src/git/git-commands.ts +27 -3
- package/src/mesh/mesh-node-identity.ts +79 -3
- package/src/mesh/mesh-node-slots.ts +13 -2
- package/src/repo-mesh-types.ts +41 -0
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* (e.g. `from '.../commands/router.js'`) keep working.
|
|
12
12
|
*/
|
|
13
13
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
14
|
+
import type { MeshReportedMemberState } from '../repo-mesh-types.js';
|
|
14
15
|
import { awaitWithWarmupDeadline } from '../mesh/mesh-warmup-deadline.js';
|
|
15
16
|
export declare function readProviderPriorityFromPolicy(policy: unknown): string[];
|
|
16
17
|
/**
|
|
@@ -51,6 +52,7 @@ export declare function recordInlineMeshDirectGitTruth(node: any, git: Record<st
|
|
|
51
52
|
reporterMachineNickname: string | null;
|
|
52
53
|
reporterProviderVersions: Record<string, string> | null;
|
|
53
54
|
reporterDaemonBuildVersion: string | null;
|
|
55
|
+
reportedMemberState: MeshReportedMemberState | null;
|
|
54
56
|
};
|
|
55
57
|
/**
|
|
56
58
|
* Persist the live self-reported platform/arch onto the local meshes.json node
|
|
@@ -69,6 +71,7 @@ export declare function persistNodeReporterPlatform(meshSource: 'inline_cache' |
|
|
|
69
71
|
reporterMachineNickname?: string | null;
|
|
70
72
|
reporterProviderVersions?: Record<string, string> | null;
|
|
71
73
|
reporterDaemonBuildVersion?: string | null;
|
|
74
|
+
reportedMemberState?: MeshReportedMemberState | null;
|
|
72
75
|
}): void;
|
|
73
76
|
export declare function inlineMeshCarriesTransientNodeTruth(inlineMesh: any): boolean;
|
|
74
77
|
export declare function readInlineMeshNodeId(node: any): string;
|
|
@@ -13,8 +13,19 @@ 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
|
-
*
|
|
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
|
+
*
|
|
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.
|
|
28
|
+
*
|
|
18
29
|
* (The former per-provider `providerRoles` cap has been removed; a persisted
|
|
19
30
|
* meshes.json is migrated to slots on load, so by the time a node reaches routing
|
|
20
31
|
* its cap already lives on `slots[].maxParallel`.)
|
|
@@ -300,6 +300,32 @@ export interface RepoMeshRelatedRepo {
|
|
|
300
300
|
* persisted meshes.json that still carries it is migrated to `slots` on load
|
|
301
301
|
* (see migrateLoadedMeshConfig).
|
|
302
302
|
*/
|
|
303
|
+
/**
|
|
304
|
+
* Unified mirrored member state — the per-machine RUNTIME facts a remote member
|
|
305
|
+
* self-reports (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix).
|
|
306
|
+
*
|
|
307
|
+
* A single self-reported observability envelope stamped by the daemon that OWNS a
|
|
308
|
+
* node's workspace and carried wholesale on the git_status envelope
|
|
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
|
|
312
|
+
* (mesh-node-identity.recordInlineMeshDirectGitTruth).
|
|
313
|
+
*
|
|
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.
|
|
320
|
+
*/
|
|
321
|
+
export interface MeshReportedMemberState {
|
|
322
|
+
/** Reporter node's detected provider CLI/ACP versions (keyed by provider id). */
|
|
323
|
+
providerVersions?: Record<string, string>;
|
|
324
|
+
/** Reporter daemon's build version (getDaemonBuildInfo().version). */
|
|
325
|
+
daemonBuildVersion?: string;
|
|
326
|
+
/** Epoch ms the report was stamped; used for mirror staleness reasoning. */
|
|
327
|
+
lastReportedAt?: number;
|
|
328
|
+
}
|
|
303
329
|
export interface RepoMeshNodePolicy {
|
|
304
330
|
readOnly?: boolean;
|
|
305
331
|
canPush?: boolean;
|
|
@@ -634,6 +660,20 @@ export interface LocalMeshNodeEntry {
|
|
|
634
660
|
* owning daemon, carried on the git_status envelope (reporterDaemonBuildVersion)
|
|
635
661
|
* alongside the provider versions. Absent until first reported. */
|
|
636
662
|
reportedDaemonBuildVersion?: string;
|
|
663
|
+
/**
|
|
664
|
+
* Unified mirrored "member state" self-reported by the daemon that owns this
|
|
665
|
+
* node's workspace, carried wholesale on the git_status envelope
|
|
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.
|
|
675
|
+
*/
|
|
676
|
+
reportedMemberState?: MeshReportedMemberState;
|
|
637
677
|
/**
|
|
638
678
|
* The operator-set machine nickname (config.machineNickname) of the daemon
|
|
639
679
|
* that owns this node's workspace. The local coordinator stamps its own
|
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",
|
|
@@ -314,7 +314,18 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
|
|
|
314
314
|
gitCommandServices: createDefaultGitCommandServices({
|
|
315
315
|
// T7: fold this daemon's cached provider versions + build version onto the
|
|
316
316
|
// git_status envelope so the mesh coordinator self-heals each node's
|
|
317
|
-
// providerVersions
|
|
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).
|
|
318
329
|
getReporterProviderVersions: () => {
|
|
319
330
|
const providerVersions = getCachedProviderVersions(providerLoader);
|
|
320
331
|
const daemonBuildVersion = getDaemonBuildInfo().version;
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
RepoMeshCoordinatorConfig,
|
|
22
22
|
RepoMeshHostMetadata,
|
|
23
23
|
RepoMeshDaemonRole,
|
|
24
|
+
MeshReportedMemberState,
|
|
24
25
|
} from '../repo-mesh-types.js';
|
|
25
26
|
import type { MagiKindPanelMap, MagiSlot, MagiTaskKind, DifficultyBrainMap, NodeCapabilitySlot } from '@adhdev/mesh-shared';
|
|
26
27
|
import { normalizeDifficultyBrainMap, DEFAULT_DIFFICULTY_BRAINS, normalizeNodeCapabilitySlots, deriveSlotsFromLegacy } from '@adhdev/mesh-shared';
|
|
@@ -643,6 +644,13 @@ export function updateNode(
|
|
|
643
644
|
* is overwritten by the next report. */
|
|
644
645
|
reportedProviderVersions?: Record<string, string>;
|
|
645
646
|
reportedDaemonBuildVersion?: string;
|
|
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). */
|
|
653
|
+
reportedMemberState?: MeshReportedMemberState;
|
|
646
654
|
},
|
|
647
655
|
): LocalMeshNodeEntry | undefined {
|
|
648
656
|
const config = loadMeshConfig();
|
|
@@ -662,6 +670,12 @@ export function updateNode(
|
|
|
662
670
|
if (opts.reportedDaemonBuildVersion && opts.reportedDaemonBuildVersion.trim()) {
|
|
663
671
|
node.reportedDaemonBuildVersion = opts.reportedDaemonBuildVersion.trim();
|
|
664
672
|
}
|
|
673
|
+
if (opts.reportedMemberState) {
|
|
674
|
+
// Whole-object replace (auto-detected observability, overwritten by the next
|
|
675
|
+
// report so a stale mirror never sticks — same policy as the flat reported*
|
|
676
|
+
// fields). normalizeReportedMemberState upstream guarantees a clean shape.
|
|
677
|
+
node.reportedMemberState = opts.reportedMemberState;
|
|
678
|
+
}
|
|
665
679
|
if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
|
|
666
680
|
if (Object.prototype.hasOwnProperty.call(opts, 'capabilities')) {
|
|
667
681
|
// Explicit replace: normalize (trim/dedup/drop-empties); an empty result
|
package/src/git/git-commands.ts
CHANGED
|
@@ -109,7 +109,10 @@ export interface GitCommandServices {
|
|
|
109
109
|
* same way it does platform/arch. Wired at daemon boot from the cached CLI
|
|
110
110
|
* detection snapshot; omitted (⇒ versions not reported) when unavailable.
|
|
111
111
|
*/
|
|
112
|
-
getReporterProviderVersions?: () => {
|
|
112
|
+
getReporterProviderVersions?: (workspace?: string) => {
|
|
113
|
+
providerVersions?: Record<string, string>;
|
|
114
|
+
daemonBuildVersion?: string;
|
|
115
|
+
};
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
type GitCommandFailure = {
|
|
@@ -124,7 +127,13 @@ type GitCommandSuccess =
|
|
|
124
127
|
// node's userOverrides.platform/arch (the fields capability-tag routing reads).
|
|
125
128
|
// reporterMachineNickname carries the responding daemon's config.machineNickname
|
|
126
129
|
// so the coordinator can populate node.machineNickname → the friendly display label.
|
|
127
|
-
|
|
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
|
|
134
|
+
// reporterProviderVersions/reporterDaemonBuildVersion fields are ALSO emitted for
|
|
135
|
+
// back-compat during a mixed-version-mesh rollout.
|
|
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 } }
|
|
128
137
|
| { success: true; diffSummary: GitDiffSummary }
|
|
129
138
|
| { success: true; diff: GitFileDiff }
|
|
130
139
|
| { success: true; snapshot: GitSnapshot }
|
|
@@ -346,7 +355,7 @@ export async function handleGitCommand(
|
|
|
346
355
|
// snapshot, so a cold cache simply omits the fields on this probe.
|
|
347
356
|
const reporterVersions = (() => {
|
|
348
357
|
try {
|
|
349
|
-
return services.getReporterProviderVersions?.() ?? {};
|
|
358
|
+
return services.getReporterProviderVersions?.(workspace) ?? {};
|
|
350
359
|
} catch {
|
|
351
360
|
return {};
|
|
352
361
|
}
|
|
@@ -359,6 +368,20 @@ export async function handleGitCommand(
|
|
|
359
368
|
typeof reporterVersions.daemonBuildVersion === 'string' && reporterVersions.daemonBuildVersion.trim()
|
|
360
369
|
? reporterVersions.daemonBuildVersion.trim()
|
|
361
370
|
: undefined;
|
|
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.
|
|
377
|
+
const reporterMemberState =
|
|
378
|
+
reporterProviderVersions || reporterDaemonBuildVersion
|
|
379
|
+
? {
|
|
380
|
+
...(reporterProviderVersions ? { providerVersions: reporterProviderVersions } : {}),
|
|
381
|
+
...(reporterDaemonBuildVersion ? { daemonBuildVersion: reporterDaemonBuildVersion } : {}),
|
|
382
|
+
lastReportedAt: Date.now(),
|
|
383
|
+
}
|
|
384
|
+
: undefined;
|
|
362
385
|
return {
|
|
363
386
|
success: true,
|
|
364
387
|
status,
|
|
@@ -367,6 +390,7 @@ export async function handleGitCommand(
|
|
|
367
390
|
...(reporterMachineNickname ? { reporterMachineNickname } : {}),
|
|
368
391
|
...(reporterProviderVersions ? { reporterProviderVersions } : {}),
|
|
369
392
|
...(reporterDaemonBuildVersion ? { reporterDaemonBuildVersion } : {}),
|
|
393
|
+
...(reporterMemberState ? { reporterMemberState } : {}),
|
|
370
394
|
};
|
|
371
395
|
}
|
|
372
396
|
|
|
@@ -16,6 +16,7 @@ import { detectCLI, getCachedProviderVersions } from '../detection/cli-detector.
|
|
|
16
16
|
import { getDaemonBuildInfo } from '../build-info.js';
|
|
17
17
|
import { getGitRepoStatus } from '../git/git-status.js';
|
|
18
18
|
import { normalizeGitStatus as sharedNormalizeGitStatus, pickBestTransitGitStatus as sharedPickBestTransitGitStatus, summarizeGitShape as sharedSummarizeGitShape, normalizeMeshNodeId, daemonIdsEquivalent, meshWorkspacesEquivalent, sessionIdsEquivalent, withStatusProbeMarker } from '@adhdev/mesh-shared';
|
|
19
|
+
import type { MeshReportedMemberState } from '../repo-mesh-types.js';
|
|
19
20
|
import { LOG } from '../logging/logger.js';
|
|
20
21
|
import { getSessionHostSurfaceKind } from '../session-host/runtime-surface.js';
|
|
21
22
|
import { awaitWithWarmupDeadline, resolveWarmupDeadlineOpts } from '../mesh/mesh-warmup-deadline.js';
|
|
@@ -326,6 +327,7 @@ export function recordInlineMeshDirectGitTruth(
|
|
|
326
327
|
reporterMachineNickname: string | null;
|
|
327
328
|
reporterProviderVersions: Record<string, string> | null;
|
|
328
329
|
reporterDaemonBuildVersion: string | null;
|
|
330
|
+
reportedMemberState: MeshReportedMemberState | null;
|
|
329
331
|
} {
|
|
330
332
|
if (!node || typeof node !== 'object' || Array.isArray(node)) {
|
|
331
333
|
return {
|
|
@@ -334,6 +336,7 @@ export function recordInlineMeshDirectGitTruth(
|
|
|
334
336
|
reporterMachineNickname: null,
|
|
335
337
|
reporterProviderVersions: null,
|
|
336
338
|
reporterDaemonBuildVersion: null,
|
|
339
|
+
reportedMemberState: null,
|
|
337
340
|
};
|
|
338
341
|
}
|
|
339
342
|
const checkedAt = readNumberValue(git.lastCheckedAt) ?? Date.now();
|
|
@@ -389,21 +392,79 @@ export function recordInlineMeshDirectGitTruth(
|
|
|
389
392
|
// carries no reporter* versions — mirror the platform/arch self-heal above and read
|
|
390
393
|
// this daemon's own warm version cache directly, so the coordinator's self node gets
|
|
391
394
|
// the same provider/build chips a remote node does.
|
|
395
|
+
// Direction-B: parse the UNIFIED reportedMemberState envelope first (versions +
|
|
396
|
+
// build + the member's own resolved slots), falling back to the flat legacy
|
|
397
|
+
// fields so a mixed-version mesh during rollout still ingests. This is the single
|
|
398
|
+
// place the coordinator mirrors a remote member's reported state wholesale.
|
|
399
|
+
const unified = normalizeReportedMemberState(git.reporterMemberState);
|
|
392
400
|
const reporterProviderVersions =
|
|
393
|
-
readProviderVersionsRecord(
|
|
401
|
+
(unified?.providerVersions ? readProviderVersionsRecord(unified.providerVersions) : null)
|
|
402
|
+
?? readProviderVersionsRecord(git.reporterProviderVersions)
|
|
394
403
|
?? (isLocalSource ? readLocalReporterProviderVersions() : null);
|
|
395
404
|
if (reporterProviderVersions) node.reportedProviderVersions = reporterProviderVersions;
|
|
396
405
|
const reporterDaemonBuildVersion =
|
|
397
|
-
(readStringValue(
|
|
406
|
+
(readStringValue(unified?.daemonBuildVersion)
|
|
407
|
+
?? readStringValue(git.reporterDaemonBuildVersion)
|
|
398
408
|
?? (isLocalSource ? readLocalReporterDaemonBuildVersion() : null))
|
|
399
409
|
?? null;
|
|
400
410
|
if (reporterDaemonBuildVersion) node.reportedDaemonBuildVersion = reporterDaemonBuildVersion;
|
|
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).
|
|
419
|
+
if (!isLocalSource) {
|
|
420
|
+
const mirrored: {
|
|
421
|
+
providerVersions?: Record<string, string>;
|
|
422
|
+
daemonBuildVersion?: string;
|
|
423
|
+
lastReportedAt?: number;
|
|
424
|
+
} = {
|
|
425
|
+
...(reporterProviderVersions ? { providerVersions: reporterProviderVersions } : {}),
|
|
426
|
+
...(reporterDaemonBuildVersion ? { daemonBuildVersion: reporterDaemonBuildVersion } : {}),
|
|
427
|
+
lastReportedAt: readNumberValue(unified?.lastReportedAt) ?? checkedAt,
|
|
428
|
+
};
|
|
429
|
+
// Only stamp when there's actual reported content beyond the timestamp — a
|
|
430
|
+
// bare {lastReportedAt} carries no observability and would just churn the
|
|
431
|
+
// record on every probe.
|
|
432
|
+
if (mirrored.providerVersions || mirrored.daemonBuildVersion) {
|
|
433
|
+
node.reportedMemberState = mirrored;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
401
436
|
return {
|
|
402
437
|
reporterPlatform,
|
|
403
438
|
reporterArch,
|
|
404
439
|
reporterMachineNickname,
|
|
405
440
|
reporterProviderVersions,
|
|
406
441
|
reporterDaemonBuildVersion,
|
|
442
|
+
reportedMemberState: (!isLocalSource ? (node.reportedMemberState ?? null) : null) as MeshReportedMemberState | null,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Coerce an unknown git-envelope `reporterMemberState` into a clean
|
|
448
|
+
* {@link MeshReportedMemberState}. Reuses readProviderVersionsRecord for the version
|
|
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
|
+
*/
|
|
455
|
+
function normalizeReportedMemberState(value: unknown): MeshReportedMemberState | null {
|
|
456
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
457
|
+
const raw = value as Record<string, unknown>;
|
|
458
|
+
const providerVersions = readProviderVersionsRecord(raw.providerVersions);
|
|
459
|
+
const daemonBuildVersion = readStringValue(raw.daemonBuildVersion);
|
|
460
|
+
const lastReportedAt = readNumberValue(raw.lastReportedAt);
|
|
461
|
+
if (!providerVersions && !daemonBuildVersion && lastReportedAt === undefined) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
...(providerVersions ? { providerVersions } : {}),
|
|
466
|
+
...(daemonBuildVersion ? { daemonBuildVersion } : {}),
|
|
467
|
+
...(lastReportedAt !== undefined ? { lastReportedAt } : {}),
|
|
407
468
|
};
|
|
408
469
|
}
|
|
409
470
|
|
|
@@ -493,6 +554,7 @@ export function persistNodeReporterPlatform(
|
|
|
493
554
|
reporterMachineNickname?: string | null;
|
|
494
555
|
reporterProviderVersions?: Record<string, string> | null;
|
|
495
556
|
reporterDaemonBuildVersion?: string | null;
|
|
557
|
+
reportedMemberState?: MeshReportedMemberState | null;
|
|
496
558
|
},
|
|
497
559
|
): void {
|
|
498
560
|
if (meshSource !== 'local_config') return;
|
|
@@ -503,12 +565,18 @@ export function persistNodeReporterPlatform(
|
|
|
503
565
|
const reportedMachineNickname = reporter.reporterMachineNickname ?? undefined;
|
|
504
566
|
const reportedProviderVersions = reporter.reporterProviderVersions ?? undefined;
|
|
505
567
|
const reportedDaemonBuildVersion = reporter.reporterDaemonBuildVersion ?? undefined;
|
|
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
|
+
const reportedMemberState = reporter.reportedMemberState ?? undefined;
|
|
506
573
|
if (
|
|
507
574
|
!reportedPlatform &&
|
|
508
575
|
!reportedArch &&
|
|
509
576
|
!reportedMachineNickname &&
|
|
510
577
|
!reportedProviderVersions &&
|
|
511
|
-
!reportedDaemonBuildVersion
|
|
578
|
+
!reportedDaemonBuildVersion &&
|
|
579
|
+
!reportedMemberState
|
|
512
580
|
) {
|
|
513
581
|
return;
|
|
514
582
|
}
|
|
@@ -519,6 +587,7 @@ export function persistNodeReporterPlatform(
|
|
|
519
587
|
reportedMachineNickname,
|
|
520
588
|
reportedProviderVersions,
|
|
521
589
|
reportedDaemonBuildVersion,
|
|
590
|
+
reportedMemberState,
|
|
522
591
|
}))
|
|
523
592
|
.catch(() => { /* best-effort self-heal; never block status assembly */ });
|
|
524
593
|
}
|
|
@@ -1549,6 +1618,13 @@ async function probeRemoteMeshGitStatus(args: {
|
|
|
1549
1618
|
if (reporterProviderVersions) git.reporterProviderVersions = reporterProviderVersions;
|
|
1550
1619
|
const reporterDaemonBuildVersion = readStringValue(remoteResult?.reporterDaemonBuildVersion);
|
|
1551
1620
|
if (reporterDaemonBuildVersion) git.reporterDaemonBuildVersion = reporterDaemonBuildVersion;
|
|
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.
|
|
1626
|
+
const reporterMemberState = normalizeReportedMemberState(remoteResult?.reporterMemberState);
|
|
1627
|
+
if (reporterMemberState) git.reporterMemberState = reporterMemberState;
|
|
1552
1628
|
return git;
|
|
1553
1629
|
}
|
|
1554
1630
|
|
|
@@ -34,8 +34,19 @@ export function normalizeProviderPriority(policy: unknown): string[] {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Resolve a node's capability slots
|
|
38
|
-
*
|
|
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).
|
|
39
|
+
*
|
|
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.
|
|
49
|
+
*
|
|
39
50
|
* (The former per-provider `providerRoles` cap has been removed; a persisted
|
|
40
51
|
* meshes.json is migrated to slots on load, so by the time a node reaches routing
|
|
41
52
|
* its cap already lives on `slots[].maxParallel`.)
|
package/src/repo-mesh-types.ts
CHANGED
|
@@ -398,6 +398,33 @@ export interface RepoMeshRelatedRepo {
|
|
|
398
398
|
* (see migrateLoadedMeshConfig).
|
|
399
399
|
*/
|
|
400
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Unified mirrored member state — the per-machine RUNTIME facts a remote member
|
|
403
|
+
* self-reports (REMOTE-NODE-SLOTS-COORDINATOR-LOCAL fix).
|
|
404
|
+
*
|
|
405
|
+
* A single self-reported observability envelope stamped by the daemon that OWNS a
|
|
406
|
+
* node's workspace and carried wholesale on the git_status envelope
|
|
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
|
|
410
|
+
* (mesh-node-identity.recordInlineMeshDirectGitTruth).
|
|
411
|
+
*
|
|
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.
|
|
418
|
+
*/
|
|
419
|
+
export interface MeshReportedMemberState {
|
|
420
|
+
/** Reporter node's detected provider CLI/ACP versions (keyed by provider id). */
|
|
421
|
+
providerVersions?: Record<string, string>;
|
|
422
|
+
/** Reporter daemon's build version (getDaemonBuildInfo().version). */
|
|
423
|
+
daemonBuildVersion?: string;
|
|
424
|
+
/** Epoch ms the report was stamped; used for mirror staleness reasoning. */
|
|
425
|
+
lastReportedAt?: number;
|
|
426
|
+
}
|
|
427
|
+
|
|
401
428
|
export interface RepoMeshNodePolicy {
|
|
402
429
|
readOnly?: boolean;
|
|
403
430
|
canPush?: boolean;
|
|
@@ -915,6 +942,20 @@ export interface LocalMeshNodeEntry {
|
|
|
915
942
|
* owning daemon, carried on the git_status envelope (reporterDaemonBuildVersion)
|
|
916
943
|
* alongside the provider versions. Absent until first reported. */
|
|
917
944
|
reportedDaemonBuildVersion?: string;
|
|
945
|
+
/**
|
|
946
|
+
* Unified mirrored "member state" self-reported by the daemon that owns this
|
|
947
|
+
* node's workspace, carried wholesale on the git_status envelope
|
|
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.
|
|
957
|
+
*/
|
|
958
|
+
reportedMemberState?: MeshReportedMemberState;
|
|
918
959
|
/**
|
|
919
960
|
* The operator-set machine nickname (config.machineNickname) of the daemon
|
|
920
961
|
* that owns this node's workspace. The local coordinator stamps its own
|