claude-flow 3.6.21 → 3.6.23
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/scheduled_tasks.lock +1 -1
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.d.ts +85 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +214 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +47 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +145 -13
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +98 -10
- package/v3/@claude-flow/cli/dist/src/ruvector/agent-wasm.d.ts +12 -1
- package/v3/@claude-flow/cli/dist/src/ruvector/agent-wasm.js +38 -3
- package/v3/@claude-flow/cli/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"sessionId":"36428a63-dfb2-42a4-a159-cf8be916193e","pid":
|
|
1
|
+
{"sessionId":"36428a63-dfb2-42a4-a159-cf8be916193e","pid":71027,"procStart":"Sun May 3 23:00:23 2026","acquiredAt":1777849234814}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.23",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared agent-execution core.
|
|
3
|
+
*
|
|
4
|
+
* Both the agent_execute MCP tool and the workflow runtime (G3) need
|
|
5
|
+
* to dispatch a prompt to an agent's configured Anthropic model. This
|
|
6
|
+
* module factors that path out so it's testable and reusable, and
|
|
7
|
+
* keeps the wire from agent_spawn → ProviderManager (real) in one
|
|
8
|
+
* place rather than duplicated.
|
|
9
|
+
*/
|
|
10
|
+
type ClaudeModel = 'haiku' | 'sonnet' | 'opus' | 'inherit';
|
|
11
|
+
export interface AgentRecord {
|
|
12
|
+
agentId: string;
|
|
13
|
+
agentType: string;
|
|
14
|
+
status: 'idle' | 'busy' | 'terminated';
|
|
15
|
+
health: number;
|
|
16
|
+
taskCount: number;
|
|
17
|
+
config: Record<string, unknown>;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
domain?: string;
|
|
20
|
+
model?: ClaudeModel;
|
|
21
|
+
modelRoutedBy?: 'explicit' | 'router' | 'agent-booster' | 'default';
|
|
22
|
+
lastResult?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface AnthropicCallInput {
|
|
25
|
+
prompt: string;
|
|
26
|
+
systemPrompt?: string;
|
|
27
|
+
model?: string;
|
|
28
|
+
maxTokens?: number;
|
|
29
|
+
temperature?: number;
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface AnthropicCallResult {
|
|
33
|
+
success: boolean;
|
|
34
|
+
model?: string;
|
|
35
|
+
messageId?: string;
|
|
36
|
+
stopReason?: string;
|
|
37
|
+
output?: string;
|
|
38
|
+
usage?: {
|
|
39
|
+
inputTokens: number;
|
|
40
|
+
outputTokens: number;
|
|
41
|
+
totalTokens: number;
|
|
42
|
+
};
|
|
43
|
+
durationMs?: number;
|
|
44
|
+
error?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Generic Anthropic Messages API call. No agent registry coupling — used
|
|
48
|
+
* by agent_execute (with the agent's configured model) and by the WASM
|
|
49
|
+
* agent runtime (G4) when the bundled WASM only echoes input.
|
|
50
|
+
*/
|
|
51
|
+
export declare function callAnthropicMessages(input: AnthropicCallInput): Promise<AnthropicCallResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a model identifier to an Anthropic model ID. Accepts:
|
|
54
|
+
* - logical names: 'haiku', 'sonnet', 'opus', 'inherit'
|
|
55
|
+
* - prefixed: 'anthropic:claude-3-5-sonnet-latest'
|
|
56
|
+
* - direct: 'claude-3-5-sonnet-latest'
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveAnthropicModel(input: string | undefined): string;
|
|
59
|
+
export interface AgentExecuteInput {
|
|
60
|
+
agentId: string;
|
|
61
|
+
prompt: string;
|
|
62
|
+
systemPrompt?: string;
|
|
63
|
+
maxTokens?: number;
|
|
64
|
+
temperature?: number;
|
|
65
|
+
timeoutMs?: number;
|
|
66
|
+
}
|
|
67
|
+
export interface AgentExecuteResult {
|
|
68
|
+
success: boolean;
|
|
69
|
+
agentId: string;
|
|
70
|
+
model?: string;
|
|
71
|
+
messageId?: string;
|
|
72
|
+
stopReason?: string;
|
|
73
|
+
output?: string;
|
|
74
|
+
usage?: {
|
|
75
|
+
inputTokens: number;
|
|
76
|
+
outputTokens: number;
|
|
77
|
+
totalTokens: number;
|
|
78
|
+
};
|
|
79
|
+
durationMs?: number;
|
|
80
|
+
error?: string;
|
|
81
|
+
remediation?: string;
|
|
82
|
+
}
|
|
83
|
+
export declare function executeAgentTask(input: AgentExecuteInput): Promise<AgentExecuteResult>;
|
|
84
|
+
export {};
|
|
85
|
+
//# sourceMappingURL=agent-execute-core.d.ts.map
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared agent-execution core.
|
|
3
|
+
*
|
|
4
|
+
* Both the agent_execute MCP tool and the workflow runtime (G3) need
|
|
5
|
+
* to dispatch a prompt to an agent's configured Anthropic model. This
|
|
6
|
+
* module factors that path out so it's testable and reusable, and
|
|
7
|
+
* keeps the wire from agent_spawn → ProviderManager (real) in one
|
|
8
|
+
* place rather than duplicated.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { getProjectCwd } from './types.js';
|
|
13
|
+
const STORAGE_DIR = '.claude-flow';
|
|
14
|
+
const AGENT_DIR = 'agents';
|
|
15
|
+
const AGENT_FILE = 'store.json';
|
|
16
|
+
function getAgentDir() { return join(getProjectCwd(), STORAGE_DIR, AGENT_DIR); }
|
|
17
|
+
function getAgentPath() { return join(getAgentDir(), AGENT_FILE); }
|
|
18
|
+
function ensureAgentDir() {
|
|
19
|
+
const dir = getAgentDir();
|
|
20
|
+
if (!existsSync(dir))
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
function loadAgentStore() {
|
|
24
|
+
try {
|
|
25
|
+
if (existsSync(getAgentPath()))
|
|
26
|
+
return JSON.parse(readFileSync(getAgentPath(), 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
catch { /* fall through */ }
|
|
29
|
+
return { agents: {}, version: '3.0.0' };
|
|
30
|
+
}
|
|
31
|
+
function saveAgentStore(store) {
|
|
32
|
+
ensureAgentDir();
|
|
33
|
+
writeFileSync(getAgentPath(), JSON.stringify(store, null, 2), 'utf-8');
|
|
34
|
+
}
|
|
35
|
+
const MODEL_MAP = {
|
|
36
|
+
haiku: 'claude-3-5-haiku-latest',
|
|
37
|
+
sonnet: 'claude-3-5-sonnet-latest',
|
|
38
|
+
opus: 'claude-3-opus-latest',
|
|
39
|
+
inherit: 'claude-3-5-sonnet-latest',
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Generic Anthropic Messages API call. No agent registry coupling — used
|
|
43
|
+
* by agent_execute (with the agent's configured model) and by the WASM
|
|
44
|
+
* agent runtime (G4) when the bundled WASM only echoes input.
|
|
45
|
+
*/
|
|
46
|
+
export async function callAnthropicMessages(input) {
|
|
47
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
48
|
+
if (!apiKey) {
|
|
49
|
+
return { success: false, error: 'ANTHROPIC_API_KEY not set in environment' };
|
|
50
|
+
}
|
|
51
|
+
const model = input.model || 'claude-3-5-sonnet-latest';
|
|
52
|
+
const startedAt = Date.now();
|
|
53
|
+
try {
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
const timer = setTimeout(() => controller.abort(), input.timeoutMs || 60000);
|
|
56
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: {
|
|
59
|
+
'x-api-key': apiKey,
|
|
60
|
+
'anthropic-version': '2023-06-01',
|
|
61
|
+
'content-type': 'application/json',
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
model,
|
|
65
|
+
max_tokens: input.maxTokens || 1024,
|
|
66
|
+
temperature: typeof input.temperature === 'number' ? input.temperature : 0.7,
|
|
67
|
+
...(input.systemPrompt ? { system: input.systemPrompt } : {}),
|
|
68
|
+
messages: [{ role: 'user', content: input.prompt }],
|
|
69
|
+
}),
|
|
70
|
+
signal: controller.signal,
|
|
71
|
+
});
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
const errText = await res.text().catch(() => '<unreadable error body>');
|
|
75
|
+
return { success: false, model, error: `Anthropic API error ${res.status}: ${errText.slice(0, 400)}` };
|
|
76
|
+
}
|
|
77
|
+
const data = await res.json();
|
|
78
|
+
const textOut = data.content
|
|
79
|
+
.filter(c => c.type === 'text' && typeof c.text === 'string')
|
|
80
|
+
.map(c => c.text)
|
|
81
|
+
.join('');
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
model: data.model,
|
|
85
|
+
messageId: data.id,
|
|
86
|
+
stopReason: data.stop_reason,
|
|
87
|
+
output: textOut,
|
|
88
|
+
usage: {
|
|
89
|
+
inputTokens: data.usage.input_tokens,
|
|
90
|
+
outputTokens: data.usage.output_tokens,
|
|
91
|
+
totalTokens: data.usage.input_tokens + data.usage.output_tokens,
|
|
92
|
+
},
|
|
93
|
+
durationMs: Date.now() - startedAt,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
model,
|
|
100
|
+
error: err instanceof Error ? err.message : String(err),
|
|
101
|
+
durationMs: Date.now() - startedAt,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a model identifier to an Anthropic model ID. Accepts:
|
|
107
|
+
* - logical names: 'haiku', 'sonnet', 'opus', 'inherit'
|
|
108
|
+
* - prefixed: 'anthropic:claude-3-5-sonnet-latest'
|
|
109
|
+
* - direct: 'claude-3-5-sonnet-latest'
|
|
110
|
+
*/
|
|
111
|
+
export function resolveAnthropicModel(input) {
|
|
112
|
+
if (!input)
|
|
113
|
+
return 'claude-3-5-sonnet-latest';
|
|
114
|
+
if (input in MODEL_MAP)
|
|
115
|
+
return MODEL_MAP[input];
|
|
116
|
+
if (input.startsWith('anthropic:'))
|
|
117
|
+
return input.slice('anthropic:'.length);
|
|
118
|
+
return input;
|
|
119
|
+
}
|
|
120
|
+
export async function executeAgentTask(input) {
|
|
121
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
122
|
+
if (!apiKey) {
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
agentId: input.agentId,
|
|
126
|
+
error: 'ANTHROPIC_API_KEY not set in environment',
|
|
127
|
+
remediation: 'Set the env var and re-run. The key is read at call time.',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const store = loadAgentStore();
|
|
131
|
+
const agent = store.agents[input.agentId];
|
|
132
|
+
if (!agent)
|
|
133
|
+
return { success: false, agentId: input.agentId, error: 'Agent not found' };
|
|
134
|
+
if (agent.status === 'terminated')
|
|
135
|
+
return { success: false, agentId: input.agentId, error: 'Agent has been terminated' };
|
|
136
|
+
const anthropicModel = MODEL_MAP[agent.model || 'sonnet'] || 'claude-3-5-sonnet-latest';
|
|
137
|
+
const systemPrompt = input.systemPrompt ||
|
|
138
|
+
`You are a ${agent.agentType} agent operating as part of a Ruflo swarm. ` +
|
|
139
|
+
`Agent ID: ${input.agentId}. Domain: ${agent.domain ?? 'general'}. ` +
|
|
140
|
+
`Respond directly and stay focused on the task. If you need information you don't have, state that explicitly.`;
|
|
141
|
+
agent.status = 'busy';
|
|
142
|
+
agent.taskCount = (agent.taskCount || 0) + 1;
|
|
143
|
+
saveAgentStore(store);
|
|
144
|
+
const startedAt = Date.now();
|
|
145
|
+
try {
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
const timeoutMs = input.timeoutMs || 60000;
|
|
148
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
149
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
headers: {
|
|
152
|
+
'x-api-key': apiKey,
|
|
153
|
+
'anthropic-version': '2023-06-01',
|
|
154
|
+
'content-type': 'application/json',
|
|
155
|
+
},
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
model: anthropicModel,
|
|
158
|
+
max_tokens: input.maxTokens || 1024,
|
|
159
|
+
temperature: typeof input.temperature === 'number' ? input.temperature : 0.7,
|
|
160
|
+
system: systemPrompt,
|
|
161
|
+
messages: [{ role: 'user', content: input.prompt }],
|
|
162
|
+
}),
|
|
163
|
+
signal: controller.signal,
|
|
164
|
+
});
|
|
165
|
+
clearTimeout(timer);
|
|
166
|
+
if (!res.ok) {
|
|
167
|
+
const errText = await res.text().catch(() => '<unreadable error body>');
|
|
168
|
+
agent.status = 'idle';
|
|
169
|
+
saveAgentStore(store);
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
agentId: input.agentId,
|
|
173
|
+
model: anthropicModel,
|
|
174
|
+
error: `Anthropic API error ${res.status}: ${errText.slice(0, 400)}`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const data = await res.json();
|
|
178
|
+
const textOut = data.content
|
|
179
|
+
.filter(c => c.type === 'text' && typeof c.text === 'string')
|
|
180
|
+
.map(c => c.text)
|
|
181
|
+
.join('');
|
|
182
|
+
const result = {
|
|
183
|
+
success: true,
|
|
184
|
+
agentId: input.agentId,
|
|
185
|
+
messageId: data.id,
|
|
186
|
+
model: data.model,
|
|
187
|
+
stopReason: data.stop_reason,
|
|
188
|
+
output: textOut,
|
|
189
|
+
usage: {
|
|
190
|
+
inputTokens: data.usage.input_tokens,
|
|
191
|
+
outputTokens: data.usage.output_tokens,
|
|
192
|
+
totalTokens: data.usage.input_tokens + data.usage.output_tokens,
|
|
193
|
+
},
|
|
194
|
+
durationMs: Date.now() - startedAt,
|
|
195
|
+
};
|
|
196
|
+
agent.status = 'idle';
|
|
197
|
+
agent.lastResult = result;
|
|
198
|
+
saveAgentStore(store);
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
agent.status = 'idle';
|
|
203
|
+
saveAgentStore(store);
|
|
204
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
agentId: input.agentId,
|
|
208
|
+
model: anthropicModel,
|
|
209
|
+
error: `agent_execute failed: ${msg}`,
|
|
210
|
+
durationMs: Date.now() - startedAt,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=agent-execute-core.js.map
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { getProjectCwd } from './types.js';
|
|
10
|
-
import { validateIdentifier, validateAgentSpawn } from './validate-input.js';
|
|
10
|
+
import { validateIdentifier, validateText, validateAgentSpawn } from './validate-input.js';
|
|
11
|
+
import { executeAgentTask } from './agent-execute-core.js';
|
|
11
12
|
// Storage paths
|
|
12
13
|
const STORAGE_DIR = '.claude-flow';
|
|
13
14
|
const AGENT_DIR = 'agents';
|
|
@@ -199,7 +200,10 @@ export const agentTools = [
|
|
|
199
200
|
modelRoutedBy: routingResult.routedBy,
|
|
200
201
|
status: 'registered',
|
|
201
202
|
createdAt: agent.createdAt,
|
|
202
|
-
note: 'Agent registered for coordination.
|
|
203
|
+
note: 'Agent registered for coordination. Three execution paths: ' +
|
|
204
|
+
'(1) call agent_execute(agentId, prompt) — direct LLM call via Anthropic Messages API (requires ANTHROPIC_API_KEY); ' +
|
|
205
|
+
'(2) Claude Code Task tool — spawns a real subagent; ' +
|
|
206
|
+
'(3) claude -p — headless background instance.',
|
|
203
207
|
};
|
|
204
208
|
// Add Agent Booster info if task can skip LLM
|
|
205
209
|
if (routingResult.canSkipLLM) {
|
|
@@ -214,6 +218,47 @@ export const agentTools = [
|
|
|
214
218
|
return response;
|
|
215
219
|
},
|
|
216
220
|
},
|
|
221
|
+
{
|
|
222
|
+
// ADR-095 G1: real LLM execution via the agent registry. Previously
|
|
223
|
+
// agent_spawn registered metadata but nothing dispatched work to a
|
|
224
|
+
// provider — the wire between AnthropicProvider and the agent
|
|
225
|
+
// registry was missing, as the April audit (@roman-rr) called out.
|
|
226
|
+
// agent_execute closes that wire by reading the agent's configured
|
|
227
|
+
// model, calling the Anthropic Messages API directly via fetch, and
|
|
228
|
+
// updating the agent record with lastResult / taskCount / status.
|
|
229
|
+
// No mock — actual HTTP request to api.anthropic.com.
|
|
230
|
+
name: 'agent_execute',
|
|
231
|
+
description: 'Execute a task on a spawned agent — calls the Anthropic Messages API with the agent\'s configured model. Requires ANTHROPIC_API_KEY in env.',
|
|
232
|
+
category: 'agent',
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: 'object',
|
|
235
|
+
properties: {
|
|
236
|
+
agentId: { type: 'string', description: 'ID of the spawned agent' },
|
|
237
|
+
prompt: { type: 'string', description: 'Task / prompt for the agent to execute' },
|
|
238
|
+
systemPrompt: { type: 'string', description: 'Optional system prompt (overrides agent default)' },
|
|
239
|
+
maxTokens: { type: 'number', description: 'Max output tokens (default 1024)' },
|
|
240
|
+
temperature: { type: 'number', description: 'Sampling temperature 0..1 (default 0.7)' },
|
|
241
|
+
},
|
|
242
|
+
required: ['agentId', 'prompt'],
|
|
243
|
+
},
|
|
244
|
+
handler: async (input) => {
|
|
245
|
+
const vId = validateIdentifier(input.agentId, 'agentId');
|
|
246
|
+
if (!vId.valid)
|
|
247
|
+
return { success: false, error: `Input validation failed: ${vId.error}` };
|
|
248
|
+
const vP = validateText(input.prompt, 'prompt');
|
|
249
|
+
if (!vP.valid)
|
|
250
|
+
return { success: false, error: `Input validation failed: ${vP.error}` };
|
|
251
|
+
// Delegate to the shared core (also used by the workflow runtime).
|
|
252
|
+
return executeAgentTask({
|
|
253
|
+
agentId: input.agentId,
|
|
254
|
+
prompt: input.prompt,
|
|
255
|
+
systemPrompt: input.systemPrompt,
|
|
256
|
+
maxTokens: input.maxTokens,
|
|
257
|
+
temperature: input.temperature,
|
|
258
|
+
timeoutMs: input.timeoutMs,
|
|
259
|
+
});
|
|
260
|
+
},
|
|
261
|
+
},
|
|
217
262
|
{
|
|
218
263
|
name: 'agent_terminate',
|
|
219
264
|
description: 'Terminate an agent',
|
|
@@ -7,6 +7,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { getProjectCwd } from './types.js';
|
|
9
9
|
import { validateIdentifier, validatePath, validateText } from './validate-input.js';
|
|
10
|
+
import { executeAgentTask } from './agent-execute-core.js';
|
|
10
11
|
// Storage paths
|
|
11
12
|
const STORAGE_DIR = '.claude-flow';
|
|
12
13
|
const WORKFLOW_DIR = 'workflows';
|
|
@@ -247,25 +248,156 @@ export const workflowTools = [
|
|
|
247
248
|
workflow.status = 'running';
|
|
248
249
|
workflow.startedAt = new Date().toISOString();
|
|
249
250
|
workflow.currentStep = input.startFromStep || 0;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
251
|
+
saveWorkflowStore(store);
|
|
252
|
+
// ADR-095 G3: real workflow runtime. Walk the steps in order;
|
|
253
|
+
// dispatch each by type. Persist progress after each step so a
|
|
254
|
+
// crash or pause can resume cleanly. No mock — task steps make
|
|
255
|
+
// real LLM calls via agent_execute (G1's wire).
|
|
256
|
+
const stepResults = [];
|
|
257
|
+
// Variable substitution: {{name}} → workflow.variables[name] OR steps[stepId].output
|
|
258
|
+
const interp = (text) => {
|
|
259
|
+
return text.replace(/\{\{\s*([a-zA-Z_][\w.]*)\s*\}\}/g, (_, key) => {
|
|
260
|
+
// Direct variable
|
|
261
|
+
if (key in workflow.variables)
|
|
262
|
+
return String(workflow.variables[key]);
|
|
263
|
+
// Step-output reference: stepId.output
|
|
264
|
+
const dot = key.indexOf('.');
|
|
265
|
+
if (dot > 0) {
|
|
266
|
+
const stepId = key.slice(0, dot);
|
|
267
|
+
const field = key.slice(dot + 1);
|
|
268
|
+
const prior = stepResults.find(s => s.stepId === stepId);
|
|
269
|
+
if (prior && field === 'output' && typeof prior.output === 'string')
|
|
270
|
+
return prior.output;
|
|
271
|
+
}
|
|
272
|
+
return '';
|
|
259
273
|
});
|
|
274
|
+
};
|
|
275
|
+
const startedAt = Date.now();
|
|
276
|
+
let i = workflow.currentStep;
|
|
277
|
+
while (i < workflow.steps.length) {
|
|
278
|
+
// Honor pause/cancel signals between steps.
|
|
279
|
+
const live = loadWorkflowStore().workflows[workflowId];
|
|
280
|
+
if (!live || live.status === 'paused') {
|
|
281
|
+
workflow.status = 'paused';
|
|
282
|
+
workflow.currentStep = i;
|
|
283
|
+
saveWorkflowStore(store);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
if (live.status === 'failed') {
|
|
287
|
+
workflow.status = 'failed';
|
|
288
|
+
saveWorkflowStore(store);
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
const step = workflow.steps[i];
|
|
292
|
+
step.status = 'running';
|
|
293
|
+
step.startedAt = new Date().toISOString();
|
|
294
|
+
const stepStart = Date.now();
|
|
295
|
+
saveWorkflowStore(store);
|
|
296
|
+
let stepEntry = { stepId: step.stepId, type: step.type, status: 'running' };
|
|
297
|
+
try {
|
|
298
|
+
if (step.type === 'task') {
|
|
299
|
+
const cfg = step.config;
|
|
300
|
+
const agentId = cfg.agentId || workflow.variables.defaultAgentId;
|
|
301
|
+
const promptTpl = cfg.prompt || step.name;
|
|
302
|
+
if (!agentId)
|
|
303
|
+
throw new Error(`task step ${step.stepId} requires config.agentId or workflow.variables.defaultAgentId`);
|
|
304
|
+
const prompt = interp(promptTpl);
|
|
305
|
+
const result = await executeAgentTask({
|
|
306
|
+
agentId,
|
|
307
|
+
prompt,
|
|
308
|
+
systemPrompt: cfg.systemPrompt ? interp(String(cfg.systemPrompt)) : undefined,
|
|
309
|
+
maxTokens: cfg.maxTokens,
|
|
310
|
+
temperature: cfg.temperature,
|
|
311
|
+
timeoutMs: cfg.timeoutMs,
|
|
312
|
+
});
|
|
313
|
+
if (!result.success)
|
|
314
|
+
throw new Error(result.error || 'agent_execute failed');
|
|
315
|
+
step.result = result;
|
|
316
|
+
workflow.variables[`${step.stepId}.output`] = result.output;
|
|
317
|
+
workflow.variables.lastStepOutput = result.output;
|
|
318
|
+
stepEntry = { stepId: step.stepId, type: 'task', status: 'completed', durationMs: result.durationMs, output: result.output };
|
|
319
|
+
}
|
|
320
|
+
else if (step.type === 'wait') {
|
|
321
|
+
const cfg = step.config;
|
|
322
|
+
const ms = Math.min(Math.max(0, cfg.ms || 0), 60000);
|
|
323
|
+
await new Promise(r => setTimeout(r, ms));
|
|
324
|
+
step.result = { waitedMs: ms };
|
|
325
|
+
stepEntry = { stepId: step.stepId, type: 'wait', status: 'completed', durationMs: ms };
|
|
326
|
+
}
|
|
327
|
+
else if (step.type === 'condition') {
|
|
328
|
+
// Simple condition: config.when is a JS expression evaluated against workflow.variables.
|
|
329
|
+
// For safety, we only support `var === 'value'` or `var === number`.
|
|
330
|
+
const cfg = step.config;
|
|
331
|
+
const expr = String(cfg.when || 'true').trim();
|
|
332
|
+
const m = expr.match(/^([a-zA-Z_][\w]*)\s*===?\s*(['\"])?([^'\"]*)\2?$/);
|
|
333
|
+
let truthy = false;
|
|
334
|
+
if (m) {
|
|
335
|
+
const v = workflow.variables[m[1]];
|
|
336
|
+
const expected = m[2] ? m[3] : Number(m[3]);
|
|
337
|
+
truthy = v === expected;
|
|
338
|
+
}
|
|
339
|
+
else if (expr === 'true')
|
|
340
|
+
truthy = true;
|
|
341
|
+
step.result = { conditionExpr: expr, truthy };
|
|
342
|
+
// condition can declare a target step index to jump to via cfg.thenStep / cfg.elseStep
|
|
343
|
+
if (typeof cfg.thenStep === 'number' && truthy)
|
|
344
|
+
i = cfg.thenStep - 1;
|
|
345
|
+
if (typeof cfg.elseStep === 'number' && !truthy)
|
|
346
|
+
i = cfg.elseStep - 1;
|
|
347
|
+
stepEntry = { stepId: step.stepId, type: 'condition', status: 'completed' };
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// parallel/loop are deferred — mark skipped honestly rather than mock-completing.
|
|
351
|
+
step.result = { _note: `step type '${step.type}' not yet implemented in runtime` };
|
|
352
|
+
stepEntry = { stepId: step.stepId, type: step.type, status: 'skipped' };
|
|
353
|
+
}
|
|
354
|
+
step.status = stepEntry.status === 'skipped' ? 'skipped' : 'completed';
|
|
355
|
+
step.completedAt = new Date().toISOString();
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
359
|
+
step.status = 'failed';
|
|
360
|
+
step.result = { error: msg };
|
|
361
|
+
step.completedAt = new Date().toISOString();
|
|
362
|
+
stepEntry = { stepId: step.stepId, type: step.type, status: 'failed', durationMs: Date.now() - stepStart, error: msg };
|
|
363
|
+
stepResults.push(stepEntry);
|
|
364
|
+
workflow.status = 'failed';
|
|
365
|
+
workflow.error = msg;
|
|
366
|
+
workflow.completedAt = new Date().toISOString();
|
|
367
|
+
saveWorkflowStore(store);
|
|
368
|
+
return {
|
|
369
|
+
workflowId,
|
|
370
|
+
status: 'failed',
|
|
371
|
+
error: msg,
|
|
372
|
+
failedStep: step.stepId,
|
|
373
|
+
stepsCompleted: stepResults.filter(s => s.status === 'completed').length,
|
|
374
|
+
results: stepResults,
|
|
375
|
+
durationMs: Date.now() - startedAt,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (typeof stepEntry.durationMs !== 'number')
|
|
379
|
+
stepEntry.durationMs = Date.now() - stepStart;
|
|
380
|
+
stepResults.push(stepEntry);
|
|
381
|
+
workflow.currentStep = i + 1;
|
|
382
|
+
saveWorkflowStore(store);
|
|
383
|
+
i++;
|
|
384
|
+
}
|
|
385
|
+
if (workflow.status === 'running') {
|
|
386
|
+
workflow.status = 'completed';
|
|
387
|
+
workflow.completedAt = new Date().toISOString();
|
|
388
|
+
saveWorkflowStore(store);
|
|
260
389
|
}
|
|
261
|
-
saveWorkflowStore(store);
|
|
262
390
|
return {
|
|
263
391
|
workflowId,
|
|
264
392
|
status: workflow.status,
|
|
265
|
-
totalSteps:
|
|
266
|
-
|
|
393
|
+
totalSteps: workflow.steps.length,
|
|
394
|
+
stepsCompleted: stepResults.filter(s => s.status === 'completed').length,
|
|
395
|
+
stepsSkipped: stepResults.filter(s => s.status === 'skipped').length,
|
|
396
|
+
stepsFailed: stepResults.filter(s => s.status === 'failed').length,
|
|
397
|
+
results: stepResults,
|
|
267
398
|
startedAt: workflow.startedAt,
|
|
268
|
-
|
|
399
|
+
completedAt: workflow.completedAt,
|
|
400
|
+
durationMs: Date.now() - startedAt,
|
|
269
401
|
};
|
|
270
402
|
},
|
|
271
403
|
},
|
|
@@ -186,20 +186,108 @@ async function getRegistry(dbPath) {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
catch { /* router optional */ }
|
|
189
|
+
// ADR-095 G7: load disabled-by-default controllers via direct
|
|
190
|
+
// file:// URLs from the bundled agentdb. agentdb's exports
|
|
191
|
+
// field doesn't expose these subpaths and we can't reliably
|
|
192
|
+
// patch it across pnpm-hoisted multi-version trees, so we
|
|
193
|
+
// sidestep the exports field entirely and import the file
|
|
194
|
+
// by absolute URL. Only loads controllers whose constructor
|
|
195
|
+
// is safe with no special prerequisites — others remain off
|
|
196
|
+
// pending per-controller activation ADRs.
|
|
197
|
+
try {
|
|
198
|
+
const { createRequire } = await import('node:module');
|
|
199
|
+
const { pathToFileURL } = await import('node:url');
|
|
200
|
+
const path = await import('node:path');
|
|
201
|
+
const fs = await import('node:fs');
|
|
202
|
+
const cjsRequire = createRequire(import.meta.url);
|
|
203
|
+
let adbPkgJsonPath = null;
|
|
204
|
+
try {
|
|
205
|
+
adbPkgJsonPath = cjsRequire.resolve('agentdb/package.json');
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
adbPkgJsonPath = null;
|
|
209
|
+
}
|
|
210
|
+
if (adbPkgJsonPath) {
|
|
211
|
+
const adbDir = path.dirname(adbPkgJsonPath);
|
|
212
|
+
const candidates = [
|
|
213
|
+
// GNNService and RVFOptimizer can construct with no args
|
|
214
|
+
// in current agentdb — safe to activate as-is.
|
|
215
|
+
{ name: 'gnnService', relPath: 'dist/src/services/GNNService.js', configurable: false },
|
|
216
|
+
{ name: 'rvfOptimizer', relPath: 'dist/src/optimizations/RVFOptimizer.js', configurable: false },
|
|
217
|
+
// ADR-095 G7 follow-up: MutationGuard constructs cleanly
|
|
218
|
+
// with no args and exposes WASM-backed proof generation.
|
|
219
|
+
// No external deps; safe-default activation.
|
|
220
|
+
{ name: 'mutationGuard', relPath: 'dist/src/security/MutationGuard.js', configurable: false },
|
|
221
|
+
// AttestationLog needs a sqlite db handle — wired below
|
|
222
|
+
// separately because we have to construct a db too.
|
|
223
|
+
// GuardedVectorBackend needs key material — leave for
|
|
224
|
+
// follow-up ADR.
|
|
225
|
+
];
|
|
226
|
+
for (const cand of candidates) {
|
|
227
|
+
if (reg.get(cand.name))
|
|
228
|
+
continue;
|
|
229
|
+
const abs = path.join(adbDir, cand.relPath);
|
|
230
|
+
if (!fs.existsSync(abs))
|
|
231
|
+
continue;
|
|
232
|
+
try {
|
|
233
|
+
const url = pathToFileURL(abs).href;
|
|
234
|
+
const mod = await import(url);
|
|
235
|
+
// Look for a default export, named export matching the
|
|
236
|
+
// file basename, or any class-typed export.
|
|
237
|
+
const baseName = path.basename(cand.relPath, '.js');
|
|
238
|
+
const Ctor = (mod[baseName] || mod.default ||
|
|
239
|
+
Object.values(mod).find(v => typeof v === 'function'));
|
|
240
|
+
if (typeof Ctor !== 'function')
|
|
241
|
+
continue;
|
|
242
|
+
const inst = new Ctor();
|
|
243
|
+
if (typeof reg.set === 'function')
|
|
244
|
+
reg.set(cand.name, inst);
|
|
245
|
+
else
|
|
246
|
+
reg._controllers = { ...(reg._controllers || {}), [cand.name]: inst };
|
|
247
|
+
}
|
|
248
|
+
catch { /* skip controllers that fail to construct */ }
|
|
249
|
+
}
|
|
250
|
+
// AttestationLog activation — needs a better-sqlite3
|
|
251
|
+
// database. We open a dedicated file at .swarm/attestation.db
|
|
252
|
+
// (separate from the main memory.db so the audit trail
|
|
253
|
+
// is isolated). Best-effort: if better-sqlite3 isn't
|
|
254
|
+
// resolvable in this env, skip cleanly.
|
|
255
|
+
if (!reg.get('attestationLog')) {
|
|
256
|
+
try {
|
|
257
|
+
const attestationFile = path.join(adbDir, 'dist/src/security/AttestationLog.js');
|
|
258
|
+
if (fs.existsSync(attestationFile)) {
|
|
259
|
+
const Database = cjsRequire('better-sqlite3');
|
|
260
|
+
const swarmDir = path.resolve(process.cwd(), '.swarm');
|
|
261
|
+
if (!fs.existsSync(swarmDir))
|
|
262
|
+
fs.mkdirSync(swarmDir, { recursive: true });
|
|
263
|
+
const dbPath = path.join(swarmDir, 'attestation.db');
|
|
264
|
+
const db = new Database(dbPath);
|
|
265
|
+
const url = pathToFileURL(attestationFile).href;
|
|
266
|
+
const mod = await import(url);
|
|
267
|
+
const Ctor = mod.AttestationLog;
|
|
268
|
+
if (typeof Ctor === 'function') {
|
|
269
|
+
const inst = new Ctor({ db });
|
|
270
|
+
if (typeof reg.set === 'function')
|
|
271
|
+
reg.set('attestationLog', inst);
|
|
272
|
+
else
|
|
273
|
+
reg._controllers = { ...(reg._controllers || {}), attestationLog: inst };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch { /* better-sqlite3 missing or schema init failed — skip silently */ }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch { /* G7 wiring optional */ }
|
|
189
282
|
})();
|
|
190
283
|
// Run both in parallel; settle either way so a single failing
|
|
191
284
|
// path doesn't tear down the rest of the post-init wiring.
|
|
192
285
|
await Promise.allSettled([intelligencePromise, agentdbPromise]);
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
// -
|
|
197
|
-
//
|
|
198
|
-
// - gnnService (graph neural net — heavy deps, needs WASM/CUDA)
|
|
199
|
-
// - guardedVectorBackend (secured vector backend variant —
|
|
200
|
-
// needs key material)
|
|
201
|
-
// - rvfOptimizer (RVF format optimizer — needs RVF storage)
|
|
202
|
-
// - graphAdapter (graph DB adapter — needs graph DB)
|
|
286
|
+
// Remaining disabled controllers tracked in ADR-095 G7 for
|
|
287
|
+
// per-controller activation ADRs (each needs config / key
|
|
288
|
+
// material that we don't pass blindly):
|
|
289
|
+
// - guardedVectorBackend (secured backend — needs key material)
|
|
290
|
+
// - graphAdapter (graph DB adapter — needs graph DB connection)
|
|
203
291
|
}
|
|
204
292
|
catch {
|
|
205
293
|
// Top-level catch — registry stays usable even if post-init wiring fails wholesale.
|
|
@@ -84,7 +84,18 @@ export declare function initAgentWasm(): Promise<void>;
|
|
|
84
84
|
*/
|
|
85
85
|
export declare function createWasmAgent(config?: WasmAgentConfig): Promise<WasmAgentInfo>;
|
|
86
86
|
/**
|
|
87
|
-
* Send a prompt to a WASM agent.
|
|
87
|
+
* Send a prompt to a WASM agent.
|
|
88
|
+
*
|
|
89
|
+
* ADR-095 G4: the bundled @ruvector/rvagent-wasm doesn't actually run an
|
|
90
|
+
* LLM — its prompt() method echoes input back as `"echo: <input>"`. We
|
|
91
|
+
* detect that stub output and route the prompt through Anthropic's
|
|
92
|
+
* Messages API so users get a real response. The WASM agent's sandbox
|
|
93
|
+
* (virtual filesystem, tool execution) still works for non-LLM ops via
|
|
94
|
+
* executeWasmTool — we're just patching the "talk to a model" hole.
|
|
95
|
+
*
|
|
96
|
+
* If ANTHROPIC_API_KEY is not set, returns the stub output verbatim so
|
|
97
|
+
* the failure mode is obvious to the caller (matches the previous
|
|
98
|
+
* behaviour rather than throwing for users without keys configured).
|
|
88
99
|
*/
|
|
89
100
|
export declare function promptWasmAgent(agentId: string, input: string): Promise<string>;
|
|
90
101
|
export declare function executeWasmTool(agentId: string, toolCall: Record<string, unknown>): Promise<ToolResult>;
|
|
@@ -80,7 +80,18 @@ export async function createWasmAgent(config = {}) {
|
|
|
80
80
|
return info;
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
|
-
* Send a prompt to a WASM agent.
|
|
83
|
+
* Send a prompt to a WASM agent.
|
|
84
|
+
*
|
|
85
|
+
* ADR-095 G4: the bundled @ruvector/rvagent-wasm doesn't actually run an
|
|
86
|
+
* LLM — its prompt() method echoes input back as `"echo: <input>"`. We
|
|
87
|
+
* detect that stub output and route the prompt through Anthropic's
|
|
88
|
+
* Messages API so users get a real response. The WASM agent's sandbox
|
|
89
|
+
* (virtual filesystem, tool execution) still works for non-LLM ops via
|
|
90
|
+
* executeWasmTool — we're just patching the "talk to a model" hole.
|
|
91
|
+
*
|
|
92
|
+
* If ANTHROPIC_API_KEY is not set, returns the stub output verbatim so
|
|
93
|
+
* the failure mode is obvious to the caller (matches the previous
|
|
94
|
+
* behaviour rather than throwing for users without keys configured).
|
|
84
95
|
*/
|
|
85
96
|
export async function promptWasmAgent(agentId, input) {
|
|
86
97
|
const entry = agents.get(agentId);
|
|
@@ -88,10 +99,34 @@ export async function promptWasmAgent(agentId, input) {
|
|
|
88
99
|
throw new Error(`WASM agent not found: ${agentId}`);
|
|
89
100
|
entry.info.state = 'running';
|
|
90
101
|
try {
|
|
91
|
-
const
|
|
102
|
+
const wasmResult = await entry.agent.prompt(input);
|
|
92
103
|
entry.info.state = 'idle';
|
|
93
104
|
syncAgentInfo(entry);
|
|
94
|
-
|
|
105
|
+
// Detect the WASM echo stub.
|
|
106
|
+
const isEchoStub = typeof wasmResult === 'string' &&
|
|
107
|
+
(wasmResult === `echo: ${input}` || /^echo: /.test(wasmResult.slice(0, 12)));
|
|
108
|
+
if (!isEchoStub) {
|
|
109
|
+
return wasmResult;
|
|
110
|
+
}
|
|
111
|
+
// Echo stub detected — route through a real LLM call.
|
|
112
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
113
|
+
// No key configured; surface the stub honestly with a hint.
|
|
114
|
+
return `${wasmResult}\n[NOTE: bundled WASM agent has no LLM; set ANTHROPIC_API_KEY to enable real responses via Anthropic Messages API]`;
|
|
115
|
+
}
|
|
116
|
+
const { callAnthropicMessages, resolveAnthropicModel } = await import('../mcp-tools/agent-execute-core.js');
|
|
117
|
+
const model = resolveAnthropicModel(entry.info.config.model);
|
|
118
|
+
const systemPrompt = entry.info.config.instructions || 'You are a helpful coding assistant running in a Ruflo WASM agent sandbox.';
|
|
119
|
+
const result = await callAnthropicMessages({
|
|
120
|
+
prompt: input,
|
|
121
|
+
systemPrompt,
|
|
122
|
+
model,
|
|
123
|
+
maxTokens: 2048,
|
|
124
|
+
});
|
|
125
|
+
if (!result.success) {
|
|
126
|
+
return `${wasmResult}\n[NOTE: bundled WASM agent has no LLM; Anthropic fallback failed: ${result.error}]`;
|
|
127
|
+
}
|
|
128
|
+
// Return the real LLM output, not the echo stub.
|
|
129
|
+
return result.output ?? '';
|
|
95
130
|
}
|
|
96
131
|
catch (err) {
|
|
97
132
|
entry.info.state = 'error';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.23",
|
|
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
|
"files": [
|
|
77
77
|
"dist",
|
|
78
78
|
"bin",
|
|
79
|
+
"scripts",
|
|
79
80
|
".claude",
|
|
80
81
|
"README.md"
|
|
81
82
|
],
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
"test": "vitest run",
|
|
85
86
|
"test:plugin-store": "npx tsx src/plugins/tests/standalone-test.ts",
|
|
86
87
|
"test:pattern-store": "npx tsx src/transfer/store/tests/standalone-test.ts",
|
|
87
|
-
"postinstall": "node
|
|
88
|
+
"postinstall": "node ./scripts/postinstall.cjs",
|
|
88
89
|
"prepublishOnly": "cp ../../../README.md ./README.md",
|
|
89
90
|
"release": "npm version prerelease --preid=alpha && npm run publish:all",
|
|
90
91
|
"publish:all": "./scripts/publish.sh"
|