claude-flow 3.58.0 → 3.59.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/proven-config.json +1 -1
- package/README.md +2 -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/package.json +2 -1
- package/v3/@claude-flow/cli/README.md +62 -174
- package/v3/@claude-flow/cli/bin/cli.js +19 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +5 -5
- package/v3/@claude-flow/cli/dist/src/commands/harness.d.ts +40 -0
- package/v3/@claude-flow/cli/dist/src/commands/harness.js +142 -0
- package/v3/@claude-flow/cli/dist/src/commands/index.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +116 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +6 -1
- package/v3/@claude-flow/cli/dist/src/commands/status.js +2 -2
- package/v3/@claude-flow/cli/dist/src/index.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/index.js +12 -2
- package/v3/@claude-flow/cli/dist/src/init/grok-generator.d.ts +96 -0
- package/v3/@claude-flow/cli/dist/src/init/grok-generator.js +371 -0
- package/v3/@claude-flow/cli/dist/src/init/grok-templates.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/init/grok-templates.js +28 -0
- package/v3/@claude-flow/cli/dist/src/init/index.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/init/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +4 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +59 -12
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +5 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +11 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +13 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +42 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +7 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/team-tools.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/team-tools.js +244 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +163 -3
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +70 -37
- package/v3/@claude-flow/cli/dist/src/memory/rabitq-index.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/memory/rabitq-index.js +117 -1
- package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +113 -113
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +193 -193
- package/v3/@claude-flow/cli/dist/src/parser.js +3 -2
- package/v3/@claude-flow/cli/dist/src/services/live-harness.d.ts +111 -0
- package/v3/@claude-flow/cli/dist/src/services/live-harness.js +427 -0
- package/v3/@claude-flow/cli/package.json +4 -3
- package/v3/@claude-flow/cli/templates/grok/agents/ruflo-architect.md +35 -0
- package/v3/@claude-flow/cli/templates/grok/agents/ruflo-coder.md +34 -0
- package/v3/@claude-flow/cli/templates/grok/agents/ruflo-reviewer.md +30 -0
- package/v3/@claude-flow/cli/templates/grok/agents/ruflo-tester.md +26 -0
- package/v3/@claude-flow/cli/templates/grok/config.toml +63 -0
- package/v3/@claude-flow/cli/templates/grok/docs/README.md +186 -0
- package/v3/@claude-flow/cli/templates/grok/hooks/subagent-stop-team.json +15 -0
- package/v3/@claude-flow/cli/templates/grok/rules/ruflo-grok.md +132 -0
- package/v3/@claude-flow/cli/templates/grok/scripts/grok-subagent-stop-hook.mjs +90 -0
- package/v3/@claude-flow/cli/templates/grok/scripts/grok-team-bus.mjs +525 -0
- package/v3/@claude-flow/cli/templates/grok/scripts/grok-team-store.mjs +287 -0
- package/v3/@claude-flow/cli/templates/grok/scripts/host-statusline.mjs +281 -0
- package/v3/@claude-flow/cli/templates/grok/skills/agent-teams-grok/SKILL.md +98 -0
|
@@ -184,8 +184,9 @@ export class CommandParser {
|
|
|
184
184
|
let commandSlotAvailable = true;
|
|
185
185
|
while (i < args.length) {
|
|
186
186
|
const arg = args[i];
|
|
187
|
-
// Check for end of flags marker
|
|
188
|
-
|
|
187
|
+
// Check for end of flags marker. Only the first "--" changes parser
|
|
188
|
+
// state; a later literal "--" is an ordinary positional (#4310).
|
|
189
|
+
if (parsingFlags && arg === '--') {
|
|
189
190
|
parsingFlags = false;
|
|
190
191
|
i++;
|
|
191
192
|
continue;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live harness probe.
|
|
3
|
+
*
|
|
4
|
+
* Reports what is already running for a working tree: the host session, a
|
|
5
|
+
* live daemon pid, ruflo MCP servers whose cwd is inside the tree, processes
|
|
6
|
+
* holding the memory databases open, and teams that are still active.
|
|
7
|
+
*
|
|
8
|
+
* The probe is observe-only. It never writes to disk, never starts anything,
|
|
9
|
+
* and never touches the network. It spawns at most one `ps` and two `lsof`
|
|
10
|
+
* calls, all under one overall deadline, and says "unavailable" instead of
|
|
11
|
+
* guessing when those tools are missing (Windows, minimal Linux).
|
|
12
|
+
*
|
|
13
|
+
* All OS access goes through `HarnessSys` so the probe can be tested without
|
|
14
|
+
* real processes.
|
|
15
|
+
*/
|
|
16
|
+
export type ProbeState = 'ok' | 'unavailable' | 'timeout';
|
|
17
|
+
export interface RunResult {
|
|
18
|
+
stdout: string;
|
|
19
|
+
state: ProbeState;
|
|
20
|
+
status: number | null;
|
|
21
|
+
}
|
|
22
|
+
export interface HarnessSys {
|
|
23
|
+
platform: NodeJS.Platform;
|
|
24
|
+
env: NodeJS.ProcessEnv;
|
|
25
|
+
pid: number;
|
|
26
|
+
ppid: number;
|
|
27
|
+
cwd(): string;
|
|
28
|
+
now(): number;
|
|
29
|
+
run(cmd: string, args: string[], timeoutMs: number): RunResult;
|
|
30
|
+
signal0(pid: number): 'alive' | 'dead' | 'eperm';
|
|
31
|
+
exists(path: string): boolean;
|
|
32
|
+
readFile(path: string): string;
|
|
33
|
+
readDir(path: string): Array<{
|
|
34
|
+
name: string;
|
|
35
|
+
isDir: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
mtimeMs(path: string): number | undefined;
|
|
38
|
+
realpath(path: string): string;
|
|
39
|
+
readlink(path: string): string | undefined;
|
|
40
|
+
}
|
|
41
|
+
export interface ProcRow {
|
|
42
|
+
pid: number;
|
|
43
|
+
ppid: number;
|
|
44
|
+
argv: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface LiveHarness {
|
|
47
|
+
root: string;
|
|
48
|
+
host: string;
|
|
49
|
+
parent: {
|
|
50
|
+
pid: number;
|
|
51
|
+
name: string;
|
|
52
|
+
};
|
|
53
|
+
session: Record<string, string>;
|
|
54
|
+
daemon: {
|
|
55
|
+
state: 'live' | 'down' | 'stale';
|
|
56
|
+
pid?: number;
|
|
57
|
+
verified?: boolean;
|
|
58
|
+
reason?: string;
|
|
59
|
+
};
|
|
60
|
+
mcp: {
|
|
61
|
+
count: number | null;
|
|
62
|
+
servers: Array<{
|
|
63
|
+
pid: number;
|
|
64
|
+
command: string;
|
|
65
|
+
}>;
|
|
66
|
+
};
|
|
67
|
+
memory: {
|
|
68
|
+
files: string[];
|
|
69
|
+
openPids: number[] | null;
|
|
70
|
+
};
|
|
71
|
+
teams: Array<{
|
|
72
|
+
name: string;
|
|
73
|
+
status: string;
|
|
74
|
+
members: number;
|
|
75
|
+
lastActivity?: string;
|
|
76
|
+
}>;
|
|
77
|
+
staleTeams: string[];
|
|
78
|
+
probes: {
|
|
79
|
+
ps: ProbeState;
|
|
80
|
+
lsof: ProbeState;
|
|
81
|
+
};
|
|
82
|
+
status: 'live' | 'idle' | 'unknown';
|
|
83
|
+
elapsedMs: number;
|
|
84
|
+
line: string;
|
|
85
|
+
}
|
|
86
|
+
export interface CollectOptions {
|
|
87
|
+
root?: string;
|
|
88
|
+
budgetMs?: number;
|
|
89
|
+
teamTtlMs?: number;
|
|
90
|
+
}
|
|
91
|
+
export declare const realSys: HarnessSys;
|
|
92
|
+
/** The host's project dir if it says so, else the nearest `.git` above cwd, else cwd. */
|
|
93
|
+
export declare function findWorkspaceRoot(sys: HarnessSys): string;
|
|
94
|
+
/** True when `path` is one of `roots` or lies inside one on a path boundary. */
|
|
95
|
+
export declare function insideRoot(path: string, roots: string[]): boolean;
|
|
96
|
+
export declare function parsePs(stdout: string): ProcRow[];
|
|
97
|
+
/** Name a process by its program, looking past node/bun to the script. */
|
|
98
|
+
export declare function programName(argv: string[]): string;
|
|
99
|
+
/** A ruflo MCP server: a launcher, a ruflo identity token, then `mcp [start]`. */
|
|
100
|
+
export declare function isRufloMcp(argv: string[]): boolean;
|
|
101
|
+
/** Program name and subcommand words only; every argument is dropped and counted. */
|
|
102
|
+
export declare function redactCommand(argv: string[]): string;
|
|
103
|
+
export declare function detectHost(sys: HarnessSys, table: Map<number, ProcRow> | null): {
|
|
104
|
+
host: string;
|
|
105
|
+
parent: {
|
|
106
|
+
pid: number;
|
|
107
|
+
name: string;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
export declare function collectLiveHarness(sys?: HarnessSys, opts?: CollectOptions): LiveHarness;
|
|
111
|
+
//# sourceMappingURL=live-harness.d.ts.map
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live harness probe.
|
|
3
|
+
*
|
|
4
|
+
* Reports what is already running for a working tree: the host session, a
|
|
5
|
+
* live daemon pid, ruflo MCP servers whose cwd is inside the tree, processes
|
|
6
|
+
* holding the memory databases open, and teams that are still active.
|
|
7
|
+
*
|
|
8
|
+
* The probe is observe-only. It never writes to disk, never starts anything,
|
|
9
|
+
* and never touches the network. It spawns at most one `ps` and two `lsof`
|
|
10
|
+
* calls, all under one overall deadline, and says "unavailable" instead of
|
|
11
|
+
* guessing when those tools are missing (Windows, minimal Linux).
|
|
12
|
+
*
|
|
13
|
+
* All OS access goes through `HarnessSys` so the probe can be tested without
|
|
14
|
+
* real processes.
|
|
15
|
+
*/
|
|
16
|
+
import { spawnSync } from 'node:child_process';
|
|
17
|
+
import { existsSync, readdirSync, readFileSync, readlinkSync, realpathSync, statSync } from 'node:fs';
|
|
18
|
+
import { basename, dirname, join, sep } from 'node:path';
|
|
19
|
+
const DEFAULT_BUDGET_MS = 4000;
|
|
20
|
+
const PER_CALL_CAP_MS = 1500;
|
|
21
|
+
const MIN_CALL_MS = 100;
|
|
22
|
+
const DEFAULT_TEAM_TTL_MS = 30 * 60 * 1000;
|
|
23
|
+
const SHELLS = new Set(['sh', 'bash', 'zsh', 'dash', 'fish', 'ksh', 'tcsh', 'csh', 'env', 'timeout', 'npm', 'npx', 'pnpm', 'script']);
|
|
24
|
+
const INTERPRETERS = new Set(['node', 'nodejs', 'bun', 'deno']);
|
|
25
|
+
const MCP_LAUNCHERS = new Set(['node', 'nodejs', 'bun', 'deno', 'npx', 'npm', 'pnpm', 'pnpx', 'yarn', 'ruflo', 'claude-flow']);
|
|
26
|
+
const RUFLO_ID = /(^|\/)(ruflo|claude-flow)(@[\w.-]+)?$|(^|\/)@claude-flow\/cli(@[\w.-]+)?$|\/@claude-flow\/cli\/bin\/cli\.js$|\/ruflo\/bin\/[\w.-]+\.js$/;
|
|
27
|
+
const TEAM_ENDED = new Set(['shutdown', 'stopped']);
|
|
28
|
+
export const realSys = {
|
|
29
|
+
platform: process.platform,
|
|
30
|
+
env: process.env,
|
|
31
|
+
pid: process.pid,
|
|
32
|
+
ppid: process.ppid,
|
|
33
|
+
cwd: () => process.cwd(),
|
|
34
|
+
now: () => Date.now(),
|
|
35
|
+
run(cmd, args, timeoutMs) {
|
|
36
|
+
const r = spawnSync(cmd, args, {
|
|
37
|
+
encoding: 'utf8',
|
|
38
|
+
timeout: timeoutMs,
|
|
39
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
40
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
41
|
+
windowsHide: true,
|
|
42
|
+
});
|
|
43
|
+
const code = r.error?.code;
|
|
44
|
+
if (code === 'ETIMEDOUT')
|
|
45
|
+
return { stdout: r.stdout ?? '', state: 'timeout', status: r.status };
|
|
46
|
+
if (r.error)
|
|
47
|
+
return { stdout: '', state: 'unavailable', status: r.status };
|
|
48
|
+
return { stdout: r.stdout ?? '', state: 'ok', status: r.status };
|
|
49
|
+
},
|
|
50
|
+
signal0(pid) {
|
|
51
|
+
try {
|
|
52
|
+
process.kill(pid, 0);
|
|
53
|
+
return 'alive';
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// EPERM: the pid exists but belongs to another user.
|
|
57
|
+
return error.code === 'EPERM' ? 'eperm' : 'dead';
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
exists: (path) => existsSync(path),
|
|
61
|
+
readFile: (path) => readFileSync(path, 'utf8'),
|
|
62
|
+
readDir: (path) => readdirSync(path, { withFileTypes: true }).map((d) => ({ name: d.name, isDir: d.isDirectory() })),
|
|
63
|
+
mtimeMs(path) {
|
|
64
|
+
try {
|
|
65
|
+
return statSync(path).mtimeMs;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
realpath(path) {
|
|
72
|
+
try {
|
|
73
|
+
return realpathSync(path);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return path;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
readlink(path) {
|
|
80
|
+
try {
|
|
81
|
+
return readlinkSync(path);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
/** Overall deadline shared by every external call. */
|
|
89
|
+
class Budget {
|
|
90
|
+
sys;
|
|
91
|
+
end;
|
|
92
|
+
constructor(sys, totalMs) {
|
|
93
|
+
this.sys = sys;
|
|
94
|
+
this.end = sys.now() + totalMs;
|
|
95
|
+
}
|
|
96
|
+
run(cmd, args) {
|
|
97
|
+
const left = this.end - this.sys.now();
|
|
98
|
+
if (left < MIN_CALL_MS)
|
|
99
|
+
return { stdout: '', state: 'timeout', status: null };
|
|
100
|
+
return this.sys.run(cmd, args, Math.min(PER_CALL_CAP_MS, left));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** The host's project dir if it says so, else the nearest `.git` above cwd, else cwd. */
|
|
104
|
+
export function findWorkspaceRoot(sys) {
|
|
105
|
+
const fromEnv = sys.env.GROK_WORKSPACE_ROOT || sys.env.CLAUDE_PROJECT_DIR || sys.env.CODEX_PROJECT_DIR;
|
|
106
|
+
if (fromEnv)
|
|
107
|
+
return fromEnv;
|
|
108
|
+
const start = sys.cwd();
|
|
109
|
+
let dir = start;
|
|
110
|
+
for (;;) {
|
|
111
|
+
if (sys.exists(join(dir, '.git')))
|
|
112
|
+
return dir;
|
|
113
|
+
const parent = dirname(dir);
|
|
114
|
+
if (parent === dir)
|
|
115
|
+
return start;
|
|
116
|
+
dir = parent;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** True when `path` is one of `roots` or lies inside one on a path boundary. */
|
|
120
|
+
export function insideRoot(path, roots) {
|
|
121
|
+
return roots.some((root) => {
|
|
122
|
+
const base = root.length > 1 && root.endsWith(sep) ? root.slice(0, -1) : root;
|
|
123
|
+
return path === base || path.startsWith(base + sep);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
export function parsePs(stdout) {
|
|
127
|
+
const rows = [];
|
|
128
|
+
for (const line of stdout.split('\n')) {
|
|
129
|
+
const m = /^\s*(\d+)\s+(\d+)\s+(.*\S)\s*$/.exec(line);
|
|
130
|
+
if (m)
|
|
131
|
+
rows.push({ pid: Number(m[1]), ppid: Number(m[2]), argv: m[3].split(/\s+/) });
|
|
132
|
+
}
|
|
133
|
+
return rows;
|
|
134
|
+
}
|
|
135
|
+
/** Name a process by its program, looking past node/bun to the script. */
|
|
136
|
+
export function programName(argv) {
|
|
137
|
+
const first = basename(argv[0] ?? '').replace(/^-/, '');
|
|
138
|
+
if (INTERPRETERS.has(first) && argv[1] && !argv[1].startsWith('-')) {
|
|
139
|
+
return basename(argv[1]).replace(/\.(c|m)?js$/, '');
|
|
140
|
+
}
|
|
141
|
+
return first;
|
|
142
|
+
}
|
|
143
|
+
/** A ruflo MCP server: a launcher, a ruflo identity token, then `mcp [start]`. */
|
|
144
|
+
export function isRufloMcp(argv) {
|
|
145
|
+
if (!MCP_LAUNCHERS.has(basename(argv[0] ?? '')))
|
|
146
|
+
return false;
|
|
147
|
+
const at = argv.indexOf('mcp');
|
|
148
|
+
if (at < 0)
|
|
149
|
+
return false;
|
|
150
|
+
const next = argv[at + 1];
|
|
151
|
+
if (next !== undefined && next !== 'start' && !next.startsWith('-'))
|
|
152
|
+
return false;
|
|
153
|
+
return argv.slice(0, at).some((token) => RUFLO_ID.test(token));
|
|
154
|
+
}
|
|
155
|
+
/** Program name and subcommand words only; every argument is dropped and counted. */
|
|
156
|
+
export function redactCommand(argv) {
|
|
157
|
+
const words = [programName(argv), 'mcp'];
|
|
158
|
+
const at = argv.indexOf('mcp');
|
|
159
|
+
if (argv[at + 1] === 'start')
|
|
160
|
+
words.push('start');
|
|
161
|
+
const kept = at + words.length - 1;
|
|
162
|
+
const dropped = Math.max(0, argv.length - kept);
|
|
163
|
+
return dropped > 0 ? `${words.join(' ')} (+${dropped} args)` : words.join(' ');
|
|
164
|
+
}
|
|
165
|
+
export function detectHost(sys, table) {
|
|
166
|
+
const env = sys.env;
|
|
167
|
+
let parent = { pid: sys.ppid, name: '' };
|
|
168
|
+
const names = [];
|
|
169
|
+
if (table) {
|
|
170
|
+
// Hooks usually run under `sh -c`; walk past shells and wrappers.
|
|
171
|
+
let pid = sys.ppid;
|
|
172
|
+
for (let depth = 0; depth < 8 && pid > 1; depth++) {
|
|
173
|
+
const row = table.get(pid);
|
|
174
|
+
if (!row)
|
|
175
|
+
break;
|
|
176
|
+
const name = programName(row.argv);
|
|
177
|
+
if (!SHELLS.has(name)) {
|
|
178
|
+
if (!parent.name)
|
|
179
|
+
parent = { pid, name };
|
|
180
|
+
names.push(name);
|
|
181
|
+
}
|
|
182
|
+
pid = row.ppid;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const seen = (re) => names.some((n) => re.test(n));
|
|
186
|
+
let host;
|
|
187
|
+
if (env.WEFTOS_SESSION || env.WEFT_SESSION || seen(/^weftos/i))
|
|
188
|
+
host = 'weftos';
|
|
189
|
+
else if (env.GROK_SESSION_ID || env.GROK_WORKSPACE_ROOT || seen(/^grok([-_.]|$)/))
|
|
190
|
+
host = 'grok';
|
|
191
|
+
else if (env.CODEX_THREAD_ID || env.CODEX_SANDBOX || env.CODEX_CI || seen(/^codex([-_.]|$)/))
|
|
192
|
+
host = 'codex';
|
|
193
|
+
else if (env.CLAUDECODE || env.CLAUDE_CODE_ENTRYPOINT || seen(/^claude([-_.]|$)/))
|
|
194
|
+
host = 'claude';
|
|
195
|
+
else
|
|
196
|
+
host = parent.name ? `process:${parent.name}` : 'unknown';
|
|
197
|
+
return { host, parent };
|
|
198
|
+
}
|
|
199
|
+
function sessionIds(env) {
|
|
200
|
+
const out = {};
|
|
201
|
+
for (const key of ['GROK_SESSION_ID', 'CODEX_THREAD_ID', 'CLAUDE_SESSION_ID', 'WEFTOS_SESSION', 'WEFT_SESSION']) {
|
|
202
|
+
const value = env[key];
|
|
203
|
+
if (value)
|
|
204
|
+
out[key] = value;
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
function daemonState(sys, root, roots, table) {
|
|
209
|
+
const pidFile = join(root, '.claude-flow', 'daemon.pid');
|
|
210
|
+
if (!sys.exists(pidFile))
|
|
211
|
+
return { state: 'down' };
|
|
212
|
+
let raw;
|
|
213
|
+
try {
|
|
214
|
+
raw = sys.readFile(pidFile).trim();
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return { state: 'down' };
|
|
218
|
+
}
|
|
219
|
+
const pid = Number(raw);
|
|
220
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
221
|
+
return { state: 'stale', reason: 'bad pidfile' };
|
|
222
|
+
// EPERM means the pid exists under another user; only ESRCH means gone.
|
|
223
|
+
if (sys.signal0(pid) === 'dead')
|
|
224
|
+
return { state: 'stale', pid, reason: 'not running' };
|
|
225
|
+
const row = table?.get(pid);
|
|
226
|
+
if (!row)
|
|
227
|
+
return { state: 'live', pid, verified: false };
|
|
228
|
+
// The pid is alive; make sure it is still our daemon and not a recycled pid.
|
|
229
|
+
if (!row.argv.includes('daemon'))
|
|
230
|
+
return { state: 'stale', pid, reason: 'pid reused' };
|
|
231
|
+
const ws = row.argv.indexOf('--workspace');
|
|
232
|
+
const workspace = ws >= 0 ? row.argv[ws + 1] : undefined;
|
|
233
|
+
if (workspace && !roots.includes(workspace) && !roots.includes(sys.realpath(workspace))) {
|
|
234
|
+
return { state: 'stale', pid, reason: 'pid belongs to another workspace' };
|
|
235
|
+
}
|
|
236
|
+
return { state: 'live', pid, verified: true };
|
|
237
|
+
}
|
|
238
|
+
/** cwd for each pid: /proc on Linux, then one batched lsof for the rest. */
|
|
239
|
+
function processCwds(sys, budget, pids) {
|
|
240
|
+
const cwds = new Map();
|
|
241
|
+
if (sys.platform === 'linux') {
|
|
242
|
+
for (const pid of pids) {
|
|
243
|
+
const cwd = sys.readlink(`/proc/${pid}/cwd`);
|
|
244
|
+
if (cwd)
|
|
245
|
+
cwds.set(pid, cwd);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const rest = pids.filter((pid) => !cwds.has(pid));
|
|
249
|
+
if (rest.length === 0)
|
|
250
|
+
return { cwds, lsof: null };
|
|
251
|
+
const r = budget.run('lsof', ['-a', '-d', 'cwd', '-p', rest.join(','), '-Fpn']);
|
|
252
|
+
let current = 0;
|
|
253
|
+
for (const line of r.stdout.split('\n')) {
|
|
254
|
+
if (line.startsWith('p'))
|
|
255
|
+
current = Number(line.slice(1));
|
|
256
|
+
else if (line.startsWith('n') && current)
|
|
257
|
+
cwds.set(current, line.slice(1));
|
|
258
|
+
}
|
|
259
|
+
return { cwds, lsof: r.state };
|
|
260
|
+
}
|
|
261
|
+
function mcpServers(sys, budget, roots, rows) {
|
|
262
|
+
let candidates = rows.filter((row) => row.pid !== sys.pid && isRufloMcp(row.argv));
|
|
263
|
+
// `npx ruflo mcp start` forks the real server; count the leaf only.
|
|
264
|
+
const parents = new Set(candidates.map((row) => row.ppid));
|
|
265
|
+
candidates = candidates.filter((row) => !parents.has(row.pid));
|
|
266
|
+
if (candidates.length === 0)
|
|
267
|
+
return { mcp: { count: 0, servers: [] }, lsof: null };
|
|
268
|
+
const { cwds, lsof } = processCwds(sys, budget, candidates.map((row) => row.pid));
|
|
269
|
+
if (lsof !== null && lsof !== 'ok' && cwds.size < candidates.length) {
|
|
270
|
+
return { mcp: { count: null, servers: [] }, lsof };
|
|
271
|
+
}
|
|
272
|
+
const servers = candidates
|
|
273
|
+
.filter((row) => {
|
|
274
|
+
const cwd = cwds.get(row.pid);
|
|
275
|
+
return cwd !== undefined && (insideRoot(cwd, roots) || insideRoot(sys.realpath(cwd), roots));
|
|
276
|
+
})
|
|
277
|
+
.map((row) => ({ pid: row.pid, command: redactCommand(row.argv) }));
|
|
278
|
+
return { mcp: { count: servers.length, servers }, lsof };
|
|
279
|
+
}
|
|
280
|
+
function memoryHolders(sys, budget, root) {
|
|
281
|
+
// memory.db is the sql.js writer; agentdb-memory.db is the native AgentDB store.
|
|
282
|
+
const files = [];
|
|
283
|
+
for (const db of ['memory.db', 'agentdb-memory.db']) {
|
|
284
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
285
|
+
const path = join(root, '.swarm', db + suffix);
|
|
286
|
+
if (sys.exists(path))
|
|
287
|
+
files.push(path);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (files.length === 0)
|
|
291
|
+
return { memory: { files, openPids: [] }, lsof: null };
|
|
292
|
+
if (sys.platform === 'win32')
|
|
293
|
+
return { memory: { files, openPids: null }, lsof: 'unavailable' };
|
|
294
|
+
const r = budget.run('lsof', ['-t', '--', ...files]);
|
|
295
|
+
if (r.state !== 'ok')
|
|
296
|
+
return { memory: { files, openPids: null }, lsof: r.state };
|
|
297
|
+
const pids = r.stdout
|
|
298
|
+
.split('\n')
|
|
299
|
+
.map((row) => Number(row.trim()))
|
|
300
|
+
.filter((pid) => pid > 0 && pid !== sys.pid);
|
|
301
|
+
return { memory: { files, openPids: [...new Set(pids)] }, lsof: 'ok' };
|
|
302
|
+
}
|
|
303
|
+
function lastActivity(sys, file, team) {
|
|
304
|
+
const stamps = [team.createdAt, team.updatedAt, team.plan?.updatedAt];
|
|
305
|
+
for (const member of Object.values(team.members ?? {})) {
|
|
306
|
+
stamps.push(member?.registeredAt, member?.lastStopAt, member?.lastSeenAt);
|
|
307
|
+
}
|
|
308
|
+
const times = stamps.map((s) => (s ? Date.parse(s) : NaN)).filter((t) => Number.isFinite(t));
|
|
309
|
+
return Math.max(sys.mtimeMs(file) ?? 0, ...times, 0);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Teams under .claude-flow/teams: `<name>/team.json` (the team bus layout)
|
|
313
|
+
* and the legacy flat `<name>.json`. A team counts as live only if it has not
|
|
314
|
+
* shut down AND shows activity within the TTL or has a member pid running.
|
|
315
|
+
*/
|
|
316
|
+
function teamsIn(sys, root, ttlMs) {
|
|
317
|
+
const dir = join(root, '.claude-flow', 'teams');
|
|
318
|
+
const live = [];
|
|
319
|
+
const stale = [];
|
|
320
|
+
if (!sys.exists(dir))
|
|
321
|
+
return { live, stale };
|
|
322
|
+
let entries;
|
|
323
|
+
try {
|
|
324
|
+
entries = sys.readDir(dir);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return { live, stale };
|
|
328
|
+
}
|
|
329
|
+
for (const entry of entries) {
|
|
330
|
+
const file = entry.isDir
|
|
331
|
+
? join(dir, entry.name, 'team.json')
|
|
332
|
+
: entry.name.endsWith('.json') ? join(dir, entry.name) : '';
|
|
333
|
+
if (!file || !sys.exists(file))
|
|
334
|
+
continue;
|
|
335
|
+
let team;
|
|
336
|
+
try {
|
|
337
|
+
team = JSON.parse(sys.readFile(file));
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
continue; // an unreadable team file is not a live team
|
|
341
|
+
}
|
|
342
|
+
if (!team || typeof team !== 'object')
|
|
343
|
+
continue;
|
|
344
|
+
if (team.shutdownAt || TEAM_ENDED.has(String(team.status)))
|
|
345
|
+
continue;
|
|
346
|
+
const name = team.name || entry.name.replace(/\.json$/, '');
|
|
347
|
+
const last = lastActivity(sys, file, team);
|
|
348
|
+
const pidAlive = Object.values(team.members ?? {}).some((m) => Number.isInteger(m?.pid) && m.pid > 0 && sys.signal0(m.pid) !== 'dead');
|
|
349
|
+
if (!pidAlive && sys.now() - last > ttlMs) {
|
|
350
|
+
stale.push(name);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
live.push({
|
|
354
|
+
name,
|
|
355
|
+
status: team.status || 'unknown',
|
|
356
|
+
members: team.members ? Object.keys(team.members).length : 0,
|
|
357
|
+
lastActivity: last ? new Date(last).toISOString() : undefined,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return { live, stale };
|
|
361
|
+
}
|
|
362
|
+
function worst(...states) {
|
|
363
|
+
const rank = { ok: 0, timeout: 1, unavailable: 2 };
|
|
364
|
+
return states.reduce((acc, s) => (s !== null && rank[s] > rank[acc] ? s : acc), 'ok');
|
|
365
|
+
}
|
|
366
|
+
function teamTtl(sys, fallback) {
|
|
367
|
+
const secs = Number(sys.env.RUFLO_HARNESS_TEAM_TTL_SECS);
|
|
368
|
+
return Number.isFinite(secs) && secs > 0 ? secs * 1000 : fallback;
|
|
369
|
+
}
|
|
370
|
+
export function collectLiveHarness(sys = realSys, opts = {}) {
|
|
371
|
+
const start = sys.now();
|
|
372
|
+
const budget = new Budget(sys, opts.budgetMs ?? DEFAULT_BUDGET_MS);
|
|
373
|
+
const root = opts.root ?? findWorkspaceRoot(sys);
|
|
374
|
+
// Both spellings of the root, so macOS /tmp and /private/tmp compare equal.
|
|
375
|
+
const roots = [...new Set([root, sys.realpath(root)])];
|
|
376
|
+
let ps = 'unavailable';
|
|
377
|
+
let rows = [];
|
|
378
|
+
if (sys.platform !== 'win32') {
|
|
379
|
+
const r = budget.run('ps', ['-ax', '-o', 'pid=,ppid=,command=']);
|
|
380
|
+
rows = r.state === 'ok' ? parsePs(r.stdout) : [];
|
|
381
|
+
// busybox ps rejects -ax: "ran but listed nothing" is unavailable, not idle.
|
|
382
|
+
ps = r.state === 'ok' && rows.length === 0 ? 'unavailable' : r.state;
|
|
383
|
+
}
|
|
384
|
+
const table = ps === 'ok' ? new Map(rows.map((row) => [row.pid, row])) : null;
|
|
385
|
+
const { host, parent } = detectHost(sys, table);
|
|
386
|
+
const daemon = daemonState(sys, root, roots, table);
|
|
387
|
+
const mcpProbe = ps === 'ok'
|
|
388
|
+
? mcpServers(sys, budget, roots, rows)
|
|
389
|
+
: { mcp: { count: null, servers: [] }, lsof: null };
|
|
390
|
+
const memProbe = memoryHolders(sys, budget, root);
|
|
391
|
+
const teams = teamsIn(sys, root, teamTtl(sys, opts.teamTtlMs ?? DEFAULT_TEAM_TTL_MS));
|
|
392
|
+
const probes = { ps, lsof: worst(mcpProbe.lsof, memProbe.lsof) };
|
|
393
|
+
const openCount = memProbe.memory.openPids === null ? null : memProbe.memory.openPids.length;
|
|
394
|
+
const live = daemon.state === 'live' || (mcpProbe.mcp.count ?? 0) > 0 || (openCount ?? 0) > 0 || teams.live.length > 0;
|
|
395
|
+
const unknown = mcpProbe.mcp.count === null || openCount === null;
|
|
396
|
+
const status = live ? 'live' : unknown ? 'unknown' : 'idle';
|
|
397
|
+
const unavailable = Object.entries(probes).filter(([, s]) => s !== 'ok').map(([k, s]) => `${k}:${s}`);
|
|
398
|
+
const count = (n) => (n === null ? '?' : String(n));
|
|
399
|
+
const parts = [
|
|
400
|
+
'harness',
|
|
401
|
+
status,
|
|
402
|
+
`host=${host}`,
|
|
403
|
+
parent.name ? `parent=${parent.name}` : '',
|
|
404
|
+
`daemon=${daemon.state}${daemon.pid ? `:${daemon.pid}` : ''}`,
|
|
405
|
+
`mcp=${count(mcpProbe.mcp.count)}`,
|
|
406
|
+
`memory-open=${count(openCount)}`,
|
|
407
|
+
`teams=${teams.live.length}`,
|
|
408
|
+
teams.stale.length ? `stale-teams=${teams.stale.length}` : '',
|
|
409
|
+
unavailable.length ? `probe-unavailable=${unavailable.join(',')}` : '',
|
|
410
|
+
].filter(Boolean);
|
|
411
|
+
return {
|
|
412
|
+
root,
|
|
413
|
+
host,
|
|
414
|
+
parent,
|
|
415
|
+
session: sessionIds(sys.env),
|
|
416
|
+
daemon,
|
|
417
|
+
mcp: mcpProbe.mcp,
|
|
418
|
+
memory: memProbe.memory,
|
|
419
|
+
teams: teams.live,
|
|
420
|
+
staleTeams: teams.stale,
|
|
421
|
+
probes,
|
|
422
|
+
status,
|
|
423
|
+
elapsedMs: sys.now() - start,
|
|
424
|
+
line: parts.join(' │ '),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
//# sourceMappingURL=live-harness.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.59.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"!dist/**/*.tsbuildinfo",
|
|
77
77
|
"bin",
|
|
78
78
|
"scripts/postinstall.cjs",
|
|
79
|
+
"templates",
|
|
79
80
|
".claude",
|
|
80
81
|
"plugins",
|
|
81
82
|
"README.md",
|
|
@@ -126,11 +127,11 @@
|
|
|
126
127
|
"ws": "^8.21.0",
|
|
127
128
|
"yaml": "^2.8.0",
|
|
128
129
|
"zod": "^3.22.0",
|
|
129
|
-
"@claude-flow/memory": "3.0.
|
|
130
|
+
"@claude-flow/memory": "3.0.8"
|
|
130
131
|
},
|
|
131
132
|
"optionalDependencies": {
|
|
132
133
|
"@agntcy/slim-bindings": "2.0.0-alpha.5",
|
|
133
|
-
"@claude-flow/memory": "3.0.
|
|
134
|
+
"@claude-flow/memory": "3.0.8",
|
|
134
135
|
"@metaharness/darwin": "~0.12.0",
|
|
135
136
|
"@metaharness/flywheel": "~0.1.10",
|
|
136
137
|
"@metaharness/radio": "~0.1.0",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ruflo-architect
|
|
3
|
+
description: >
|
|
4
|
+
Ruflo swarm architect for Grok. Designs implementation blueprints, ADRs, and
|
|
5
|
+
file-level plans. Use for multi-file features before coding. Read-only by default;
|
|
6
|
+
hand off to ruflo-coder via team bus.
|
|
7
|
+
prompt_mode: full
|
|
8
|
+
permission_mode: plan
|
|
9
|
+
agents_md: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You are the **architect** on a Ruflo Agent Team running under **Grok Build**.
|
|
13
|
+
|
|
14
|
+
This file is a session profile (`grok --agent-profile ruflo-architect` or `/agents`). On Grok Build 1.0.41, `spawn_subagent` does not select it. A spawned child is general-purpose, follows the spawn prompt, and must not call `spawn_subagent`.
|
|
15
|
+
|
|
16
|
+
## Comms protocol (mandatory)
|
|
17
|
+
|
|
18
|
+
- There is **no** Claude `SendMessage` tool.
|
|
19
|
+
- At start, drain your inbox:
|
|
20
|
+
`node scripts/grok-team-bus.mjs inbox --team <TEAM> --agent architect`
|
|
21
|
+
- When design is ready, hand off:
|
|
22
|
+
`node scripts/grok-team-bus.mjs send --team <TEAM> --to developer --from architect --summary "design-ready" --message "<paths + decisions>"`
|
|
23
|
+
- Prefer storing durable design under memory namespace `team:<TEAM>` when Ruflo MCP is available.
|
|
24
|
+
|
|
25
|
+
## Deliverable
|
|
26
|
+
|
|
27
|
+
Produce a decisive blueprint:
|
|
28
|
+
|
|
29
|
+
1. Patterns found (file:line)
|
|
30
|
+
2. Chosen architecture + trade-offs
|
|
31
|
+
3. Files to create/modify
|
|
32
|
+
4. Build sequence for developer → tester → reviewer
|
|
33
|
+
5. Risks / security notes
|
|
34
|
+
|
|
35
|
+
Do **not** implement code. Do not wait for Ruflo to execute — you design; the lead/developer executes.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ruflo-coder
|
|
3
|
+
description: >
|
|
4
|
+
Ruflo implementation agent for Grok. Writes production code from architect designs.
|
|
5
|
+
Prefer isolation=worktree when spawned. Handoffs via team bus, not SendMessage.
|
|
6
|
+
prompt_mode: full
|
|
7
|
+
agents_md: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are the **developer/coder** on a Ruflo Agent Team under **Grok Build**.
|
|
11
|
+
|
|
12
|
+
This file is a session profile (`grok --agent-profile ruflo-coder` or `/agents`). On Grok Build 1.0.41, `spawn_subagent` does not select it. A spawned child is general-purpose, follows the spawn prompt (worktree isolation when the plan says so), and must not call `spawn_subagent`.
|
|
13
|
+
|
|
14
|
+
## Comms protocol (mandatory)
|
|
15
|
+
|
|
16
|
+
- No Claude `SendMessage`.
|
|
17
|
+
- Start: `node scripts/grok-team-bus.mjs inbox --team <TEAM> --agent developer`
|
|
18
|
+
- If architect design is in the inbox or memory `team:<TEAM>`, follow it.
|
|
19
|
+
- When implementation is ready:
|
|
20
|
+
`node scripts/grok-team-bus.mjs send --team <TEAM> --to tester --from developer --summary "impl-ready" --message "<files changed + how to test>"`
|
|
21
|
+
- On stop: `node scripts/grok-team-bus.mjs on-stop --team <TEAM> --agent developer`
|
|
22
|
+
|
|
23
|
+
## Rules
|
|
24
|
+
|
|
25
|
+
- Prefer editing existing files; keep changes focused.
|
|
26
|
+
- Match project conventions (Claude.md / Agents.md / ADRs).
|
|
27
|
+
- Run available unit tests for touched areas when practical.
|
|
28
|
+
- If using a git worktree, leave a clear summary of the worktree path and branch for the lead to merge.
|
|
29
|
+
|
|
30
|
+
## Anti-patterns
|
|
31
|
+
|
|
32
|
+
- Waiting after `swarm_init` for code to appear by magic
|
|
33
|
+
- Inventing Claude Task / SendMessage APIs
|
|
34
|
+
- Parallel uncoordinated edits outside your worktree
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ruflo-reviewer
|
|
3
|
+
description: >
|
|
4
|
+
Ruflo code review agent for Grok. Reviews quality, security, and test evidence.
|
|
5
|
+
Read-oriented; reports findings to the team lead via team bus.
|
|
6
|
+
prompt_mode: full
|
|
7
|
+
permission_mode: plan
|
|
8
|
+
agents_md: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
You are the **reviewer** on a Ruflo Agent Team under **Grok Build**.
|
|
12
|
+
|
|
13
|
+
This file is a session profile (`grok --agent-profile ruflo-reviewer` or `/agents`). On Grok Build 1.0.41, `spawn_subagent` does not select it. A spawned child is general-purpose, follows the spawn prompt, and must not call `spawn_subagent`.
|
|
14
|
+
|
|
15
|
+
## Comms protocol
|
|
16
|
+
|
|
17
|
+
- Inbox: `node scripts/grok-team-bus.mjs inbox --team <TEAM> --agent reviewer`
|
|
18
|
+
- Final report to lead:
|
|
19
|
+
`node scripts/grok-team-bus.mjs send --team <TEAM> --to lead --from reviewer --summary "review-complete" --message "<findings>"`
|
|
20
|
+
(If `lead` mailbox is unused, print the full review as your final response for the parent session.)
|
|
21
|
+
- `node scripts/grok-team-bus.mjs on-stop --team <TEAM> --agent reviewer`
|
|
22
|
+
|
|
23
|
+
## Review focus
|
|
24
|
+
|
|
25
|
+
1. Correctness vs design handoff
|
|
26
|
+
2. Security (injection, secrets, path traversal)
|
|
27
|
+
3. Test evidence quality
|
|
28
|
+
4. Maintainability / ADR alignment
|
|
29
|
+
|
|
30
|
+
Severity: blocker / major / minor / nit. Blockers must be explicit.
|