claude-flow 3.6.25 → 3.6.27
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/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +90 -2
- package/v3/@claude-flow/cli/dist/src/commands/providers.js +11 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +111 -4
- package/v3/@claude-flow/cli/package.json +1 -1
- package/.claude/scheduled_tasks.lock +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.27",
|
|
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",
|
|
@@ -8,8 +8,11 @@ import { output } from '../output.js';
|
|
|
8
8
|
import { existsSync, readFileSync, statSync } from 'fs';
|
|
9
9
|
import { join, dirname } from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
+
import { createHash } from 'crypto';
|
|
11
12
|
import { execSync, exec } from 'child_process';
|
|
12
13
|
import { promisify } from 'util';
|
|
14
|
+
import { decodeKey, isEncryptionEnabled } from '../encryption/vault.js';
|
|
15
|
+
import { isEncryptedBlob } from '../encryption/vault.js';
|
|
13
16
|
// Promisified exec with proper shell and env inheritance for cross-platform support
|
|
14
17
|
const execAsync = promisify(exec);
|
|
15
18
|
/**
|
|
@@ -423,6 +426,89 @@ async function checkAgenticFlow() {
|
|
|
423
426
|
return { name: 'agentic-flow', status: 'warn', message: 'Check failed' };
|
|
424
427
|
}
|
|
425
428
|
}
|
|
429
|
+
// Check encryption-at-rest status (ADR-096 Phase 5)
|
|
430
|
+
//
|
|
431
|
+
// Reports four facets without disclosing the key itself:
|
|
432
|
+
// 1. Gate status — is CLAUDE_FLOW_ENCRYPT_AT_REST set?
|
|
433
|
+
// 2. Key resolution — does CLAUDE_FLOW_ENCRYPTION_KEY resolve to a valid
|
|
434
|
+
// 32-byte key (env-var path only; keychain/passphrase are deferred)?
|
|
435
|
+
// 3. Key fingerprint — first 16 hex chars of sha256(key) so users can
|
|
436
|
+
// sanity-check across machines without ever logging the key bytes.
|
|
437
|
+
// 4. High-tier store presence — for sessions/, terminals/, .swarm/memory.db
|
|
438
|
+
// report whether on-disk bytes carry the RFE1 magic (encrypted) or not.
|
|
439
|
+
async function checkEncryptionAtRest() {
|
|
440
|
+
if (!isEncryptionEnabled()) {
|
|
441
|
+
return {
|
|
442
|
+
name: 'Encryption at Rest',
|
|
443
|
+
status: 'warn',
|
|
444
|
+
message: 'Off — session/terminal/memory stores are plaintext (mode 0600 only)',
|
|
445
|
+
fix: 'export CLAUDE_FLOW_ENCRYPT_AT_REST=1 && export CLAUDE_FLOW_ENCRYPTION_KEY=<64-char-hex>',
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
// Gate is on — try to resolve the key. Fail-closed if missing or malformed.
|
|
449
|
+
const rawKey = process.env.CLAUDE_FLOW_ENCRYPTION_KEY;
|
|
450
|
+
if (!rawKey) {
|
|
451
|
+
return {
|
|
452
|
+
name: 'Encryption at Rest',
|
|
453
|
+
status: 'fail',
|
|
454
|
+
message: 'Gate is on but CLAUDE_FLOW_ENCRYPTION_KEY is unset (fail-closed)',
|
|
455
|
+
fix: 'Generate a key: openssl rand -hex 32 → export CLAUDE_FLOW_ENCRYPTION_KEY=<value>',
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
let keyFingerprint;
|
|
459
|
+
try {
|
|
460
|
+
const key = decodeKey(rawKey);
|
|
461
|
+
keyFingerprint = createHash('sha256').update(key).digest('hex').slice(0, 16);
|
|
462
|
+
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
return {
|
|
465
|
+
name: 'Encryption at Rest',
|
|
466
|
+
status: 'fail',
|
|
467
|
+
message: `CLAUDE_FLOW_ENCRYPTION_KEY invalid: ${err instanceof Error ? err.message : String(err)}`,
|
|
468
|
+
fix: 'Provide a 64-char hex or 44-char base64 key (32 bytes)',
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
// Check the three high-tier store paths for RFE1 magic
|
|
472
|
+
const cwd = process.cwd();
|
|
473
|
+
const stores = [
|
|
474
|
+
{ label: 'sessions/', path: join(cwd, '.claude-flow', 'sessions') },
|
|
475
|
+
{ label: 'terminals', path: join(cwd, '.claude-flow', 'terminals', 'store.json') },
|
|
476
|
+
{ label: 'memory.db', path: join(cwd, '.swarm', 'memory.db') },
|
|
477
|
+
];
|
|
478
|
+
const status = [];
|
|
479
|
+
for (const s of stores) {
|
|
480
|
+
if (!existsSync(s.path)) {
|
|
481
|
+
status.push(`${s.label}=∅`);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
const stat = statSync(s.path);
|
|
486
|
+
if (stat.isDirectory()) {
|
|
487
|
+
// Sessions: probe the first .json file
|
|
488
|
+
const { readdirSync } = await import('fs');
|
|
489
|
+
const files = readdirSync(s.path).filter(f => f.endsWith('.json'));
|
|
490
|
+
if (files.length === 0) {
|
|
491
|
+
status.push(`${s.label}=∅`);
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const first = readFileSync(join(s.path, files[0]));
|
|
495
|
+
status.push(`${s.label}=${isEncryptedBlob(first) ? 'enc' : 'plain'}`);
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
const buf = readFileSync(s.path);
|
|
499
|
+
status.push(`${s.label}=${isEncryptedBlob(buf) ? 'enc' : 'plain'}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
status.push(`${s.label}=err`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
name: 'Encryption at Rest',
|
|
508
|
+
status: 'pass',
|
|
509
|
+
message: `On — key fp:${keyFingerprint}… (${status.join(' ')})`,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
426
512
|
// Format health check result
|
|
427
513
|
function formatCheck(check) {
|
|
428
514
|
const icon = check.status === 'pass' ? output.success('✓') :
|
|
@@ -494,7 +580,8 @@ export const doctorCommand = {
|
|
|
494
580
|
checkMcpServers,
|
|
495
581
|
checkDiskSpace,
|
|
496
582
|
checkBuildTools,
|
|
497
|
-
checkAgenticFlow
|
|
583
|
+
checkAgenticFlow,
|
|
584
|
+
checkEncryptionAtRest, // ADR-096 Phase 5
|
|
498
585
|
];
|
|
499
586
|
const componentMap = {
|
|
500
587
|
'version': checkVersionFreshness,
|
|
@@ -510,7 +597,8 @@ export const doctorCommand = {
|
|
|
510
597
|
'mcp': checkMcpServers,
|
|
511
598
|
'disk': checkDiskSpace,
|
|
512
599
|
'typescript': checkBuildTools,
|
|
513
|
-
'agentic-flow': checkAgenticFlow
|
|
600
|
+
'agentic-flow': checkAgenticFlow,
|
|
601
|
+
'encryption': checkEncryptionAtRest, // ADR-096 Phase 5
|
|
514
602
|
};
|
|
515
603
|
let checksToRun = allChecks;
|
|
516
604
|
if (component && componentMap[component]) {
|
|
@@ -11,6 +11,9 @@ const PROVIDER_CATALOG = [
|
|
|
11
11
|
{ name: 'OpenAI', type: 'LLM', models: 'gpt-4o, gpt-4-turbo', envVar: 'OPENAI_API_KEY', configName: 'openai' },
|
|
12
12
|
{ name: 'OpenAI', type: 'Embedding', models: 'text-embedding-3-small/large', envVar: 'OPENAI_API_KEY', configName: 'openai' },
|
|
13
13
|
{ name: 'Google', type: 'LLM', models: 'gemini-pro, gemini-ultra', envVar: 'GOOGLE_API_KEY', configName: 'google' },
|
|
14
|
+
// #1725: Ollama Cloud — Tier-2 default per ADR-026 (~$100/mo flat-rate alternative
|
|
15
|
+
// to per-token pricing). OpenAI-compat API at https://ollama.com/v1/chat/completions.
|
|
16
|
+
{ name: 'Ollama', type: 'LLM', models: 'gpt-oss:120b-cloud, llama3:70b-cloud, qwen2.5-coder:32b-cloud', envVar: 'OLLAMA_API_KEY', configName: 'ollama' },
|
|
14
17
|
{ name: 'Transformers.js', type: 'Embedding', models: 'Xenova/all-MiniLM-L6-v2' },
|
|
15
18
|
{ name: 'Agentic Flow', type: 'Embedding', models: 'ONNX optimized' },
|
|
16
19
|
{ name: 'Mock', type: 'All', models: 'mock-*' },
|
|
@@ -30,6 +33,7 @@ function resolveApiKey(providerName, configuredProviders) {
|
|
|
30
33
|
anthropic: 'ANTHROPIC_API_KEY',
|
|
31
34
|
openai: 'OPENAI_API_KEY',
|
|
32
35
|
google: 'GOOGLE_API_KEY',
|
|
36
|
+
ollama: 'OLLAMA_API_KEY', // #1725 — Tier-2 routing
|
|
33
37
|
};
|
|
34
38
|
const envVar = envMapping[providerName.toLowerCase()];
|
|
35
39
|
if (envVar && process.env[envVar]) {
|
|
@@ -60,6 +64,13 @@ async function testProviderConnectivity(providerName, apiKey) {
|
|
|
60
64
|
url: `https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`,
|
|
61
65
|
headers: {},
|
|
62
66
|
},
|
|
67
|
+
// #1725 — Ollama Cloud uses an OpenAI-compatible /v1 surface.
|
|
68
|
+
ollama: {
|
|
69
|
+
url: 'https://ollama.com/api/tags',
|
|
70
|
+
headers: {
|
|
71
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
63
74
|
};
|
|
64
75
|
const endpointConfig = endpoints[providerName.toLowerCase()];
|
|
65
76
|
if (!endpointConfig) {
|
|
@@ -47,6 +47,12 @@ export interface AnthropicCallResult {
|
|
|
47
47
|
* Generic Anthropic Messages API call. No agent registry coupling — used
|
|
48
48
|
* by agent_execute (with the agent's configured model) and by the WASM
|
|
49
49
|
* agent runtime (G4) when the bundled WASM only echoes input.
|
|
50
|
+
*
|
|
51
|
+
* #1725 — falls back to Ollama Cloud (Tier-2, OpenAI-compat) when
|
|
52
|
+
* ANTHROPIC_API_KEY is unset and OLLAMA_API_KEY is present, or when
|
|
53
|
+
* RUFLO_PROVIDER=ollama is explicitly set. Response shape is normalized
|
|
54
|
+
* to the Anthropic-flavored AnthropicCallResult so existing callers
|
|
55
|
+
* don't need to know which provider answered.
|
|
50
56
|
*/
|
|
51
57
|
export declare function callAnthropicMessages(input: AnthropicCallInput): Promise<AnthropicCallResult>;
|
|
52
58
|
/**
|
|
@@ -42,11 +42,26 @@ const MODEL_MAP = {
|
|
|
42
42
|
* Generic Anthropic Messages API call. No agent registry coupling — used
|
|
43
43
|
* by agent_execute (with the agent's configured model) and by the WASM
|
|
44
44
|
* agent runtime (G4) when the bundled WASM only echoes input.
|
|
45
|
+
*
|
|
46
|
+
* #1725 — falls back to Ollama Cloud (Tier-2, OpenAI-compat) when
|
|
47
|
+
* ANTHROPIC_API_KEY is unset and OLLAMA_API_KEY is present, or when
|
|
48
|
+
* RUFLO_PROVIDER=ollama is explicitly set. Response shape is normalized
|
|
49
|
+
* to the Anthropic-flavored AnthropicCallResult so existing callers
|
|
50
|
+
* don't need to know which provider answered.
|
|
45
51
|
*/
|
|
46
52
|
export async function callAnthropicMessages(input) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
const explicitProvider = (process.env.RUFLO_PROVIDER || '').toLowerCase();
|
|
54
|
+
const ollamaKey = process.env.OLLAMA_API_KEY;
|
|
55
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
56
|
+
const useOllama = explicitProvider === 'ollama' || (!anthropicKey && !!ollamaKey);
|
|
57
|
+
if (useOllama && ollamaKey) {
|
|
58
|
+
return callOllamaCompat({ ...input, apiKey: ollamaKey });
|
|
59
|
+
}
|
|
60
|
+
if (!anthropicKey) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: 'No LLM provider configured. Set ANTHROPIC_API_KEY (Tier-3) or OLLAMA_API_KEY (Tier-2 Ollama Cloud — see issue #1725).',
|
|
64
|
+
};
|
|
50
65
|
}
|
|
51
66
|
const model = input.model || 'claude-3-5-sonnet-latest';
|
|
52
67
|
const startedAt = Date.now();
|
|
@@ -56,7 +71,7 @@ export async function callAnthropicMessages(input) {
|
|
|
56
71
|
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
57
72
|
method: 'POST',
|
|
58
73
|
headers: {
|
|
59
|
-
'x-api-key':
|
|
74
|
+
'x-api-key': anthropicKey,
|
|
60
75
|
'anthropic-version': '2023-06-01',
|
|
61
76
|
'content-type': 'application/json',
|
|
62
77
|
},
|
|
@@ -102,6 +117,98 @@ export async function callAnthropicMessages(input) {
|
|
|
102
117
|
};
|
|
103
118
|
}
|
|
104
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Ollama Cloud / OpenAI-compat provider — Tier-2 routing per ADR-026 + #1725.
|
|
122
|
+
*
|
|
123
|
+
* Endpoint: https://ollama.com/v1/chat/completions
|
|
124
|
+
* Auth: Authorization: Bearer <OLLAMA_API_KEY>
|
|
125
|
+
*
|
|
126
|
+
* Translates the Anthropic-flavored input shape onto OpenAI chat-completions
|
|
127
|
+
* and translates the response back so callers never see provider-specific
|
|
128
|
+
* fields. Logical model names are mapped to Ollama Cloud defaults:
|
|
129
|
+
* - 'haiku' / 'sonnet' → 'gpt-oss:120b-cloud' (sensible single default)
|
|
130
|
+
* - 'opus' → 'gpt-oss:120b-cloud' (no opus tier on Ollama)
|
|
131
|
+
* - explicit 'ollama:<model>' or bare provider-native name → passed through
|
|
132
|
+
*/
|
|
133
|
+
async function callOllamaCompat(input) {
|
|
134
|
+
const model = resolveOllamaModel(input.model);
|
|
135
|
+
const startedAt = Date.now();
|
|
136
|
+
// OLLAMA_BASE_URL lets users point at local/self-hosted endpoints
|
|
137
|
+
// (e.g. http://ruvultra:11434, http://localhost:11434) instead of
|
|
138
|
+
// Ollama Cloud. Default is the public cloud endpoint.
|
|
139
|
+
const base = (process.env.OLLAMA_BASE_URL || 'https://ollama.com').replace(/\/+$/, '');
|
|
140
|
+
const url = `${base}/v1/chat/completions`;
|
|
141
|
+
// Self-hosted endpoints typically don't need an Authorization header
|
|
142
|
+
// (the daemon binds to 11434 with no auth by default), but Ollama Cloud
|
|
143
|
+
// does. Send the bearer when the key is non-empty AND looks cloud-shaped.
|
|
144
|
+
const sendAuth = input.apiKey && input.apiKey !== 'local';
|
|
145
|
+
try {
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
const timer = setTimeout(() => controller.abort(), input.timeoutMs || 60000);
|
|
148
|
+
const res = await fetch(url, {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: {
|
|
151
|
+
...(sendAuth ? { Authorization: `Bearer ${input.apiKey}` } : {}),
|
|
152
|
+
'content-type': 'application/json',
|
|
153
|
+
},
|
|
154
|
+
body: JSON.stringify({
|
|
155
|
+
model,
|
|
156
|
+
max_tokens: input.maxTokens || 1024,
|
|
157
|
+
temperature: typeof input.temperature === 'number' ? input.temperature : 0.7,
|
|
158
|
+
messages: [
|
|
159
|
+
...(input.systemPrompt
|
|
160
|
+
? [{ role: 'system', content: input.systemPrompt }]
|
|
161
|
+
: []),
|
|
162
|
+
{ role: 'user', content: input.prompt },
|
|
163
|
+
],
|
|
164
|
+
}),
|
|
165
|
+
signal: controller.signal,
|
|
166
|
+
});
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
const errText = await res.text().catch(() => '<unreadable error body>');
|
|
170
|
+
return { success: false, model, error: `Ollama API error ${res.status} at ${url}: ${errText.slice(0, 400)}` };
|
|
171
|
+
}
|
|
172
|
+
const data = (await res.json());
|
|
173
|
+
const textOut = data.choices?.[0]?.message?.content ?? '';
|
|
174
|
+
const usage = data.usage ?? {};
|
|
175
|
+
return {
|
|
176
|
+
success: true,
|
|
177
|
+
model: data.model ?? model,
|
|
178
|
+
messageId: data.id ?? `ollama-${Date.now()}`,
|
|
179
|
+
stopReason: data.choices?.[0]?.finish_reason ?? 'end_turn',
|
|
180
|
+
output: textOut,
|
|
181
|
+
usage: {
|
|
182
|
+
inputTokens: usage.prompt_tokens ?? 0,
|
|
183
|
+
outputTokens: usage.completion_tokens ?? 0,
|
|
184
|
+
totalTokens: usage.total_tokens ?? 0,
|
|
185
|
+
},
|
|
186
|
+
durationMs: Date.now() - startedAt,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
model,
|
|
193
|
+
error: err instanceof Error ? err.message : String(err),
|
|
194
|
+
durationMs: Date.now() - startedAt,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function resolveOllamaModel(input) {
|
|
199
|
+
const DEFAULT = 'gpt-oss:120b-cloud';
|
|
200
|
+
if (!input)
|
|
201
|
+
return DEFAULT;
|
|
202
|
+
// Logical → cloud default
|
|
203
|
+
if (input === 'haiku' || input === 'sonnet' || input === 'opus' || input === 'inherit') {
|
|
204
|
+
return DEFAULT;
|
|
205
|
+
}
|
|
206
|
+
// Explicit provider prefix
|
|
207
|
+
if (input.startsWith('ollama:'))
|
|
208
|
+
return input.slice('ollama:'.length);
|
|
209
|
+
// Bare name with cloud suffix (e.g. 'llama3:70b-cloud') passes through
|
|
210
|
+
return input;
|
|
211
|
+
}
|
|
105
212
|
/**
|
|
106
213
|
* Resolve a model identifier to an Anthropic model ID. Accepts:
|
|
107
214
|
* - logical names: 'haiku', 'sonnet', 'opus', 'inherit'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.27",
|
|
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",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"sessionId":"36428a63-dfb2-42a4-a159-cf8be916193e","pid":71027,"procStart":"Sun May 3 23:00:23 2026","acquiredAt":1777849234814}
|