claude-flow 3.7.0-alpha.44 → 3.7.0-alpha.45
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/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hive-consensus-runtime.d.ts +149 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hive-consensus-runtime.js +296 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hive-mind-tools.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hive-mind-tools.js +185 -4
- package/v3/@claude-flow/cli/package.json +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.7.0-alpha.
|
|
3
|
+
"version": "3.7.0-alpha.45",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hive-Mind Consensus Runtime — ADR-095 G2.2.
|
|
3
|
+
*
|
|
4
|
+
* Before this module, `hive-mind_*` MCP tools were a JSON-file state machine
|
|
5
|
+
* that hand-rolled raft/byzantine/quorum voting on top of `state.consensus`
|
|
6
|
+
* and never touched `@claude-flow/swarm`'s real `ConsensusEngine`. That worked
|
|
7
|
+
* for a single MCP server but ignored every cross-host machinery G2 was meant
|
|
8
|
+
* to unlock: real RequestVote/AppendEntries RPCs, Ed25519-signed PBFT
|
|
9
|
+
* messages, FederationTransport over ADR-104 WS, BFT quorum derived from
|
|
10
|
+
* actual cluster size.
|
|
11
|
+
*
|
|
12
|
+
* This module owns a process-level `ConsensusEngine` plus a pluggable
|
|
13
|
+
* `ConsensusTransport`. `hive-mind_init` calls `init()` here; the consensus /
|
|
14
|
+
* broadcast / shutdown MCP tools delegate to it.
|
|
15
|
+
*
|
|
16
|
+
* Transport selection:
|
|
17
|
+
* - `transport: 'local'` → LocalTransport (in-process registry).
|
|
18
|
+
* Matches the legacy single-process path;
|
|
19
|
+
* real engine, just no WS.
|
|
20
|
+
* - `transport: 'federation'` → FederationTransport over an
|
|
21
|
+
* agentic-flow/transport/loader wire.
|
|
22
|
+
* Real cross-host messaging. Requires
|
|
23
|
+
* `agentic-flow` to be resolvable; if
|
|
24
|
+
* it's not, init returns
|
|
25
|
+
* `transport: 'local'` with `degraded:
|
|
26
|
+
* true` and a fallbackReason — the
|
|
27
|
+
* engine still works locally, the caller
|
|
28
|
+
* just knows the cross-host wire didn't
|
|
29
|
+
* come up.
|
|
30
|
+
* - `transport: 'auto'` (default)→ Probe for `agentic-flow`; if loadable
|
|
31
|
+
* AND `peers` were supplied with
|
|
32
|
+
* addresses, use federation; else local.
|
|
33
|
+
*
|
|
34
|
+
* Why peers are passed in (not auto-discovered): the federation plugin's
|
|
35
|
+
* DiscoveryService is the canonical source of peers, but wiring it into the
|
|
36
|
+
* MCP runtime is a separate concern (it has its own lifecycle / consent
|
|
37
|
+
* gates). Until that lands as G2.3, callers explicitly supply the peer
|
|
38
|
+
* list; the runtime is honest about what it received.
|
|
39
|
+
*/
|
|
40
|
+
import type { ConsensusEngine as ConsensusEngineType, ConsensusTransport as ConsensusTransportType, ConsensusVote, ConsensusProposal, ConsensusResult, ConsensusAlgorithm } from '@claude-flow/swarm';
|
|
41
|
+
export type HiveTransportKind = 'local' | 'federation' | 'auto';
|
|
42
|
+
export type HiveAlgorithm = 'raft' | 'byzantine' | 'gossip';
|
|
43
|
+
export interface HivePeerConfig {
|
|
44
|
+
/** Consensus node id used by Raft/PBFT/Gossip as `from`. */
|
|
45
|
+
readonly nodeId: string;
|
|
46
|
+
/**
|
|
47
|
+
* Wire-level address (e.g. `wss://host:port`). Required for
|
|
48
|
+
* `transport: 'federation'`; ignored for `transport: 'local'`.
|
|
49
|
+
*/
|
|
50
|
+
readonly address?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Ed25519 SPKI PEM public key for inbound signature verification.
|
|
53
|
+
* When provided, FederationTransport will fail-closed on unverifiable
|
|
54
|
+
* messages from this peer.
|
|
55
|
+
*/
|
|
56
|
+
readonly publicKeyPem?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface HiveRuntimeInitOptions {
|
|
59
|
+
readonly nodeId: string;
|
|
60
|
+
readonly algorithm?: HiveAlgorithm;
|
|
61
|
+
readonly transport?: HiveTransportKind;
|
|
62
|
+
readonly peers?: readonly HivePeerConfig[];
|
|
63
|
+
/** Default per-send timeout for transports. */
|
|
64
|
+
readonly timeoutMs?: number;
|
|
65
|
+
/** Consensus threshold (0-1). Forwarded to ConsensusEngine. */
|
|
66
|
+
readonly threshold?: number;
|
|
67
|
+
}
|
|
68
|
+
export interface HiveRuntimeInitResult {
|
|
69
|
+
readonly initialized: true;
|
|
70
|
+
readonly nodeId: string;
|
|
71
|
+
readonly algorithm: HiveAlgorithm;
|
|
72
|
+
/** What we actually got — may differ from requested if federation degraded. */
|
|
73
|
+
readonly transport: 'local' | 'federation';
|
|
74
|
+
/** True when the caller asked for federation but we fell back to local. */
|
|
75
|
+
readonly degraded: boolean;
|
|
76
|
+
/** Human-readable reason for the degradation. */
|
|
77
|
+
readonly fallbackReason?: string;
|
|
78
|
+
readonly peerCount: number;
|
|
79
|
+
readonly source: 'engine';
|
|
80
|
+
}
|
|
81
|
+
export interface HiveRuntimeStatus {
|
|
82
|
+
readonly initialized: boolean;
|
|
83
|
+
readonly nodeId?: string;
|
|
84
|
+
readonly algorithm?: HiveAlgorithm;
|
|
85
|
+
readonly transport: 'local' | 'federation' | null;
|
|
86
|
+
readonly degraded: boolean;
|
|
87
|
+
readonly peers: readonly string[];
|
|
88
|
+
readonly engine: {
|
|
89
|
+
readonly algorithm: ConsensusAlgorithm;
|
|
90
|
+
readonly totalProposals: number;
|
|
91
|
+
readonly pendingProposals: number;
|
|
92
|
+
readonly acceptedProposals: number;
|
|
93
|
+
readonly rejectedProposals: number;
|
|
94
|
+
readonly expiredProposals: number;
|
|
95
|
+
} | null;
|
|
96
|
+
}
|
|
97
|
+
declare class HiveConsensusRuntime {
|
|
98
|
+
private engine;
|
|
99
|
+
private transport;
|
|
100
|
+
private wire;
|
|
101
|
+
private transportKind;
|
|
102
|
+
private nodeId;
|
|
103
|
+
private algorithm;
|
|
104
|
+
private peers;
|
|
105
|
+
private degraded;
|
|
106
|
+
private fallbackReason;
|
|
107
|
+
isInitialized(): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Lazy-initialize the consensus engine + transport. Idempotent on the
|
|
110
|
+
* same nodeId/algorithm/transport tuple — subsequent calls return the
|
|
111
|
+
* existing state. Different parameters trigger a clean shutdown + reinit.
|
|
112
|
+
*/
|
|
113
|
+
init(opts: HiveRuntimeInitOptions): Promise<HiveRuntimeInitResult>;
|
|
114
|
+
/**
|
|
115
|
+
* Probe for the agentic-flow QUIC/WS transport loader. Returns the wire
|
|
116
|
+
* when loadable; an explanatory reason when not. Never throws — failure
|
|
117
|
+
* is data, not control flow, so the runtime can degrade cleanly.
|
|
118
|
+
*/
|
|
119
|
+
private tryLoadFederationWire;
|
|
120
|
+
/** Propose a value through the real ConsensusEngine. */
|
|
121
|
+
propose(value: unknown, proposerId?: string): Promise<ConsensusProposal>;
|
|
122
|
+
/** Record a vote against an active proposal. */
|
|
123
|
+
vote(proposalId: string, vote: ConsensusVote): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Block until a proposal resolves (accepted / rejected / expired).
|
|
126
|
+
* Used by the MCP tool when callers want a synchronous result instead
|
|
127
|
+
* of polling `proposal-status`.
|
|
128
|
+
*/
|
|
129
|
+
awaitConsensus(proposalId: string): Promise<ConsensusResult>;
|
|
130
|
+
/**
|
|
131
|
+
* Broadcast a free-form payload to all known peers via the transport.
|
|
132
|
+
* Gossip-style, no reply expected. Wraps the payload so peers don't
|
|
133
|
+
* confuse it with protocol messages — `type: 'hive-broadcast'`.
|
|
134
|
+
*/
|
|
135
|
+
broadcast(payload: unknown, priority?: 'low' | 'normal' | 'high' | 'critical'): Promise<{
|
|
136
|
+
delivered: number;
|
|
137
|
+
transport: 'local' | 'federation';
|
|
138
|
+
}>;
|
|
139
|
+
status(): HiveRuntimeStatus;
|
|
140
|
+
shutdown(): Promise<void>;
|
|
141
|
+
/** Test seam: replace the singleton's underlying engine + transport. */
|
|
142
|
+
__setForTest(engine: ConsensusEngineType, transport: ConsensusTransportType, nodeId: string, kind?: 'local' | 'federation'): void;
|
|
143
|
+
private toInitResult;
|
|
144
|
+
}
|
|
145
|
+
/** Process-level singleton — one engine per MCP server. */
|
|
146
|
+
export declare const hiveConsensusRuntime: HiveConsensusRuntime;
|
|
147
|
+
/** Re-export the singleton's type for tests / advanced consumers. */
|
|
148
|
+
export type { HiveConsensusRuntime };
|
|
149
|
+
//# sourceMappingURL=hive-consensus-runtime.d.ts.map
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hive-Mind Consensus Runtime — ADR-095 G2.2.
|
|
3
|
+
*
|
|
4
|
+
* Before this module, `hive-mind_*` MCP tools were a JSON-file state machine
|
|
5
|
+
* that hand-rolled raft/byzantine/quorum voting on top of `state.consensus`
|
|
6
|
+
* and never touched `@claude-flow/swarm`'s real `ConsensusEngine`. That worked
|
|
7
|
+
* for a single MCP server but ignored every cross-host machinery G2 was meant
|
|
8
|
+
* to unlock: real RequestVote/AppendEntries RPCs, Ed25519-signed PBFT
|
|
9
|
+
* messages, FederationTransport over ADR-104 WS, BFT quorum derived from
|
|
10
|
+
* actual cluster size.
|
|
11
|
+
*
|
|
12
|
+
* This module owns a process-level `ConsensusEngine` plus a pluggable
|
|
13
|
+
* `ConsensusTransport`. `hive-mind_init` calls `init()` here; the consensus /
|
|
14
|
+
* broadcast / shutdown MCP tools delegate to it.
|
|
15
|
+
*
|
|
16
|
+
* Transport selection:
|
|
17
|
+
* - `transport: 'local'` → LocalTransport (in-process registry).
|
|
18
|
+
* Matches the legacy single-process path;
|
|
19
|
+
* real engine, just no WS.
|
|
20
|
+
* - `transport: 'federation'` → FederationTransport over an
|
|
21
|
+
* agentic-flow/transport/loader wire.
|
|
22
|
+
* Real cross-host messaging. Requires
|
|
23
|
+
* `agentic-flow` to be resolvable; if
|
|
24
|
+
* it's not, init returns
|
|
25
|
+
* `transport: 'local'` with `degraded:
|
|
26
|
+
* true` and a fallbackReason — the
|
|
27
|
+
* engine still works locally, the caller
|
|
28
|
+
* just knows the cross-host wire didn't
|
|
29
|
+
* come up.
|
|
30
|
+
* - `transport: 'auto'` (default)→ Probe for `agentic-flow`; if loadable
|
|
31
|
+
* AND `peers` were supplied with
|
|
32
|
+
* addresses, use federation; else local.
|
|
33
|
+
*
|
|
34
|
+
* Why peers are passed in (not auto-discovered): the federation plugin's
|
|
35
|
+
* DiscoveryService is the canonical source of peers, but wiring it into the
|
|
36
|
+
* MCP runtime is a separate concern (it has its own lifecycle / consent
|
|
37
|
+
* gates). Until that lands as G2.3, callers explicitly supply the peer
|
|
38
|
+
* list; the runtime is honest about what it received.
|
|
39
|
+
*/
|
|
40
|
+
class HiveConsensusRuntime {
|
|
41
|
+
engine = null;
|
|
42
|
+
transport = null;
|
|
43
|
+
wire = null;
|
|
44
|
+
transportKind = null;
|
|
45
|
+
nodeId = null;
|
|
46
|
+
algorithm = 'raft';
|
|
47
|
+
peers = [];
|
|
48
|
+
degraded = false;
|
|
49
|
+
fallbackReason;
|
|
50
|
+
isInitialized() {
|
|
51
|
+
return this.engine !== null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Lazy-initialize the consensus engine + transport. Idempotent on the
|
|
55
|
+
* same nodeId/algorithm/transport tuple — subsequent calls return the
|
|
56
|
+
* existing state. Different parameters trigger a clean shutdown + reinit.
|
|
57
|
+
*/
|
|
58
|
+
async init(opts) {
|
|
59
|
+
const requestedAlgorithm = opts.algorithm ?? 'raft';
|
|
60
|
+
const requestedTransport = opts.transport ?? 'auto';
|
|
61
|
+
const peers = [...(opts.peers ?? [])];
|
|
62
|
+
// If already initialized with the same shape, return existing state.
|
|
63
|
+
if (this.engine && this.nodeId === opts.nodeId && this.algorithm === requestedAlgorithm) {
|
|
64
|
+
return this.toInitResult();
|
|
65
|
+
}
|
|
66
|
+
// Different shape — tear down before re-instantiating.
|
|
67
|
+
if (this.engine) {
|
|
68
|
+
await this.shutdown();
|
|
69
|
+
}
|
|
70
|
+
const swarm = await import('@claude-flow/swarm');
|
|
71
|
+
// Resolve transport. 'auto' prefers federation when the loader is
|
|
72
|
+
// available AND we have peer addresses; otherwise local.
|
|
73
|
+
let resolvedKind;
|
|
74
|
+
let resolvedTransport;
|
|
75
|
+
let degraded = false;
|
|
76
|
+
let fallbackReason;
|
|
77
|
+
if (requestedTransport === 'local') {
|
|
78
|
+
resolvedKind = 'local';
|
|
79
|
+
resolvedTransport = new swarm.LocalTransport(opts.nodeId, {
|
|
80
|
+
defaultTimeoutMs: opts.timeoutMs,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// 'federation' or 'auto' → probe agentic-flow loader.
|
|
85
|
+
const probe = await this.tryLoadFederationWire(opts);
|
|
86
|
+
if (probe.wire) {
|
|
87
|
+
// Build the addressOf map from peers config.
|
|
88
|
+
const addressMap = new Map();
|
|
89
|
+
const pubkeyMap = new Map();
|
|
90
|
+
for (const p of peers) {
|
|
91
|
+
if (p.address)
|
|
92
|
+
addressMap.set(p.nodeId, p.address);
|
|
93
|
+
if (p.publicKeyPem)
|
|
94
|
+
pubkeyMap.set(p.nodeId, p.publicKeyPem);
|
|
95
|
+
}
|
|
96
|
+
this.wire = probe.wire;
|
|
97
|
+
resolvedKind = 'federation';
|
|
98
|
+
resolvedTransport = new swarm.FederationTransport(probe.wire, {
|
|
99
|
+
nodeId: opts.nodeId,
|
|
100
|
+
addressOf: (id) => addressMap.get(id),
|
|
101
|
+
peerIds: () => peers.map(p => p.nodeId),
|
|
102
|
+
defaultTimeoutMs: opts.timeoutMs,
|
|
103
|
+
resolvePeerPublicKey: pubkeyMap.size > 0 ? (id) => pubkeyMap.get(id) : undefined,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Couldn't load federation wire. 'federation' requested → degrade
|
|
108
|
+
// honestly. 'auto' → silently fall back to local.
|
|
109
|
+
if (requestedTransport === 'federation') {
|
|
110
|
+
degraded = true;
|
|
111
|
+
fallbackReason = probe.reason ?? 'agentic-flow loader unavailable';
|
|
112
|
+
}
|
|
113
|
+
resolvedKind = 'local';
|
|
114
|
+
resolvedTransport = new swarm.LocalTransport(opts.nodeId, {
|
|
115
|
+
defaultTimeoutMs: opts.timeoutMs,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Build the engine with the resolved transport. We pass `transport`
|
|
120
|
+
// through `config.transport` (typed `unknown` on `ConsensusConfig` in
|
|
121
|
+
// swarm's types.ts — the engine narrows it via a structural check).
|
|
122
|
+
const engine = new swarm.ConsensusEngine(opts.nodeId, {
|
|
123
|
+
algorithm: requestedAlgorithm,
|
|
124
|
+
threshold: opts.threshold ?? 0.66,
|
|
125
|
+
timeoutMs: opts.timeoutMs ?? 30_000,
|
|
126
|
+
maxRounds: 10,
|
|
127
|
+
requireQuorum: true,
|
|
128
|
+
transport: resolvedTransport,
|
|
129
|
+
});
|
|
130
|
+
await engine.initialize();
|
|
131
|
+
// Register peers with the consensus engine so its protocol-internal
|
|
132
|
+
// bookkeeping (Raft's `peers` map, BFT's node set, gossip neighbors)
|
|
133
|
+
// matches the transport's peer list.
|
|
134
|
+
for (const p of peers) {
|
|
135
|
+
if (p.nodeId === opts.nodeId)
|
|
136
|
+
continue;
|
|
137
|
+
engine.addNode(p.nodeId);
|
|
138
|
+
}
|
|
139
|
+
this.engine = engine;
|
|
140
|
+
this.transport = resolvedTransport;
|
|
141
|
+
this.transportKind = resolvedKind;
|
|
142
|
+
this.nodeId = opts.nodeId;
|
|
143
|
+
this.algorithm = requestedAlgorithm;
|
|
144
|
+
this.peers = peers;
|
|
145
|
+
this.degraded = degraded;
|
|
146
|
+
this.fallbackReason = fallbackReason;
|
|
147
|
+
return this.toInitResult();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Probe for the agentic-flow QUIC/WS transport loader. Returns the wire
|
|
151
|
+
* when loadable; an explanatory reason when not. Never throws — failure
|
|
152
|
+
* is data, not control flow, so the runtime can degrade cleanly.
|
|
153
|
+
*/
|
|
154
|
+
async tryLoadFederationWire(_opts) {
|
|
155
|
+
// agentic-flow is an *optional* peer dep — when it isn't installed,
|
|
156
|
+
// dynamic import throws and we degrade. The bare-string specifier
|
|
157
|
+
// is wrapped in a `new Function(...)` so the TS compiler doesn't try
|
|
158
|
+
// to resolve types at build time (the same pattern the federation
|
|
159
|
+
// plugin's loader uses, see ADR-120 step 2 comments).
|
|
160
|
+
const importDynamic = new Function('s', 'return import(s)');
|
|
161
|
+
let mod;
|
|
162
|
+
try {
|
|
163
|
+
mod = await importDynamic('agentic-flow/transport/loader');
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
return { wire: null, reason: `agentic-flow not installed (${err.message ?? 'unknown'})` };
|
|
167
|
+
}
|
|
168
|
+
const m = mod;
|
|
169
|
+
const fn = typeof m.loadQuicTransport === 'function'
|
|
170
|
+
? m.loadQuicTransport
|
|
171
|
+
: m.default?.loadQuicTransport;
|
|
172
|
+
if (typeof fn !== 'function') {
|
|
173
|
+
return { wire: null, reason: 'agentic-flow loader does not expose loadQuicTransport' };
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const wire = await fn();
|
|
177
|
+
if (!wire || typeof wire.send !== 'function' || typeof wire.onMessage !== 'function') {
|
|
178
|
+
return { wire: null, reason: 'agentic-flow loader returned a wire without send/onMessage' };
|
|
179
|
+
}
|
|
180
|
+
return { wire };
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
return { wire: null, reason: `agentic-flow loader threw: ${err.message ?? 'unknown'}` };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Propose a value through the real ConsensusEngine. */
|
|
187
|
+
async propose(value, proposerId) {
|
|
188
|
+
if (!this.engine)
|
|
189
|
+
throw new Error('hive-consensus-runtime: not initialized');
|
|
190
|
+
return this.engine.propose(value, proposerId ?? this.nodeId ?? 'unknown');
|
|
191
|
+
}
|
|
192
|
+
/** Record a vote against an active proposal. */
|
|
193
|
+
async vote(proposalId, vote) {
|
|
194
|
+
if (!this.engine)
|
|
195
|
+
throw new Error('hive-consensus-runtime: not initialized');
|
|
196
|
+
await this.engine.vote(proposalId, vote);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Block until a proposal resolves (accepted / rejected / expired).
|
|
200
|
+
* Used by the MCP tool when callers want a synchronous result instead
|
|
201
|
+
* of polling `proposal-status`.
|
|
202
|
+
*/
|
|
203
|
+
async awaitConsensus(proposalId) {
|
|
204
|
+
if (!this.engine)
|
|
205
|
+
throw new Error('hive-consensus-runtime: not initialized');
|
|
206
|
+
return this.engine.awaitConsensus(proposalId);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Broadcast a free-form payload to all known peers via the transport.
|
|
210
|
+
* Gossip-style, no reply expected. Wraps the payload so peers don't
|
|
211
|
+
* confuse it with protocol messages — `type: 'hive-broadcast'`.
|
|
212
|
+
*/
|
|
213
|
+
async broadcast(payload, priority = 'normal') {
|
|
214
|
+
if (!this.transport)
|
|
215
|
+
throw new Error('hive-consensus-runtime: not initialized');
|
|
216
|
+
const peers = this.transport.peers();
|
|
217
|
+
await this.transport.broadcast({
|
|
218
|
+
type: 'hive-broadcast',
|
|
219
|
+
payload: { value: payload, priority, sentAt: new Date().toISOString() },
|
|
220
|
+
});
|
|
221
|
+
return { delivered: peers.length, transport: this.transportKind };
|
|
222
|
+
}
|
|
223
|
+
status() {
|
|
224
|
+
if (!this.engine || !this.transport) {
|
|
225
|
+
return {
|
|
226
|
+
initialized: false,
|
|
227
|
+
transport: null,
|
|
228
|
+
degraded: false,
|
|
229
|
+
peers: [],
|
|
230
|
+
engine: null,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
initialized: true,
|
|
235
|
+
nodeId: this.nodeId,
|
|
236
|
+
algorithm: this.algorithm,
|
|
237
|
+
transport: this.transportKind,
|
|
238
|
+
degraded: this.degraded,
|
|
239
|
+
peers: [...this.transport.peers()],
|
|
240
|
+
engine: this.engine.getStats(),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
async shutdown() {
|
|
244
|
+
const engine = this.engine;
|
|
245
|
+
const transport = this.transport;
|
|
246
|
+
const wire = this.wire;
|
|
247
|
+
this.engine = null;
|
|
248
|
+
this.transport = null;
|
|
249
|
+
this.wire = null;
|
|
250
|
+
this.transportKind = null;
|
|
251
|
+
this.nodeId = null;
|
|
252
|
+
this.peers = [];
|
|
253
|
+
this.degraded = false;
|
|
254
|
+
this.fallbackReason = undefined;
|
|
255
|
+
if (engine) {
|
|
256
|
+
try {
|
|
257
|
+
await engine.shutdown();
|
|
258
|
+
}
|
|
259
|
+
catch { /* best-effort */ }
|
|
260
|
+
}
|
|
261
|
+
if (transport) {
|
|
262
|
+
try {
|
|
263
|
+
await transport.close();
|
|
264
|
+
}
|
|
265
|
+
catch { /* best-effort */ }
|
|
266
|
+
}
|
|
267
|
+
if (wire && typeof wire.close === 'function') {
|
|
268
|
+
try {
|
|
269
|
+
await wire.close();
|
|
270
|
+
}
|
|
271
|
+
catch { /* best-effort */ }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/** Test seam: replace the singleton's underlying engine + transport. */
|
|
275
|
+
__setForTest(engine, transport, nodeId, kind = 'local') {
|
|
276
|
+
this.engine = engine;
|
|
277
|
+
this.transport = transport;
|
|
278
|
+
this.nodeId = nodeId;
|
|
279
|
+
this.transportKind = kind;
|
|
280
|
+
}
|
|
281
|
+
toInitResult() {
|
|
282
|
+
return {
|
|
283
|
+
initialized: true,
|
|
284
|
+
nodeId: this.nodeId,
|
|
285
|
+
algorithm: this.algorithm,
|
|
286
|
+
transport: this.transportKind,
|
|
287
|
+
degraded: this.degraded,
|
|
288
|
+
fallbackReason: this.fallbackReason,
|
|
289
|
+
peerCount: this.peers.length,
|
|
290
|
+
source: 'engine',
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/** Process-level singleton — one engine per MCP server. */
|
|
295
|
+
export const hiveConsensusRuntime = new HiveConsensusRuntime();
|
|
296
|
+
//# sourceMappingURL=hive-consensus-runtime.js.map
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* Hive-Mind MCP Tools for CLI
|
|
3
3
|
*
|
|
4
4
|
* Tool definitions for collective intelligence and swarm coordination.
|
|
5
|
+
*
|
|
6
|
+
* ADR-095 G2.2 — these tools now delegate inter-node consensus to the real
|
|
7
|
+
* `@claude-flow/swarm` ConsensusEngine via `hive-consensus-runtime`. The
|
|
8
|
+
* legacy JSON-state-machine voting (in `hive-mind_consensus`'s 'propose' /
|
|
9
|
+
* 'vote' / 'status' / 'list' actions) remains as a fallback when the
|
|
10
|
+
* runtime isn't initialized — preserves backward compatibility for callers
|
|
11
|
+
* that skip `hive-mind_init` or run in a swarm-less context.
|
|
5
12
|
*/
|
|
6
13
|
import { type MCPTool } from './types.js';
|
|
7
14
|
export declare const hiveMindTools: MCPTool[];
|
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
* Hive-Mind MCP Tools for CLI
|
|
3
3
|
*
|
|
4
4
|
* Tool definitions for collective intelligence and swarm coordination.
|
|
5
|
+
*
|
|
6
|
+
* ADR-095 G2.2 — these tools now delegate inter-node consensus to the real
|
|
7
|
+
* `@claude-flow/swarm` ConsensusEngine via `hive-consensus-runtime`. The
|
|
8
|
+
* legacy JSON-state-machine voting (in `hive-mind_consensus`'s 'propose' /
|
|
9
|
+
* 'vote' / 'status' / 'list' actions) remains as a fallback when the
|
|
10
|
+
* runtime isn't initialized — preserves backward compatibility for callers
|
|
11
|
+
* that skip `hive-mind_init` or run in a swarm-less context.
|
|
5
12
|
*/
|
|
6
13
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
14
|
import { join } from 'node:path';
|
|
8
15
|
import { getProjectCwd } from './types.js';
|
|
9
16
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
17
|
+
import { hiveConsensusRuntime } from './hive-consensus-runtime.js';
|
|
10
18
|
// Storage paths
|
|
11
19
|
const STORAGE_DIR = '.claude-flow';
|
|
12
20
|
const HIVE_DIR = 'hive-mind';
|
|
@@ -227,6 +235,33 @@ export const hiveMindTools = [
|
|
|
227
235
|
description: 'Consensus strategy. Default: raft (anti-drift). Use byzantine for f<n/3 fault tolerance.',
|
|
228
236
|
},
|
|
229
237
|
queenId: { type: 'string', description: 'Initial queen agent ID' },
|
|
238
|
+
// ADR-095 G2.2 — pluggable transport for the underlying consensus
|
|
239
|
+
// engine. 'local' (default) preserves single-process behavior;
|
|
240
|
+
// 'federation' wires Raft/PBFT/Gossip over ADR-104 WS via
|
|
241
|
+
// agentic-flow; 'auto' picks federation when reachable + peers
|
|
242
|
+
// supplied, else local.
|
|
243
|
+
transport: {
|
|
244
|
+
type: 'string',
|
|
245
|
+
enum: ['local', 'federation', 'auto'],
|
|
246
|
+
description: 'Consensus transport. local=in-process; federation=ADR-104 WS (cross-host); auto=federation if reachable, else local. Default: auto.',
|
|
247
|
+
},
|
|
248
|
+
// ADR-095 G2.2 — peers for the consensus engine. When transport is
|
|
249
|
+
// 'federation' or 'auto', each peer should include a wire address
|
|
250
|
+
// (e.g. wss://host:8443) and optionally an Ed25519 public key PEM
|
|
251
|
+
// for inbound signature verification.
|
|
252
|
+
peers: {
|
|
253
|
+
type: 'array',
|
|
254
|
+
description: 'Consensus peer list. Each entry: { nodeId, address?, publicKeyPem? }.',
|
|
255
|
+
items: {
|
|
256
|
+
type: 'object',
|
|
257
|
+
properties: {
|
|
258
|
+
nodeId: { type: 'string' },
|
|
259
|
+
address: { type: 'string' },
|
|
260
|
+
publicKeyPem: { type: 'string' },
|
|
261
|
+
},
|
|
262
|
+
required: ['nodeId'],
|
|
263
|
+
},
|
|
264
|
+
},
|
|
230
265
|
},
|
|
231
266
|
},
|
|
232
267
|
handler: async (input) => {
|
|
@@ -249,6 +284,39 @@ export const hiveMindTools = [
|
|
|
249
284
|
term: 1,
|
|
250
285
|
};
|
|
251
286
|
saveHiveState(state);
|
|
287
|
+
// ADR-095 G2.2 — bring up the real consensus runtime so subsequent
|
|
288
|
+
// hive-mind_consensus / hive-mind_broadcast calls go through the
|
|
289
|
+
// engine + transport instead of the JSON state machine. Done after
|
|
290
|
+
// saveHiveState so a runtime failure doesn't leave persisted state
|
|
291
|
+
// inconsistent — we surface the runtime error in the response but
|
|
292
|
+
// the hive itself stays initialized at the file-state layer.
|
|
293
|
+
const requestedTransport = input.transport || 'auto';
|
|
294
|
+
// Map the wider consensus strategy enum to the runtime's algorithm.
|
|
295
|
+
// 'crdt' and 'quorum' don't have engine implementations; they keep
|
|
296
|
+
// the legacy state-machine voting and we tag the runtime as 'raft'
|
|
297
|
+
// (the safe default — quorum semantics are layered on by the
|
|
298
|
+
// state-machine code in hive-mind_consensus below).
|
|
299
|
+
const runtimeAlgorithm = requestedConsensus === 'byzantine' ? 'byzantine'
|
|
300
|
+
: requestedConsensus === 'gossip' ? 'gossip'
|
|
301
|
+
: 'raft';
|
|
302
|
+
const peers = Array.isArray(input.peers)
|
|
303
|
+
? input.peers.filter((p) => {
|
|
304
|
+
return !!p && typeof p === 'object' && typeof p.nodeId === 'string';
|
|
305
|
+
})
|
|
306
|
+
: [];
|
|
307
|
+
let runtimeResult = null;
|
|
308
|
+
let runtimeError;
|
|
309
|
+
try {
|
|
310
|
+
runtimeResult = await hiveConsensusRuntime.init({
|
|
311
|
+
nodeId: queenId,
|
|
312
|
+
algorithm: runtimeAlgorithm,
|
|
313
|
+
transport: requestedTransport,
|
|
314
|
+
peers,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
runtimeError = err instanceof Error ? err.message : String(err);
|
|
319
|
+
}
|
|
252
320
|
return {
|
|
253
321
|
success: true,
|
|
254
322
|
hiveId,
|
|
@@ -264,6 +332,22 @@ export const hiveMindTools = [
|
|
|
264
332
|
memoryBackend: input.memoryBackend || 'hybrid',
|
|
265
333
|
},
|
|
266
334
|
createdAt: state.createdAt,
|
|
335
|
+
// ADR-095 G2.2 — runtime observability so callers know which
|
|
336
|
+
// transport / algorithm is actually live.
|
|
337
|
+
runtime: runtimeResult
|
|
338
|
+
? {
|
|
339
|
+
engine: 'enabled',
|
|
340
|
+
algorithm: runtimeResult.algorithm,
|
|
341
|
+
transport: runtimeResult.transport,
|
|
342
|
+
degraded: runtimeResult.degraded,
|
|
343
|
+
fallbackReason: runtimeResult.fallbackReason,
|
|
344
|
+
peerCount: runtimeResult.peerCount,
|
|
345
|
+
}
|
|
346
|
+
: {
|
|
347
|
+
engine: 'unavailable',
|
|
348
|
+
error: runtimeError,
|
|
349
|
+
note: 'Consensus engine could not be brought up — legacy state-machine voting will be used for hive-mind_consensus / broadcast.',
|
|
350
|
+
},
|
|
267
351
|
};
|
|
268
352
|
},
|
|
269
353
|
},
|
|
@@ -442,7 +526,7 @@ export const hiveMindTools = [
|
|
|
442
526
|
inputSchema: {
|
|
443
527
|
type: 'object',
|
|
444
528
|
properties: {
|
|
445
|
-
action: { type: 'string', enum: ['propose', 'vote', 'status', 'list'], description: 'Consensus action' },
|
|
529
|
+
action: { type: 'string', enum: ['propose', 'vote', 'status', 'list', 'engine-stats'], description: 'Consensus action' },
|
|
446
530
|
proposalId: { type: 'string', description: 'Proposal ID (for vote/status)' },
|
|
447
531
|
type: { type: 'string', description: 'Proposal type (for propose)' },
|
|
448
532
|
value: { description: 'Proposal value (for propose)' },
|
|
@@ -452,6 +536,12 @@ export const hiveMindTools = [
|
|
|
452
536
|
quorumPreset: { type: 'string', enum: ['unanimous', 'majority', 'supermajority'], description: 'Quorum threshold preset (for quorum strategy, default: majority)' },
|
|
453
537
|
term: { type: 'number', description: 'Term number (for raft strategy)' },
|
|
454
538
|
timeoutMs: { type: 'number', description: 'Timeout in ms for raft re-proposal (default: 30000)' },
|
|
539
|
+
// ADR-095 G2.2 — route through the real consensus engine when set.
|
|
540
|
+
// Default false to preserve backward compatibility for callers that
|
|
541
|
+
// depend on the JSON-state-machine semantics (BFT detection across
|
|
542
|
+
// proposals, quorum presets, etc.).
|
|
543
|
+
useEngine: { type: 'boolean', description: 'Route propose/vote through the real ConsensusEngine (ADR-095 G2.2). Requires hive-mind_init to have brought up the runtime.' },
|
|
544
|
+
confidence: { type: 'number', description: 'Vote confidence 0-1 (only used when useEngine is true).' },
|
|
455
545
|
},
|
|
456
546
|
required: ['action'],
|
|
457
547
|
},
|
|
@@ -475,6 +565,63 @@ export const hiveMindTools = [
|
|
|
475
565
|
const action = input.action;
|
|
476
566
|
const strategy = input.strategy || 'raft';
|
|
477
567
|
const totalNodes = state.workers.length || 1;
|
|
568
|
+
// ADR-095 G2.2 — engine-stats surfaces the live runtime state.
|
|
569
|
+
// Returns a 'not-initialized' shape (not an error) when the runtime
|
|
570
|
+
// is offline so monitoring tools don't have to special-case startup.
|
|
571
|
+
if (action === 'engine-stats') {
|
|
572
|
+
const status = hiveConsensusRuntime.status();
|
|
573
|
+
return { action, ...status };
|
|
574
|
+
}
|
|
575
|
+
// ADR-095 G2.2 — when useEngine is true and the runtime is up,
|
|
576
|
+
// propose/vote go through the real ConsensusEngine. Falls back to
|
|
577
|
+
// the JSON-state-machine path below when the runtime isn't
|
|
578
|
+
// initialized, so callers that don't ask for engine routing get
|
|
579
|
+
// the legacy behavior.
|
|
580
|
+
const useEngine = input.useEngine === true && hiveConsensusRuntime.isInitialized();
|
|
581
|
+
if (useEngine && action === 'propose') {
|
|
582
|
+
try {
|
|
583
|
+
const proposal = await hiveConsensusRuntime.propose(input.value, input.voterId || (state.queen?.agentId ?? 'queen'));
|
|
584
|
+
return {
|
|
585
|
+
action,
|
|
586
|
+
proposalId: proposal.id,
|
|
587
|
+
type: input.type || 'general',
|
|
588
|
+
strategy: hiveConsensusRuntime.status().algorithm,
|
|
589
|
+
status: proposal.status,
|
|
590
|
+
term: proposal.term,
|
|
591
|
+
engine: 'enabled',
|
|
592
|
+
transport: hiveConsensusRuntime.status().transport,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
catch (err) {
|
|
596
|
+
return { action, error: `consensus engine propose failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (useEngine && action === 'vote') {
|
|
600
|
+
const voterId = input.voterId;
|
|
601
|
+
if (!voterId)
|
|
602
|
+
return { action, error: 'voterId is required for voting' };
|
|
603
|
+
if (!input.proposalId)
|
|
604
|
+
return { action, error: 'proposalId is required for voting' };
|
|
605
|
+
try {
|
|
606
|
+
await hiveConsensusRuntime.vote(input.proposalId, {
|
|
607
|
+
voterId,
|
|
608
|
+
approve: input.vote,
|
|
609
|
+
confidence: typeof input.confidence === 'number' ? input.confidence : 1.0,
|
|
610
|
+
timestamp: new Date(),
|
|
611
|
+
});
|
|
612
|
+
return {
|
|
613
|
+
action,
|
|
614
|
+
proposalId: input.proposalId,
|
|
615
|
+
voterId,
|
|
616
|
+
vote: input.vote,
|
|
617
|
+
engine: 'enabled',
|
|
618
|
+
transport: hiveConsensusRuntime.status().transport,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
catch (err) {
|
|
622
|
+
return { action, error: `consensus engine vote failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
623
|
+
}
|
|
624
|
+
}
|
|
478
625
|
if (action === 'propose') {
|
|
479
626
|
const proposalId = `proposal-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
480
627
|
const quorumPreset = input.quorumPreset || 'majority';
|
|
@@ -750,24 +897,46 @@ export const hiveMindTools = [
|
|
|
750
897
|
return { success: false, error: v.error };
|
|
751
898
|
}
|
|
752
899
|
const messageId = `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
753
|
-
|
|
900
|
+
const priority = input.priority || 'normal';
|
|
901
|
+
// Store in shared memory (always — this is the durable record).
|
|
754
902
|
const messages = state.sharedMemory.broadcasts || [];
|
|
755
903
|
messages.push({
|
|
756
904
|
messageId,
|
|
757
905
|
message: input.message,
|
|
758
|
-
priority
|
|
906
|
+
priority,
|
|
759
907
|
fromId: input.fromId || 'system',
|
|
760
908
|
timestamp: new Date().toISOString(),
|
|
761
909
|
});
|
|
762
910
|
// Keep only last 100 broadcasts
|
|
763
911
|
state.sharedMemory.broadcasts = messages.slice(-100);
|
|
764
912
|
saveHiveState(state);
|
|
913
|
+
// ADR-095 G2.2 — when the consensus runtime is up, also push the
|
|
914
|
+
// broadcast over its transport so cross-process / cross-host
|
|
915
|
+
// peers actually receive it. Local-only runtimes still benefit
|
|
916
|
+
// (in-process peers get a real handler invocation).
|
|
917
|
+
let transportDelivered;
|
|
918
|
+
let transportKind;
|
|
919
|
+
let transportError;
|
|
920
|
+
if (hiveConsensusRuntime.isInitialized()) {
|
|
921
|
+
try {
|
|
922
|
+
const res = await hiveConsensusRuntime.broadcast({ messageId, message: input.message, fromId: input.fromId || 'system' }, priority);
|
|
923
|
+
transportDelivered = res.delivered;
|
|
924
|
+
transportKind = res.transport;
|
|
925
|
+
}
|
|
926
|
+
catch (err) {
|
|
927
|
+
transportError = err instanceof Error ? err.message : String(err);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
765
930
|
return {
|
|
766
931
|
success: true,
|
|
767
932
|
messageId,
|
|
768
933
|
recipients: state.workers.length,
|
|
769
|
-
priority
|
|
934
|
+
priority,
|
|
770
935
|
broadcastAt: new Date().toISOString(),
|
|
936
|
+
transport: transportKind
|
|
937
|
+
? { kind: transportKind, delivered: transportDelivered }
|
|
938
|
+
: { kind: 'state-machine-only', note: 'runtime offline — broadcast persisted to sharedMemory only' },
|
|
939
|
+
...(transportError ? { transportError } : {}),
|
|
771
940
|
};
|
|
772
941
|
},
|
|
773
942
|
},
|
|
@@ -818,6 +987,17 @@ export const hiveMindTools = [
|
|
|
818
987
|
// Keep history for reference
|
|
819
988
|
state.sharedMemory = {};
|
|
820
989
|
saveHiveState(state);
|
|
990
|
+
// ADR-095 G2.2 — tear down the consensus runtime alongside the
|
|
991
|
+
// file-state. Best-effort: a runtime that's already offline is
|
|
992
|
+
// not an error.
|
|
993
|
+
let runtimeShutdown = false;
|
|
994
|
+
try {
|
|
995
|
+
if (hiveConsensusRuntime.isInitialized()) {
|
|
996
|
+
await hiveConsensusRuntime.shutdown();
|
|
997
|
+
runtimeShutdown = true;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
catch { /* best-effort */ }
|
|
821
1001
|
return {
|
|
822
1002
|
success: true,
|
|
823
1003
|
shutdownAt: shutdownTime,
|
|
@@ -825,6 +1005,7 @@ export const hiveMindTools = [
|
|
|
825
1005
|
workersTerminated: workerCount,
|
|
826
1006
|
previousQueen,
|
|
827
1007
|
consensusCleared: pendingConsensus,
|
|
1008
|
+
runtimeShutdown,
|
|
828
1009
|
message: `Hive-mind shutdown complete. ${workerCount} workers terminated.`,
|
|
829
1010
|
};
|
|
830
1011
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.7.0-alpha.
|
|
3
|
+
"version": "3.7.0-alpha.45",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -105,7 +105,8 @@
|
|
|
105
105
|
"yaml": "^2.8.0",
|
|
106
106
|
"@claude-flow/memory": "^3.0.0-alpha.17",
|
|
107
107
|
"@claude-flow/embeddings": "^3.0.0-alpha.18",
|
|
108
|
-
"@claude-flow/security": "^3.0.0-alpha.8"
|
|
108
|
+
"@claude-flow/security": "^3.0.0-alpha.8",
|
|
109
|
+
"@claude-flow/swarm": "^3.0.0-alpha.8"
|
|
109
110
|
},
|
|
110
111
|
"optionalDependencies": {
|
|
111
112
|
"@claude-flow/aidefence": "^3.0.2",
|