claude-flow 3.14.4 → 3.16.0

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.
Files changed (23) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/package.json +1 -1
  3. package/v3/@claude-flow/cli/dist/src/business-pods/bbs-budget-tracker.d.ts +139 -0
  4. package/v3/@claude-flow/cli/dist/src/business-pods/bbs-budget-tracker.js +358 -0
  5. package/v3/@claude-flow/cli/dist/src/business-pods/domain-affinity-policy.d.ts +47 -0
  6. package/v3/@claude-flow/cli/dist/src/business-pods/domain-affinity-policy.js +65 -0
  7. package/v3/@claude-flow/cli/dist/src/business-pods/pod-schema.d.ts +96 -0
  8. package/v3/@claude-flow/cli/dist/src/business-pods/pod-schema.js +225 -0
  9. package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +20 -5
  10. package/v3/@claude-flow/cli/dist/src/mcp-client.js +21 -0
  11. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-tools.d.ts +28 -0
  12. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-tools.js +394 -0
  13. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.d.ts +36 -0
  14. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +253 -0
  15. package/v3/@claude-flow/cli/dist/src/mcp-tools/business-pod-tools.d.ts +20 -0
  16. package/v3/@claude-flow/cli/dist/src/mcp-tools/business-pod-tools.js +169 -0
  17. package/v3/@claude-flow/cli/dist/src/mcp-tools/http-fetch-tools.d.ts +55 -0
  18. package/v3/@claude-flow/cli/dist/src/mcp-tools/http-fetch-tools.js +329 -0
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +4 -0
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +8 -0
  21. package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.d.ts +6 -0
  22. package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.js +83 -4
  23. package/v3/@claude-flow/cli/package.json +4 -1
