@adhdev/daemon-core 0.9.82-rc.460 → 0.9.82-rc.462
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 +7 -0
- package/dist/detection/cli-detector.d.ts +17 -0
- package/dist/git/git-commands.d.ts +14 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +567 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +565 -27
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events-pending.d.ts +75 -1
- package/dist/mesh/mesh-events.d.ts +2 -2
- package/dist/mesh/mesh-node-identity.d.ts +4 -0
- package/dist/mesh/mesh-reconcile-loop.d.ts +12 -0
- package/dist/mesh/mesh-runtime-store.d.ts +17 -0
- package/dist/providers/approval-utils.d.ts +1 -0
- package/dist/repo-mesh-types.d.ts +111 -0
- package/package.json +3 -3
- package/src/boot/daemon-lifecycle.ts +15 -2
- package/src/commands/high-family/mesh-events.ts +14 -1
- package/src/commands/high-family/mesh-status.ts +29 -2
- package/src/config/mesh-config.ts +13 -0
- package/src/detection/cli-detector.ts +66 -0
- package/src/git/git-commands.ts +35 -2
- package/src/index.ts +1 -1
- package/src/mesh/coordinator-prompt.ts +19 -1
- package/src/mesh/mesh-event-forwarding.ts +19 -1
- package/src/mesh/mesh-events-pending.ts +465 -5
- package/src/mesh/mesh-events.ts +5 -0
- package/src/mesh/mesh-node-identity.ts +77 -6
- package/src/mesh/mesh-reconcile-loop.ts +74 -1
- package/src/mesh/mesh-runtime-store.ts +30 -0
- package/src/providers/approval-utils.ts +1 -1
- package/src/providers/cli-provider-instance.ts +24 -12
- package/src/repo-mesh-types.ts +111 -0
|
@@ -52,6 +52,63 @@ export interface PendingEventEmitHint {
|
|
|
52
52
|
/** coordinatorRunId to fold into the derived identity when the event lacks one. */
|
|
53
53
|
coordinatorRunId?: string;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* T6 (B3c) enforce switch. When ON, the drain path stops passing an unversioned
|
|
57
|
+
* (v1) or a validation-failing v2 event through to the coordinator — it QUARANTINES
|
|
58
|
+
* it instead (excluded from the delivered batch + WARN + counter), and unicast
|
|
59
|
+
* routing is the only delivery path (there is no v1 broadcast fallback). Off (the
|
|
60
|
+
* default) preserves the accept-and-warn rollout behaviour exactly.
|
|
61
|
+
*
|
|
62
|
+
* Per the rollout plan (§1 decision 4): enforce is `MESH_PROTOCOL_V2_ENFORCE` (env)
|
|
63
|
+
* — its activation is a deliberate operational step taken ONLY after daemonBuilds
|
|
64
|
+
* confirms every node emits v2 (§배포 게이트 1 / risk §4). So the code default is
|
|
65
|
+
* OFF; flipping the env back to accept mode is a pure-env rollback (no code change,
|
|
66
|
+
* no data migration — the schema is additive). Read at call time so a test /
|
|
67
|
+
* operator can toggle it without a restart.
|
|
68
|
+
*
|
|
69
|
+
* Quarantine (not drop) keeps the loss-free invariant. The DESTRUCTIVE drain has
|
|
70
|
+
* already consumed the event from its store by the time routing runs, so "held
|
|
71
|
+
* back" here means: excluded from the delivered batch AND mirrored into the mesh
|
|
72
|
+
* ledger as a recoverable `event_held` entry (the same recovery channel the
|
|
73
|
+
* pending-trim path uses). It is observable via the counters + the ledger, so an
|
|
74
|
+
* operator can requeue it after fixing the producer. The non-destructive PEEK path
|
|
75
|
+
* (countMetrics=false) merely omits the event from the returned list — it never
|
|
76
|
+
* consumed it and must not ledger-record on every status poll.
|
|
77
|
+
*/
|
|
78
|
+
export declare function isMeshProtocolV2EnforceEnabled(): boolean;
|
|
79
|
+
/** Observability counters for the v2 drain path. Read by tests and surfaced in
|
|
80
|
+
* mesh_status (B4/T6). Process-lifetime totals — never reset in production. */
|
|
81
|
+
declare const meshV2DrainCounters: {
|
|
82
|
+
/** v2 events that passed validation and unicast/broadcast routing → delivered. */
|
|
83
|
+
v2Delivered: number;
|
|
84
|
+
/** v2 unicast events skipped because intendedFor addressed another coordinator. */
|
|
85
|
+
v2RoutedAway: number;
|
|
86
|
+
/** v2 events skipped because their eventId was already drained (idempotency). */
|
|
87
|
+
v2DedupSkipped: number;
|
|
88
|
+
/** v2 events that failed assertPendingMeshCoordinatorEventV2 but were PASSED
|
|
89
|
+
* THROUGH (accept mode). Non-zero here is the rollout signal that a producer
|
|
90
|
+
* emits a malformed envelope. */
|
|
91
|
+
v2ValidationFailedAccepted: number;
|
|
92
|
+
/** unicast events re-attributed to the drainer via daemon-core match (a
|
|
93
|
+
* coordinatorRunId change orphaned them). */
|
|
94
|
+
v2ReattributedToDrainer: number;
|
|
95
|
+
/** v1 (unversioned) events passed through as broadcast (rollout baseline). */
|
|
96
|
+
v1BroadcastAccepted: number;
|
|
97
|
+
/** T6 enforce: v2 events that FAILED validation and were QUARANTINED (held back
|
|
98
|
+
* from delivery, not dropped). Non-zero here means a producer is still emitting a
|
|
99
|
+
* malformed envelope after enforce was turned on. */
|
|
100
|
+
v2ValidationFailedQuarantined: number;
|
|
101
|
+
/** T6 enforce: v1 (unversioned) events QUARANTINED because no v2 envelope could be
|
|
102
|
+
* derived at emit time. Non-zero here means a producer path still emits v1 after
|
|
103
|
+
* enforce — it should reach 0 once every node is on a v2-stamping build. */
|
|
104
|
+
v1UnversionedQuarantined: number;
|
|
105
|
+
};
|
|
106
|
+
/** Test/observability accessor for the v2 drain counters (snapshot copy). */
|
|
107
|
+
export declare function getMeshV2DrainCounters(): Readonly<typeof meshV2DrainCounters>;
|
|
108
|
+
/** Test helper: zero the v2 drain counters so a test starts from a clean slate. */
|
|
109
|
+
export declare function __resetMeshV2DrainCountersForTests(): void;
|
|
110
|
+
/** Test helper: clear the one-shot WARN dedup set. */
|
|
111
|
+
export declare function __resetMeshV2WarnDedupForTests(): void;
|
|
55
112
|
export declare function readRefineJobId(event: {
|
|
56
113
|
metadataEvent?: Record<string, unknown>;
|
|
57
114
|
} | Record<string, unknown>): string;
|
|
@@ -73,6 +130,19 @@ export declare function hasPendingCoordinatorEventDuplicate(event: PendingMeshCo
|
|
|
73
130
|
* key is stable across re-delivery. An already-stamped event is returned as-is.
|
|
74
131
|
*/
|
|
75
132
|
export declare function stampPendingEventV2(event: PendingMeshCoordinatorEvent, hint?: PendingEventEmitHint): PendingMeshCoordinatorEvent;
|
|
133
|
+
/**
|
|
134
|
+
* Copy the v2 envelope fields that are present on `event` onto a flat wire
|
|
135
|
+
* payload. Only sets a field when it is present, so a v1 event contributes
|
|
136
|
+
* nothing (the payload stays v1-shaped and version-skew safe).
|
|
137
|
+
*/
|
|
138
|
+
export declare function serializeV2EnvelopeToWire(event: PendingMeshCoordinatorEvent): Record<string, unknown>;
|
|
139
|
+
/**
|
|
140
|
+
* Restore the v2 envelope fields from a flat wire payload for a re-queue. Only
|
|
141
|
+
* returns fields that survive validation; a payload missing/malforming a field
|
|
142
|
+
* yields a partial (or empty) object so the re-queue path stays v1-safe. The
|
|
143
|
+
* eventId is returned verbatim — its preservation is the idempotency guarantee.
|
|
144
|
+
*/
|
|
145
|
+
export declare function readV2EnvelopeFromWire(payload: Record<string, unknown>): Partial<Pick<PendingMeshCoordinatorEvent, 'protocolVersion' | 'eventId' | 'scope' | 'dispatchedBy' | 'intendedFor'>>;
|
|
76
146
|
export declare function queuePendingMeshCoordinatorEvent(rawEvent: PendingMeshCoordinatorEvent, hint?: PendingEventEmitHint): boolean;
|
|
77
147
|
/**
|
|
78
148
|
* Drain and return pending coordinator events for meshId, removing the drained
|
|
@@ -87,6 +157,7 @@ export declare function queuePendingMeshCoordinatorEvent(rawEvent: PendingMeshCo
|
|
|
87
157
|
*/
|
|
88
158
|
export declare function drainPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string | ReadonlyArray<string>, opts?: {
|
|
89
159
|
onlyEvents?: ReadonlySet<string>;
|
|
160
|
+
drainerIdentity?: CoordinatorIdentity;
|
|
90
161
|
}): PendingMeshCoordinatorEvent[];
|
|
91
162
|
/**
|
|
92
163
|
* FALSE-BLOCKER-CLONE-QUEUE: retract any still-UNDELIVERED `mesh:dispatch_blocked`
|
|
@@ -103,7 +174,9 @@ export declare function drainPendingMeshCoordinatorEvents(meshId?: string, coord
|
|
|
103
174
|
*/
|
|
104
175
|
export declare function retractPendingDispatchBlockedEvent(meshId: string | undefined, taskId: string | undefined, coordinatorDaemonId?: string): number;
|
|
105
176
|
/** Peek at pending coordinator events without draining (non-destructive). */
|
|
106
|
-
export declare function getPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string | ReadonlyArray<string
|
|
177
|
+
export declare function getPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string | ReadonlyArray<string>, opts?: {
|
|
178
|
+
drainerIdentity?: CoordinatorIdentity;
|
|
179
|
+
}): readonly PendingMeshCoordinatorEvent[];
|
|
107
180
|
/**
|
|
108
181
|
* Test helper: purge all pending-event state for a mesh — SQLite rows
|
|
109
182
|
* (including drained fingerprint history) and JSONL files.
|
|
@@ -111,3 +184,4 @@ export declare function getPendingMeshCoordinatorEvents(meshId?: string, coordin
|
|
|
111
184
|
export declare function __clearMeshPendingEventsForTests(meshId: string): void;
|
|
112
185
|
/** Explicitly clear all pending coordinator events for a mesh (and coordinator if scoped). */
|
|
113
186
|
export declare function clearPendingMeshCoordinatorEvents(meshId?: string, coordinatorDaemonId?: string): void;
|
|
187
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { PendingMeshCoordinatorEvent } from './mesh-events-pending.js';
|
|
2
|
-
export { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, } from './mesh-events-pending.js';
|
|
2
|
+
export { queuePendingMeshCoordinatorEvent, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, serializeV2EnvelopeToWire, readV2EnvelopeFromWire, getMeshV2DrainCounters, isMeshProtocolV2EnforceEnabled, } from './mesh-events-pending.js';
|
|
3
3
|
export { reconcileDirectDispatchCompletionFromTranscript, } from './mesh-events-stale.js';
|
|
4
|
-
export { setupMeshReconcileLoop, runMeshReconcileTick, resolveCoordinatorDrainDeliverability, shouldHoldPendingDrainForBusyLocalCoordinator, } from './mesh-reconcile-loop.js';
|
|
4
|
+
export { setupMeshReconcileLoop, runMeshReconcileTick, resolveCoordinatorDrainDeliverability, shouldHoldPendingDrainForBusyLocalCoordinator, getMeshV2BackstopCounters, } from './mesh-reconcile-loop.js';
|
|
5
5
|
export type { MeshQueueTriggerResult } from './mesh-events-coordinator.js';
|
|
6
6
|
export { tryAssignQueueTask, isSessionActivelyGenerating, triggerMeshQueue, handleMeshForwardEvent, setupMeshEventForwarding, isMeshCoordinatorEvent, __resetIdleAutoFastForwardForTests, __resetMeshWorkspaceCacheForTests, } from './mesh-events-coordinator.js';
|
|
@@ -49,6 +49,8 @@ export declare function recordInlineMeshDirectGitTruth(node: any, git: Record<st
|
|
|
49
49
|
reporterPlatform: string | null;
|
|
50
50
|
reporterArch: string | null;
|
|
51
51
|
reporterMachineNickname: string | null;
|
|
52
|
+
reporterProviderVersions: Record<string, string> | null;
|
|
53
|
+
reporterDaemonBuildVersion: string | null;
|
|
52
54
|
};
|
|
53
55
|
/**
|
|
54
56
|
* Persist the live self-reported platform/arch onto the local meshes.json node
|
|
@@ -65,6 +67,8 @@ export declare function persistNodeReporterPlatform(meshSource: 'inline_cache' |
|
|
|
65
67
|
reporterPlatform: string | null;
|
|
66
68
|
reporterArch: string | null;
|
|
67
69
|
reporterMachineNickname?: string | null;
|
|
70
|
+
reporterProviderVersions?: Record<string, string> | null;
|
|
71
|
+
reporterDaemonBuildVersion?: string | null;
|
|
68
72
|
}): void;
|
|
69
73
|
export declare function inlineMeshCarriesTransientNodeTruth(inlineMesh: any): boolean;
|
|
70
74
|
export declare function readInlineMeshNodeId(node: any): string;
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
|
|
2
|
+
declare const meshV2BackstopCounters: {
|
|
3
|
+
/** PHASE-4 transcript synthesis actually reconciled a missing completion. */
|
|
4
|
+
phase4SynthesisFired: number;
|
|
5
|
+
/** Acked-hold transcript fast-track promoted a synth ahead of the death deadline. */
|
|
6
|
+
ackedHoldFastTrackFired: number;
|
|
7
|
+
/** Acked-hold death-deadline backstop released a held synth (the 8-min net). */
|
|
8
|
+
ackedHoldDeathDeadlineFired: number;
|
|
9
|
+
};
|
|
10
|
+
/** Test/observability accessor for the v2 last-resort backstop counters (snapshot). */
|
|
11
|
+
export declare function getMeshV2BackstopCounters(): Readonly<typeof meshV2BackstopCounters>;
|
|
12
|
+
/** Test helper: zero the backstop counters so a case starts from a clean slate. */
|
|
13
|
+
export declare function __resetMeshV2BackstopCountersForTests(): void;
|
|
2
14
|
export declare function __resetReconcileInFlightSynthDebounceForTests(): void;
|
|
3
15
|
/**
|
|
4
16
|
* DRAIN-WITHOUT-INJECT guard. Classify, for a mesh on THIS daemon, whether a
|
|
@@ -425,6 +425,23 @@ export declare class MeshRuntimeStore {
|
|
|
425
425
|
payload: unknown;
|
|
426
426
|
}>;
|
|
427
427
|
hasPendingEventFingerprint(meshId: string, fingerprint: string): boolean;
|
|
428
|
+
/**
|
|
429
|
+
* B3a — v2 eventId idempotency. Returns true when a row with this event_id has
|
|
430
|
+
* ALREADY been drained (drained = 1) for the mesh. Drained rows are retained
|
|
431
|
+
* (soft-marked, not deleted until mesh deletion), so this is a durable, restart-
|
|
432
|
+
* surviving dedup: a v2 event whose eventId was already consumed is skipped on
|
|
433
|
+
* re-delivery even when its content fingerprint differs. Scoped by mesh_id +
|
|
434
|
+
* the partial event_id index (idx_mesh_pending_events_event_id).
|
|
435
|
+
*/
|
|
436
|
+
hasDrainedEventId(meshId: string, eventId: string): boolean;
|
|
437
|
+
/**
|
|
438
|
+
* B3a — snapshot of the v2 event_ids ALREADY drained (drained = 1) for the mesh.
|
|
439
|
+
* Taken BEFORE a drain call marks the current batch drained=1, so the resulting
|
|
440
|
+
* set names only PRIOR drains — the re-delivery dedup baseline. (Reading it after
|
|
441
|
+
* the drain would self-match the batch's own freshly-drained rows.) Non-v2 rows
|
|
442
|
+
* have a NULL event_id and are excluded by the index/WHERE.
|
|
443
|
+
*/
|
|
444
|
+
drainedEventIdsForMesh(meshId: string): Set<string>;
|
|
428
445
|
upsertMission(mission: {
|
|
429
446
|
id: string;
|
|
430
447
|
meshId: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ProviderModule } from './contracts.js';
|
|
2
|
+
export declare function normalizeApprovalLabel(value: string): string;
|
|
2
3
|
/**
|
|
3
4
|
* True when any of the given button labels reads as a decline/negative option
|
|
4
5
|
* (No / Deny / Cancel / Skip / …). Used as the second half of an approval-modal
|
|
@@ -447,6 +447,23 @@ export interface RepoMeshNodeCapabilities {
|
|
|
447
447
|
canPush?: boolean;
|
|
448
448
|
readOnly?: boolean;
|
|
449
449
|
userLabels?: string[];
|
|
450
|
+
/**
|
|
451
|
+
* Detected provider CLI/ACP versions on this node, keyed by provider id
|
|
452
|
+
* (e.g. `{ 'claude-cli': '1.2.3', 'codex-cli': '0.9.0' }`). Populated from the
|
|
453
|
+
* same CLI detection pass that feeds providerPriority (see buildProviderVersions
|
|
454
|
+
* over detectCLIs' CLIInfo[]). Absent/undefined when detection has not run or a
|
|
455
|
+
* daemon predates the exposure — never a hard signal, purely observability so a
|
|
456
|
+
* coordinator can spot a provider-version skew across nodes before dispatch.
|
|
457
|
+
* Additive: existing status consumers ignore it.
|
|
458
|
+
*/
|
|
459
|
+
providerVersions?: Record<string, string>;
|
|
460
|
+
/**
|
|
461
|
+
* The daemon build version (package.json version baked into the running bundle,
|
|
462
|
+
* see getDaemonBuildInfo().version) that detected the above providerVersions.
|
|
463
|
+
* Complements the commit-level daemonBuild stamp with a human-readable version
|
|
464
|
+
* for node-card rendering. Absent when the build define was not injected.
|
|
465
|
+
*/
|
|
466
|
+
daemonBuildVersion?: string;
|
|
450
467
|
}
|
|
451
468
|
export interface DetectedCommand {
|
|
452
469
|
command: string;
|
|
@@ -580,6 +597,19 @@ export interface LocalMeshNodeEntry {
|
|
|
580
597
|
*/
|
|
581
598
|
reportedPlatform?: string;
|
|
582
599
|
reportedArch?: string;
|
|
600
|
+
/**
|
|
601
|
+
* Live, self-healed provider CLI/ACP versions reported by the daemon that owns
|
|
602
|
+
* this node's workspace, carried on the git_status envelope (reporterProviderVersions)
|
|
603
|
+
* and persisted by the coordinator on each direct git probe — mirrors the
|
|
604
|
+
* reportedPlatform/reportedArch self-heal pattern. Auto-detected truth, overwritten
|
|
605
|
+
* by the next report so a stale value never sticks. Absent until the first probe
|
|
606
|
+
* carrying versions succeeds. Surfaced as RepoMeshNodeStatus.providerVersions.
|
|
607
|
+
*/
|
|
608
|
+
reportedProviderVersions?: Record<string, string>;
|
|
609
|
+
/** Live, self-healed daemon build version (getDaemonBuildInfo().version) of the
|
|
610
|
+
* owning daemon, carried on the git_status envelope (reporterDaemonBuildVersion)
|
|
611
|
+
* alongside the provider versions. Absent until first reported. */
|
|
612
|
+
reportedDaemonBuildVersion?: string;
|
|
583
613
|
/**
|
|
584
614
|
* The operator-set machine nickname (config.machineNickname) of the daemon
|
|
585
615
|
* that owns this node's workspace. The local coordinator stamps its own
|
|
@@ -697,6 +727,76 @@ export interface RepoMeshStatus {
|
|
|
697
727
|
* optional. Mirrors the MCP `mesh_status` tool's `magiActivity` field.
|
|
698
728
|
*/
|
|
699
729
|
magiActivity?: MeshMagiActivitySummary[];
|
|
730
|
+
/**
|
|
731
|
+
* T7 (visibility 7-2b): provider CLI/ACP version skew across nodes. Each entry
|
|
732
|
+
* names a provider running ≥2 distinct versions across the nodes that reported
|
|
733
|
+
* it, with the node ids per version. Observational only — never a dispatch
|
|
734
|
+
* blocker. Omitted when every reported provider is uniform (or none reported).
|
|
735
|
+
* Mirrors the MCP `mesh_status` tool's `providerVersionSkew` field.
|
|
736
|
+
*/
|
|
737
|
+
providerVersionSkew?: MeshProviderVersionSkew[];
|
|
738
|
+
/** Human-readable companion warning to providerVersionSkew. Omitted when no skew. */
|
|
739
|
+
providerVersionSkewWarning?: string;
|
|
740
|
+
/**
|
|
741
|
+
* T7 (B4): mesh-protocol-v2 adoption metrics for the batch of pending events
|
|
742
|
+
* surfaced in the drain backing this status. Snapshot, not a durable counter.
|
|
743
|
+
* Omitted when nothing was drained. Mirrors the MCP tool's meshProtocolMetrics.
|
|
744
|
+
*/
|
|
745
|
+
meshProtocolMetrics?: MeshProtocolMetrics;
|
|
746
|
+
/**
|
|
747
|
+
* T6 (B3c): live process-lifetime mesh-protocol-v2 enforce counters from THIS
|
|
748
|
+
* daemon — the enforce flag state, drain-routing tallies (deliver / route-away /
|
|
749
|
+
* dedup / quarantine), and the last-resort backstop fire counts (PHASE-4 synth,
|
|
750
|
+
* acked-hold fast-track / death-deadline). Diagnostic-only and never cached (a
|
|
751
|
+
* live snapshot). Under enforce, non-zero quarantine or backstop counts are the
|
|
752
|
+
* rollout-health signal (target 0). Omitted when unavailable.
|
|
753
|
+
*/
|
|
754
|
+
meshProtocolV2Counters?: MeshProtocolV2Counters;
|
|
755
|
+
}
|
|
756
|
+
/** T6 (B3c) live v2 enforce/observability counters (see RepoMeshStatus.meshProtocolV2Counters). */
|
|
757
|
+
export interface MeshProtocolV2Counters {
|
|
758
|
+
/** True when MESH_PROTOCOL_V2_ENFORCE is active on this daemon. */
|
|
759
|
+
enforce: boolean;
|
|
760
|
+
/** Drain-path routing tallies (accept + enforce). Process-lifetime totals. */
|
|
761
|
+
drain: {
|
|
762
|
+
v2Delivered: number;
|
|
763
|
+
v2RoutedAway: number;
|
|
764
|
+
v2DedupSkipped: number;
|
|
765
|
+
v2ValidationFailedAccepted: number;
|
|
766
|
+
v2ReattributedToDrainer: number;
|
|
767
|
+
v1BroadcastAccepted: number;
|
|
768
|
+
v2ValidationFailedQuarantined: number;
|
|
769
|
+
v1UnversionedQuarantined: number;
|
|
770
|
+
};
|
|
771
|
+
/** Last-resort backstop fire counts. Target 0 under a healthy v2 contract. */
|
|
772
|
+
backstop: {
|
|
773
|
+
phase4SynthesisFired: number;
|
|
774
|
+
ackedHoldFastTrackFired: number;
|
|
775
|
+
ackedHoldDeathDeadlineFired: number;
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
/** One provider's version skew across mesh nodes (see RepoMeshStatus.providerVersionSkew). */
|
|
779
|
+
export interface MeshProviderVersionSkew {
|
|
780
|
+
/** Provider id (e.g. 'claude-cli'). */
|
|
781
|
+
provider: string;
|
|
782
|
+
/** Each distinct detected version and the node ids running it. */
|
|
783
|
+
versions: Array<{
|
|
784
|
+
version: string;
|
|
785
|
+
nodeIds: string[];
|
|
786
|
+
}>;
|
|
787
|
+
}
|
|
788
|
+
/** Mesh-protocol-v2 adoption snapshot over one drain (see RepoMeshStatus.meshProtocolMetrics). */
|
|
789
|
+
export interface MeshProtocolMetrics {
|
|
790
|
+
/** Total pending events surfaced in the drain. */
|
|
791
|
+
total: number;
|
|
792
|
+
/** Count carrying a v2 envelope (protocolVersion '2.0'). */
|
|
793
|
+
v2: number;
|
|
794
|
+
/** Count still on v1 (unstamped). */
|
|
795
|
+
v1: number;
|
|
796
|
+
/** v2/total, rounded to 2 decimals (0 when total is 0). */
|
|
797
|
+
v2Ratio: number;
|
|
798
|
+
/** Scope breakdown of the v2 events (unicast/broadcast/system/unspecified → count). */
|
|
799
|
+
scopes: Record<string, number>;
|
|
700
800
|
}
|
|
701
801
|
export type { RepoMeshSessionStatus } from '@adhdev/mesh-shared';
|
|
702
802
|
import type { RepoMeshSessionStatus } from '@adhdev/mesh-shared';
|
|
@@ -741,6 +841,17 @@ export interface RepoMeshNodeStatus {
|
|
|
741
841
|
*/
|
|
742
842
|
gitProbePending?: boolean;
|
|
743
843
|
providers: string[];
|
|
844
|
+
/**
|
|
845
|
+
* Detected provider CLI/ACP versions on this node, keyed by provider id. Mirrors
|
|
846
|
+
* RepoMeshNodeCapabilities.providerVersions onto the status snapshot so the mesh
|
|
847
|
+
* UI / coordinator prompt can render per-provider versions and flag a version
|
|
848
|
+
* skew across nodes. Optional — omitted by daemons predating the exposure or when
|
|
849
|
+
* detection has not run. Additive; existing consumers ignore it. */
|
|
850
|
+
providerVersions?: Record<string, string>;
|
|
851
|
+
/** Human-readable daemon build version (getDaemonBuildInfo().version) of the
|
|
852
|
+
* daemon that owns this node. Complements the per-daemon commit stamp
|
|
853
|
+
* (daemonBuilds) for node-card display. Omitted when unknown. */
|
|
854
|
+
daemonBuildVersion?: string;
|
|
744
855
|
activeSessions: string[];
|
|
745
856
|
activeSessionDetails?: RepoMeshSessionStatus[];
|
|
746
857
|
providerPriority?: string[];
|
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.462",
|
|
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.462",
|
|
51
|
+
"@adhdev/session-host-core": "0.9.82-rc.462",
|
|
52
52
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
@@ -25,7 +25,8 @@ import { VersionArchive, detectAllVersions } from '../providers/version-archive.
|
|
|
25
25
|
import { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
|
|
26
26
|
import { DevServer } from '../daemon/dev-server.js';
|
|
27
27
|
import { detectIDEs, type IDEInfo } from '../detection/ide-detector.js';
|
|
28
|
-
import { detectCLI, detectCLIs } from '../detection/cli-detector.js';
|
|
28
|
+
import { detectCLI, detectCLIs, getCachedProviderVersions } from '../detection/cli-detector.js';
|
|
29
|
+
import { getDaemonBuildInfo } from '../build-info.js';
|
|
29
30
|
import { SessionRegistry } from '../sessions/registry.js';
|
|
30
31
|
import { LOG, installGlobalInterceptor } from '../logging/logger.js';
|
|
31
32
|
import {
|
|
@@ -304,7 +305,19 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
|
|
|
304
305
|
providerLoader,
|
|
305
306
|
instanceManager,
|
|
306
307
|
sessionRegistry,
|
|
307
|
-
gitCommandServices: createDefaultGitCommandServices(
|
|
308
|
+
gitCommandServices: createDefaultGitCommandServices({
|
|
309
|
+
// T7: fold this daemon's cached provider versions + build version onto the
|
|
310
|
+
// git_status envelope so the mesh coordinator self-heals each node's
|
|
311
|
+
// providerVersions. Non-blocking: reads a TTL cache, lazily refreshed.
|
|
312
|
+
getReporterProviderVersions: () => {
|
|
313
|
+
const providerVersions = getCachedProviderVersions(providerLoader);
|
|
314
|
+
const daemonBuildVersion = getDaemonBuildInfo().version;
|
|
315
|
+
return {
|
|
316
|
+
...(Object.keys(providerVersions).length > 0 ? { providerVersions } : {}),
|
|
317
|
+
...(daemonBuildVersion && daemonBuildVersion !== 'unknown' ? { daemonBuildVersion } : {}),
|
|
318
|
+
};
|
|
319
|
+
},
|
|
320
|
+
}),
|
|
308
321
|
onProviderSettingChanged: async (providerType) => {
|
|
309
322
|
await refreshProviderAvailability(providerType);
|
|
310
323
|
config.onStatusChange?.();
|
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
drainPendingMeshCoordinatorEvents,
|
|
13
13
|
shouldHoldPendingDrainForBusyLocalCoordinator,
|
|
14
14
|
resolveCoordinatorDrainDeliverability,
|
|
15
|
+
getMeshV2DrainCounters,
|
|
16
|
+
getMeshV2BackstopCounters,
|
|
17
|
+
isMeshProtocolV2EnforceEnabled,
|
|
15
18
|
} from '../../mesh/mesh-events.js';
|
|
16
19
|
import { normalizeInteractivePromptResponse } from '../../providers/types/interactive-prompt.js';
|
|
17
20
|
import type { HighFamilyContext, HighFamilyHandler } from './types.js';
|
|
@@ -66,11 +69,21 @@ export const meshEventsHandlers: Record<string, HighFamilyHandler> = {
|
|
|
66
69
|
return { success: true, events: [], heldForBusyLocalCoordinator: true, hasLiveCliCoordinator };
|
|
67
70
|
}
|
|
68
71
|
const events = drainPendingMeshCoordinatorEvents(meshId || undefined, coordinatorDaemonId);
|
|
72
|
+
// T6 (B3c): ride the live v2 enforce/backstop counters on the drain response so a
|
|
73
|
+
// pure stdio MCP coordinator (which reads its inbox via this IPC call, not the
|
|
74
|
+
// daemon-core mesh_status command) sees the same enforce state + quarantine /
|
|
75
|
+
// last-resort-backstop tallies. Process-lifetime snapshot; the counters were just
|
|
76
|
+
// updated by the drain above. Additive — omitting it keeps version-skewed pullers safe.
|
|
77
|
+
const meshProtocolV2Counters = {
|
|
78
|
+
enforce: isMeshProtocolV2EnforceEnabled(),
|
|
79
|
+
drain: { ...getMeshV2DrainCounters() },
|
|
80
|
+
backstop: { ...getMeshV2BackstopCounters() },
|
|
81
|
+
};
|
|
69
82
|
// SELF-COORDINATOR INBOX LEVEL-DRAIN: when the busy local coordinator drained its OWN
|
|
70
83
|
// inbox (selfCoordinatorInboxRead), tell the puller these events were surfaced through
|
|
71
84
|
// the caller's tool result — it must NOT re-forward them into the (busy) PTY (that is the
|
|
72
85
|
// lossy path). Absent the flag, delivery is unchanged (reconcile-owned PTY / remote pull).
|
|
73
|
-
return { success: true, events, hasLiveCliCoordinator, ...(selfCoordinatorInboxRead ? { surfacedForSelfCoordinator: true } : {}) };
|
|
86
|
+
return { success: true, events, hasLiveCliCoordinator, meshProtocolV2Counters, ...(selfCoordinatorInboxRead ? { surfacedForSelfCoordinator: true } : {}) };
|
|
74
87
|
},
|
|
75
88
|
|
|
76
89
|
interactive_prompt_response: async (ctx: HighFamilyContext, args: any) => {
|
|
@@ -19,7 +19,13 @@ import {
|
|
|
19
19
|
normalizeMeshNodeId,
|
|
20
20
|
daemonIdsEquivalent,
|
|
21
21
|
} from '@adhdev/mesh-shared';
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
getPendingMeshCoordinatorEvents,
|
|
24
|
+
getMeshV2DrainCounters,
|
|
25
|
+
getMeshV2BackstopCounters,
|
|
26
|
+
isMeshProtocolV2EnforceEnabled,
|
|
27
|
+
} from '../../mesh/mesh-events.js';
|
|
28
|
+
import type { MeshProtocolV2Counters } from '../../repo-mesh-types.js';
|
|
23
29
|
import { getRecentUnroutableDeliveries } from '../../mesh/mesh-routing.js';
|
|
24
30
|
import { normalizeMeshDaemonRole, resolveMeshHostStatus } from '../../mesh/mesh-host-ownership.js';
|
|
25
31
|
import { buildPreviewFreshness } from '../../mesh/preview-freshness.js';
|
|
@@ -340,6 +346,16 @@ export const meshStatusHandlers: Record<string, HighFamilyHandler> = {
|
|
|
340
346
|
machineStatus: node.machineStatus,
|
|
341
347
|
health: 'unknown',
|
|
342
348
|
providers: node.providers || [],
|
|
349
|
+
// T7: surface self-healed provider versions + build version (from the
|
|
350
|
+
// git_status envelope, persisted on the node) so the coordinator/UI can
|
|
351
|
+
// spot a provider-version skew across nodes. Additive; omitted when a
|
|
352
|
+
// node has never reported them.
|
|
353
|
+
...(node.reportedProviderVersions && typeof node.reportedProviderVersions === 'object'
|
|
354
|
+
? { providerVersions: node.reportedProviderVersions }
|
|
355
|
+
: {}),
|
|
356
|
+
...(typeof node.reportedDaemonBuildVersion === 'string' && node.reportedDaemonBuildVersion
|
|
357
|
+
? { daemonBuildVersion: node.reportedDaemonBuildVersion }
|
|
358
|
+
: {}),
|
|
343
359
|
providerPriority,
|
|
344
360
|
activeSessions: [],
|
|
345
361
|
activeSessionDetails: [],
|
|
@@ -582,6 +598,15 @@ export const meshStatusHandlers: Record<string, HighFamilyHandler> = {
|
|
|
582
598
|
// that a worker completion was lost (envelope present, mesh unresolved) instead
|
|
583
599
|
// of it vanishing silently. Diagnostic-only — never cached (see omit below).
|
|
584
600
|
const unroutableDeliveries = getRecentUnroutableDeliveries();
|
|
601
|
+
// T6 (B3c): live enforce/observability counters from this daemon. A
|
|
602
|
+
// process-lifetime snapshot (never cached — like unroutableDeliveries)
|
|
603
|
+
// so an operator/coordinator can read enforce state, quarantine tallies,
|
|
604
|
+
// and last-resort backstop fires straight from the aggregate status.
|
|
605
|
+
const meshProtocolV2Counters: MeshProtocolV2Counters = {
|
|
606
|
+
enforce: isMeshProtocolV2EnforceEnabled(),
|
|
607
|
+
drain: { ...getMeshV2DrainCounters() },
|
|
608
|
+
backstop: { ...getMeshV2BackstopCounters() },
|
|
609
|
+
};
|
|
585
610
|
const previewFreshness = (() => {
|
|
586
611
|
const localRepoRoot = nodeStatuses
|
|
587
612
|
.map((node: any) => readStringValue(node?.git?.repoRoot, node?.repoRoot, node?.workspace))
|
|
@@ -697,6 +722,7 @@ export const meshStatusHandlers: Record<string, HighFamilyHandler> = {
|
|
|
697
722
|
...(historicalSessions ? { historicalSessions } : {}),
|
|
698
723
|
...(pendingCoordinatorEvents.length > 0 ? { pendingCoordinatorEvents } : {}),
|
|
699
724
|
...(unroutableDeliveries.length > 0 ? { unroutableDeliveries } : {}),
|
|
725
|
+
meshProtocolV2Counters,
|
|
700
726
|
activeRefineJobs: Array.from(ctx.runningRefineJobs.values())
|
|
701
727
|
.filter(job => job.meshId === meshId)
|
|
702
728
|
.map(job => ({
|
|
@@ -708,7 +734,7 @@ export const meshStatusHandlers: Record<string, HighFamilyHandler> = {
|
|
|
708
734
|
targetCoordinatorDaemonId: job.targetCoordinatorDaemonId,
|
|
709
735
|
})),
|
|
710
736
|
};
|
|
711
|
-
const { pendingCoordinatorEvents: _pendingCoordinatorEvents, unroutableDeliveries: _unroutableDeliveries, ...cacheableStatusResult } = statusResult as any;
|
|
737
|
+
const { pendingCoordinatorEvents: _pendingCoordinatorEvents, unroutableDeliveries: _unroutableDeliveries, meshProtocolV2Counters: _meshProtocolV2Counters, ...cacheableStatusResult } = statusResult as any;
|
|
712
738
|
// Verbose carries full mission goals; never store it in the shared
|
|
713
739
|
// (compact) aggregate cache or a later compact poll would return the
|
|
714
740
|
// heavy goals from cache. Return it without caching.
|
|
@@ -719,6 +745,7 @@ export const meshStatusHandlers: Record<string, HighFamilyHandler> = {
|
|
|
719
745
|
...rememberedStatus,
|
|
720
746
|
...(pendingCoordinatorEvents.length > 0 ? { pendingCoordinatorEvents } : {}),
|
|
721
747
|
...(unroutableDeliveries.length > 0 ? { unroutableDeliveries } : {}),
|
|
748
|
+
meshProtocolV2Counters,
|
|
722
749
|
};
|
|
723
750
|
logRepoMeshStatusDebug('return_live', {
|
|
724
751
|
meshId,
|
|
@@ -583,6 +583,13 @@ export function updateNode(
|
|
|
583
583
|
* git_status envelope. Persisted so the friendly label survives across
|
|
584
584
|
* coordinator restarts (mirrors reportedPlatform/reportedArch). */
|
|
585
585
|
reportedMachineNickname?: string;
|
|
586
|
+
/** Owning daemon's self-reported provider CLI/ACP versions + build version,
|
|
587
|
+
* carried on the git_status envelope. Persisted distinctly from userOverrides
|
|
588
|
+
* (auto-detected observability, not operator intent), mirroring the
|
|
589
|
+
* reportedPlatform/reportedArch self-heal so the value survives restarts and
|
|
590
|
+
* is overwritten by the next report. */
|
|
591
|
+
reportedProviderVersions?: Record<string, string>;
|
|
592
|
+
reportedDaemonBuildVersion?: string;
|
|
586
593
|
},
|
|
587
594
|
): LocalMeshNodeEntry | undefined {
|
|
588
595
|
const config = loadMeshConfig();
|
|
@@ -596,6 +603,12 @@ export function updateNode(
|
|
|
596
603
|
if (opts.reportedPlatform && opts.reportedPlatform.trim()) node.reportedPlatform = opts.reportedPlatform.trim();
|
|
597
604
|
if (opts.reportedArch && opts.reportedArch.trim()) node.reportedArch = opts.reportedArch.trim();
|
|
598
605
|
if (opts.reportedMachineNickname && opts.reportedMachineNickname.trim()) node.machineNickname = opts.reportedMachineNickname.trim();
|
|
606
|
+
if (opts.reportedProviderVersions && Object.keys(opts.reportedProviderVersions).length > 0) {
|
|
607
|
+
node.reportedProviderVersions = { ...opts.reportedProviderVersions };
|
|
608
|
+
}
|
|
609
|
+
if (opts.reportedDaemonBuildVersion && opts.reportedDaemonBuildVersion.trim()) {
|
|
610
|
+
node.reportedDaemonBuildVersion = opts.reportedDaemonBuildVersion.trim();
|
|
611
|
+
}
|
|
599
612
|
if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
|
|
600
613
|
if (opts.worktreeBootstrap) node.worktreeBootstrap = opts.worktreeBootstrap;
|
|
601
614
|
if (Object.prototype.hasOwnProperty.call(opts, 'systemPrompt')) {
|
|
@@ -200,3 +200,69 @@ export async function detectCLI(
|
|
|
200
200
|
const all = await detectCLIs(providerLoader, options);
|
|
201
201
|
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
202
202
|
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Fold a detected CLIInfo[] into the `{ providerId: version }` map surfaced as a
|
|
206
|
+
* node's providerVersions (see RepoMeshNodeCapabilities.providerVersions). Only
|
|
207
|
+
* installed providers that produced a parseable version string are included, so a
|
|
208
|
+
* missing entry means "not installed or version unknown" — never a fabricated value.
|
|
209
|
+
* Pure/deterministic: the same detection input always yields the same map, which is
|
|
210
|
+
* what the git_status envelope carries and the mesh_status skew check compares.
|
|
211
|
+
*/
|
|
212
|
+
export function buildProviderVersions(detected: CLIInfo[]): Record<string, string> {
|
|
213
|
+
const out: Record<string, string> = {};
|
|
214
|
+
for (const cli of detected) {
|
|
215
|
+
if (!cli.installed) continue;
|
|
216
|
+
const version = typeof cli.version === 'string' ? cli.version.trim() : '';
|
|
217
|
+
if (!version) continue;
|
|
218
|
+
out[cli.id] = version;
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ─── Cached provider-versions snapshot (mesh visibility, T7) ────────────────
|
|
224
|
+
//
|
|
225
|
+
// A node's providerVersions rides the git_status envelope, which a coordinator
|
|
226
|
+
// probes on every graph refresh. Running the full `--version` exec fan-out on
|
|
227
|
+
// each probe would be far too costly (one spawn per provider per probe), so the
|
|
228
|
+
// snapshot is memoized with a TTL and refreshed lazily in the background: a probe
|
|
229
|
+
// reads the current cache immediately (empty until the first refresh completes)
|
|
230
|
+
// and kicks off a refresh only when the cache is stale, never blocking the git
|
|
231
|
+
// response on version detection. Best-effort observability — a cold/empty map
|
|
232
|
+
// simply omits providerVersions from that probe, and the next probe carries it.
|
|
233
|
+
const PROVIDER_VERSIONS_TTL_MS = 5 * 60 * 1000; // 5 min — provider installs change rarely
|
|
234
|
+
let cachedProviderVersions: Record<string, string> = {};
|
|
235
|
+
let cachedProviderVersionsAt = 0;
|
|
236
|
+
let providerVersionsRefreshInFlight: Promise<void> | null = null;
|
|
237
|
+
|
|
238
|
+
function refreshProviderVersionsSnapshot(providerLoader?: ProviderLoader): Promise<void> {
|
|
239
|
+
if (providerVersionsRefreshInFlight) return providerVersionsRefreshInFlight;
|
|
240
|
+
providerVersionsRefreshInFlight = (async () => {
|
|
241
|
+
try {
|
|
242
|
+
const detected = await detectCLIs(providerLoader, { includeVersion: true });
|
|
243
|
+
cachedProviderVersions = buildProviderVersions(detected);
|
|
244
|
+
cachedProviderVersionsAt = Date.now();
|
|
245
|
+
} catch {
|
|
246
|
+
// Best-effort: keep the last good snapshot on failure.
|
|
247
|
+
} finally {
|
|
248
|
+
providerVersionsRefreshInFlight = null;
|
|
249
|
+
}
|
|
250
|
+
})();
|
|
251
|
+
return providerVersionsRefreshInFlight;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Return the current cached provider-versions map for this daemon, triggering a
|
|
256
|
+
* lazy background refresh when the snapshot is stale/cold. Never blocks: returns
|
|
257
|
+
* whatever is currently cached (an empty object before the first refresh lands).
|
|
258
|
+
* This is the source the git_status envelope reads to self-report a node's
|
|
259
|
+
* provider versions to the mesh coordinator.
|
|
260
|
+
*/
|
|
261
|
+
export function getCachedProviderVersions(providerLoader?: ProviderLoader): Record<string, string> {
|
|
262
|
+
const stale = Date.now() - cachedProviderVersionsAt > PROVIDER_VERSIONS_TTL_MS;
|
|
263
|
+
if (stale) {
|
|
264
|
+
// Fire-and-forget; the current (possibly empty) snapshot is returned now.
|
|
265
|
+
void refreshProviderVersionsSnapshot(providerLoader);
|
|
266
|
+
}
|
|
267
|
+
return { ...cachedProviderVersions };
|
|
268
|
+
}
|