claude-flow 3.56.9 → 3.56.10
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/.proven-config-version +1 -0
- package/.claude/helpers/intelligence.cjs +51 -9
- package/.claude/proven-config.json +42 -0
- package/.claude/skills/verification-quality/SKILL.md +14 -11
- package/README.md +1 -1
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
- package/package.json +2 -2
- package/v3/@claude-flow/cli/dist/src/autopilot-state.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/autopilot-state.js +36 -8
- package/v3/@claude-flow/cli/dist/src/command-resolution.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/command-resolution.js +25 -0
- package/v3/@claude-flow/cli/dist/src/commands/agent.js +17 -17
- package/v3/@claude-flow/cli/dist/src/commands/claims.js +6 -8
- package/v3/@claude-flow/cli/dist/src/commands/daemon.js +86 -31
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +14 -2
- package/v3/@claude-flow/cli/dist/src/commands/security-code-patterns.d.ts +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/security-code-patterns.js +61 -3
- package/v3/@claude-flow/cli/dist/src/commands/security.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/commands/security.js +81 -35
- package/v3/@claude-flow/cli/dist/src/commands/tui.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/commands/tui.js +29 -3
- package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.js +24 -3
- package/v3/@claude-flow/cli/dist/src/funnel/local-signals.js +2 -1
- package/v3/@claude-flow/cli/dist/src/index.js +6 -42
- package/v3/@claude-flow/cli/dist/src/init/types.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-health-metrics.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-health-metrics.js +33 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +45 -22
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-federation.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-federation.js +7 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +25 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/aidefence-known-misses.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/aidefence-known-misses.js +57 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/analyze-tools.js +10 -11
- package/v3/@claude-flow/cli/dist/src/mcp-tools/autopilot-tools.js +2 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-intent-tools.js +6 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +29 -7
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +4 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +70 -20
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +28 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/ruvllm-tools.js +6 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/security-tools.js +55 -17
- package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +66 -17
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.d.ts +5 -1
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.js +105 -29
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +10 -3
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +2 -1
- package/v3/@claude-flow/cli/dist/src/memory/search-defaults.d.ts +54 -0
- package/v3/@claude-flow/cli/dist/src/memory/search-defaults.js +37 -0
- package/v3/@claude-flow/cli/dist/src/memory/sona-optimizer.d.ts +28 -0
- package/v3/@claude-flow/cli/dist/src/memory/sona-optimizer.js +59 -2
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +24 -24
- package/v3/@claude-flow/cli/dist/src/ruvector/agent-wasm.d.ts +10 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/agent-wasm.js +27 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/diff-classifier.d.ts +2 -4
- package/v3/@claude-flow/cli/dist/src/ruvector/diff-classifier.js +202 -69
- package/v3/@claude-flow/cli/dist/src/ruvector/ruvllm-wasm.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/ruvllm-wasm.js +12 -1
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +7 -1
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +37 -0
- package/v3/@claude-flow/cli/dist/src/transfer/store/discovery.js +16 -17
- package/v3/@claude-flow/cli/dist/src/transfer/store/types.d.ts +4 -0
- package/v3/@claude-flow/cli/package.json +4 -4
|
@@ -84,6 +84,27 @@ export function currentInstallEnv(cwd = process.cwd()) {
|
|
|
84
84
|
catch { /* none */ }
|
|
85
85
|
return env;
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* #4176: is `cwd` a ruflo project? A bare `.claude/` is not enough: Claude Code
|
|
89
|
+
* itself creates `.claude/.cc-writes/` in whatever directory a Bash command ran
|
|
90
|
+
* in, so every subdirectory a session cd-ed into used to qualify and received a
|
|
91
|
+
* proven-config.json. A ruflo project carries `ruflo init`'s own markers: a
|
|
92
|
+
* `.claude/settings.json`, the `.claude/helpers/` directory, or an earlier
|
|
93
|
+
* adoption stamp.
|
|
94
|
+
*/
|
|
95
|
+
export function isRufloProject(cwd) {
|
|
96
|
+
try {
|
|
97
|
+
const claudeDir = path.join(cwd, '.claude');
|
|
98
|
+
if (!fs.statSync(claudeDir).isDirectory())
|
|
99
|
+
return false;
|
|
100
|
+
return fs.existsSync(path.join(claudeDir, 'settings.json'))
|
|
101
|
+
|| fs.existsSync(path.join(claudeDir, 'helpers'))
|
|
102
|
+
|| fs.existsSync(path.join(claudeDir, PROVEN_CONFIG_STAMP));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
87
108
|
/**
|
|
88
109
|
* The testable adoption core: given a signed champion and the local env, adopt
|
|
89
110
|
* it into `cwd/.claude` iff it is newer than the stamp AND passes both gates.
|
|
@@ -92,8 +113,8 @@ export function currentInstallEnv(cwd = process.cwd()) {
|
|
|
92
113
|
export function adoptSignedConfig(cwd, signed, env, opts = {}) {
|
|
93
114
|
try {
|
|
94
115
|
const claudeDir = path.join(cwd, '.claude');
|
|
95
|
-
if (!
|
|
96
|
-
return { adopted: false }; // not a ruflo project
|
|
116
|
+
if (!isRufloProject(cwd))
|
|
117
|
+
return { adopted: false }; // not a ruflo project (#4176)
|
|
97
118
|
const championId = signed.manifest?.policy?.ref;
|
|
98
119
|
if (!championId)
|
|
99
120
|
return { adopted: false, skipped: 'manifest missing policy.ref' };
|
|
@@ -137,7 +158,7 @@ export function adoptSignedConfig(cwd, signed, env, opts = {}) {
|
|
|
137
158
|
*/
|
|
138
159
|
export async function autoAdoptProvenConfigIfStale(cwd = process.cwd()) {
|
|
139
160
|
try {
|
|
140
|
-
if (!
|
|
161
|
+
if (!isRufloProject(cwd))
|
|
141
162
|
return { adopted: false };
|
|
142
163
|
const src = findPackageProvenConfig();
|
|
143
164
|
if (!src)
|
|
@@ -34,7 +34,8 @@ export function getSecurityStatus(cwd = process.cwd()) {
|
|
|
34
34
|
const rawFindings = scan.summary?.total ?? scan.totalFindings ?? scan.findings?.length ?? 0;
|
|
35
35
|
const findings = Math.max(0, Number.isFinite(rawFindings) ? rawFindings : 0);
|
|
36
36
|
return {
|
|
37
|
-
|
|
37
|
+
// #4124: a scan whose dependency phase could not run is not a clean scan.
|
|
38
|
+
status: findings > 0 ? 'ISSUES' : scan.incomplete === true ? 'PENDING' : 'CLEAN',
|
|
38
39
|
findings,
|
|
39
40
|
scannedAt: typeof scan.timestamp === 'string' ? scan.timestamp : undefined,
|
|
40
41
|
cvesFixed: 0,
|
|
@@ -16,6 +16,7 @@ import { fileURLToPath } from 'url';
|
|
|
16
16
|
import { dirname, join } from 'path';
|
|
17
17
|
import { commandParser } from './parser.js';
|
|
18
18
|
import { output } from './output.js';
|
|
19
|
+
import { resolveTargetCommand } from './command-resolution.js';
|
|
19
20
|
import { commands, getCommandsByCategory, getCommand, getCommandAsync, getCommandNames, getLazyCommandNames, hasCommand } from './commands/index.js';
|
|
20
21
|
import { suggestCommand } from './suggest.js';
|
|
21
22
|
import { runStartupUpdateCheck } from './update/index.js';
|
|
@@ -264,48 +265,11 @@ export class CLI {
|
|
|
264
265
|
this.output.writeln(this.output.dim(` ${message}`));
|
|
265
266
|
process.exit(1);
|
|
266
267
|
}
|
|
267
|
-
// Handle
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (commandPath.length > 1 && command.subcommands) {
|
|
273
|
-
const subcommandName = commandPath[1];
|
|
274
|
-
const subcommand = command.subcommands.find(sc => sc.name === subcommandName || sc.aliases?.includes(subcommandName));
|
|
275
|
-
if (subcommand) {
|
|
276
|
-
targetCommand = subcommand;
|
|
277
|
-
// Parser already extracted subcommand from positional, so use as-is
|
|
278
|
-
subcommandArgs = positional;
|
|
279
|
-
// Check for nested subcommand (level 2)
|
|
280
|
-
if (commandPath.length > 2 && subcommand.subcommands) {
|
|
281
|
-
const nestedName = commandPath[2];
|
|
282
|
-
const nestedSubcommand = subcommand.subcommands.find(sc => sc.name === nestedName || sc.aliases?.includes(nestedName));
|
|
283
|
-
if (nestedSubcommand) {
|
|
284
|
-
targetCommand = nestedSubcommand;
|
|
285
|
-
// Parser already extracted nested subcommand too
|
|
286
|
-
subcommandArgs = positional;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
else if (positional.length > 0 && command.subcommands) {
|
|
292
|
-
// Check if first positional is a subcommand
|
|
293
|
-
const subcommandName = positional[0];
|
|
294
|
-
const subcommand = command.subcommands.find(sc => sc.name === subcommandName || sc.aliases?.includes(subcommandName));
|
|
295
|
-
if (subcommand) {
|
|
296
|
-
targetCommand = subcommand;
|
|
297
|
-
subcommandArgs = positional.slice(1);
|
|
298
|
-
// Check for nested subcommand (level 2 from positional)
|
|
299
|
-
if (subcommandArgs.length > 0 && subcommand.subcommands) {
|
|
300
|
-
const nestedName = subcommandArgs[0];
|
|
301
|
-
const nestedSubcommand = subcommand.subcommands.find(sc => sc.name === nestedName || sc.aliases?.includes(nestedName));
|
|
302
|
-
if (nestedSubcommand) {
|
|
303
|
-
targetCommand = nestedSubcommand;
|
|
304
|
-
subcommandArgs = subcommandArgs.slice(1);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
268
|
+
// Handle subcommands at any depth (#4157): walk the parsed command path, or the
|
|
269
|
+
// positional arguments when the path only names the top-level command.
|
|
270
|
+
const resolved = resolveTargetCommand(command, commandPath, positional);
|
|
271
|
+
const targetCommand = resolved.target;
|
|
272
|
+
const subcommandArgs = resolved.args;
|
|
309
273
|
// Validate flags
|
|
310
274
|
const validationErrors = this.parser.validateFlags(flags, targetCommand);
|
|
311
275
|
if (validationErrors.length > 0) {
|
|
@@ -76,7 +76,7 @@ export const DEFAULT_INIT_OPTIONS = {
|
|
|
76
76
|
github: true,
|
|
77
77
|
flowNexus: false,
|
|
78
78
|
browser: true,
|
|
79
|
-
v3:
|
|
79
|
+
v3: false, // ADR-128: opt-in like agents.v3; use --full or `init skills --v3`
|
|
80
80
|
dualMode: false, // Optional: enable with --dual flag
|
|
81
81
|
all: false,
|
|
82
82
|
},
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* keeps the wire from agent_spawn → ProviderManager (real) in one
|
|
8
8
|
* place rather than duplicated.
|
|
9
9
|
*/
|
|
10
|
+
import { type AgentExecutionSample } from './agent-health-metrics.js';
|
|
10
11
|
type ClaudeModel = 'haiku' | 'sonnet' | 'opus' | 'opus-4.7' | 'inherit';
|
|
11
12
|
export interface AgentRecord {
|
|
12
13
|
agentId: string;
|
|
@@ -32,6 +33,7 @@ export interface AgentRecord {
|
|
|
32
33
|
/** ADR-148 phase 2 — concrete OpenRouter slug when provider='openrouter'. */
|
|
33
34
|
openrouterModel?: string;
|
|
34
35
|
lastResult?: Record<string, unknown>;
|
|
36
|
+
executionHistory?: AgentExecutionSample[];
|
|
35
37
|
}
|
|
36
38
|
export declare const DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-5";
|
|
37
39
|
export declare function modelRejectsSamplingParams(model: string): boolean;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { getProjectCwd } from './types.js';
|
|
13
|
+
import { appendAgentExecution, summarizeAgentExecutions } from './agent-health-metrics.js';
|
|
13
14
|
import { configManager } from '../services/config-file-manager.js';
|
|
14
15
|
const STORAGE_DIR = '.claude-flow';
|
|
15
16
|
const AGENT_DIR = 'agents';
|
|
@@ -669,6 +670,20 @@ export async function executeAgentTask(input) {
|
|
|
669
670
|
}
|
|
670
671
|
if (out)
|
|
671
672
|
current.lastResult = out;
|
|
673
|
+
const previousMeasuredHealth = summarizeAgentExecutions(current.executionHistory).health;
|
|
674
|
+
const mayUpdateStoredHealth = previousMeasuredHealth === null
|
|
675
|
+
? current.health === agent.health
|
|
676
|
+
: current.health === previousMeasuredHealth;
|
|
677
|
+
current.executionHistory = appendAgentExecution(current.executionHistory, {
|
|
678
|
+
at: new Date().toISOString(),
|
|
679
|
+
success: result.success,
|
|
680
|
+
durationMs: result.durationMs ?? Date.now() - startedAt,
|
|
681
|
+
});
|
|
682
|
+
// An explicit agent_update during the provider call still wins. The
|
|
683
|
+
// measured value remains available from executionHistory in agent_health.
|
|
684
|
+
if (mayUpdateStoredHealth) {
|
|
685
|
+
current.health = summarizeAgentExecutions(current.executionHistory).health ?? current.health;
|
|
686
|
+
}
|
|
672
687
|
saveAgentStore(latest);
|
|
673
688
|
};
|
|
674
689
|
if (result.success) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Bounded execution evidence for agent_health. Registry records are not processes. */
|
|
2
|
+
export interface AgentExecutionSample {
|
|
3
|
+
at: string;
|
|
4
|
+
success: boolean;
|
|
5
|
+
durationMs: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const AGENT_HEALTH_WINDOW = 20;
|
|
8
|
+
export declare function appendAgentExecution(value: unknown, sample: AgentExecutionSample): AgentExecutionSample[];
|
|
9
|
+
export declare function summarizeAgentExecutions(value: unknown): {
|
|
10
|
+
health: number | null;
|
|
11
|
+
errorRate: number | null;
|
|
12
|
+
sampleCount: number;
|
|
13
|
+
successes: number;
|
|
14
|
+
failures: number;
|
|
15
|
+
latency: {
|
|
16
|
+
avg: number | null;
|
|
17
|
+
p50: number | null;
|
|
18
|
+
p95: number | null;
|
|
19
|
+
p99: number | null;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=agent-health-metrics.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const AGENT_HEALTH_WINDOW = 20;
|
|
2
|
+
function validSamples(value) {
|
|
3
|
+
if (!Array.isArray(value))
|
|
4
|
+
return [];
|
|
5
|
+
return value.filter((sample) => sample !== null && typeof sample === 'object' &&
|
|
6
|
+
typeof sample.at === 'string' && typeof sample.success === 'boolean' &&
|
|
7
|
+
typeof sample.durationMs === 'number' && Number.isFinite(sample.durationMs) && sample.durationMs >= 0).slice(-AGENT_HEALTH_WINDOW);
|
|
8
|
+
}
|
|
9
|
+
export function appendAgentExecution(value, sample) {
|
|
10
|
+
return [...validSamples(value), sample].slice(-AGENT_HEALTH_WINDOW);
|
|
11
|
+
}
|
|
12
|
+
export function summarizeAgentExecutions(value) {
|
|
13
|
+
const samples = validSamples(value);
|
|
14
|
+
const sampleCount = samples.length;
|
|
15
|
+
const successes = samples.filter(sample => sample.success).length;
|
|
16
|
+
const failures = sampleCount - successes;
|
|
17
|
+
const durations = samples.map(sample => sample.durationMs).sort((a, b) => a - b);
|
|
18
|
+
const percentile = (p) => sampleCount ? durations[Math.ceil(p * sampleCount) - 1] : null;
|
|
19
|
+
return {
|
|
20
|
+
health: sampleCount ? successes / sampleCount : null,
|
|
21
|
+
errorRate: sampleCount ? failures / sampleCount : null,
|
|
22
|
+
sampleCount,
|
|
23
|
+
successes,
|
|
24
|
+
failures,
|
|
25
|
+
latency: {
|
|
26
|
+
avg: sampleCount ? durations.reduce((sum, duration) => sum + duration, 0) / sampleCount : null,
|
|
27
|
+
p50: percentile(0.5),
|
|
28
|
+
p95: percentile(0.95),
|
|
29
|
+
p99: percentile(0.99),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=agent-health-metrics.js.map
|
|
@@ -10,6 +10,7 @@ import { readRecordStore } from './record-store.js';
|
|
|
10
10
|
import { getProjectCwd } from './types.js';
|
|
11
11
|
import { validateIdentifier, validateText, validateAgentSpawn } from './validate-input.js';
|
|
12
12
|
import { executeAgentTask } from './agent-execute-core.js';
|
|
13
|
+
import { AGENT_HEALTH_WINDOW, summarizeAgentExecutions } from './agent-health-metrics.js';
|
|
13
14
|
import { pheromoneAgentEligibility } from './swarm-tools.js';
|
|
14
15
|
// Storage paths
|
|
15
16
|
const STORAGE_DIR = '.claude-flow';
|
|
@@ -691,7 +692,7 @@ export const agentTools = [
|
|
|
691
692
|
},
|
|
692
693
|
{
|
|
693
694
|
name: 'agent_health',
|
|
694
|
-
description: '
|
|
695
|
+
description: 'Report measured agent_execute provider-call outcomes over the last 20 calls: API success-ratio health (0-1), error rate, and response-latency p50/p95. This does not measure task correctness. Agents with no observed calls have unknown health; registration is not proof of a running process. Native Task and hooks_post-task outcomes are not recorded here. Use when you need measured reliability for agents driven through agent_execute; native Task results are invisible to this tool, so inspect those directly.',
|
|
695
696
|
category: 'agent',
|
|
696
697
|
inputSchema: {
|
|
697
698
|
type: 'object',
|
|
@@ -708,58 +709,80 @@ export const agentTools = [
|
|
|
708
709
|
}
|
|
709
710
|
const store = loadAgentStore();
|
|
710
711
|
const agents = Object.values(store.agents).filter(a => a.status !== 'terminated');
|
|
711
|
-
const threshold = input.threshold
|
|
712
|
+
const threshold = input.threshold === undefined ? 0.5 : input.threshold;
|
|
713
|
+
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
|
|
714
|
+
return { error: 'threshold must be a finite number between 0 and 1' };
|
|
715
|
+
}
|
|
716
|
+
const healthStatus = (health) => health === null ? 'unknown' : health >= threshold ? 'healthy' : health >= 0.3 ? 'degraded' : 'unhealthy';
|
|
712
717
|
if (input.agentId) {
|
|
713
718
|
const agent = store.agents[input.agentId];
|
|
714
719
|
if (agent) {
|
|
720
|
+
const metrics = summarizeAgentExecutions(agent.executionHistory);
|
|
715
721
|
return {
|
|
716
722
|
agentId: agent.agentId,
|
|
717
|
-
|
|
723
|
+
...metrics,
|
|
724
|
+
healthStatus: healthStatus(metrics.health),
|
|
718
725
|
status: agent.status,
|
|
719
|
-
healthy:
|
|
726
|
+
healthy: metrics.health === null ? null : metrics.health >= threshold,
|
|
720
727
|
taskCount: agent.taskCount,
|
|
721
|
-
uptime:
|
|
728
|
+
uptime: null,
|
|
729
|
+
registryAgeMs: Date.now() - new Date(agent.createdAt).getTime(),
|
|
730
|
+
windowSize: AGENT_HEALTH_WINDOW,
|
|
731
|
+
source: 'agent_execute',
|
|
722
732
|
};
|
|
723
733
|
}
|
|
724
734
|
return { agentId: input.agentId, error: 'Agent not found' };
|
|
725
735
|
}
|
|
726
|
-
const
|
|
727
|
-
const
|
|
728
|
-
const
|
|
729
|
-
const
|
|
736
|
+
const observed = agents.map(agent => ({ agent, metrics: summarizeAgentExecutions(agent.executionHistory) }));
|
|
737
|
+
const healthyAgents = observed.filter(({ metrics }) => healthStatus(metrics.health) === 'healthy');
|
|
738
|
+
const degradedAgents = observed.filter(({ metrics }) => healthStatus(metrics.health) === 'degraded');
|
|
739
|
+
const unhealthyAgents = observed.filter(({ metrics }) => healthStatus(metrics.health) === 'unhealthy');
|
|
740
|
+
const measuredAgents = observed.filter(({ metrics }) => metrics.health !== null);
|
|
741
|
+
const unknownCount = agents.length - measuredAgents.length;
|
|
742
|
+
const avgHealth = measuredAgents.length > 0
|
|
743
|
+
? measuredAgents.reduce((sum, { metrics }) => sum + metrics.health, 0) / measuredAgents.length
|
|
744
|
+
: null;
|
|
730
745
|
return {
|
|
731
746
|
// CLI expected fields
|
|
732
|
-
agents:
|
|
733
|
-
const uptime = Date.now() - new Date(a.createdAt).getTime();
|
|
747
|
+
agents: observed.map(({ agent, metrics }) => {
|
|
734
748
|
return {
|
|
735
|
-
id:
|
|
736
|
-
type:
|
|
737
|
-
health:
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
749
|
+
id: agent.agentId,
|
|
750
|
+
type: agent.agentType,
|
|
751
|
+
health: healthStatus(metrics.health),
|
|
752
|
+
score: metrics.health,
|
|
753
|
+
sampleCount: metrics.sampleCount,
|
|
754
|
+
errorRate: metrics.errorRate,
|
|
755
|
+
latency: metrics.latency,
|
|
756
|
+
errors: { count: metrics.failures },
|
|
757
|
+
uptime: null,
|
|
758
|
+
registryAgeMs: Date.now() - new Date(agent.createdAt).getTime(),
|
|
759
|
+
tasks: { active: agent.status === 'busy' ? 1 : 0, queued: null, completed: metrics.successes, failed: metrics.failures },
|
|
760
|
+
_note: `Provider-call outcomes and latency cover only the last ${AGENT_HEALTH_WINDOW} agent_execute calls; task correctness, process liveness and CPU/memory are not observed`,
|
|
741
761
|
};
|
|
742
762
|
}),
|
|
743
763
|
overall: {
|
|
744
764
|
healthy: healthyAgents.length,
|
|
745
765
|
degraded: degradedAgents.length,
|
|
746
766
|
unhealthy: unhealthyAgents.length,
|
|
767
|
+
unknown: unknownCount,
|
|
747
768
|
cpu: null,
|
|
748
769
|
memory: null,
|
|
749
770
|
_note: 'Per-agent CPU/memory not available — use system_metrics for real OS-level stats',
|
|
750
|
-
score: Math.round(avgHealth * 100),
|
|
771
|
+
score: avgHealth === null ? null : Math.round(avgHealth * 100),
|
|
751
772
|
issues: unhealthyAgents.length,
|
|
752
773
|
},
|
|
753
774
|
// Additional fields
|
|
754
775
|
total: agents.length,
|
|
776
|
+
observedCount: measuredAgents.length,
|
|
777
|
+
unknownCount,
|
|
755
778
|
healthyCount: healthyAgents.length,
|
|
756
779
|
unhealthyCount: unhealthyAgents.length,
|
|
757
780
|
threshold,
|
|
758
781
|
avgHealth,
|
|
759
|
-
unhealthyAgents: unhealthyAgents.map(
|
|
760
|
-
agentId:
|
|
761
|
-
health:
|
|
762
|
-
status:
|
|
782
|
+
unhealthyAgents: unhealthyAgents.map(({ agent, metrics }) => ({
|
|
783
|
+
agentId: agent.agentId,
|
|
784
|
+
health: metrics.health,
|
|
785
|
+
status: agent.status,
|
|
763
786
|
})),
|
|
764
787
|
};
|
|
765
788
|
},
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
*
|
|
51
51
|
* @module @claude-flow/cli/mcp-tools/agentbbs-federation
|
|
52
52
|
*/
|
|
53
|
-
import {
|
|
53
|
+
import type { Server } from 'node:http';
|
|
54
54
|
/** Max envelopes accepted from one peer in one sync. */
|
|
55
55
|
export declare const MAX_ENVELOPES_PER_SYNC = 5000;
|
|
56
56
|
/** Max serialized bytes for a single envelope. */
|
|
@@ -53,7 +53,12 @@
|
|
|
53
53
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, chmodSync } from 'node:fs';
|
|
54
54
|
import { join } from 'node:path';
|
|
55
55
|
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
56
|
-
import {
|
|
56
|
+
import { createRequire } from 'node:module';
|
|
57
|
+
// Lazy, CommonJS-style load of node:http: an ESM `import … from 'node:http'`
|
|
58
|
+
// materialises every export of the builtin, which trips http's lazy undici
|
|
59
|
+
// getters (~2.5 MB heap / ~6 MB RSS) at MCP startup even when no server is
|
|
60
|
+
// ever started. require() of the builtin does not touch those getters.
|
|
61
|
+
const loadHttp = () => createRequire(import.meta.url)('node:http');
|
|
57
62
|
/** Max envelopes accepted from one peer in one sync. */
|
|
58
63
|
export const MAX_ENVELOPES_PER_SYNC = 5_000;
|
|
59
64
|
/** Max serialized bytes for a single envelope. */
|
|
@@ -389,7 +394,7 @@ export async function syncRoomFromPeer(basePath, peer, roomId, fetchImpl = fetch
|
|
|
389
394
|
*/
|
|
390
395
|
export function serveFederation(basePath, opts = {}) {
|
|
391
396
|
const host = opts.bindHost ?? '127.0.0.1';
|
|
392
|
-
const server = createServer((req, res) => {
|
|
397
|
+
const server = loadHttp().createServer((req, res) => {
|
|
393
398
|
const send = (code, obj) => {
|
|
394
399
|
const buf = Buffer.from(JSON.stringify(obj));
|
|
395
400
|
res.writeHead(code, { 'content-type': 'application/json', 'content-length': buf.length });
|
|
@@ -80,6 +80,21 @@ async function bridgeUnavailableResult() {
|
|
|
80
80
|
hint: 'Check Node version + try `ruflo memory init` to initialize manually.',
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
+
// #4135 / #4161: graph reads (agentdb_graph-query, agentdb_graph-pathfinder) open the
|
|
84
|
+
// graph_edges database; under encryption at rest that file is an RFE1 blob they cannot
|
|
85
|
+
// open. A recorded edge is then unreadable even though the write reports success, so the
|
|
86
|
+
// write says so instead of letting a caller assume it can read the edge back.
|
|
87
|
+
async function unreadableEdgeWarning() {
|
|
88
|
+
try {
|
|
89
|
+
const { isBridgeDbEncryptedAtRest } = await getGraphEdgeWriter();
|
|
90
|
+
if (!isBridgeDbEncryptedAtRest())
|
|
91
|
+
return undefined;
|
|
92
|
+
return 'Edge recorded, but graph reads (agentdb_graph-query, agentdb_graph-pathfinder) cannot open the graph_edges database because it is encrypted at rest (RFE1), so this edge cannot be read back yet. Your data is intact (#2889, #4135).';
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
83
98
|
let memInitMod = null;
|
|
84
99
|
async function getMemInit() {
|
|
85
100
|
if (!memInitMod)
|
|
@@ -554,7 +569,9 @@ export const agentdbCausalEdge = {
|
|
|
554
569
|
// Also record in AgentDB bridge for compatibility
|
|
555
570
|
const bridge = await getBridge();
|
|
556
571
|
await bridge.bridgeRecordCausalEdge({ sourceId, targetId, relation, weight: typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined }).catch(() => { });
|
|
557
|
-
|
|
572
|
+
const unreadable = await unreadableEdgeWarning();
|
|
573
|
+
const warning = [legacyWarning, unreadable].filter(Boolean).join(' ');
|
|
574
|
+
return { ...graphResult, _graphNodeBackend: true, ...(unreadable && { readable: false }), ...(warning && { warning }) };
|
|
558
575
|
}
|
|
559
576
|
}
|
|
560
577
|
}
|
|
@@ -567,7 +584,9 @@ export const agentdbCausalEdge = {
|
|
|
567
584
|
weight: typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined,
|
|
568
585
|
});
|
|
569
586
|
const baseResult = result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' };
|
|
570
|
-
|
|
587
|
+
const unreadable = baseResult.success === false ? undefined : await unreadableEdgeWarning();
|
|
588
|
+
const warning = [legacyWarning, unreadable].filter(Boolean).join(' ');
|
|
589
|
+
return warning ? { ...baseResult, ...(unreadable && { readable: false }), warning } : baseResult;
|
|
571
590
|
}
|
|
572
591
|
catch (error) {
|
|
573
592
|
return { success: false, error: sanitizeError(error) };
|
|
@@ -1157,6 +1176,10 @@ export const agentdbGraphQuery = {
|
|
|
1157
1176
|
catch (error) {
|
|
1158
1177
|
return { success: false, error: `Retained SQL graph query failed: ${sanitizeError(error)}`, mode, nodeId };
|
|
1159
1178
|
}
|
|
1179
|
+
// #4135: name the real reason when the retained store is encrypted at rest.
|
|
1180
|
+
const encryptedAtRest = await getGraphEdgeWriter().then((w) => w.isBridgeDbEncryptedAtRest()).catch(() => false);
|
|
1181
|
+
if (encryptedAtRest)
|
|
1182
|
+
return { success: false, ...(await bridgeUnavailableResult()), mode, nodeId };
|
|
1160
1183
|
return { success: false, error: 'Retained SQL graph_edges backend is unavailable for k-hop query', mode, nodeId };
|
|
1161
1184
|
}
|
|
1162
1185
|
// ── semantic mode ────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known-miss registry for aidefence_learn (#4147).
|
|
3
|
+
*
|
|
4
|
+
* The detector's learned patterns are written by `learnFromDetection`, but
|
|
5
|
+
* they are only created from threats the detector already found, and
|
|
6
|
+
* `detect()` never reads them back. A threat the detector MISSED (a false
|
|
7
|
+
* negative, the case feedback matters most for) therefore had nowhere to go.
|
|
8
|
+
*
|
|
9
|
+
* This registry stores a SHA-256 of the whitespace/case-normalised input so a
|
|
10
|
+
* reported miss is flagged next time the identical text is scanned. It is an
|
|
11
|
+
* exact-match list, not generalisation: the raw text is never stored.
|
|
12
|
+
*/
|
|
13
|
+
export interface KnownMiss {
|
|
14
|
+
hash: string;
|
|
15
|
+
threatType: string;
|
|
16
|
+
reportedAt: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function hashInput(input: string): string;
|
|
19
|
+
/** Returns true when the miss was newly recorded, false when it was already known. */
|
|
20
|
+
export declare function recordKnownMiss(input: string, threatType: string, cwd?: string): boolean;
|
|
21
|
+
export declare function findKnownMiss(input: string, cwd?: string): KnownMiss | undefined;
|
|
22
|
+
//# sourceMappingURL=aidefence-known-misses.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Known-miss registry for aidefence_learn (#4147).
|
|
3
|
+
*
|
|
4
|
+
* The detector's learned patterns are written by `learnFromDetection`, but
|
|
5
|
+
* they are only created from threats the detector already found, and
|
|
6
|
+
* `detect()` never reads them back. A threat the detector MISSED (a false
|
|
7
|
+
* negative, the case feedback matters most for) therefore had nowhere to go.
|
|
8
|
+
*
|
|
9
|
+
* This registry stores a SHA-256 of the whitespace/case-normalised input so a
|
|
10
|
+
* reported miss is flagged next time the identical text is scanned. It is an
|
|
11
|
+
* exact-match list, not generalisation: the raw text is never stored.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
const MAX_ENTRIES = 500;
|
|
17
|
+
function storePath(cwd) {
|
|
18
|
+
return join(cwd, '.claude-flow', 'aidefence-known-misses.json');
|
|
19
|
+
}
|
|
20
|
+
export function hashInput(input) {
|
|
21
|
+
const normalised = input.normalize('NFKC').toLowerCase().replace(/\s+/g, ' ').trim();
|
|
22
|
+
return createHash('sha256').update(normalised).digest('hex');
|
|
23
|
+
}
|
|
24
|
+
function load(cwd) {
|
|
25
|
+
try {
|
|
26
|
+
const p = storePath(cwd);
|
|
27
|
+
if (!existsSync(p))
|
|
28
|
+
return [];
|
|
29
|
+
const parsed = JSON.parse(readFileSync(p, 'utf8'));
|
|
30
|
+
return Array.isArray(parsed) ? parsed.filter((e) => e && typeof e.hash === 'string') : [];
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Returns true when the miss was newly recorded, false when it was already known. */
|
|
37
|
+
export function recordKnownMiss(input, threatType, cwd = process.cwd()) {
|
|
38
|
+
const hash = hashInput(input);
|
|
39
|
+
const entries = load(cwd);
|
|
40
|
+
if (entries.some((e) => e.hash === hash))
|
|
41
|
+
return false;
|
|
42
|
+
entries.push({ hash, threatType, reportedAt: new Date().toISOString() });
|
|
43
|
+
const dir = join(cwd, '.claude-flow');
|
|
44
|
+
mkdirSync(dir, { recursive: true });
|
|
45
|
+
const tmp = `${storePath(cwd)}.${process.pid}.tmp`;
|
|
46
|
+
writeFileSync(tmp, JSON.stringify(entries.slice(-MAX_ENTRIES), null, 2), { mode: 0o600 });
|
|
47
|
+
renameSync(tmp, storePath(cwd));
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
export function findKnownMiss(input, cwd = process.cwd()) {
|
|
51
|
+
const entries = load(cwd);
|
|
52
|
+
if (entries.length === 0)
|
|
53
|
+
return undefined;
|
|
54
|
+
const hash = hashInput(input);
|
|
55
|
+
return entries.find((e) => e.hash === hash);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=aidefence-known-misses.js.map
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* Provides diff analysis and classification via MCP protocol
|
|
4
4
|
*/
|
|
5
5
|
import { validateGitRef, validatePath } from './validate-input.js';
|
|
6
|
-
import { analyzeDiff, assessFileRisk, assessOverallRisk,
|
|
6
|
+
import { analyzeDiff, assessFileRisk, assessOverallRisk, suggestReviewers, getGitDiffNumstat, } from '../ruvector/diff-classifier.js';
|
|
7
7
|
/**
|
|
8
8
|
* Diff Analysis Tool
|
|
9
9
|
* Analyzes git diffs for change risk assessment and classification
|
|
10
10
|
*/
|
|
11
11
|
export const analyzeDiffTool = {
|
|
12
12
|
name: 'analyze_diff',
|
|
13
|
-
description: '
|
|
13
|
+
description: 'Heuristically analyze git diff paths, changed lines, and one-commit subjects for risk and classification. Use when preparing a review that needs a quick evidence-based summary; inspect the diff for final judgment.',
|
|
14
14
|
category: 'analyze',
|
|
15
15
|
tags: ['diff', 'risk', 'classification', 'git'],
|
|
16
16
|
inputSchema: {
|
|
@@ -85,7 +85,7 @@ export const analyzeDiffTool = {
|
|
|
85
85
|
*/
|
|
86
86
|
export const diffRiskTool = {
|
|
87
87
|
name: 'analyze_diff-risk',
|
|
88
|
-
description: '
|
|
88
|
+
description: 'Heuristically assess git diff risk from changed file paths, sizes, and status. Use when triaging which files merit closer human review; this is not a security guarantee.',
|
|
89
89
|
category: 'analyze',
|
|
90
90
|
tags: ['diff', 'risk', 'git'],
|
|
91
91
|
inputSchema: {
|
|
@@ -130,7 +130,7 @@ export const diffRiskTool = {
|
|
|
130
130
|
*/
|
|
131
131
|
export const diffClassifyTool = {
|
|
132
132
|
name: 'analyze_diff-classify',
|
|
133
|
-
description: '
|
|
133
|
+
description: 'Heuristically classify a git diff from changed lines, file paths, and a single-commit subject when available. Use when you need a coarse change category; inspect the diff to confirm.',
|
|
134
134
|
category: 'analyze',
|
|
135
135
|
tags: ['diff', 'classification', 'git'],
|
|
136
136
|
inputSchema: {
|
|
@@ -151,12 +151,11 @@ export const diffClassifyTool = {
|
|
|
151
151
|
}
|
|
152
152
|
const ref = params.ref || 'HEAD';
|
|
153
153
|
try {
|
|
154
|
-
const
|
|
155
|
-
const classification = classifyDiff(files);
|
|
154
|
+
const result = await analyzeDiff({ ref });
|
|
156
155
|
return {
|
|
157
156
|
ref,
|
|
158
|
-
classification,
|
|
159
|
-
files: files.length,
|
|
157
|
+
classification: result.classification,
|
|
158
|
+
files: result.files.length,
|
|
160
159
|
};
|
|
161
160
|
}
|
|
162
161
|
catch (error) {
|
|
@@ -174,7 +173,7 @@ export const diffClassifyTool = {
|
|
|
174
173
|
*/
|
|
175
174
|
export const diffReviewersTool = {
|
|
176
175
|
name: 'analyze_diff-reviewers',
|
|
177
|
-
description: 'Suggest
|
|
176
|
+
description: 'Suggest reviewer roles from git diff paths and risk; does not identify people or read CODEOWNERS. Use when triaging expertise for a change, then consult CODEOWNERS to assign people.',
|
|
178
177
|
category: 'analyze',
|
|
179
178
|
tags: ['diff', 'reviewers', 'git'],
|
|
180
179
|
inputSchema: {
|
|
@@ -225,7 +224,7 @@ export const diffReviewersTool = {
|
|
|
225
224
|
*/
|
|
226
225
|
export const fileRiskTool = {
|
|
227
226
|
name: 'analyze_file-risk',
|
|
228
|
-
description: '
|
|
227
|
+
description: 'Heuristically assess risk for a file path and optional change metadata. Use when triaging a changed file for focused review; inspect the source for final judgment.',
|
|
229
228
|
category: 'analyze',
|
|
230
229
|
tags: ['file', 'risk'],
|
|
231
230
|
inputSchema: {
|
|
@@ -280,7 +279,7 @@ export const fileRiskTool = {
|
|
|
280
279
|
*/
|
|
281
280
|
export const diffStatsTool = {
|
|
282
281
|
name: 'analyze_diff-stats',
|
|
283
|
-
description: '
|
|
282
|
+
description: 'Count changed files, additions, and deletions in a git diff. Use when you need a quick size summary before inspecting the full patch.',
|
|
284
283
|
category: 'analyze',
|
|
285
284
|
tags: ['diff', 'stats', 'git'],
|
|
286
285
|
inputSchema: {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module @claude-flow/cli/mcp-tools/autopilot
|
|
9
9
|
*/
|
|
10
10
|
import { validateText } from './validate-input.js';
|
|
11
|
-
import { loadState, saveState, appendLog, loadLog, discoverTasks, isTerminal, tryLoadLearning, validateNumber, validateConfiguredTaskSources, VALID_TASK_SOURCES, } from '../autopilot-state.js';
|
|
11
|
+
import { loadState, saveState, appendLog, loadLog, discoverTasks, isTerminal, tryLoadLearning, getLearningLoadError, validateNumber, validateConfiguredTaskSources, VALID_TASK_SOURCES, } from '../autopilot-state.js';
|
|
12
12
|
function ok(data) {
|
|
13
13
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
14
14
|
}
|
|
@@ -175,7 +175,7 @@ const autopilotLearn = {
|
|
|
175
175
|
]);
|
|
176
176
|
return ok({ metrics, patterns });
|
|
177
177
|
}
|
|
178
|
-
return ok({ available: false, reason: 'AgentDB/AutopilotLearning not initialized', patterns: [] });
|
|
178
|
+
return ok({ available: false, reason: getLearningLoadError() ?? 'AgentDB/AutopilotLearning not initialized', patterns: [] });
|
|
179
179
|
},
|
|
180
180
|
};
|
|
181
181
|
const autopilotHistory = {
|