@@ -0,0 +1,394 @@
1
+ /**
2
+ * AgentBBS MCP Tools — Federated business-domain BBS room surface (ADR-164 Phase 1).
3
+ *
4
+ * Exposes the `agentbbs@~0.1.0` (a sibling BBS-style federation peer by the same
5
+ * author as ruflo) as MCP tools so ruflo agents can register business rooms
6
+ * (#sales, #finance, #marketing, ...), publish/watch typed envelopes, and
7
+ * mint single-use human-join tokens for SSH/web cockpit access.
8
+ *
9
+ * Motivation:
10
+ * ADR-164 wires the "business autopilot" cockpit on top of the existing
11
+ * ruflo federation primitives (FederationEnvelope, PII pipeline, budget
12
+ * circuit breaker). The 4 tools here are the ruflo-side handles into the
13
+ * agentbbs Rust workspace (`crates/agentbbs-federation/` and
14
+ * `crates/agentbbs-mcp/`) that already implement Ed25519-signed envelopes
15
+ * and an MCP transport. Phase 1 ships the surface; Phases 2-5 wire deeper.
16
+ *
17
+ * Architectural constraint (mirrors metaharness-tools.ts / agenticow-tools.ts):
18
+ * - `agentbbs` lives in `optionalDependencies` — must NOT be a hard runtime dep
19
+ * - When the package is missing, every tool returns
20
+ * `{success: true, degraded: true, reason: 'agentbbs-not-found'}`
21
+ * so callers see one contract regardless of install state
22
+ * - Phase 1: polling-based watch (streaming subscriptions are Phase 4)
23
+ *
24
+ * @module @claude-flow/cli/mcp-tools/agentbbs
25
+ */
26
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from 'node:fs';
27
+ import { resolve, isAbsolute, join } from 'node:path';
28
+ import { randomBytes, createHash } from 'node:crypto';
29
+ import { getProjectCwd } from './types.js';
30
+ const PACKAGE_NAME = 'agentbbs';
31
+ // Cache: amortize dynamic-import cost across handler calls.
32
+ // null = not yet attempted; false = unavailable; module = loaded.
33
+ let _agentbbsMod = null;
34
+ let _loadAttempted = false;
35
+ async function loadAgentbbs() {
36
+ if (_loadAttempted)
37
+ return _agentbbsMod || null;
38
+ _loadAttempted = true;
39
+ try {
40
+ _agentbbsMod = await import(PACKAGE_NAME);
41
+ return _agentbbsMod;
42
+ }
43
+ catch (err) {
44
+ if (err && (err.code === 'ERR_MODULE_NOT_FOUND' || err.code === 'MODULE_NOT_FOUND' ||
45
+ /Cannot find (module|package)/i.test(String(err?.message)))) {
46
+ _agentbbsMod = false;
47
+ return null;
48
+ }
49
+ throw err;
50
+ }
51
+ }
52
+ function degradedResult(reason) {
53
+ return { success: true, degraded: true, reason };
54
+ }
55
+ function resolveBasePath(input) {
56
+ const p = input && typeof input === 'string' && input.length > 0
57
+ ? input
58
+ : '.agentbbs';
59
+ if (/\.\.[\\/]|\0/.test(p))
60
+ throw new Error('basePath contains disallowed characters');
61
+ const abs = isAbsolute(p) ? p : resolve(getProjectCwd(), p);
62
+ return abs;
63
+ }
64
+ function validateRoomLabel(label) {
65
+ if (!label || typeof label !== 'string')
66
+ throw new Error('roomLabel is required');
67
+ if (label.length > 128)
68
+ throw new Error('roomLabel exceeds 128 chars');
69
+ // Rooms are conventionally `#sales`, `#finance`, etc. — keep `#` in the allow-list.
70
+ if (!/^[A-Za-z0-9_.\-:/@#]+$/.test(label)) {
71
+ throw new Error('roomLabel may only contain [A-Za-z0-9_.\\-:/@#]');
72
+ }
73
+ return label;
74
+ }
75
+ function validateRoomId(roomId) {
76
+ if (!roomId || typeof roomId !== 'string')
77
+ throw new Error('roomId is required');
78
+ if (roomId.length > 128)
79
+ throw new Error('roomId exceeds 128 chars');
80
+ if (!/^[A-Za-z0-9_.\-:/@#]+$/.test(roomId)) {
81
+ throw new Error('roomId may only contain [A-Za-z0-9_.\\-:/@#]');
82
+ }
83
+ return roomId;
84
+ }
85
+ function ensureDir(dir) {
86
+ if (!existsSync(dir))
87
+ mkdirSync(dir, { recursive: true });
88
+ }
89
+ function roomIdFromLabel(label) {
90
+ // Stable, deterministic roomId — strip leading `#`, lowercase, and append a
91
+ // short hash so we don't collide across two rooms with the same canonical
92
+ // label but different policies. Phase 1: deterministic over (label).
93
+ const norm = label.replace(/^#/, '').toLowerCase();
94
+ const h = createHash('sha256').update(`agentbbs:room:${norm}`).digest('hex').slice(0, 8);
95
+ return `${norm}-${h}`;
96
+ }
97
+ function roomLogPath(basePath, roomId) {
98
+ return join(basePath, `room-${roomId}.jsonl`);
99
+ }
100
+ function roomsRegistryPath(basePath) {
101
+ return join(basePath, 'rooms.json');
102
+ }
103
+ function readEnvelopes(path) {
104
+ if (!existsSync(path))
105
+ return [];
106
+ const raw = readFileSync(path, 'utf-8');
107
+ const out = [];
108
+ for (const line of raw.split(/\r?\n/)) {
109
+ if (!line.trim())
110
+ continue;
111
+ try {
112
+ out.push(JSON.parse(line));
113
+ }
114
+ catch { /* skip malformed line */ }
115
+ }
116
+ return out;
117
+ }
118
+ function nextSeq(path) {
119
+ const env = readEnvelopes(path);
120
+ if (env.length === 0)
121
+ return 1;
122
+ return (env[env.length - 1].seq ?? env.length) + 1;
123
+ }
124
+ /**
125
+ * Ephemeral per-process Ed25519 keypair for human-join token signing.
126
+ * Phase 1 contract: keys are NOT persisted across process restart — every
127
+ * invocation can mint tokens but cross-process replay protection is the
128
+ * agentbbs server's responsibility (nonce JTI tracking, per ADR-164 §3.2.4).
129
+ * Phase 2+ will wire this to the existing federation Ed25519 keypair.
130
+ */
131
+ let _signingKey = null;
132
+ async function getSigningKey() {
133
+ if (_signingKey)
134
+ return _signingKey;
135
+ // Use @noble/ed25519 (already a hard dep of @claude-flow/cli for IPFS signing).
136
+ const ed = await import('@noble/ed25519');
137
+ const priv = ed.utils.randomPrivateKey
138
+ ? ed.utils.randomPrivateKey()
139
+ : randomBytes(32);
140
+ const pub = await (ed.getPublicKeyAsync ?? ed.getPublicKey)(priv);
141
+ _signingKey = { priv, pub };
142
+ return _signingKey;
143
+ }
144
+ function base64url(buf) {
145
+ return Buffer.from(buf).toString('base64')
146
+ .replace(/\+/g, '-')
147
+ .replace(/\//g, '_')
148
+ .replace(/=+$/, '');
149
+ }
150
+ export const agentbbsTools = [
151
+ {
152
+ name: 'federation_bbs_register',
153
+ description: 'agentbbs@~0.1.0 — Register a BBS room as a named federation peer (ADR-164 Phase 1). Maps a business-domain label like "#sales" or "#finance" to a stable roomId and emits an attested PeerHello envelope. Use when you are scaffolding the business-autopilot cockpit and need a room handle that subsequent publish/watch calls can target. Calling FederationCoordinator.joinPeer() directly is wrong because it bypasses the room policy bag (PII mode, budget cap, preferLocal routing) that the BBS plugin layers on top. Optional dep — degrades to {degraded:true} when missing.',
154
+ category: 'federation',
155
+ tags: ['agentbbs', 'federation', 'bbs', 'register', 'adr-164'],
156
+ inputSchema: {
157
+ type: 'object',
158
+ properties: {
159
+ basePath: {
160
+ type: 'string',
161
+ description: 'Directory where BBS room state is persisted (defaults to <cwd>/.agentbbs).',
162
+ },
163
+ roomLabel: {
164
+ type: 'string',
165
+ description: 'Human-readable room label, e.g. "#sales" or "finance". May include alnum + _.-:/@# .',
166
+ },
167
+ agentbbsBin: {
168
+ type: 'string',
169
+ description: 'Optional explicit path to the agentbbs binary. Reserved for Phase 2 wire-up; ignored in Phase 1.',
170
+ },
171
+ },
172
+ required: ['roomLabel'],
173
+ },
174
+ handler: async (input) => {
175
+ // Validate inputs FIRST so callers see deterministic errors regardless of
176
+ // optional-dep state. Degradation is a runtime-state branch, not a way
177
+ // to mask malformed requests.
178
+ const roomLabel = validateRoomLabel(String(input.roomLabel));
179
+ const basePath = resolveBasePath(input.basePath);
180
+ const api = await loadAgentbbs();
181
+ if (!api)
182
+ return degradedResult('agentbbs-not-found');
183
+ ensureDir(basePath);
184
+ const roomId = roomIdFromLabel(roomLabel);
185
+ const registryPath = roomsRegistryPath(basePath);
186
+ const registry = existsSync(registryPath) ? JSON.parse(readFileSync(registryPath, 'utf-8')) : {};
187
+ // Idempotent — re-registering the same label updates timestamp but keeps id stable.
188
+ const entry = {
189
+ roomId,
190
+ roomLabel,
191
+ registeredAt: new Date().toISOString(),
192
+ trustLevel: 'attested',
193
+ };
194
+ registry[roomId] = entry;
195
+ writeFileSync(registryPath, JSON.stringify(registry, null, 2));
196
+ // Emit a synthetic PeerHello envelope into the room log so watchers see the join.
197
+ const logPath = roomLogPath(basePath, roomId);
198
+ const env = {
199
+ envelopeId: base64url(randomBytes(12)),
200
+ roomId,
201
+ seq: nextSeq(logPath),
202
+ msgType: 'PeerHello',
203
+ payload: { roomLabel, trustLevel: 'attested' },
204
+ timestamp: entry.registeredAt,
205
+ };
206
+ appendFileSync(logPath, JSON.stringify(env) + '\n');
207
+ // nodeId: deterministic per (cwd, roomId) so re-registers reuse identity.
208
+ const nodeId = createHash('sha256')
209
+ .update(`agentbbs:node:${basePath}:${roomId}`)
210
+ .digest('hex')
211
+ .slice(0, 16);
212
+ return {
213
+ success: true,
214
+ roomId,
215
+ nodeId,
216
+ trustLevel: 'attested',
217
+ };
218
+ },
219
+ },
220
+ {
221
+ name: 'federation_bbs_publish',
222
+ description: 'agentbbs — Publish a domain event from a pod agent to a BBS room (ADR-164 Phase 1). Wraps the payload in a ReplicateMessage envelope (envelopeId, seq, ts, msgType, payload) and appends it to the room log. Use when an agent has produced a typed event (pod-status, task-result, alert, human-override-ack, bench-result) that the human cockpit or other pods need to see. Storing into raw memory_store is wrong because it skips the room-scoped budget cap, PII pipeline gating, and monotonic seq numbering that ADR-164 §3.2.2 requires. Optional dep — degrades to {degraded:true} when missing.',
223
+ category: 'federation',
224
+ tags: ['agentbbs', 'federation', 'bbs', 'publish', 'adr-164'],
225
+ inputSchema: {
226
+ type: 'object',
227
+ properties: {
228
+ basePath: {
229
+ type: 'string',
230
+ description: 'Directory where BBS room state is persisted (defaults to <cwd>/.agentbbs).',
231
+ },
232
+ roomId: {
233
+ type: 'string',
234
+ description: 'Target room identifier as returned by federation_bbs_register.',
235
+ },
236
+ msgType: {
237
+ type: 'string',
238
+ description: 'Typed event kind (pod-status / task-result / alert / human-override-ack / bench-result).',
239
+ },
240
+ payload: {
241
+ type: 'object',
242
+ description: 'Event-specific JSON-serializable payload.',
243
+ },
244
+ signature: {
245
+ type: 'string',
246
+ description: 'Optional Ed25519 signature over the canonical envelope bytes. Phase 1: pass-through.',
247
+ },
248
+ },
249
+ required: ['roomId', 'msgType', 'payload'],
250
+ },
251
+ handler: async (input) => {
252
+ const basePath = resolveBasePath(input.basePath);
253
+ const roomId = validateRoomId(String(input.roomId));
254
+ const msgType = String(input.msgType ?? '');
255
+ if (!msgType)
256
+ throw new Error('msgType is required');
257
+ if (msgType.length > 64 || !/^[A-Za-z0-9_-]+$/.test(msgType)) {
258
+ throw new Error('msgType must be alnum + _ - and ≤64 chars');
259
+ }
260
+ if (typeof input.payload !== 'object' || input.payload === null) {
261
+ throw new Error('payload must be a JSON object');
262
+ }
263
+ const api = await loadAgentbbs();
264
+ if (!api)
265
+ return degradedResult('agentbbs-not-found');
266
+ ensureDir(basePath);
267
+ const logPath = roomLogPath(basePath, roomId);
268
+ const env = {
269
+ envelopeId: base64url(randomBytes(12)),
270
+ roomId,
271
+ seq: nextSeq(logPath),
272
+ msgType,
273
+ payload: input.payload,
274
+ timestamp: new Date().toISOString(),
275
+ signature: input.signature ? String(input.signature) : undefined,
276
+ };
277
+ appendFileSync(logPath, JSON.stringify(env) + '\n');
278
+ // Phase 1: recipientHopCount is always 0 (single-node). Phase 4+ will
279
+ // surface real hop counts from the WG mesh transport.
280
+ return {
281
+ success: true,
282
+ envelopeId: env.envelopeId,
283
+ recipientHopCount: 0,
284
+ };
285
+ },
286
+ },
287
+ {
288
+ name: 'federation_bbs_watch',
289
+ description: 'agentbbs — Poll recent envelopes from a BBS room (ADR-164 Phase 1). Returns envelopes newer than the optional sinceEnvelopeId, up to limit. Use when a pod agent needs to see incoming human overrides, new tasks, or peer events posted to its room. Polling memory_search for the room namespace is wrong because it loses the monotonic seq ordering and re-runs PII gating per query; this tool reads the canonical envelope log directly. Phase 1 is polling — Phase 4 layers streaming on the same surface. Optional dep — degrades to {degraded:true} when missing.',
290
+ category: 'federation',
291
+ tags: ['agentbbs', 'federation', 'bbs', 'watch', 'adr-164'],
292
+ inputSchema: {
293
+ type: 'object',
294
+ properties: {
295
+ basePath: {
296
+ type: 'string',
297
+ description: 'Directory where BBS room state is persisted (defaults to <cwd>/.agentbbs).',
298
+ },
299
+ roomId: {
300
+ type: 'string',
301
+ description: 'Room identifier to watch.',
302
+ },
303
+ sinceEnvelopeId: {
304
+ type: 'string',
305
+ description: 'Only return envelopes strictly after this id. Omit to return the most recent window.',
306
+ },
307
+ limit: {
308
+ type: 'integer',
309
+ description: 'Maximum envelopes to return (default 50, max 500).',
310
+ },
311
+ },
312
+ required: ['roomId'],
313
+ },
314
+ handler: async (input) => {
315
+ const basePath = resolveBasePath(input.basePath);
316
+ const roomId = validateRoomId(String(input.roomId));
317
+ const api = await loadAgentbbs();
318
+ if (!api)
319
+ return degradedResult('agentbbs-not-found');
320
+ const limitRaw = typeof input.limit === 'number' ? input.limit : 50;
321
+ const limit = Math.max(1, Math.min(500, Math.trunc(limitRaw)));
322
+ const sinceEnvelopeId = input.sinceEnvelopeId ? String(input.sinceEnvelopeId) : undefined;
323
+ const logPath = roomLogPath(basePath, roomId);
324
+ const all = readEnvelopes(logPath);
325
+ let slice = all;
326
+ if (sinceEnvelopeId) {
327
+ const idx = all.findIndex(e => e.envelopeId === sinceEnvelopeId);
328
+ slice = idx >= 0 ? all.slice(idx + 1) : all;
329
+ }
330
+ const envelopes = slice.slice(-limit);
331
+ return {
332
+ success: true,
333
+ roomId,
334
+ envelopes,
335
+ count: envelopes.length,
336
+ hasMore: slice.length > envelopes.length,
337
+ };
338
+ },
339
+ },
340
+ {
341
+ name: 'federation_bbs_human_join',
342
+ description: 'agentbbs — Mint a single-use Ed25519-signed token a human business owner presents to the agentbbs SSH/web front door to join a room (ADR-164 §3.2.4). Returns webUrl + sshCommand + handshakeToken + expiresAt. Use when the cockpit operator needs scoped, time-limited access to a room without sharing the federation root keypair. Issuing a permanent bearer token is wrong because the agentbbs server enforces single-use JTI replay protection and the 15-min default TTL — a long-lived token defeats both. Optional dep — degrades to {degraded:true} when missing.',
343
+ category: 'federation',
344
+ tags: ['agentbbs', 'federation', 'bbs', 'human-join', 'token', 'adr-164'],
345
+ inputSchema: {
346
+ type: 'object',
347
+ properties: {
348
+ roomId: {
349
+ type: 'string',
350
+ description: 'Room the token authorizes.',
351
+ },
352
+ ttlSeconds: {
353
+ type: 'integer',
354
+ description: 'Token lifetime in seconds. Max 900 (15 min). Default 300.',
355
+ },
356
+ },
357
+ required: ['roomId'],
358
+ },
359
+ handler: async (input) => {
360
+ const roomId = validateRoomId(String(input.roomId));
361
+ const api = await loadAgentbbs();
362
+ if (!api)
363
+ return degradedResult('agentbbs-not-found');
364
+ const ttlRaw = typeof input.ttlSeconds === 'number' ? input.ttlSeconds : 300;
365
+ const ttlSeconds = Math.max(30, Math.min(900, Math.trunc(ttlRaw)));
366
+ const now = Date.now();
367
+ const expiresAt = new Date(now + ttlSeconds * 1000).toISOString();
368
+ const nonce = base64url(randomBytes(16));
369
+ const payload = { roomId, nonce, expiresAt };
370
+ const canonical = JSON.stringify(payload);
371
+ const ed = await import('@noble/ed25519');
372
+ const { priv, pub } = await getSigningKey();
373
+ const sigBytes = await (ed.signAsync ?? ed.sign)(new TextEncoder().encode(canonical), priv);
374
+ // Token format: base64url(JSON{payload, sig, pub}). Single string, easy
375
+ // to paste into an SSH command line. The verifier reconstructs canonical
376
+ // bytes from payload and checks sig against pub.
377
+ const token = base64url(Buffer.from(JSON.stringify({
378
+ payload,
379
+ sig: base64url(sigBytes),
380
+ pub: base64url(pub),
381
+ })));
382
+ const webUrl = `https://agentbbs.local/rooms/${encodeURIComponent(roomId)}?token=${token}`;
383
+ const sshCommand = `ssh -p 2222 agentbbs.local -- join ${roomId} ${token}`;
384
+ return {
385
+ success: true,
386
+ webUrl,
387
+ sshCommand,
388
+ handshakeToken: token,
389
+ expiresAt,
390
+ };
391
+ },
392
+ },
393
+ ];
394
+ //# sourceMappingURL=agentbbs-tools.js.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Agenticow MCP Tools — Copy-On-Write memory branching surface.
3
+ *
4
+ * Exposes `agenticow@~0.2.3` (a sibling RVF-based COW vector store by the same
5
+ * author as ruflo) as MCP tools so agents can branch, checkpoint, rollback,
6
+ * and promote memory state without copying GB-scale `.rvf` files.
7
+ *
8
+ * Motivation:
9
+ * The v3.14.4 release uncovered a tarball-bloat regression where Darwin
10
+ * loops' git-worktree-per-agent pattern accumulated 3.3 GB of disk. The
11
+ * structural cause was full-copy snapshot semantics. Measured agenticow
12
+ * branches are exactly 162 bytes regardless of base size (see
13
+ * `docs/agenticow/findings.md` for the bench data).
14
+ *
15
+ * Architectural constraint (mirrors metaharness-tools.ts / testgen-tools.ts):
16
+ * - `agenticow` lives in `optionalDependencies` — must NOT be a hard runtime dep
17
+ * - When the package is missing, every tool returns
18
+ * `{success: true, degraded: true, reason: 'agenticow-not-found'}`
19
+ * so callers see one contract regardless of install state
20
+ *
21
+ * Measured performance vs published claims (agenticow@0.2.3):
22
+ * ✅ 162-byte branches — confirmed exact
23
+ * ✅ 3,000×–180,000× smaller than full-copy at N=1k–50k
24
+ * ❌ 0.5 ms branch — measured ~10ms (fixed cost, not size-proportional)
25
+ * ❌ 83× faster — only beats full-copy past N ≈ 30k crossover
26
+ *
27
+ * Use cases (per ADR / findings doc):
28
+ * - Per-Darwin-iteration memory branching (eliminates worktree bloat)
29
+ * - Per-user / per-session personalization (cheap fork, no full copy)
30
+ * - Federation: branch → promote back as merge semantics
31
+ *
32
+ * @module @claude-flow/cli/mcp-tools/agenticow
33
+ */
34
+ import type { MCPTool } from './types.js';
35
+ export declare const agenticowTools: MCPTool[];
36
+ //# sourceMappingURL=agenticow-tools.d.ts.map