@claude-flow/cli 3.56.9 → 3.57.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/helpers/.helpers-version +1 -1
- package/.claude/helpers/helpers.manifest.json +3 -3
- package/.claude/helpers/intelligence.cjs +51 -9
- package/.claude/helpers/router.cjs +0 -0
- package/.claude/skills/verification-quality/SKILL.md +14 -11
- package/README.md +1 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/autopilot-state.d.ts +2 -0
- package/dist/src/autopilot-state.js +36 -8
- package/dist/src/command-resolution.d.ts +15 -0
- package/dist/src/command-resolution.js +25 -0
- package/dist/src/commands/agent.js +17 -17
- package/dist/src/commands/claims.js +6 -8
- package/dist/src/commands/daemon.js +94 -33
- package/dist/src/commands/doctor.js +95 -18
- package/dist/src/commands/memory.js +14 -2
- package/dist/src/commands/policy.js +6 -1
- package/dist/src/commands/security-code-patterns.d.ts +2 -2
- package/dist/src/commands/security-code-patterns.js +61 -3
- package/dist/src/commands/security.d.ts +24 -0
- package/dist/src/commands/security.js +81 -35
- package/dist/src/commands/tui.d.ts +9 -0
- package/dist/src/commands/tui.js +29 -3
- package/dist/src/config/proven-config-refresh.d.ts +9 -0
- package/dist/src/config/proven-config-refresh.js +24 -3
- package/dist/src/funnel/local-signals.js +2 -1
- package/dist/src/index.js +11 -47
- package/dist/src/init/settings-generator.js +4 -58
- package/dist/src/init/types.js +1 -1
- package/dist/src/mcp-tools/agent-execute-core.d.ts +2 -0
- package/dist/src/mcp-tools/agent-execute-core.js +15 -0
- package/dist/src/mcp-tools/agent-health-metrics.d.ts +22 -0
- package/dist/src/mcp-tools/agent-health-metrics.js +33 -0
- package/dist/src/mcp-tools/agent-tools.js +45 -22
- package/dist/src/mcp-tools/agentbbs-federation.d.ts +1 -1
- package/dist/src/mcp-tools/agentbbs-federation.js +7 -2
- package/dist/src/mcp-tools/agentdb-tools.js +25 -2
- package/dist/src/mcp-tools/aidefence-known-misses.d.ts +22 -0
- package/dist/src/mcp-tools/aidefence-known-misses.js +57 -0
- package/dist/src/mcp-tools/analyze-tools.js +10 -11
- package/dist/src/mcp-tools/autopilot-tools.js +2 -2
- package/dist/src/mcp-tools/browser-intent-tools.js +6 -2
- package/dist/src/mcp-tools/embeddings-tools.js +29 -7
- package/dist/src/mcp-tools/hooks-tools.js +4 -0
- package/dist/src/mcp-tools/memory-tools.d.ts +1 -1
- package/dist/src/mcp-tools/memory-tools.js +100 -25
- package/dist/src/mcp-tools/metaharness-tools.js +1 -1
- package/dist/src/mcp-tools/neural-tools.js +48 -54
- package/dist/src/mcp-tools/policy-tools.js +5 -3
- package/dist/src/mcp-tools/ruvllm-tools.js +6 -0
- package/dist/src/mcp-tools/security-tools.js +55 -17
- package/dist/src/mcp-tools/workflow-tools.js +66 -17
- package/dist/src/memory/intelligence.d.ts +5 -1
- package/dist/src/memory/intelligence.js +158 -35
- package/dist/src/memory/memory-bridge.js +10 -3
- package/dist/src/memory/memory-initializer.js +2 -1
- package/dist/src/memory/search-defaults.d.ts +54 -0
- package/dist/src/memory/search-defaults.js +37 -0
- package/dist/src/memory/sona-optimizer.d.ts +28 -0
- package/dist/src/memory/sona-optimizer.js +59 -2
- package/dist/src/memory/stats-file-lock.d.ts +7 -0
- package/dist/src/memory/stats-file-lock.js +53 -0
- package/dist/src/mods/command-registry/schema.d.ts +24 -24
- package/dist/src/ruvector/agent-wasm.d.ts +10 -0
- package/dist/src/ruvector/agent-wasm.js +27 -1
- package/dist/src/ruvector/diff-classifier.d.ts +2 -4
- package/dist/src/ruvector/diff-classifier.js +202 -69
- package/dist/src/ruvector/ruvllm-wasm.d.ts +4 -0
- package/dist/src/ruvector/ruvllm-wasm.js +12 -1
- package/dist/src/services/distill-oracle.d.ts +1 -1
- package/dist/src/services/distill-oracle.js +2 -2
- package/dist/src/services/policy-anchor-log.d.ts +13 -0
- package/dist/src/services/policy-anchor-log.js +242 -0
- package/dist/src/services/policy-archive-scan.d.ts +7 -0
- package/dist/src/services/policy-archive-scan.js +50 -0
- package/dist/src/services/policy-ledger-anchor.d.ts +14 -8
- package/dist/src/services/policy-ledger-anchor.js +356 -40
- package/dist/src/services/policy-ledger-audit.d.ts +22 -0
- package/dist/src/services/policy-ledger-audit.js +260 -0
- package/dist/src/services/policy-ledger-record.d.ts +3 -0
- package/dist/src/services/policy-ledger-record.js +22 -0
- package/dist/src/services/policy-runtime.d.ts +2 -1
- package/dist/src/services/policy-runtime.js +267 -40
- package/dist/src/services/worker-daemon.d.ts +11 -0
- package/dist/src/services/worker-daemon.js +37 -0
- package/dist/src/transfer/store/discovery.js +16 -17
- package/dist/src/transfer/store/types.d.ts +4 -0
- 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/policy/engine.d.ts +17 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +99 -24
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +14 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.js +3 -1
- package/node_modules/@claude-flow/security/dist/policy/types.js.map +1 -1
- package/package.json +5 -5
- package/plugins/ruflo-console/dist/tui.mjs +162 -161
- package/plugins/ruflo-metaharness/hooks/screen.ts +7 -1
- package/plugins/ruflo-metaharness/scripts/_darwin.mjs +4 -4
package/dist/src/commands/tui.js
CHANGED
|
@@ -34,6 +34,20 @@ function cliEntry() {
|
|
|
34
34
|
const entry = join(__dirname, '..', '..', '..', 'bin', 'cli.js');
|
|
35
35
|
return existsSync(entry) ? entry : undefined;
|
|
36
36
|
}
|
|
37
|
+
/** The look and motion flags: `--look bbs|orbital|plain` (default bbs, as in the console), `--fps N` (0-30, default 8; 0 draws pictures still), `--no-anim`, `--no-pictures`. A bad value is ignored, never passed on. */
|
|
38
|
+
export function lookFlags(flags) {
|
|
39
|
+
const look = flags?.look;
|
|
40
|
+
const fps = flags?.fps;
|
|
41
|
+
const number = typeof fps === 'number' ? fps : typeof fps === 'string' && /^\d{1,2}$/.test(fps) ? Number(fps) : undefined;
|
|
42
|
+
return {
|
|
43
|
+
...(look === 'bbs' || look === 'orbital' || look === 'plain' ? { look } : {}),
|
|
44
|
+
...(number !== undefined && Number.isFinite(number) && number >= 0 && number <= 30 ? { fps: Math.floor(number) } : {}),
|
|
45
|
+
...(flags?.anim === false ? { anim: false } : {}),
|
|
46
|
+
...(flags?.pictures === false ? { pictures: false } : {}),
|
|
47
|
+
...(flags?.boot === true ? { boot: true } : {}),
|
|
48
|
+
...(flags?.mouse === true ? { mouse: true } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
37
51
|
/** The AI page's settings from the flags (and one environment variable for the endpoint list). Absent flags leave the defaults; a bad number is ignored, never passed on. */
|
|
38
52
|
export function aiPage(flags) {
|
|
39
53
|
const model = typeof flags?.model === 'string' && flags.model !== '' ? flags.model : undefined;
|
|
@@ -46,11 +60,17 @@ export const tuiCommand = {
|
|
|
46
60
|
name: 'tui',
|
|
47
61
|
description: 'The ruflo console in your terminal: Overview, Cost, Swarm, Events, Missions (read-only) and an AI page that asks Codex or OpenRouter, each question after a card.',
|
|
48
62
|
options: [
|
|
49
|
-
{ name: 'view', description: `Page to open: ${VIEWS.join(' | ')}`, type: 'string', default: 'overview' },
|
|
63
|
+
{ name: 'view', description: `Page to open: ${VIEWS.join(' | ')}, or any console view (menu, federation, settings, ...): 0 opens the menu and : goes to a page by name`, type: 'string', default: 'overview' },
|
|
50
64
|
{ name: 'ai', description: 'The AI page (ask Codex read-only or OpenRouter, each question after a confirm card). --no-ai turns it off', type: 'boolean', default: true },
|
|
51
65
|
{ name: 'model', description: 'OpenRouter model for this session (default openrouter/auto)', type: 'string' },
|
|
52
66
|
{ name: 'max-spend-usd', description: 'Stop sending to OpenRouter once the provider-reported spend reaches this (default 0.5)', type: 'number' },
|
|
53
67
|
{ name: 'convo-targets', description: 'Extra OpenAI-compatible endpoints, the console convoTargets syntax: endpoint:<name>=<https-base-url>|<KEY_ENV>|<model>;... (a key is named by its environment variable). Also RUFLO_TUI_CONVO_TARGETS', type: 'string' },
|
|
68
|
+
{ name: 'look', description: 'The console look: bbs (default, as in Claude Code) | orbital | plain. Key t cycles them', type: 'string' },
|
|
69
|
+
{ name: 'fps', description: 'Frames per second for moving pictures, 0-30 (default 8; 0 draws every picture still)', type: 'number' },
|
|
70
|
+
{ name: 'anim', description: 'Animate the pictures. --no-anim draws them still', type: 'boolean', default: true },
|
|
71
|
+
{ name: 'pictures', description: 'Draw the pictures of the console (title art, swarm graph, gauges). --no-pictures shows their words only', type: 'boolean', default: true },
|
|
72
|
+
{ name: 'mouse', description: 'Read mouse reports: click a page in the bar, scroll with the wheel (every action also has a key)', type: 'boolean', default: false },
|
|
73
|
+
{ name: 'boot', description: 'Play the look\'s start-up screen first (as the console\'s boot setting)', type: 'boolean', default: false },
|
|
54
74
|
{ name: 'plain', description: 'Print one frame as plain text and exit 0 (scripts and CI). Without it, a non-terminal gets a hint and exit 2', type: 'boolean', default: false },
|
|
55
75
|
],
|
|
56
76
|
examples: [
|
|
@@ -62,7 +82,9 @@ export const tuiCommand = {
|
|
|
62
82
|
async action(context) {
|
|
63
83
|
const ctx = context;
|
|
64
84
|
const wanted = ctx.flags?.view ?? ctx.args?.find(arg => !arg.startsWith('-')) ?? 'overview';
|
|
65
|
-
|
|
85
|
+
// The six pages are known here; any other console view (menu, federation, settings, …) is checked against the bundle's own list once it is loaded.
|
|
86
|
+
const isKnown = VIEWS.includes(wanted);
|
|
87
|
+
if (!isKnown && !/^[a-z]{2,16}$/.test(wanted)) {
|
|
66
88
|
output.writeln(output.error(`tui: unknown view "${String(wanted).slice(0, 40)}". Use one of: ${VIEWS.join(', ')}`));
|
|
67
89
|
return { success: false, exitCode: 2, data: { error: 'unknown-view' } };
|
|
68
90
|
}
|
|
@@ -80,12 +102,16 @@ export const tuiCommand = {
|
|
|
80
102
|
output.writeln(output.error(`tui: could not load the console bundle: ${error instanceof Error ? error.message.slice(0, 160) : 'unknown error'}`));
|
|
81
103
|
return { success: false, exitCode: 1, data: { error: 'bundle-load-failed' } };
|
|
82
104
|
}
|
|
105
|
+
if (!isKnown && mod.isPage?.(wanted) !== true) {
|
|
106
|
+
output.writeln(output.error(`tui: unknown view "${String(wanted).slice(0, 40)}". Use one of: ${VIEWS.join(', ')}, or any console view such as menu, federation, settings`));
|
|
107
|
+
return { success: false, exitCode: 2, data: { error: 'unknown-view' } };
|
|
108
|
+
}
|
|
83
109
|
const stop = new AbortController();
|
|
84
110
|
const onSignal = () => stop.abort();
|
|
85
111
|
process.once('SIGTERM', onSignal);
|
|
86
112
|
process.once('SIGHUP', onSignal);
|
|
87
113
|
try {
|
|
88
|
-
const code = await mod.runTui({ cwd: ctx.cwd ?? process.cwd(), view: wanted, ...(ctx.flags?.plain === true && { plain: true }), ...(ctx.flags?.ai !== false && { aiPage: aiPage(ctx.flags) }), ...(cliEntry() !== undefined && { cliEntry: cliEntry() }), ...(process.env.CLAUDE_CONFIG_DIR !== undefined && { configDir: process.env.CLAUDE_CONFIG_DIR }) }, { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr, env: process.env, signal: stop.signal });
|
|
114
|
+
const code = await mod.runTui({ cwd: ctx.cwd ?? process.cwd(), view: wanted, ...lookFlags(ctx.flags), ...(ctx.flags?.plain === true && { plain: true }), ...(ctx.flags?.ai !== false && { aiPage: aiPage(ctx.flags) }), ...(cliEntry() !== undefined && { cliEntry: cliEntry() }), ...(process.env.CLAUDE_CONFIG_DIR !== undefined && { configDir: process.env.CLAUDE_CONFIG_DIR }) }, { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr, env: process.env, signal: stop.signal });
|
|
89
115
|
return { success: code === 0, exitCode: code };
|
|
90
116
|
}
|
|
91
117
|
finally {
|
|
@@ -15,6 +15,15 @@ export declare const ADOPTED_CONFIG_FILE = "proven-config.json";
|
|
|
15
15
|
export declare function loadShippedChampion(srcPath: string): SignedProvenConfig | null;
|
|
16
16
|
/** Build the local environment the champion's constraints are checked against. */
|
|
17
17
|
export declare function currentInstallEnv(cwd?: string): InstallEnv;
|
|
18
|
+
/**
|
|
19
|
+
* #4176: is `cwd` a ruflo project? A bare `.claude/` is not enough: Claude Code
|
|
20
|
+
* itself creates `.claude/.cc-writes/` in whatever directory a Bash command ran
|
|
21
|
+
* in, so every subdirectory a session cd-ed into used to qualify and received a
|
|
22
|
+
* proven-config.json. A ruflo project carries `ruflo init`'s own markers: a
|
|
23
|
+
* `.claude/settings.json`, the `.claude/helpers/` directory, or an earlier
|
|
24
|
+
* adoption stamp.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isRufloProject(cwd: string): boolean;
|
|
18
27
|
export interface AdoptResult {
|
|
19
28
|
adopted: boolean;
|
|
20
29
|
from?: string;
|
|
@@ -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,
|
package/dist/src/index.js
CHANGED
|
@@ -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';
|
|
@@ -91,13 +92,13 @@ export class CLI {
|
|
|
91
92
|
const parseResult = this.parser.parse(args);
|
|
92
93
|
const { command: commandPath, flags, positional } = parseResult;
|
|
93
94
|
// Handle global flags
|
|
95
|
+
if (flags.noColor || flags.color === false) {
|
|
96
|
+
this.output.setColorEnabled(false);
|
|
97
|
+
}
|
|
94
98
|
if (flags.version || flags.V) {
|
|
95
99
|
this.showVersion();
|
|
96
100
|
return;
|
|
97
101
|
}
|
|
98
|
-
if (flags.noColor) {
|
|
99
|
-
this.output.setColorEnabled(false);
|
|
100
|
-
}
|
|
101
102
|
// Set verbosity level based on flags
|
|
102
103
|
if (flags.quiet) {
|
|
103
104
|
this.output.setVerbosity('quiet');
|
|
@@ -113,7 +114,7 @@ export class CLI {
|
|
|
113
114
|
this.output.printDebug(`CWD: ${process.cwd()}`);
|
|
114
115
|
}
|
|
115
116
|
// Run startup update check (non-blocking, silent on skip)
|
|
116
|
-
if (!flags.noUpdate && commandPath[0] !== 'update') {
|
|
117
|
+
if (!flags.noUpdate && flags.update !== false && commandPath[0] !== 'update' && !(commandPath[0] === 'doctor' && flags.json)) {
|
|
117
118
|
this.checkForUpdatesOnStartup().catch(() => { });
|
|
118
119
|
}
|
|
119
120
|
// Version-stamped helper auto-refresh — propagate hook fixes to an already
|
|
@@ -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) {
|
|
@@ -572,7 +536,7 @@ export class CLI {
|
|
|
572
536
|
catch (error) {
|
|
573
537
|
// Config loading is optional - don't fail if it doesn't exist
|
|
574
538
|
if (process.env.DEBUG) {
|
|
575
|
-
this.output.
|
|
539
|
+
this.output.writeErrorln(this.output.dim(`Config loading failed: ${error.message}`));
|
|
576
540
|
}
|
|
577
541
|
return undefined;
|
|
578
542
|
}
|
|
@@ -27,7 +27,6 @@ export function generateSettings(options) {
|
|
|
27
27
|
allow: [
|
|
28
28
|
'Bash(npx @claude-flow*)',
|
|
29
29
|
'Bash(npx claude-flow*)',
|
|
30
|
-
'Bash(node .claude/*)',
|
|
31
30
|
'mcp__claude-flow__*',
|
|
32
31
|
],
|
|
33
32
|
deny: [
|
|
@@ -201,7 +200,7 @@ function generateHooksConfig(config, scope) {
|
|
|
201
200
|
const hooks = {};
|
|
202
201
|
// Node.js scripts handle errors internally via try/catch.
|
|
203
202
|
// No shell-level error suppression needed (2>/dev/null || true breaks Windows).
|
|
204
|
-
// PreToolUse — validate commands
|
|
203
|
+
// PreToolUse — validate shell commands before execution
|
|
205
204
|
if (config.preToolUse) {
|
|
206
205
|
hooks.PreToolUse = [
|
|
207
206
|
{
|
|
@@ -214,19 +213,9 @@ function generateHooksConfig(config, scope) {
|
|
|
214
213
|
},
|
|
215
214
|
],
|
|
216
215
|
},
|
|
217
|
-
{
|
|
218
|
-
matcher: 'Write|Edit|MultiEdit',
|
|
219
|
-
hooks: [
|
|
220
|
-
{
|
|
221
|
-
type: 'command',
|
|
222
|
-
command: hookHandlerCmd('pre-edit', scope),
|
|
223
|
-
timeout: config.timeout,
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
},
|
|
227
216
|
];
|
|
228
217
|
}
|
|
229
|
-
// PostToolUse — record edits
|
|
218
|
+
// PostToolUse — record edits for session metrics / learning
|
|
230
219
|
if (config.postToolUse) {
|
|
231
220
|
hooks.PostToolUse = [
|
|
232
221
|
{
|
|
@@ -239,16 +228,6 @@ function generateHooksConfig(config, scope) {
|
|
|
239
228
|
},
|
|
240
229
|
],
|
|
241
230
|
},
|
|
242
|
-
{
|
|
243
|
-
matcher: 'Bash',
|
|
244
|
-
hooks: [
|
|
245
|
-
{
|
|
246
|
-
type: 'command',
|
|
247
|
-
command: hookHandlerCmd('post-bash', scope),
|
|
248
|
-
timeout: config.timeout,
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
},
|
|
252
231
|
];
|
|
253
232
|
}
|
|
254
233
|
// UserPromptSubmit — intelligent task routing
|
|
@@ -312,16 +291,13 @@ function generateHooksConfig(config, scope) {
|
|
|
312
291
|
},
|
|
313
292
|
];
|
|
314
293
|
}
|
|
315
|
-
// PreCompact —
|
|
294
|
+
// PreCompact — end the current session before compaction. The handler has no
|
|
295
|
+
// manual/automatic compaction-specific command, so don't wire no-op hooks.
|
|
316
296
|
if (config.preCompact) {
|
|
317
297
|
hooks.PreCompact = [
|
|
318
298
|
{
|
|
319
299
|
matcher: 'manual',
|
|
320
300
|
hooks: [
|
|
321
|
-
{
|
|
322
|
-
type: 'command',
|
|
323
|
-
command: hookHandlerCmd('compact-manual', scope),
|
|
324
|
-
},
|
|
325
301
|
{
|
|
326
302
|
type: 'command',
|
|
327
303
|
command: hookHandlerCmd('session-end', scope),
|
|
@@ -332,10 +308,6 @@ function generateHooksConfig(config, scope) {
|
|
|
332
308
|
{
|
|
333
309
|
matcher: 'auto',
|
|
334
310
|
hooks: [
|
|
335
|
-
{
|
|
336
|
-
type: 'command',
|
|
337
|
-
command: hookHandlerCmd('compact-auto', scope),
|
|
338
|
-
},
|
|
339
311
|
{
|
|
340
312
|
type: 'command',
|
|
341
313
|
command: hookHandlerCmd('session-end', scope),
|
|
@@ -345,18 +317,6 @@ function generateHooksConfig(config, scope) {
|
|
|
345
317
|
},
|
|
346
318
|
];
|
|
347
319
|
}
|
|
348
|
-
// SubagentStart — status update when a sub-agent is spawned
|
|
349
|
-
hooks.SubagentStart = [
|
|
350
|
-
{
|
|
351
|
-
hooks: [
|
|
352
|
-
{
|
|
353
|
-
type: 'command',
|
|
354
|
-
command: hookHandlerCmd('status', scope),
|
|
355
|
-
timeout: 3000,
|
|
356
|
-
},
|
|
357
|
-
],
|
|
358
|
-
},
|
|
359
|
-
];
|
|
360
320
|
// SubagentStop — track agent completion for metrics
|
|
361
321
|
// NOTE: The valid event is "SubagentStop" (not "SubagentEnd")
|
|
362
322
|
hooks.SubagentStop = [
|
|
@@ -370,20 +330,6 @@ function generateHooksConfig(config, scope) {
|
|
|
370
330
|
],
|
|
371
331
|
},
|
|
372
332
|
];
|
|
373
|
-
// Notification — capture Claude Code notifications for logging
|
|
374
|
-
if (config.notification) {
|
|
375
|
-
hooks.Notification = [
|
|
376
|
-
{
|
|
377
|
-
hooks: [
|
|
378
|
-
{
|
|
379
|
-
type: 'command',
|
|
380
|
-
command: hookHandlerCmd('notify', scope),
|
|
381
|
-
timeout: 3000,
|
|
382
|
-
},
|
|
383
|
-
],
|
|
384
|
-
},
|
|
385
|
-
];
|
|
386
|
-
}
|
|
387
333
|
// NOTE: TeammateIdle, TaskCompleted, and PostCompact are NOT accepted by
|
|
388
334
|
// Claude Code's settings.json validator (rejected as "Invalid key in record").
|
|
389
335
|
// Agent Teams coordination lives in claudeFlow.agentTeams.hooks instead.
|
package/dist/src/init/types.js
CHANGED
|
@@ -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 });
|