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.
- package/.claude/settings.local.json +2 -1
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/business-pods/bbs-budget-tracker.d.ts +139 -0
- package/v3/@claude-flow/cli/dist/src/business-pods/bbs-budget-tracker.js +358 -0
- package/v3/@claude-flow/cli/dist/src/business-pods/domain-affinity-policy.d.ts +47 -0
- package/v3/@claude-flow/cli/dist/src/business-pods/domain-affinity-policy.js +65 -0
- package/v3/@claude-flow/cli/dist/src/business-pods/pod-schema.d.ts +96 -0
- package/v3/@claude-flow/cli/dist/src/business-pods/pod-schema.js +225 -0
- package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +20 -5
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +21 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-tools.d.ts +28 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-tools.js +394 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.d.ts +36 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-tools.js +253 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/business-pod-tools.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/business-pod-tools.js +169 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/http-fetch-tools.d.ts +55 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/http-fetch-tools.js +329 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +8 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/metaharness-tools.js +83 -4
- package/v3/@claude-flow/cli/package.json +4 -1
|
@@ -0,0 +1,253 @@
|
|
|
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 { existsSync } from 'node:fs';
|
|
35
|
+
import { getProjectCwd } from './types.js';
|
|
36
|
+
import { resolve, isAbsolute } from 'node:path';
|
|
37
|
+
const PACKAGE_NAME = 'agenticow';
|
|
38
|
+
// Cache: module load is expensive enough to amortize across handler calls.
|
|
39
|
+
// null = not yet attempted; false = attempted and unavailable; module = loaded.
|
|
40
|
+
let _agenticowMod = null;
|
|
41
|
+
let _loadAttempted = false;
|
|
42
|
+
async function loadAgenticow() {
|
|
43
|
+
if (_loadAttempted)
|
|
44
|
+
return _agenticowMod;
|
|
45
|
+
_loadAttempted = true;
|
|
46
|
+
try {
|
|
47
|
+
_agenticowMod = await import(PACKAGE_NAME);
|
|
48
|
+
return _agenticowMod;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err && (err.code === 'ERR_MODULE_NOT_FOUND' || err.code === 'MODULE_NOT_FOUND' ||
|
|
52
|
+
/Cannot find (module|package)/i.test(String(err.message)))) {
|
|
53
|
+
_agenticowMod = false;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function degradedResult(reason) {
|
|
60
|
+
return { success: true, degraded: true, reason };
|
|
61
|
+
}
|
|
62
|
+
function resolveMemoryPath(path) {
|
|
63
|
+
if (!path || typeof path !== 'string')
|
|
64
|
+
throw new Error('memory path is required');
|
|
65
|
+
// D-2 style: reject path traversal in user-supplied paths
|
|
66
|
+
if (/\.\.[\\/]|\0/.test(path))
|
|
67
|
+
throw new Error('memory path contains disallowed characters');
|
|
68
|
+
return isAbsolute(path) ? path : resolve(getProjectCwd(), path);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Lineage manifest companion path. agenticow persists the COW chain
|
|
72
|
+
* (working → checkpoints → base) into `<file>.agenticow.json` next to the
|
|
73
|
+
* `.rvf` data file. Without this, checkpoints and forks are in-memory only
|
|
74
|
+
* and disappear when the AgenticMemory handle closes. Mirrors the bin
|
|
75
|
+
* CLI's `manifestFor(file)` helper.
|
|
76
|
+
*/
|
|
77
|
+
function manifestFor(file) {
|
|
78
|
+
return `${file}.agenticow.json`;
|
|
79
|
+
}
|
|
80
|
+
function validateLabel(label) {
|
|
81
|
+
if (!label || typeof label !== 'string')
|
|
82
|
+
throw new Error('label is required');
|
|
83
|
+
if (label.length > 256)
|
|
84
|
+
throw new Error('label exceeds 256 chars');
|
|
85
|
+
if (!/^[A-Za-z0-9_.\-:/@]+$/.test(label)) {
|
|
86
|
+
throw new Error('label may only contain [A-Za-z0-9_.\\-:/@]');
|
|
87
|
+
}
|
|
88
|
+
return label;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Open (or create) a base memory file. When a lineage manifest exists at
|
|
92
|
+
* `<file>.agenticow.json`, we load that to restore the COW chain (checkpoints,
|
|
93
|
+
* ancestors). When only the `.rvf` exists, fresh-open it. When neither exists,
|
|
94
|
+
* dimension is required to create. Mirrors the bin CLI's `loadMem()` helper.
|
|
95
|
+
*/
|
|
96
|
+
async function openWithLineage(api, file, dimension) {
|
|
97
|
+
const manifest = manifestFor(file);
|
|
98
|
+
if (existsSync(manifest)) {
|
|
99
|
+
// The class-level static method `load` reconstructs the full chain.
|
|
100
|
+
return api.AgenticMemory.load(manifest);
|
|
101
|
+
}
|
|
102
|
+
const opts = {};
|
|
103
|
+
if (typeof dimension === 'number' && Number.isInteger(dimension) && dimension > 0) {
|
|
104
|
+
opts.dimension = dimension;
|
|
105
|
+
}
|
|
106
|
+
else if (!existsSync(file)) {
|
|
107
|
+
throw new Error('dimension is required when creating a new memory file');
|
|
108
|
+
}
|
|
109
|
+
return api.open(file, opts);
|
|
110
|
+
}
|
|
111
|
+
export const agenticowTools = [
|
|
112
|
+
{
|
|
113
|
+
name: 'agenticow_branch',
|
|
114
|
+
description: 'agenticow@~0.2.3 — COW-fork a base .rvf memory file. Measured 162-byte branches regardless of base size (verified at N=1k/10k/50k). Use when you need per-Darwin-iteration / per-user / per-session memory personalization. Copying the parent .rvf file is wrong because full-copy snapshots grow linearly (the 3.3 GB Darwin-worktree bloat fixed in v3.14.4); agenticow gives read-through semantics (parent ∪ edits, child wins) at constant 162 B. Optional dep — degrades to {degraded:true} when missing.',
|
|
115
|
+
category: 'memory',
|
|
116
|
+
tags: ['agenticow', 'memory', 'cow', 'branch'],
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
basePath: { type: 'string', description: 'Path to base .rvf memory file (absolute or relative to cwd)' },
|
|
121
|
+
branchPath: { type: 'string', description: 'Path to write the branch file' },
|
|
122
|
+
label: { type: 'string', description: 'Human-readable label for the branch (alnum + _.-:/@ only)' },
|
|
123
|
+
dimension: { type: 'integer', description: 'Vector dimension (required only when basePath does not exist yet)' },
|
|
124
|
+
},
|
|
125
|
+
required: ['basePath', 'branchPath', 'label'],
|
|
126
|
+
},
|
|
127
|
+
handler: async (input) => {
|
|
128
|
+
const api = await loadAgenticow();
|
|
129
|
+
if (!api)
|
|
130
|
+
return degradedResult('agenticow-not-found');
|
|
131
|
+
const label = validateLabel(String(input.label));
|
|
132
|
+
const basePath = resolveMemoryPath(String(input.basePath));
|
|
133
|
+
const branchPath = resolveMemoryPath(String(input.branchPath));
|
|
134
|
+
const dim = input.dimension;
|
|
135
|
+
const base = await openWithLineage(api, basePath, dim);
|
|
136
|
+
try {
|
|
137
|
+
const branch = await base.fork(label, branchPath);
|
|
138
|
+
// Persist lineage manifests so the branch (and base) reopen with
|
|
139
|
+
// their COW chain intact. Without this, fork is in-memory only.
|
|
140
|
+
await branch.save?.(manifestFor(branchPath));
|
|
141
|
+
await base.save?.(manifestFor(basePath));
|
|
142
|
+
await branch.close?.();
|
|
143
|
+
return {
|
|
144
|
+
success: true,
|
|
145
|
+
basePath,
|
|
146
|
+
branchPath,
|
|
147
|
+
label,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
await base.close?.();
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'agenticow_checkpoint',
|
|
157
|
+
description: 'agenticow — freeze a labelled restore point on an .rvf memory file. Subsequent edits stay in a fresh COW child; rollback returns here. Use when you are about to run an experimental Darwin tick or speculative agent edit that may need to be discarded. Relying on the working node alone is wrong because there is no "undo last N writes" semantics — without a checkpoint, a bad ingest contaminates the base. Persists via .agenticow.json lineage manifest so it survives close+reopen.',
|
|
158
|
+
category: 'memory',
|
|
159
|
+
tags: ['agenticow', 'memory', 'cow', 'checkpoint'],
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: 'object',
|
|
162
|
+
properties: {
|
|
163
|
+
path: { type: 'string', description: 'Path to .rvf memory file' },
|
|
164
|
+
label: { type: 'string', description: 'Checkpoint label (alnum + _.-:/@ only)' },
|
|
165
|
+
},
|
|
166
|
+
required: ['path', 'label'],
|
|
167
|
+
},
|
|
168
|
+
handler: async (input) => {
|
|
169
|
+
const api = await loadAgenticow();
|
|
170
|
+
if (!api)
|
|
171
|
+
return degradedResult('agenticow-not-found');
|
|
172
|
+
const label = validateLabel(String(input.label));
|
|
173
|
+
const path = resolveMemoryPath(String(input.path));
|
|
174
|
+
const mem = await openWithLineage(api, path);
|
|
175
|
+
try {
|
|
176
|
+
const cp = await mem.checkpoint(label);
|
|
177
|
+
await mem.save?.(manifestFor(path));
|
|
178
|
+
return { success: true, path, label, checkpoint: cp };
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
await mem.close?.();
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'agenticow_rollback',
|
|
187
|
+
description: 'agenticow — discard all edits since the most recent checkpoint on an .rvf memory file. Reuses a fresh COW child derived from the checkpoint. Use when a Darwin tick or agent experiment regressed and you want to revert memory state without re-running. Deleting+rebuilding the .rvf is wrong because rebuild cost is O(N) and the data after the bad point is lost; rollback is O(edits-since-checkpoint) and the earlier history stays intact via the lineage manifest.',
|
|
188
|
+
category: 'memory',
|
|
189
|
+
tags: ['agenticow', 'memory', 'cow', 'rollback'],
|
|
190
|
+
inputSchema: {
|
|
191
|
+
type: 'object',
|
|
192
|
+
properties: {
|
|
193
|
+
path: { type: 'string', description: 'Path to .rvf memory file' },
|
|
194
|
+
},
|
|
195
|
+
required: ['path'],
|
|
196
|
+
},
|
|
197
|
+
handler: async (input) => {
|
|
198
|
+
const api = await loadAgenticow();
|
|
199
|
+
if (!api)
|
|
200
|
+
return degradedResult('agenticow-not-found');
|
|
201
|
+
const path = resolveMemoryPath(String(input.path));
|
|
202
|
+
const mem = await openWithLineage(api, path);
|
|
203
|
+
try {
|
|
204
|
+
const r = await mem.rollback();
|
|
205
|
+
await mem.save?.(manifestFor(path));
|
|
206
|
+
return { success: true, path, rolledBack: true, result: r };
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
await mem.close?.();
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: 'agenticow_promote',
|
|
215
|
+
description: 'agenticow — merge a branch\'s edits back into its base (or an explicit target) memory file. After promote, branch edits become part of base lineage. Use when a per-user / per-Darwin-iteration branch has been validated and should graduate to shared memory (federation merge, A/B winner). Manually re-ingesting edits into the base is wrong because the edit set is opaque to the caller and tombstones (deletions) are easily missed; promote applies the full edit + tombstone set atomically.',
|
|
216
|
+
category: 'memory',
|
|
217
|
+
tags: ['agenticow', 'memory', 'cow', 'promote', 'merge'],
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: {
|
|
221
|
+
branchPath: { type: 'string', description: 'Path to branch .rvf file' },
|
|
222
|
+
basePath: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
description: 'Path to base .rvf file. When omitted, promote merges into the ' +
|
|
225
|
+
'recorded fork parent (most common case).',
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
required: ['branchPath'],
|
|
229
|
+
},
|
|
230
|
+
handler: async (input) => {
|
|
231
|
+
const api = await loadAgenticow();
|
|
232
|
+
if (!api)
|
|
233
|
+
return degradedResult('agenticow-not-found');
|
|
234
|
+
const branchPath = resolveMemoryPath(String(input.branchPath));
|
|
235
|
+
const basePath = input.basePath ? resolveMemoryPath(String(input.basePath)) : undefined;
|
|
236
|
+
const branch = await openWithLineage(api, branchPath);
|
|
237
|
+
const base = basePath ? await openWithLineage(api, basePath) : undefined;
|
|
238
|
+
try {
|
|
239
|
+
const result = base ? await branch.promote(base) : await branch.promote();
|
|
240
|
+
// Persist mutated lineage so promote survives close+reopen
|
|
241
|
+
await branch.save?.(manifestFor(branchPath));
|
|
242
|
+
if (base && basePath)
|
|
243
|
+
await base.save?.(manifestFor(basePath));
|
|
244
|
+
return { success: true, branchPath, basePath: basePath ?? null, promoted: result ?? true };
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
await branch.close?.();
|
|
248
|
+
await base?.close?.();
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
//# sourceMappingURL=agenticow-tools.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Business-pod MCP tools — ADR-164 Phase 2 + Phase 3.
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 surfaces pod-template validation (`business_pod_validate`).
|
|
5
|
+
* Phase 3 adds the domain-affinity routing decision (`business_pod_route_backend`)
|
|
6
|
+
* so any caller (agent, /loop driver, CI workflow) can ask "which backend
|
|
7
|
+
* should @metaharness/router prefer for this pod?" without re-implementing
|
|
8
|
+
* the §3.4 rules. The decision is structural and deterministic — no learned
|
|
9
|
+
* weights, no schedule, just (preferLocalExecution, budgetUsdMonthly).
|
|
10
|
+
*
|
|
11
|
+
* Both tools accept either a pod template *object* or a string *path* to a
|
|
12
|
+
* JSON file. The path form is provided so /loop drivers can avoid loading
|
|
13
|
+
* the template themselves; the object form is for callers that already have
|
|
14
|
+
* the validated template in hand.
|
|
15
|
+
*
|
|
16
|
+
* @module @claude-flow/cli/mcp-tools/business-pod
|
|
17
|
+
*/
|
|
18
|
+
import type { MCPTool } from './types.js';
|
|
19
|
+
export declare const businessPodTools: MCPTool[];
|
|
20
|
+
//# sourceMappingURL=business-pod-tools.d.ts.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Business-pod MCP tools — ADR-164 Phase 2 + Phase 3.
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 surfaces pod-template validation (`business_pod_validate`).
|
|
5
|
+
* Phase 3 adds the domain-affinity routing decision (`business_pod_route_backend`)
|
|
6
|
+
* so any caller (agent, /loop driver, CI workflow) can ask "which backend
|
|
7
|
+
* should @metaharness/router prefer for this pod?" without re-implementing
|
|
8
|
+
* the §3.4 rules. The decision is structural and deterministic — no learned
|
|
9
|
+
* weights, no schedule, just (preferLocalExecution, budgetUsdMonthly).
|
|
10
|
+
*
|
|
11
|
+
* Both tools accept either a pod template *object* or a string *path* to a
|
|
12
|
+
* JSON file. The path form is provided so /loop drivers can avoid loading
|
|
13
|
+
* the template themselves; the object form is for callers that already have
|
|
14
|
+
* the validated template in hand.
|
|
15
|
+
*
|
|
16
|
+
* @module @claude-flow/cli/mcp-tools/business-pod
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
19
|
+
import { resolve, isAbsolute } from 'node:path';
|
|
20
|
+
import { validatePodTemplate, PodTemplateValidationError, KNOWN_AGENT_TYPES, } from '../business-pods/pod-schema.js';
|
|
21
|
+
import { selectAgentBackend, } from '../business-pods/domain-affinity-policy.js';
|
|
22
|
+
/**
|
|
23
|
+
* Load a pod template from either an in-memory object or a JSON-file path.
|
|
24
|
+
* Returns either the parsed (unvalidated) JSON or an `error` shape suitable
|
|
25
|
+
* for direct return from an MCP handler.
|
|
26
|
+
*/
|
|
27
|
+
function loadTemplate(input) {
|
|
28
|
+
if (input.podTemplate !== undefined && input.podTemplate !== null) {
|
|
29
|
+
if (typeof input.podTemplate !== 'object' || Array.isArray(input.podTemplate)) {
|
|
30
|
+
return { ok: false, error: 'podTemplate must be a JSON object', path: '/podTemplate' };
|
|
31
|
+
}
|
|
32
|
+
return { ok: true, raw: input.podTemplate };
|
|
33
|
+
}
|
|
34
|
+
if (typeof input.podTemplatePath === 'string' && input.podTemplatePath.length > 0) {
|
|
35
|
+
const abs = isAbsolute(input.podTemplatePath)
|
|
36
|
+
? input.podTemplatePath
|
|
37
|
+
: resolve(process.cwd(), input.podTemplatePath);
|
|
38
|
+
if (!existsSync(abs)) {
|
|
39
|
+
return { ok: false, error: `pod template file not found: ${abs}`, path: '/podTemplatePath' };
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const raw = JSON.parse(readFileSync(abs, 'utf-8'));
|
|
43
|
+
return { ok: true, raw };
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
47
|
+
return { ok: false, error: `failed to parse pod template JSON: ${msg}`, path: '/podTemplatePath' };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
error: 'either podTemplate (object) or podTemplatePath (string) must be provided',
|
|
53
|
+
path: '/',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export const businessPodTools = [
|
|
57
|
+
{
|
|
58
|
+
name: 'business_pod_validate',
|
|
59
|
+
description: 'ADR-164 Phase 2 — Validate a business-pod template JSON against the schema in ADR-164 §3.3 (name, agents[], allowedMcpTools, bench, piiPolicy, budgets, cronSchedule, auditReadView, reservationExpiryMs bounded by ADR-164.1 §3.2). Use when a /loop driver or CI workflow needs to pre-flight a pod template before pod-tick.mjs reaches it — surfacing validation as JSON keeps the optional-dep degraded path clean. Hand-parsing the JSON in the caller is wrong because it skips the JSON-pointer error path and the reservationExpiryMs [5000, 300000] ms bound check that ADR-164.1 mandates. Pair with business_pod_validate -> pod-tick.mjs in the sales-pod smoke contract.',
|
|
60
|
+
category: 'business-pods',
|
|
61
|
+
tags: ['business-pods', 'pod-template', 'validation', 'adr-164', 'adr-164.1'],
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
podTemplate: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
description: 'The pod template object to validate. Must conform to the PodTemplate interface from ADR-164 §3.3.',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ['podTemplate'],
|
|
71
|
+
},
|
|
72
|
+
handler: async (input) => {
|
|
73
|
+
if (typeof input.podTemplate !== 'object' || input.podTemplate === null) {
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
valid: false,
|
|
77
|
+
error: 'podTemplate must be a JSON object',
|
|
78
|
+
path: '/',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const template = validatePodTemplate(input.podTemplate);
|
|
83
|
+
// Lightweight agent-type sanity check — surface unknown types as a
|
|
84
|
+
// warning rather than a hard failure so operators can prototype with
|
|
85
|
+
// not-yet-registered roles. pod-tick.mjs enforces the hard check.
|
|
86
|
+
const unknownAgents = template.agents
|
|
87
|
+
.map((a) => a.agentType)
|
|
88
|
+
.filter((t) => !KNOWN_AGENT_TYPES.includes(t));
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
valid: true,
|
|
92
|
+
template,
|
|
93
|
+
warnings: unknownAgents.length > 0
|
|
94
|
+
? [`unknown agent types (pod-tick.mjs will reject): ${unknownAgents.join(', ')}`]
|
|
95
|
+
: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
if (err instanceof PodTemplateValidationError) {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
valid: false,
|
|
103
|
+
error: err.message,
|
|
104
|
+
path: err.path,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'business_pod_route_backend',
|
|
113
|
+
description: 'ADR-164 Phase 3 — Compute the domain-affinity routing decision for a business pod per ADR-164 §3.4 and return {backend, reason}. The three backends are local-stdio (preferLocalExecution=true), cloud-managed (preferLocalExecution=false AND budgetUsdMonthly >= 50), and remote-peer (everything else — small-budget non-local pods route through a federation peer node). Use when a /loop driver, @metaharness/router policy hook, or operator CLI needs the structural routing pick BEFORE the cost-optimal KRR step — surfacing this as an MCP tool keeps the rule auditable from the pod template alone and lets non-TS callers reach it. Re-implementing the rule in the caller is wrong because it forks the §3.4 source-of-truth and skips the {success,valid,error,path} envelope shape callers already rely on from business_pod_validate. Pair with business_pod_validate when pre-flighting a template, since this tool also runs full schema validation and degrades to the same error shape on malformed input. Threshold lives in CLOUD_BUDGET_THRESHOLD_USD in domain-affinity-policy.ts — keep that constant and this description aligned.',
|
|
114
|
+
category: 'business-pods',
|
|
115
|
+
tags: ['business-pods', 'pod-template', 'routing', 'domain-affinity', 'adr-164'],
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
podTemplate: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
description: 'In-memory pod template object. One of podTemplate or podTemplatePath is required.',
|
|
122
|
+
},
|
|
123
|
+
podTemplatePath: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
description: 'Absolute or cwd-relative path to a pod template JSON file. One of podTemplate or podTemplatePath is required.',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
handler: async (input) => {
|
|
130
|
+
const loaded = loadTemplate(input);
|
|
131
|
+
if (!loaded.ok) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
valid: false,
|
|
135
|
+
error: loaded.error,
|
|
136
|
+
path: loaded.path,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
let template;
|
|
140
|
+
try {
|
|
141
|
+
template = validatePodTemplate(loaded.raw);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
if (err instanceof PodTemplateValidationError) {
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
valid: false,
|
|
148
|
+
error: err.message,
|
|
149
|
+
path: err.path,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
const decision = selectAgentBackend(template);
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
valid: true,
|
|
158
|
+
backend: decision.backend,
|
|
159
|
+
reason: decision.reason,
|
|
160
|
+
pod: {
|
|
161
|
+
name: template.name,
|
|
162
|
+
preferLocalExecution: template.preferLocalExecution,
|
|
163
|
+
budgetUsdMonthly: template.budgetUsdMonthly,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
//# sourceMappingURL=business-pod-tools.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http_fetch MCP tool — ADR-164 §5.1.8.
|
|
3
|
+
*
|
|
4
|
+
* The Operations business pod (templates/ops.json) references `http_fetch` as
|
|
5
|
+
* an allowed MCP tool for the synthetic-endpoint availability bench (probe
|
|
6
|
+
* 200/500 from a configured URL, escalate to #ops on 500-rate spikes).
|
|
7
|
+
* Phase 3 left this as a TODO; Phase 4 ships the tool with secure-by-default
|
|
8
|
+
* gating per the §5.1.8 contract: URL allowlist (no file://, ftp://, no
|
|
9
|
+
* RFC-1918 / loopback unless explicitly enabled), header sanitization (no
|
|
10
|
+
* auth pass-through unless explicitly enabled), hard timeout via
|
|
11
|
+
* AbortController, response truncation, default User-Agent.
|
|
12
|
+
*
|
|
13
|
+
* Architectural constraints (load-bearing):
|
|
14
|
+
* - DEFAULT-REFUSES private addresses + loopback (CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 opt-in)
|
|
15
|
+
* - DEFAULT-REFUSES Authorization / Cookie / X-Auth-* (CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1 opt-in)
|
|
16
|
+
* - hard timeout 30s default, 60s ceiling
|
|
17
|
+
* - response truncated to 256KB default, 1MB ceiling
|
|
18
|
+
* - no redirects auto-followed beyond fetch's default; status reported as-is
|
|
19
|
+
*
|
|
20
|
+
* @module @claude-flow/cli/mcp-tools/http-fetch
|
|
21
|
+
*/
|
|
22
|
+
import type { MCPTool } from './types.js';
|
|
23
|
+
export declare class HttpFetchValidationError extends Error {
|
|
24
|
+
readonly code: string;
|
|
25
|
+
constructor(message: string, code: string);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Decide whether the URL is permitted under the default secure-by-default
|
|
29
|
+
* allowlist. Block file://, ftp://, RFC-1918 private addresses, loopback,
|
|
30
|
+
* link-local — unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 is set.
|
|
31
|
+
*/
|
|
32
|
+
export declare function validateUrl(rawUrl: string): URL;
|
|
33
|
+
export declare function validateHeaders(headers: Record<string, string>): Record<string, string>;
|
|
34
|
+
export interface HttpFetchResult {
|
|
35
|
+
success: boolean;
|
|
36
|
+
status: number;
|
|
37
|
+
statusText: string;
|
|
38
|
+
headers: Record<string, string>;
|
|
39
|
+
body: string;
|
|
40
|
+
bodyTruncated: boolean;
|
|
41
|
+
bytesRead: number;
|
|
42
|
+
durationMs: number;
|
|
43
|
+
url: string;
|
|
44
|
+
method: string;
|
|
45
|
+
error?: string;
|
|
46
|
+
errorCode?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Pure execution path so tests can call it without going through the MCP
|
|
50
|
+
* dispatcher. Returns a result object (does not throw on validation failure
|
|
51
|
+
* — it returns success: false with an errorCode).
|
|
52
|
+
*/
|
|
53
|
+
export declare function httpFetchExecute(input: Record<string, unknown>): Promise<HttpFetchResult>;
|
|
54
|
+
export declare const httpFetchTools: MCPTool[];
|
|
55
|
+
//# sourceMappingURL=http-fetch-tools.d.ts.map
|