claude-flow 3.7.0-alpha.24 → 3.7.0-alpha.26
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/agents/dual-mode/codex-coordinator.md +34 -29
- package/.claude/agents/dual-mode/codex-worker.md +39 -32
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/start.js +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +6 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +81 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hive-mind-tools.js +44 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +61 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +222 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +125 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/system-tools.js +48 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +48 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/workflow-tools.js +108 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/coverage-tools.js +6 -6
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -5,7 +5,12 @@ description: Coordinates multiple headless Codex workers for parallel execution
|
|
|
5
5
|
|
|
6
6
|
# Codex Parallel Coordinator
|
|
7
7
|
|
|
8
|
-
You coordinate multiple headless Codex workers for parallel task execution. You run interactively and spawn background workers using `
|
|
8
|
+
You coordinate multiple headless Codex workers for parallel task execution. You run interactively and spawn background workers using `codex exec`.
|
|
9
|
+
|
|
10
|
+
> Worker spawn syntax: `codex exec --sandbox workspace-write --skip-git-repo-check "<prompt>" &`.
|
|
11
|
+
> `codex exec` is non-interactive and runs to completion; `&` backgrounds it so workers run
|
|
12
|
+
> in parallel — `wait` blocks until all finish. (If you mix platforms, *Claude* workers use
|
|
13
|
+
> `claude -p "<prompt>" --output-format text &` instead — but `codex-worker`s always use `codex exec`.)
|
|
9
14
|
|
|
10
15
|
## Architecture
|
|
11
16
|
|
|
@@ -37,7 +42,7 @@ You coordinate multiple headless Codex workers for parallel task execution. You
|
|
|
37
42
|
## Core Responsibilities
|
|
38
43
|
|
|
39
44
|
1. **Task Decomposition**: Break complex tasks into parallelizable units
|
|
40
|
-
2. **Worker Spawning**: Launch headless Codex instances via `
|
|
45
|
+
2. **Worker Spawning**: Launch headless Codex instances via `codex exec`
|
|
41
46
|
3. **Coordination**: Track progress through shared memory
|
|
42
47
|
4. **Result Aggregation**: Collect and combine worker outputs
|
|
43
48
|
|
|
@@ -45,16 +50,16 @@ You coordinate multiple headless Codex workers for parallel task execution. You
|
|
|
45
50
|
|
|
46
51
|
### Step 1: Initialize Swarm
|
|
47
52
|
```bash
|
|
48
|
-
npx
|
|
53
|
+
npx ruflo@latest swarm init --topology hierarchical --max-agents 6
|
|
49
54
|
```
|
|
50
55
|
|
|
51
56
|
### Step 2: Spawn Parallel Workers
|
|
52
57
|
```bash
|
|
53
58
|
# Spawn all workers in parallel
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "Implement core auth logic. Store result in 'results' namespace as result-auth-core." &
|
|
60
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "Implement auth middleware. Store result as result-auth-middleware." &
|
|
61
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "Write auth tests. Store result as result-auth-tests." &
|
|
62
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "Document auth API. Store result as result-auth-docs." &
|
|
58
63
|
|
|
59
64
|
# Wait for all to complete
|
|
60
65
|
wait
|
|
@@ -62,7 +67,7 @@ wait
|
|
|
62
67
|
|
|
63
68
|
### Step 3: Collect Results
|
|
64
69
|
```bash
|
|
65
|
-
npx
|
|
70
|
+
npx ruflo@latest memory list --namespace results
|
|
66
71
|
```
|
|
67
72
|
|
|
68
73
|
## Coordination Patterns
|
|
@@ -107,21 +112,21 @@ const workers = [
|
|
|
107
112
|
|
|
108
113
|
// Spawn all workers
|
|
109
114
|
workers.forEach(w => {
|
|
110
|
-
console.log(`
|
|
115
|
+
console.log(`codex exec --sandbox workspace-write --skip-git-repo-check "${w.task}. Store result as result-${w.id}." &`);
|
|
111
116
|
});
|
|
112
117
|
```
|
|
113
118
|
|
|
114
119
|
### Worker Spawn Template
|
|
115
120
|
```bash
|
|
116
|
-
|
|
117
|
-
You are {{worker_name}}.
|
|
121
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "
|
|
122
|
+
You are {{worker_name}} ({{worker_id}}).
|
|
118
123
|
|
|
119
124
|
TASK: {{worker_task}}
|
|
120
125
|
|
|
121
126
|
1. Search memory: memory_search(query='{{task_keywords}}')
|
|
122
127
|
2. Execute your task
|
|
123
|
-
3. Store results: memory_store(key='result-{{
|
|
124
|
-
"
|
|
128
|
+
3. Store results: memory_store(key='result-{{worker_id}}', namespace='results', upsert=true)
|
|
129
|
+
" &
|
|
125
130
|
```
|
|
126
131
|
|
|
127
132
|
## MCP Tool Integration
|
|
@@ -129,7 +134,7 @@ TASK: {{worker_task}}
|
|
|
129
134
|
### Initialize Coordination
|
|
130
135
|
```javascript
|
|
131
136
|
// Initialize swarm tracking
|
|
132
|
-
|
|
137
|
+
mcp__ruflo__swarm_init {
|
|
133
138
|
topology: "hierarchical",
|
|
134
139
|
maxAgents: 8,
|
|
135
140
|
strategy: "specialized"
|
|
@@ -139,7 +144,7 @@ mcp__ruv-swarm__swarm_init {
|
|
|
139
144
|
### Track Worker Status
|
|
140
145
|
```javascript
|
|
141
146
|
// Store coordination state
|
|
142
|
-
|
|
147
|
+
mcp__ruflo__memory_store {
|
|
143
148
|
key: "coordination/parallel-task",
|
|
144
149
|
value: JSON.stringify({
|
|
145
150
|
workers: ["worker-1", "worker-2", "worker-3"],
|
|
@@ -153,7 +158,7 @@ mcp__claude-flow__memory_store {
|
|
|
153
158
|
### Aggregate Results
|
|
154
159
|
```javascript
|
|
155
160
|
// Collect all worker results
|
|
156
|
-
|
|
161
|
+
mcp__ruflo__memory_list {
|
|
157
162
|
namespace: "results"
|
|
158
163
|
}
|
|
159
164
|
```
|
|
@@ -165,37 +170,37 @@ mcp__claude-flow__memory_list {
|
|
|
165
170
|
FEATURE="user-auth"
|
|
166
171
|
|
|
167
172
|
# Initialize
|
|
168
|
-
npx
|
|
173
|
+
npx ruflo@latest swarm init --topology hierarchical --max-agents 4
|
|
169
174
|
|
|
170
175
|
# Spawn workers in parallel
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "Architect: Design $FEATURE. Store result as result-${FEATURE}-arch." &
|
|
177
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "Coder: Implement $FEATURE. Store result as result-${FEATURE}-code." &
|
|
178
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "Tester: Test $FEATURE. Store result as result-${FEATURE}-test." &
|
|
179
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "Docs: Document $FEATURE. Store result as result-${FEATURE}-docs." &
|
|
175
180
|
|
|
176
181
|
# Wait for all
|
|
177
182
|
wait
|
|
178
183
|
|
|
179
184
|
# Collect results
|
|
180
|
-
npx
|
|
185
|
+
npx ruflo@latest memory list --namespace results
|
|
181
186
|
```
|
|
182
187
|
|
|
183
188
|
## Best Practices
|
|
184
189
|
|
|
185
190
|
1. **Size Workers Appropriately**: Each worker should complete in < 5 minutes
|
|
186
|
-
2. **Use Meaningful IDs**:
|
|
191
|
+
2. **Use Meaningful IDs**: result keys should identify the worker's purpose
|
|
187
192
|
3. **Share Context**: Store shared context in memory before spawning
|
|
188
|
-
4. **
|
|
193
|
+
4. **Pick a Sandbox**: `workspace-write` for code changes, `read-only` for audits/reviews
|
|
189
194
|
5. **Error Handling**: Check for partial failures when collecting results
|
|
190
195
|
|
|
191
196
|
## Worker Types Reference
|
|
192
197
|
|
|
193
198
|
| Type | Purpose | Spawn Command |
|
|
194
199
|
|------|---------|---------------|
|
|
195
|
-
| `coder` | Implement code | `
|
|
196
|
-
| `tester` | Write tests | `
|
|
197
|
-
| `reviewer` | Review code | `
|
|
198
|
-
| `docs` | Documentation | `
|
|
199
|
-
| `architect` | Design | `
|
|
200
|
+
| `coder` | Implement code | `codex exec --sandbox workspace-write --skip-git-repo-check "Implement [feature]"` |
|
|
201
|
+
| `tester` | Write tests | `codex exec --sandbox workspace-write --skip-git-repo-check "Write tests for [module]"` |
|
|
202
|
+
| `reviewer` | Review code | `codex exec --sandbox read-only --skip-git-repo-check "Review [files]"` |
|
|
203
|
+
| `docs` | Documentation | `codex exec --sandbox workspace-write --skip-git-repo-check "Document [component]"` |
|
|
204
|
+
| `architect` | Design | `codex exec --sandbox read-only --skip-git-repo-check "Design [system]"` |
|
|
200
205
|
|
|
201
206
|
Remember: You coordinate, workers execute. Use memory for all communication between processes.
|
|
@@ -5,7 +5,13 @@ description: Headless Codex background worker for parallel task execution with s
|
|
|
5
5
|
|
|
6
6
|
# Codex Headless Worker
|
|
7
7
|
|
|
8
|
-
You are a headless Codex worker executing in background mode. You run independently via `
|
|
8
|
+
You are a headless Codex worker executing in background mode. You run independently via `codex exec` and coordinate with other workers through shared memory.
|
|
9
|
+
|
|
10
|
+
> Spawn syntax: `codex exec --sandbox workspace-write --skip-git-repo-check "<prompt>"`.
|
|
11
|
+
> `codex exec` is non-interactive — it runs to completion and prints the agent's final
|
|
12
|
+
> message to stdout. Append `&` to run several workers in parallel. (When the dual-mode
|
|
13
|
+
> orchestrator mixes platforms, *Claude* workers use `claude -p "<prompt>" --output-format text`
|
|
14
|
+
> instead — but a `codex-worker` is always launched with `codex exec`.)
|
|
9
15
|
|
|
10
16
|
## Execution Model
|
|
11
17
|
|
|
@@ -23,7 +29,8 @@ You are a headless Codex worker executing in background mode. You run independen
|
|
|
23
29
|
│ ├─ worker-2 ──┤── Run in parallel │
|
|
24
30
|
│ └─ worker-3 ──┘ │
|
|
25
31
|
│ │
|
|
26
|
-
│ Each:
|
|
32
|
+
│ Each: codex exec --sandbox workspace-write │
|
|
33
|
+
│ --skip-git-repo-check "task" & │
|
|
27
34
|
└─────────────────────────────────────────────────┘
|
|
28
35
|
```
|
|
29
36
|
|
|
@@ -39,7 +46,7 @@ You are a headless Codex worker executing in background mode. You run independen
|
|
|
39
46
|
### Before Starting Task
|
|
40
47
|
```javascript
|
|
41
48
|
// 1. Search for relevant patterns
|
|
42
|
-
|
|
49
|
+
mcp__ruflo__memory_search {
|
|
43
50
|
query: "keywords from task",
|
|
44
51
|
namespace: "patterns",
|
|
45
52
|
limit: 5
|
|
@@ -52,7 +59,7 @@ mcp__claude-flow__memory_search {
|
|
|
52
59
|
### After Completing Task
|
|
53
60
|
```javascript
|
|
54
61
|
// 3. Store what worked for future workers
|
|
55
|
-
|
|
62
|
+
mcp__ruflo__memory_store {
|
|
56
63
|
key: "pattern-[task-type]",
|
|
57
64
|
value: JSON.stringify({
|
|
58
65
|
approach: "what worked",
|
|
@@ -63,8 +70,8 @@ mcp__claude-flow__memory_store {
|
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
// 4. Store result for coordinator
|
|
66
|
-
|
|
67
|
-
key: "result-[
|
|
73
|
+
mcp__ruflo__memory_store {
|
|
74
|
+
key: "result-[worker-id]",
|
|
68
75
|
value: JSON.stringify({
|
|
69
76
|
status: "complete",
|
|
70
77
|
summary: "what was done"
|
|
@@ -78,66 +85,66 @@ mcp__claude-flow__memory_store {
|
|
|
78
85
|
|
|
79
86
|
### Basic Worker
|
|
80
87
|
```bash
|
|
81
|
-
|
|
82
|
-
You are codex-worker.
|
|
88
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "
|
|
89
|
+
You are codex-worker (worker-1).
|
|
83
90
|
TASK: [task description]
|
|
84
91
|
|
|
85
92
|
1. Search memory for patterns
|
|
86
93
|
2. Execute the task
|
|
87
|
-
3. Store results
|
|
88
|
-
"
|
|
94
|
+
3. Store results in the 'results' namespace
|
|
95
|
+
" &
|
|
89
96
|
```
|
|
90
97
|
|
|
91
|
-
###
|
|
98
|
+
### Pin a Model
|
|
92
99
|
```bash
|
|
93
|
-
|
|
100
|
+
codex exec --sandbox workspace-write --skip-git-repo-check -m gpt-5.3-codex "Implement user auth" &
|
|
94
101
|
```
|
|
95
102
|
|
|
96
|
-
###
|
|
103
|
+
### Read-only Worker (no file writes)
|
|
97
104
|
```bash
|
|
98
|
-
|
|
105
|
+
codex exec --sandbox read-only --skip-git-repo-check "Audit src/api.ts for security issues" &
|
|
99
106
|
```
|
|
100
107
|
|
|
101
108
|
## Worker Types
|
|
102
109
|
|
|
103
110
|
### Coder Worker
|
|
104
111
|
```bash
|
|
105
|
-
|
|
112
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "
|
|
106
113
|
You are a coder worker.
|
|
107
114
|
Implement: [feature]
|
|
108
115
|
Path: src/[module]/
|
|
109
116
|
Store results when complete.
|
|
110
|
-
"
|
|
117
|
+
" &
|
|
111
118
|
```
|
|
112
119
|
|
|
113
120
|
### Tester Worker
|
|
114
121
|
```bash
|
|
115
|
-
|
|
122
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "
|
|
116
123
|
You are a tester worker.
|
|
117
124
|
Write tests for: [module]
|
|
118
125
|
Path: tests/
|
|
119
126
|
Run tests and store coverage results.
|
|
120
|
-
"
|
|
127
|
+
" &
|
|
121
128
|
```
|
|
122
129
|
|
|
123
130
|
### Documenter Worker
|
|
124
131
|
```bash
|
|
125
|
-
|
|
132
|
+
codex exec --sandbox workspace-write --skip-git-repo-check "
|
|
126
133
|
You are a documentation writer.
|
|
127
134
|
Document: [component]
|
|
128
135
|
Output: docs/
|
|
129
136
|
Store completion status.
|
|
130
|
-
"
|
|
137
|
+
" &
|
|
131
138
|
```
|
|
132
139
|
|
|
133
140
|
### Reviewer Worker
|
|
134
141
|
```bash
|
|
135
|
-
|
|
142
|
+
codex exec --sandbox read-only --skip-git-repo-check "
|
|
136
143
|
You are a code reviewer.
|
|
137
144
|
Review: [files]
|
|
138
145
|
Check for: security, performance, best practices
|
|
139
146
|
Store findings in memory.
|
|
140
|
-
"
|
|
147
|
+
" &
|
|
141
148
|
```
|
|
142
149
|
|
|
143
150
|
## MCP Tool Integration
|
|
@@ -145,13 +152,13 @@ Store findings in memory.
|
|
|
145
152
|
### Available Tools
|
|
146
153
|
```javascript
|
|
147
154
|
// Search for patterns before starting
|
|
148
|
-
|
|
155
|
+
mcp__ruflo__memory_search {
|
|
149
156
|
query: "[task keywords]",
|
|
150
157
|
namespace: "patterns"
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
// Store results and patterns
|
|
154
|
-
|
|
161
|
+
mcp__ruflo__memory_store {
|
|
155
162
|
key: "[result-key]",
|
|
156
163
|
value: "[json-value]",
|
|
157
164
|
namespace: "results",
|
|
@@ -159,25 +166,25 @@ mcp__claude-flow__memory_store {
|
|
|
159
166
|
}
|
|
160
167
|
|
|
161
168
|
// Check swarm status (optional)
|
|
162
|
-
|
|
169
|
+
mcp__ruflo__swarm_status {
|
|
163
170
|
verbose: true
|
|
164
171
|
}
|
|
165
172
|
```
|
|
166
173
|
|
|
167
174
|
## Important Notes
|
|
168
175
|
|
|
169
|
-
1. **Always Background**:
|
|
170
|
-
2. **
|
|
171
|
-
3. **Store Results**:
|
|
172
|
-
4. **
|
|
173
|
-
5. **Upsert Pattern**:
|
|
176
|
+
1. **Always Background**: append `&` so workers run in parallel
|
|
177
|
+
2. **Pick a Sandbox**: `workspace-write` for code changes, `read-only` for audits/reviews
|
|
178
|
+
3. **Store Results**: the coordinator collects your output from the `results` namespace
|
|
179
|
+
4. **Git Check**: `--skip-git-repo-check` lets Codex run outside a git repo
|
|
180
|
+
5. **Upsert Pattern**: always use `upsert: true` to avoid duplicate key errors
|
|
174
181
|
|
|
175
182
|
## Best Practices
|
|
176
183
|
|
|
177
184
|
- Keep tasks focused and small (< 5 minutes each)
|
|
178
185
|
- Search memory before starting to leverage past patterns
|
|
179
186
|
- Store patterns that worked for future workers
|
|
180
|
-
- Use
|
|
187
|
+
- Use a clear worker id in your result keys for tracking
|
|
181
188
|
- Store completion status even on partial success
|
|
182
189
|
|
|
183
|
-
Remember: You run headlessly in background. The coordinator
|
|
190
|
+
Remember: You run headlessly in background. The coordinator collects your results via shared memory.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.7.0-alpha.
|
|
3
|
+
"version": "3.7.0-alpha.26",
|
|
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",
|
|
@@ -654,7 +654,7 @@ const stopCommand = {
|
|
|
654
654
|
}
|
|
655
655
|
// Attempt MCP cleanup
|
|
656
656
|
try {
|
|
657
|
-
await callMCPTool('
|
|
657
|
+
await callMCPTool('swarm_shutdown', { swarmId, force });
|
|
658
658
|
output.writeln(output.dim(' MCP swarm stopped'));
|
|
659
659
|
}
|
|
660
660
|
catch {
|
|
@@ -41,6 +41,10 @@ import { ruvllmWasmTools } from './mcp-tools/ruvllm-tools.js';
|
|
|
41
41
|
import { wasmAgentTools } from './mcp-tools/wasm-agent-tools.js';
|
|
42
42
|
import { guidanceTools } from './mcp-tools/guidance-tools.js';
|
|
43
43
|
import { autopilotTools } from './mcp-tools/autopilot-tools.js';
|
|
44
|
+
// #1916: coverage-aware routing tools — defined in ruvector/coverage-tools.ts
|
|
45
|
+
// but were never registered, so the `ruflo hooks coverage-*` CLI subcommands
|
|
46
|
+
// failed with `Tool not found: hooks_coverage-route`.
|
|
47
|
+
import { coverageRouterTools } from './ruvector/coverage-tools.js';
|
|
44
48
|
// #1605: Only register browser tools if agent-browser is available
|
|
45
49
|
let _browserAvailable = null;
|
|
46
50
|
function getBrowserTools() {
|
|
@@ -111,6 +115,8 @@ registerTools([
|
|
|
111
115
|
...guidanceTools,
|
|
112
116
|
// Autopilot persistent completion tools
|
|
113
117
|
...autopilotTools,
|
|
118
|
+
// #1916: coverage-aware routing (hooks_coverage-route / -suggest / -gaps)
|
|
119
|
+
...coverageRouterTools,
|
|
114
120
|
]);
|
|
115
121
|
/**
|
|
116
122
|
* MCP Client Error
|
|
@@ -13,6 +13,11 @@ import { executeAgentTask } from './agent-execute-core.js';
|
|
|
13
13
|
const STORAGE_DIR = '.claude-flow';
|
|
14
14
|
const AGENT_DIR = 'agents';
|
|
15
15
|
const AGENT_FILE = 'store.json';
|
|
16
|
+
// #1916: hive-mind_spawn writes its workers to `.claude-flow/agents.json`
|
|
17
|
+
// (a *different* file from the canonical `.claude-flow/agents/store.json`
|
|
18
|
+
// used here). agent_status / agent_list / agent_logs merge that store so a
|
|
19
|
+
// hive-spawned worker is resolvable instead of returning `not_found`.
|
|
20
|
+
const HIVE_AGENT_FILE = 'agents.json';
|
|
16
21
|
function getAgentDir() {
|
|
17
22
|
return join(getProjectCwd(), STORAGE_DIR, AGENT_DIR);
|
|
18
23
|
}
|
|
@@ -42,6 +47,33 @@ function saveAgentStore(store) {
|
|
|
42
47
|
ensureAgentDir();
|
|
43
48
|
writeFileSync(getAgentPath(), JSON.stringify(store, null, 2), 'utf-8');
|
|
44
49
|
}
|
|
50
|
+
// #1916: read hive-mind-spawned workers from `.claude-flow/agents.json`.
|
|
51
|
+
function getHiveAgentPath() {
|
|
52
|
+
return join(getProjectCwd(), STORAGE_DIR, HIVE_AGENT_FILE);
|
|
53
|
+
}
|
|
54
|
+
function loadHiveAgents() {
|
|
55
|
+
try {
|
|
56
|
+
const path = getHiveAgentPath();
|
|
57
|
+
if (existsSync(path)) {
|
|
58
|
+
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
59
|
+
if (data && typeof data.agents === 'object' && data.agents) {
|
|
60
|
+
return data.agents;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Ignore — hive store is optional/best-effort.
|
|
66
|
+
}
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* #1916: merged view of every tracked agent — the canonical agent store
|
|
71
|
+
* plus hive-mind-spawned workers. On an id collision the canonical record
|
|
72
|
+
* wins (it carries model-routing + lastResult that the hive store omits).
|
|
73
|
+
*/
|
|
74
|
+
function loadAllAgents() {
|
|
75
|
+
return { ...loadHiveAgents(), ...loadAgentStore().agents };
|
|
76
|
+
}
|
|
45
77
|
// Default model mappings for agent types (can be overridden)
|
|
46
78
|
const AGENT_TYPE_MODEL_DEFAULTS = {
|
|
47
79
|
// Complex agents → opus
|
|
@@ -309,9 +341,8 @@ export const agentTools = [
|
|
|
309
341
|
const v = validateIdentifier(input.agentId, 'agentId');
|
|
310
342
|
if (!v.valid)
|
|
311
343
|
return { agentId: input.agentId, status: 'not_found', error: `Input validation failed: ${v.error}` };
|
|
312
|
-
const store = loadAgentStore();
|
|
313
344
|
const agentId = input.agentId;
|
|
314
|
-
const agent =
|
|
345
|
+
const agent = loadAllAgents()[agentId]; // #1916: includes hive-mind-spawned workers
|
|
315
346
|
if (agent) {
|
|
316
347
|
return {
|
|
317
348
|
agentId: agent.agentId,
|
|
@@ -354,8 +385,7 @@ export const agentTools = [
|
|
|
354
385
|
if (!v.valid)
|
|
355
386
|
return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
|
|
356
387
|
}
|
|
357
|
-
|
|
358
|
-
let agents = Object.values(store.agents);
|
|
388
|
+
let agents = Object.values(loadAllAgents()); // #1916: includes hive-mind-spawned workers
|
|
359
389
|
// Filter by status
|
|
360
390
|
if (input.status) {
|
|
361
391
|
agents = agents.filter(a => a.status === input.status);
|
|
@@ -635,5 +665,52 @@ export const agentTools = [
|
|
|
635
665
|
};
|
|
636
666
|
},
|
|
637
667
|
},
|
|
668
|
+
{
|
|
669
|
+
// #1916 — the `ruflo agent logs <id>` CLI subcommand and the guidance
|
|
670
|
+
// surface both reference an `agent_logs` MCP tool that was never
|
|
671
|
+
// registered, so it errored with `MCP tool not found: agent_logs`.
|
|
672
|
+
// This is the registered handler. Note: agents don't yet keep a
|
|
673
|
+
// structured per-agent activity log (that lands with hive worker
|
|
674
|
+
// execution wiring — see #1916), so for now we surface the agent's
|
|
675
|
+
// last task result as a single synthetic entry, or an explicit empty
|
|
676
|
+
// response. The shape matches what the CLI `logs` subcommand expects:
|
|
677
|
+
// `{ agentId, entries: [{timestamp,level,message,context?}], total }`.
|
|
678
|
+
name: 'agent_logs',
|
|
679
|
+
description: 'Return recorded activity-log entries for a tracked agent (idle/running history, last task result). Use when native Task tool is wrong because you need the agent\'s log across turns (what it did, last error/result, swarm context) rather than a one-shot Task transcript. For a Task you just ran, native Task output is fine. Pair with agent_list to find the agentId. (Hive-mind-spawned workers are resolved here too.) Today this returns the last task result as a synthetic entry — full per-agent activity logs land with hive worker execution wiring (ruvnet/ruflo#1916).',
|
|
680
|
+
category: 'agent',
|
|
681
|
+
inputSchema: {
|
|
682
|
+
type: 'object',
|
|
683
|
+
properties: {
|
|
684
|
+
agentId: { type: 'string', description: 'ID of agent' },
|
|
685
|
+
tail: { type: 'number', description: 'Max recent entries to return (default 50)' },
|
|
686
|
+
level: { type: 'string', enum: ['debug', 'info', 'warn', 'error'], description: 'Minimum log level (currently advisory — entries are synthetic)' },
|
|
687
|
+
since: { type: 'string', description: 'Show logs since, e.g. "1h" / "30m" (currently advisory)' },
|
|
688
|
+
},
|
|
689
|
+
required: ['agentId'],
|
|
690
|
+
},
|
|
691
|
+
handler: async (input) => {
|
|
692
|
+
const v = validateIdentifier(input.agentId, 'agentId');
|
|
693
|
+
if (!v.valid)
|
|
694
|
+
return { agentId: input.agentId, entries: [], total: 0, error: `Input validation failed: ${v.error}` };
|
|
695
|
+
const agentId = input.agentId;
|
|
696
|
+
const agent = loadAllAgents()[agentId]; // #1916: includes hive-mind-spawned workers
|
|
697
|
+
if (!agent) {
|
|
698
|
+
return { agentId, entries: [], total: 0, error: 'Agent not found' };
|
|
699
|
+
}
|
|
700
|
+
const entries = [];
|
|
701
|
+
entries.push({ timestamp: agent.createdAt, level: 'info', message: `agent created (type=${agent.agentType}, status=${agent.status})` });
|
|
702
|
+
if (agent.lastResult) {
|
|
703
|
+
entries.push({ timestamp: agent.createdAt, level: 'info', message: 'last task result', context: agent.lastResult });
|
|
704
|
+
}
|
|
705
|
+
const tail = typeof input.tail === 'number' && input.tail > 0 ? Math.floor(input.tail) : 50;
|
|
706
|
+
const sliced = entries.slice(-tail);
|
|
707
|
+
return {
|
|
708
|
+
agentId: agent.agentId,
|
|
709
|
+
entries: sliced,
|
|
710
|
+
total: entries.length,
|
|
711
|
+
note: 'per-agent activity logging is not yet wired; entries are synthetic (ruvnet/ruflo#1916)',
|
|
712
|
+
};
|
|
713
|
+
},
|
|
714
|
+
},
|
|
638
715
|
];
|
|
639
716
|
//# sourceMappingURL=agent-tools.js.map
|
|
@@ -905,5 +905,49 @@ export const hiveMindTools = [
|
|
|
905
905
|
return { action, error: 'Unknown action' };
|
|
906
906
|
},
|
|
907
907
|
},
|
|
908
|
+
{
|
|
909
|
+
// #1916: `ruflo hive-mind optimize-memory` referenced an unregistered
|
|
910
|
+
// `hive-mind_optimize-memory` tool. Best-effort today: prunes obviously-
|
|
911
|
+
// empty shared-memory keys and reports the before/after counts; pattern
|
|
912
|
+
// quality consolidation is a follow-up (it belongs in the intelligence
|
|
913
|
+
// pipeline / agentdb curator, not here).
|
|
914
|
+
name: 'hive-mind_optimize-memory',
|
|
915
|
+
description: 'Compact the hive-mind shared-memory store (drops null/empty keys) and report before/after pattern counts. Use when native conversation memory is wrong because you need the queen-led collective\'s persisted shared state cleaned up between phases. For one-shot scratch state, no tool needed. (Pattern-quality consolidation is delegated to the intelligence pipeline — this only does the cheap structural pass for now.)',
|
|
916
|
+
category: 'hive-mind',
|
|
917
|
+
inputSchema: {
|
|
918
|
+
type: 'object',
|
|
919
|
+
properties: {
|
|
920
|
+
qualityThreshold: { type: 'number', description: 'Quality threshold for pattern retention (advisory — not enforced yet)' },
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
handler: async () => {
|
|
924
|
+
const t0 = Date.now();
|
|
925
|
+
const state = loadHiveState();
|
|
926
|
+
if (!state.initialized)
|
|
927
|
+
return { optimized: false, error: 'Hive-mind not initialized', before: { patterns: 0, memory: '0' }, after: { patterns: 0, memory: '0' }, removed: 0, consolidated: 0, timeMs: 0 };
|
|
928
|
+
const beforeKeys = Object.keys(state.sharedMemory);
|
|
929
|
+
const before = beforeKeys.length;
|
|
930
|
+
for (const k of beforeKeys) {
|
|
931
|
+
const v = state.sharedMemory[k];
|
|
932
|
+
if (v === null || v === undefined || (typeof v === 'object' && v !== null && Object.keys(v).length === 0)) {
|
|
933
|
+
delete state.sharedMemory[k];
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const after = Object.keys(state.sharedMemory).length;
|
|
937
|
+
const removed = before - after;
|
|
938
|
+
if (removed > 0)
|
|
939
|
+
saveHiveState(state);
|
|
940
|
+
const sizeStr = (n) => `${Buffer.byteLength(JSON.stringify(state.sharedMemory))}B (~${n} keys)`;
|
|
941
|
+
return {
|
|
942
|
+
optimized: removed > 0,
|
|
943
|
+
before: { patterns: before, memory: `~${before} keys` },
|
|
944
|
+
after: { patterns: after, memory: sizeStr(after) },
|
|
945
|
+
removed,
|
|
946
|
+
consolidated: 0,
|
|
947
|
+
timeMs: Date.now() - t0,
|
|
948
|
+
note: 'structural compaction only; pattern-quality consolidation is delegated to the intelligence pipeline (#1916 follow-up)',
|
|
949
|
+
};
|
|
950
|
+
},
|
|
951
|
+
},
|
|
908
952
|
];
|
|
909
953
|
//# sourceMappingURL=hive-mind-tools.js.map
|
|
@@ -39,6 +39,8 @@ export declare const hooksModelRoute: MCPTool;
|
|
|
39
39
|
export declare const hooksModelOutcome: MCPTool;
|
|
40
40
|
export declare const hooksModelStats: MCPTool;
|
|
41
41
|
export declare const hooksWorkerCancel: MCPTool;
|
|
42
|
+
export declare const hooksTeammateIdle: MCPTool;
|
|
43
|
+
export declare const hooksTaskCompleted: MCPTool;
|
|
42
44
|
export declare const hooksTools: MCPTool[];
|
|
43
45
|
export default hooksTools;
|
|
44
46
|
//# sourceMappingURL=hooks-tools.d.ts.map
|
|
@@ -3799,8 +3799,69 @@ export const hooksWorkerCancel = {
|
|
|
3799
3799
|
};
|
|
3800
3800
|
},
|
|
3801
3801
|
};
|
|
3802
|
+
// #1916: the `ruflo hooks teammate-idle` / `ruflo hooks task-completed` CLI
|
|
3803
|
+
// subcommands (Agent Teams hooks) referenced unregistered tools. Minimal
|
|
3804
|
+
// acknowledgement handlers with the shapes the CLI expects — auto-assignment
|
|
3805
|
+
// and pattern-learning are delegated to the task-queue consumer / intelligence
|
|
3806
|
+
// pipeline (a tracked #1916 follow-up).
|
|
3807
|
+
export const hooksTeammateIdle = {
|
|
3808
|
+
name: 'hooks_teammate-idle',
|
|
3809
|
+
description: 'Agent Teams hook — fired when a teammate agent finishes its turn; reports whether a pending task can be auto-assigned. Use when native Task is wrong because you have a persistent multi-agent team with a shared task list and want idle workers picked up automatically rather than re-spawning subagents. For a one-shot Task, native Task is fine. (Auto-assignment is delegated to the task-queue consumer — this acknowledges the event today.)',
|
|
3810
|
+
category: 'hooks',
|
|
3811
|
+
inputSchema: {
|
|
3812
|
+
type: 'object',
|
|
3813
|
+
properties: {
|
|
3814
|
+
teammateId: { type: 'string', description: 'ID of the idle teammate' },
|
|
3815
|
+
teamName: { type: 'string', description: 'Team name' },
|
|
3816
|
+
autoAssign: { type: 'boolean', description: 'Auto-assign a pending task if available' },
|
|
3817
|
+
checkTaskList: { type: 'boolean', description: 'Consult the shared task list' },
|
|
3818
|
+
timestamp: { type: 'number', description: 'Event timestamp (ms)' },
|
|
3819
|
+
},
|
|
3820
|
+
},
|
|
3821
|
+
handler: async (input) => {
|
|
3822
|
+
const teammateId = String(input.teammateId ?? '');
|
|
3823
|
+
return {
|
|
3824
|
+
success: true,
|
|
3825
|
+
teammateId,
|
|
3826
|
+
action: 'waiting',
|
|
3827
|
+
pendingTasks: 0,
|
|
3828
|
+
message: 'teammate-idle acknowledged; auto-assignment requires the task-queue consumer (#1916 follow-up)',
|
|
3829
|
+
};
|
|
3830
|
+
},
|
|
3831
|
+
};
|
|
3832
|
+
export const hooksTaskCompleted = {
|
|
3833
|
+
name: 'hooks_task-completed',
|
|
3834
|
+
description: 'Agent Teams hook — fired when a task is marked complete; records completion and (eventually) trains patterns + notifies the team lead. Use when native TodoWrite is wrong because the work was a persisted, agent-assigned task whose outcome should feed cross-session learning and team coordination. For an in-session checklist tick, native TodoWrite is fine. (Pattern-learning is delegated to the intelligence pipeline — this records the completion today.)',
|
|
3835
|
+
category: 'hooks',
|
|
3836
|
+
inputSchema: {
|
|
3837
|
+
type: 'object',
|
|
3838
|
+
properties: {
|
|
3839
|
+
taskId: { type: 'string', description: 'ID of the completed task' },
|
|
3840
|
+
teammateId: { type: 'string', description: 'Teammate that completed it' },
|
|
3841
|
+
success: { type: 'boolean', description: 'Whether the task succeeded' },
|
|
3842
|
+
quality: { type: 'number', description: 'Quality score 0-1' },
|
|
3843
|
+
trainPatterns: { type: 'boolean', description: 'Feed the outcome to the learning pipeline' },
|
|
3844
|
+
notifyLead: { type: 'boolean', description: 'Notify the team lead' },
|
|
3845
|
+
},
|
|
3846
|
+
required: ['taskId'],
|
|
3847
|
+
},
|
|
3848
|
+
handler: async (input) => {
|
|
3849
|
+
const taskId = String(input.taskId ?? '');
|
|
3850
|
+
const quality = typeof input.quality === 'number' ? input.quality : (input.success === false ? 0 : 1);
|
|
3851
|
+
return {
|
|
3852
|
+
success: true,
|
|
3853
|
+
taskId,
|
|
3854
|
+
patternsLearned: 0,
|
|
3855
|
+
leadNotified: input.notifyLead === true,
|
|
3856
|
+
metrics: { duration: 0, quality, learningUpdates: 0 },
|
|
3857
|
+
note: 'completion recorded; pattern-learning is delegated to the intelligence pipeline (#1916 follow-up)',
|
|
3858
|
+
};
|
|
3859
|
+
},
|
|
3860
|
+
};
|
|
3802
3861
|
// Export all hooks tools
|
|
3803
3862
|
export const hooksTools = [
|
|
3863
|
+
hooksTeammateIdle,
|
|
3864
|
+
hooksTaskCompleted,
|
|
3804
3865
|
hooksPreEdit,
|
|
3805
3866
|
hooksPostEdit,
|
|
3806
3867
|
hooksPreCommand,
|
|
@@ -920,5 +920,227 @@ export const memoryTools = [
|
|
|
920
920
|
};
|
|
921
921
|
},
|
|
922
922
|
},
|
|
923
|
+
{
|
|
924
|
+
// #1916: `ruflo status memory` (the detailed view) referenced an
|
|
925
|
+
// unregistered `memory_detailed-stats` tool. memory_stats returns a
|
|
926
|
+
// different shape; this returns what the CLI renders.
|
|
927
|
+
name: 'memory_detailed-stats',
|
|
928
|
+
description: 'Detailed memory-store report — backend, entry count, total bytes, per-namespace counts, and (placeholder) perf metrics. Use when native Read/Glob is wrong because the data lives in .swarm/memory.db, not files, and you want an aggregate health view. For a quick count use memory_stats; for "what is in memory" use memory_list.',
|
|
929
|
+
category: 'memory',
|
|
930
|
+
inputSchema: { type: 'object', properties: {} },
|
|
931
|
+
handler: async () => {
|
|
932
|
+
await ensureInitialized();
|
|
933
|
+
const { listEntries } = await getMemoryFunctions();
|
|
934
|
+
const all = await listEntries({ limit: 100000 });
|
|
935
|
+
const nsCounts = {};
|
|
936
|
+
let bytes = 0;
|
|
937
|
+
for (const e of all.entries) {
|
|
938
|
+
nsCounts[e.namespace] = (nsCounts[e.namespace] || 0) + 1;
|
|
939
|
+
bytes += e.size || 0;
|
|
940
|
+
}
|
|
941
|
+
return {
|
|
942
|
+
backend: 'sql.js + HNSW',
|
|
943
|
+
entries: all.total ?? all.entries.length,
|
|
944
|
+
size: bytes,
|
|
945
|
+
namespaces: Object.entries(nsCounts).map(([name, entries]) => ({ name, entries })),
|
|
946
|
+
performance: { avgSearchTime: 0, avgWriteTime: 0, cacheHitRate: 0, hnswEnabled: true },
|
|
947
|
+
note: 'perf metrics are placeholders; HNSW is always enabled in the sql.js backend',
|
|
948
|
+
};
|
|
949
|
+
},
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
// #1916: `ruflo memory cleanup` referenced an unregistered `memory_cleanup`
|
|
953
|
+
// tool. Removes entries whose TTL has expired. Defaults to a dry run —
|
|
954
|
+
// pass dryRun:false to actually delete.
|
|
955
|
+
name: 'memory_cleanup',
|
|
956
|
+
description: 'Prune memory entries whose TTL has expired (dry run by default; pass dryRun:false to delete). Use when native rm is wrong because the entries are rows in .swarm/memory.db, not files. For removing a specific known key use memory_delete. Stale/low-quality pruning is delegated to the agentdb consolidation curator (#1916 follow-up).',
|
|
957
|
+
category: 'memory',
|
|
958
|
+
inputSchema: {
|
|
959
|
+
type: 'object',
|
|
960
|
+
properties: {
|
|
961
|
+
dryRun: { type: 'boolean', description: 'Only report candidates, do not delete (default true)' },
|
|
962
|
+
namespace: { type: 'string', description: 'Limit cleanup to one namespace' },
|
|
963
|
+
},
|
|
964
|
+
},
|
|
965
|
+
handler: async (input) => {
|
|
966
|
+
await ensureInitialized();
|
|
967
|
+
const { listEntries, deleteEntry } = await getMemoryFunctions();
|
|
968
|
+
const dryRun = input.dryRun !== false; // default true
|
|
969
|
+
const namespace = input.namespace ? String(input.namespace) : undefined;
|
|
970
|
+
if (namespace) {
|
|
971
|
+
const v = validateIdentifier(namespace, 'namespace');
|
|
972
|
+
if (!v.valid)
|
|
973
|
+
throw new Error(v.error);
|
|
974
|
+
}
|
|
975
|
+
const all = await listEntries({ limit: 100000, namespace });
|
|
976
|
+
const now = Date.now();
|
|
977
|
+
const expired = all.entries.filter(e => {
|
|
978
|
+
const exp = e.expiresAt;
|
|
979
|
+
if (!exp)
|
|
980
|
+
return false;
|
|
981
|
+
const t = typeof exp === 'number' ? exp : Date.parse(String(exp));
|
|
982
|
+
return Number.isFinite(t) && t < now;
|
|
983
|
+
});
|
|
984
|
+
let freedBytes = 0;
|
|
985
|
+
let deleted = 0;
|
|
986
|
+
if (!dryRun) {
|
|
987
|
+
for (const e of expired) {
|
|
988
|
+
try {
|
|
989
|
+
await deleteEntry({ key: e.key, namespace: e.namespace });
|
|
990
|
+
freedBytes += e.size || 0;
|
|
991
|
+
deleted++;
|
|
992
|
+
}
|
|
993
|
+
catch { /* ignore individual delete errors */ }
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
freedBytes = expired.reduce((s, e) => s + (e.size || 0), 0);
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
dryRun,
|
|
1001
|
+
candidates: { expired: expired.length, stale: 0, lowQuality: 0, total: expired.length },
|
|
1002
|
+
deleted: { entries: dryRun ? 0 : deleted, vectors: 0, patterns: 0 },
|
|
1003
|
+
freed: { bytes: freedBytes },
|
|
1004
|
+
note: dryRun ? 'dry run — re-run with dryRun:false to delete' : undefined,
|
|
1005
|
+
};
|
|
1006
|
+
},
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
// #1916: `ruflo memory compress` referenced an unregistered tool. The
|
|
1010
|
+
// sql.js backend has no on-disk compression; this reports current sizes.
|
|
1011
|
+
name: 'memory_compress',
|
|
1012
|
+
description: 'Report memory-store size breakdown (the sql.js backend has no on-disk compression — entries are already stored compactly; quantized embeddings via RaBitQ are configured elsewhere). Use when native du is wrong because the data is in .swarm/memory.db. For pruning expired entries use memory_cleanup.',
|
|
1013
|
+
category: 'memory',
|
|
1014
|
+
inputSchema: { type: 'object', properties: {} },
|
|
1015
|
+
handler: async () => {
|
|
1016
|
+
await ensureInitialized();
|
|
1017
|
+
const { listEntries } = await getMemoryFunctions();
|
|
1018
|
+
const all = await listEntries({ limit: 100000 });
|
|
1019
|
+
const bytes = all.entries.reduce((s, e) => s + (e.size || 0), 0);
|
|
1020
|
+
const human = `${bytes}B`;
|
|
1021
|
+
const sizes = { totalSize: human, vectorsSize: 'n/a', textSize: human, patternsSize: 'n/a', indexSize: 'n/a' };
|
|
1022
|
+
return {
|
|
1023
|
+
before: sizes,
|
|
1024
|
+
after: sizes,
|
|
1025
|
+
compression: { ratio: 1, savedBytes: 0, method: 'none' },
|
|
1026
|
+
note: 'sql.js backend has no on-disk compression; nothing to compress. (RaBitQ embedding quantization is a separate feature.)',
|
|
1027
|
+
};
|
|
1028
|
+
},
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
// #1916: `ruflo memory export -o <file>` referenced an unregistered tool.
|
|
1032
|
+
// Dumps entry metadata (and values when the backend returns them) to JSON.
|
|
1033
|
+
name: 'memory_export',
|
|
1034
|
+
description: 'Export memory entries to a JSON file (keys, namespaces, timestamps, and values when available). Use when native Write is wrong because the data is rows in .swarm/memory.db, not a file you can copy. For ingesting an export elsewhere use memory_import. (CSV output and embedding-vector export are follow-ups.)',
|
|
1035
|
+
category: 'memory',
|
|
1036
|
+
inputSchema: {
|
|
1037
|
+
type: 'object',
|
|
1038
|
+
properties: {
|
|
1039
|
+
outputPath: { type: 'string', description: 'File path to write the JSON export to' },
|
|
1040
|
+
format: { type: 'string', enum: ['json', 'csv'], description: 'Export format (csv falls back to json today)' },
|
|
1041
|
+
namespace: { type: 'string', description: 'Limit export to one namespace' },
|
|
1042
|
+
includeVectors: { type: 'boolean', description: 'Include embedding vectors (advisory — not exported yet)' },
|
|
1043
|
+
},
|
|
1044
|
+
required: ['outputPath'],
|
|
1045
|
+
},
|
|
1046
|
+
handler: async (input) => {
|
|
1047
|
+
await ensureInitialized();
|
|
1048
|
+
const { listEntries } = await getMemoryFunctions();
|
|
1049
|
+
const outputPath = String(input.outputPath ?? '');
|
|
1050
|
+
if (!outputPath)
|
|
1051
|
+
return { error: 'outputPath is required' };
|
|
1052
|
+
const namespace = input.namespace ? String(input.namespace) : undefined;
|
|
1053
|
+
if (namespace) {
|
|
1054
|
+
const v = validateIdentifier(namespace, 'namespace');
|
|
1055
|
+
if (!v.valid)
|
|
1056
|
+
throw new Error(v.error);
|
|
1057
|
+
}
|
|
1058
|
+
const all = await listEntries({ limit: 100000, namespace });
|
|
1059
|
+
const payload = {
|
|
1060
|
+
schema: 'ruflo-memory-export/v1',
|
|
1061
|
+
exportedAt: new Date().toISOString(),
|
|
1062
|
+
namespace: namespace ?? null,
|
|
1063
|
+
count: all.entries.length,
|
|
1064
|
+
entries: all.entries.map(e => ({
|
|
1065
|
+
key: e.key, namespace: e.namespace, value: e.value ?? null,
|
|
1066
|
+
createdAt: e.createdAt, updatedAt: e.updatedAt, accessCount: e.accessCount, hasEmbedding: e.hasEmbedding, size: e.size,
|
|
1067
|
+
})),
|
|
1068
|
+
};
|
|
1069
|
+
try {
|
|
1070
|
+
writeFileSync(outputPath, JSON.stringify(payload, null, 2), 'utf-8');
|
|
1071
|
+
}
|
|
1072
|
+
catch (e) {
|
|
1073
|
+
return { error: `Could not write ${outputPath}: ${e.message}` };
|
|
1074
|
+
}
|
|
1075
|
+
const vectorsWithEmb = all.entries.filter(e => e.hasEmbedding).length;
|
|
1076
|
+
return {
|
|
1077
|
+
outputPath,
|
|
1078
|
+
format: input.format || 'json',
|
|
1079
|
+
exported: { entries: all.entries.length, vectors: vectorsWithEmb, patterns: 0 },
|
|
1080
|
+
fileSize: `${Buffer.byteLength(JSON.stringify(payload))}B`,
|
|
1081
|
+
note: input.format === 'csv' ? 'CSV not implemented yet — wrote JSON' : undefined,
|
|
1082
|
+
};
|
|
1083
|
+
},
|
|
1084
|
+
},
|
|
1085
|
+
{
|
|
1086
|
+
// #1916: `ruflo memory import <file>` referenced an unregistered tool.
|
|
1087
|
+
// Reads a ruflo-memory-export JSON and re-stores each entry.
|
|
1088
|
+
name: 'memory_import',
|
|
1089
|
+
description: 'Import memory entries from a JSON export file (produced by memory_export) into .swarm/memory.db, re-embedding values. Use when native Read is wrong because the data must be re-stored as memory rows (with new embeddings), not just read. For importing Claude Code\'s own memory files use memory_import_claude. Pair with memory_export on the source.',
|
|
1090
|
+
category: 'memory',
|
|
1091
|
+
inputSchema: {
|
|
1092
|
+
type: 'object',
|
|
1093
|
+
properties: {
|
|
1094
|
+
inputPath: { type: 'string', description: 'Path to the JSON export file' },
|
|
1095
|
+
merge: { type: 'boolean', description: 'Merge into existing entries (upsert) vs. fail on conflict (default true)' },
|
|
1096
|
+
namespace: { type: 'string', description: 'Override the namespace for all imported entries' },
|
|
1097
|
+
},
|
|
1098
|
+
required: ['inputPath'],
|
|
1099
|
+
},
|
|
1100
|
+
handler: async (input) => {
|
|
1101
|
+
await ensureInitialized();
|
|
1102
|
+
const { storeEntry } = await getMemoryFunctions();
|
|
1103
|
+
const t0 = Date.now();
|
|
1104
|
+
const inputPath = String(input.inputPath ?? '');
|
|
1105
|
+
if (!inputPath || !existsSync(inputPath))
|
|
1106
|
+
return { error: `File not found: ${inputPath || '(empty)'}` };
|
|
1107
|
+
let doc;
|
|
1108
|
+
try {
|
|
1109
|
+
doc = JSON.parse(readFileSync(inputPath, 'utf-8'));
|
|
1110
|
+
}
|
|
1111
|
+
catch (e) {
|
|
1112
|
+
return { error: `Invalid export JSON: ${e.message}` };
|
|
1113
|
+
}
|
|
1114
|
+
const entries = Array.isArray(doc.entries) ? doc.entries : [];
|
|
1115
|
+
const nsOverride = input.namespace ? String(input.namespace) : undefined;
|
|
1116
|
+
if (nsOverride) {
|
|
1117
|
+
const v = validateIdentifier(nsOverride, 'namespace');
|
|
1118
|
+
if (!v.valid)
|
|
1119
|
+
throw new Error(v.error);
|
|
1120
|
+
}
|
|
1121
|
+
let imported = 0;
|
|
1122
|
+
let skipped = 0;
|
|
1123
|
+
for (const e of entries) {
|
|
1124
|
+
if (!e || typeof e.key !== 'string') {
|
|
1125
|
+
skipped++;
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
const value = typeof e.value === 'string' ? e.value : JSON.stringify(e.value ?? null);
|
|
1129
|
+
try {
|
|
1130
|
+
await storeEntry({ key: e.key, value, namespace: nsOverride ?? e.namespace ?? 'default', upsert: input.merge !== false });
|
|
1131
|
+
imported++;
|
|
1132
|
+
}
|
|
1133
|
+
catch {
|
|
1134
|
+
skipped++;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
inputPath,
|
|
1139
|
+
imported: { entries: imported, vectors: 0, patterns: 0 },
|
|
1140
|
+
skipped,
|
|
1141
|
+
duration: Date.now() - t0,
|
|
1142
|
+
};
|
|
1143
|
+
},
|
|
1144
|
+
},
|
|
923
1145
|
];
|
|
924
1146
|
//# sourceMappingURL=memory-tools.js.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tool definitions for session management with file persistence.
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, readFileSync, readdirSync, unlinkSync, statSync } from 'node:fs';
|
|
6
|
+
import { existsSync, readFileSync, readdirSync, unlinkSync, statSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { getProjectCwd } from './types.js';
|
|
9
9
|
import { mkdirRestricted, readFileMaybeEncrypted, writeFileRestricted, } from '../fs-secure.js';
|
|
@@ -389,5 +389,129 @@ export const sessionTools = [
|
|
|
389
389
|
};
|
|
390
390
|
},
|
|
391
391
|
},
|
|
392
|
+
{
|
|
393
|
+
// #1916: `ruflo session current` referenced an unregistered
|
|
394
|
+
// `session_current` tool. Returns the most-recently-saved session.
|
|
395
|
+
name: 'session_current',
|
|
396
|
+
description: 'Return the most-recently-saved session (id, name, stats) — the de-facto "current" one. Use when native conversation memory is wrong because you need to know which durable session is active before exporting/restoring it. For in-session continuation only, no tool needed. Pair with session_export / session_restore.',
|
|
397
|
+
category: 'session',
|
|
398
|
+
inputSchema: { type: 'object', properties: {} },
|
|
399
|
+
handler: async () => {
|
|
400
|
+
const dir = getSessionDir();
|
|
401
|
+
if (!existsSync(dir))
|
|
402
|
+
return { sessionId: '', status: 'none', startedAt: '', error: 'No saved sessions' };
|
|
403
|
+
const files = readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
404
|
+
if (files.length === 0)
|
|
405
|
+
return { sessionId: '', status: 'none', startedAt: '', error: 'No saved sessions' };
|
|
406
|
+
let newest = files[0];
|
|
407
|
+
let newestMtime = 0;
|
|
408
|
+
for (const f of files) {
|
|
409
|
+
const mt = statSync(join(dir, f)).mtimeMs;
|
|
410
|
+
if (mt >= newestMtime) {
|
|
411
|
+
newestMtime = mt;
|
|
412
|
+
newest = f;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const sessionId = newest.replace(/\.json$/, '');
|
|
416
|
+
const session = loadSession(sessionId);
|
|
417
|
+
if (!session)
|
|
418
|
+
return { sessionId, status: 'unknown', startedAt: '', error: 'Session file unreadable' };
|
|
419
|
+
return {
|
|
420
|
+
sessionId: session.sessionId,
|
|
421
|
+
name: session.name,
|
|
422
|
+
status: 'active',
|
|
423
|
+
startedAt: session.savedAt,
|
|
424
|
+
stats: session.stats,
|
|
425
|
+
};
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
// #1916: `ruflo session export <id> -o <file>` referenced an unregistered
|
|
430
|
+
// `session_export` tool. Writes the session JSON to a file (if given) and
|
|
431
|
+
// returns the session payload.
|
|
432
|
+
name: 'session_export',
|
|
433
|
+
description: 'Export a saved session (agents, tasks, memory snapshot) to a JSON file and/or return the payload. Use when native Write is wrong because the data is the structured session record (not a freeform file) and you want it serialized consistently for transfer/backup. For writing arbitrary content, native Write is fine. Pair with session_import on the other end.',
|
|
434
|
+
category: 'session',
|
|
435
|
+
inputSchema: {
|
|
436
|
+
type: 'object',
|
|
437
|
+
properties: {
|
|
438
|
+
sessionId: { type: 'string', description: 'Session ID to export' },
|
|
439
|
+
outputPath: { type: 'string', description: 'File path to write the export to (optional)' },
|
|
440
|
+
includeMemory: { type: 'boolean', description: 'Include the memory snapshot (advisory — already in the saved record)' },
|
|
441
|
+
},
|
|
442
|
+
required: ['sessionId'],
|
|
443
|
+
},
|
|
444
|
+
handler: async (input) => {
|
|
445
|
+
const vId = validateIdentifier(input.sessionId, 'sessionId');
|
|
446
|
+
if (!vId.valid)
|
|
447
|
+
return { success: false, error: vId.error };
|
|
448
|
+
const sessionId = input.sessionId;
|
|
449
|
+
const session = loadSession(sessionId);
|
|
450
|
+
if (!session)
|
|
451
|
+
return { sessionId, error: 'Session not found' };
|
|
452
|
+
let path = null;
|
|
453
|
+
const outputPath = input.outputPath ? String(input.outputPath) : null;
|
|
454
|
+
if (outputPath) {
|
|
455
|
+
try {
|
|
456
|
+
writeFileSync(outputPath, JSON.stringify(session, null, 2), 'utf-8');
|
|
457
|
+
path = outputPath;
|
|
458
|
+
}
|
|
459
|
+
catch (e) {
|
|
460
|
+
return { sessionId, error: `Could not write ${outputPath}: ${e.message}` };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return { sessionId, name: session.name, data: session, path, exportedAt: new Date().toISOString() };
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
// #1916: `ruflo session import <file>` referenced an unregistered
|
|
468
|
+
// `session_import` tool. Reads a session JSON and re-saves it locally.
|
|
469
|
+
name: 'session_import',
|
|
470
|
+
description: 'Import a session JSON file (produced by session_export) into the local session store and optionally activate it. Use when native Read is wrong because the file is a structured session record that must be re-registered (new id, stats recomputed) rather than just read. For reading the file, native Read is fine. Pair with session_export on the source.',
|
|
471
|
+
category: 'session',
|
|
472
|
+
inputSchema: {
|
|
473
|
+
type: 'object',
|
|
474
|
+
properties: {
|
|
475
|
+
inputPath: { type: 'string', description: 'Path to the session JSON file to import' },
|
|
476
|
+
name: { type: 'string', description: 'Override the imported session name' },
|
|
477
|
+
activate: { type: 'boolean', description: 'Make the imported session the current one (advisory)' },
|
|
478
|
+
},
|
|
479
|
+
required: ['inputPath'],
|
|
480
|
+
},
|
|
481
|
+
handler: async (input) => {
|
|
482
|
+
const inputPath = String(input.inputPath ?? '');
|
|
483
|
+
if (!inputPath || !existsSync(inputPath))
|
|
484
|
+
return { error: `File not found: ${inputPath || '(empty)'}` };
|
|
485
|
+
let parsed;
|
|
486
|
+
try {
|
|
487
|
+
parsed = JSON.parse(readFileSync(inputPath, 'utf-8'));
|
|
488
|
+
}
|
|
489
|
+
catch (e) {
|
|
490
|
+
return { error: `Invalid session JSON: ${e.message}` };
|
|
491
|
+
}
|
|
492
|
+
const newId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
493
|
+
const stats = parsed.stats || { tasks: 0, agents: 0, memoryEntries: 0, totalSize: 0 };
|
|
494
|
+
const session = {
|
|
495
|
+
sessionId: newId,
|
|
496
|
+
name: input.name ? String(input.name) : (parsed.name || 'imported-session'),
|
|
497
|
+
description: parsed.description,
|
|
498
|
+
savedAt: new Date().toISOString(),
|
|
499
|
+
stats,
|
|
500
|
+
data: parsed.data,
|
|
501
|
+
};
|
|
502
|
+
saveSession(session);
|
|
503
|
+
return {
|
|
504
|
+
sessionId: newId,
|
|
505
|
+
name: session.name,
|
|
506
|
+
importedAt: session.savedAt,
|
|
507
|
+
stats: {
|
|
508
|
+
agentsImported: stats.agents,
|
|
509
|
+
tasksImported: stats.tasks,
|
|
510
|
+
memoryEntriesImported: stats.memoryEntries,
|
|
511
|
+
},
|
|
512
|
+
activated: input.activate === true,
|
|
513
|
+
};
|
|
514
|
+
},
|
|
515
|
+
},
|
|
392
516
|
];
|
|
393
517
|
//# sourceMappingURL=session-tools.js.map
|
|
@@ -622,5 +622,53 @@ export const systemTools = [
|
|
|
622
622
|
};
|
|
623
623
|
},
|
|
624
624
|
},
|
|
625
|
+
{
|
|
626
|
+
// #1916: `ruflo start` referenced an unregistered `mcp_start` tool. MCP
|
|
627
|
+
// tools run *in-process* via the CLI's TOOL_REGISTRY — there is no
|
|
628
|
+
// separate server process to spawn from inside an MCP call. If this tool
|
|
629
|
+
// responds, MCP is already up. (`ruflo mcp start` runs a standalone
|
|
630
|
+
// stdio/HTTP server; that's a process command, not an MCP tool.)
|
|
631
|
+
name: 'mcp_start',
|
|
632
|
+
description: 'Report that the in-process MCP toolset is available (no-op "start" — if this tool responds, MCP is up). Use when native `claude mcp list` is wrong because you want Ruflo-side confirmation that the in-process registry loaded. For a standalone stdio/HTTP MCP server, run `ruflo mcp start` (a process command, not this tool). Pair with mcp_status for detail.',
|
|
633
|
+
category: 'system',
|
|
634
|
+
inputSchema: {
|
|
635
|
+
type: 'object',
|
|
636
|
+
properties: {
|
|
637
|
+
port: { type: 'number', description: 'Port (advisory — in-process MCP has no port)' },
|
|
638
|
+
transport: { type: 'string', description: 'Transport (advisory)' },
|
|
639
|
+
tools: { type: 'array', items: { type: 'string' }, description: 'Tool namespaces (advisory — all are loaded)' },
|
|
640
|
+
},
|
|
641
|
+
},
|
|
642
|
+
handler: async (input) => {
|
|
643
|
+
const isStdio = !process.stdin.isTTY;
|
|
644
|
+
return {
|
|
645
|
+
serverId: `in-process-${process.pid}`,
|
|
646
|
+
port: typeof input.port === 'number' ? input.port : (parseInt(process.env.CLAUDE_FLOW_MCP_PORT || '0', 10) || null),
|
|
647
|
+
transport: input.transport || process.env.CLAUDE_FLOW_MCP_TRANSPORT || (isStdio ? 'stdio' : 'in-process'),
|
|
648
|
+
startedAt: new Date().toISOString(),
|
|
649
|
+
note: 'MCP tools run in-process via the CLI; no separate server process was started. Use `ruflo mcp start` for a standalone server.',
|
|
650
|
+
};
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
// #1916: `ruflo stop` referenced an unregistered `mcp_stop` tool. Same
|
|
655
|
+
// story as mcp_start — nothing to stop for the in-process registry.
|
|
656
|
+
name: 'mcp_stop',
|
|
657
|
+
description: 'No-op "stop" for the in-process MCP toolset (there is no separate server process to stop from inside an MCP call). Use when native process-kill is wrong because you mistakenly think Ruflo runs a daemon — it does not, the tools live in the CLI process. To stop a standalone server run `ruflo mcp stop` or terminate that process. Pair with mcp_status.',
|
|
658
|
+
category: 'system',
|
|
659
|
+
inputSchema: {
|
|
660
|
+
type: 'object',
|
|
661
|
+
properties: {
|
|
662
|
+
graceful: { type: 'boolean', description: 'Advisory (no-op)' },
|
|
663
|
+
timeout: { type: 'number', description: 'Advisory (no-op)' },
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
handler: async () => {
|
|
667
|
+
return {
|
|
668
|
+
stopped: false,
|
|
669
|
+
note: 'no separate MCP server process; nothing to stop. The in-process toolset goes away when the CLI process exits. Use `ruflo mcp stop` for a standalone server.',
|
|
670
|
+
};
|
|
671
|
+
},
|
|
672
|
+
},
|
|
625
673
|
];
|
|
626
674
|
//# sourceMappingURL=system-tools.js.map
|
|
@@ -435,5 +435,53 @@ export const taskTools = [
|
|
|
435
435
|
};
|
|
436
436
|
},
|
|
437
437
|
},
|
|
438
|
+
{
|
|
439
|
+
// #1916: the `ruflo task retry <id>` CLI subcommand referenced an
|
|
440
|
+
// unregistered `task_retry` tool. Re-queues a finished/cancelled task by
|
|
441
|
+
// cloning its spec into a fresh pending task (the original is left intact
|
|
442
|
+
// as history).
|
|
443
|
+
name: 'task_retry',
|
|
444
|
+
description: 'Re-queue a failed/cancelled/completed task by cloning its spec into a fresh pending task (the original record is kept as history). Use when native TodoWrite is wrong because you need the original task\'s persisted spec (type, priority, assignees, tags) and a stable taskId chain across runs rather than hand-retyping a checklist item. For ad-hoc re-runs, native TodoWrite is fine.',
|
|
445
|
+
category: 'task',
|
|
446
|
+
inputSchema: {
|
|
447
|
+
type: 'object',
|
|
448
|
+
properties: {
|
|
449
|
+
taskId: { type: 'string', description: 'ID of the task to retry' },
|
|
450
|
+
resetState: { type: 'boolean', description: 'Reset progress/result on the new task (default true)' },
|
|
451
|
+
},
|
|
452
|
+
required: ['taskId'],
|
|
453
|
+
},
|
|
454
|
+
handler: async (input) => {
|
|
455
|
+
const v = validateIdentifier(input.taskId, 'taskId');
|
|
456
|
+
if (!v.valid)
|
|
457
|
+
return { success: false, error: v.error };
|
|
458
|
+
const store = loadTaskStore();
|
|
459
|
+
const taskId = input.taskId;
|
|
460
|
+
const original = store.tasks[taskId];
|
|
461
|
+
if (!original)
|
|
462
|
+
return { success: false, taskId, error: 'Task not found' };
|
|
463
|
+
const newTaskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
464
|
+
store.tasks[newTaskId] = {
|
|
465
|
+
taskId: newTaskId,
|
|
466
|
+
type: original.type,
|
|
467
|
+
description: original.description,
|
|
468
|
+
priority: original.priority,
|
|
469
|
+
status: 'pending',
|
|
470
|
+
progress: 0,
|
|
471
|
+
assignedTo: [...original.assignedTo],
|
|
472
|
+
tags: [...original.tags, 'retry-of:' + taskId],
|
|
473
|
+
createdAt: new Date().toISOString(),
|
|
474
|
+
startedAt: null,
|
|
475
|
+
completedAt: null,
|
|
476
|
+
};
|
|
477
|
+
saveTaskStore(store);
|
|
478
|
+
return {
|
|
479
|
+
taskId,
|
|
480
|
+
newTaskId,
|
|
481
|
+
previousStatus: original.status,
|
|
482
|
+
status: 'pending',
|
|
483
|
+
};
|
|
484
|
+
},
|
|
485
|
+
},
|
|
438
486
|
];
|
|
439
487
|
//# sourceMappingURL=task-tools.js.map
|
|
@@ -772,5 +772,113 @@ export const workflowTools = [
|
|
|
772
772
|
return { action, error: 'Unknown action' };
|
|
773
773
|
},
|
|
774
774
|
},
|
|
775
|
+
{
|
|
776
|
+
// #1916: `ruflo workflow stop <id>` referenced an unregistered
|
|
777
|
+
// `workflow_stop` tool. Equivalent to workflow_cancel but returns the
|
|
778
|
+
// shape the CLI expects (`{ workflowId, stopped, stoppedAt }`).
|
|
779
|
+
name: 'workflow_stop',
|
|
780
|
+
description: 'Stop a running/paused workflow and skip its remaining steps. Use when native TodoWrite + sequential Bash is wrong because the work has a real dependency graph that needs persistence, pause/resume, and step-output binding — and you need to halt it cleanly mid-run. For a single linear todo list, native TodoWrite is fine. (Same effect as workflow_cancel; this name is what the CLI `workflow stop` subcommand calls.)',
|
|
781
|
+
category: 'workflow',
|
|
782
|
+
inputSchema: {
|
|
783
|
+
type: 'object',
|
|
784
|
+
properties: {
|
|
785
|
+
workflowId: { type: 'string', description: 'Workflow ID' },
|
|
786
|
+
graceful: { type: 'boolean', description: 'Let the current step finish (advisory)' },
|
|
787
|
+
},
|
|
788
|
+
required: ['workflowId'],
|
|
789
|
+
},
|
|
790
|
+
handler: async (input) => {
|
|
791
|
+
const vId = validateIdentifier(input.workflowId, 'workflowId');
|
|
792
|
+
if (!vId.valid)
|
|
793
|
+
return { success: false, error: vId.error };
|
|
794
|
+
const store = loadWorkflowStore();
|
|
795
|
+
const workflowId = input.workflowId;
|
|
796
|
+
const workflow = store.workflows[workflowId];
|
|
797
|
+
if (!workflow)
|
|
798
|
+
return { workflowId, error: 'Workflow not found' };
|
|
799
|
+
if (workflow.status === 'completed' || workflow.status === 'failed') {
|
|
800
|
+
return { workflowId, error: 'Workflow already finished' };
|
|
801
|
+
}
|
|
802
|
+
workflow.status = 'failed';
|
|
803
|
+
workflow.error = 'Stopped by user';
|
|
804
|
+
workflow.completedAt = new Date().toISOString();
|
|
805
|
+
for (let i = workflow.currentStep; i < workflow.steps.length; i++) {
|
|
806
|
+
workflow.steps[i].status = 'skipped';
|
|
807
|
+
}
|
|
808
|
+
saveWorkflowStore(store);
|
|
809
|
+
return { workflowId, stopped: true, stoppedAt: workflow.completedAt };
|
|
810
|
+
},
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
// #1916: `ruflo workflow validate -f <file>` referenced an unregistered
|
|
814
|
+
// `workflow_validate` tool. Structural sanity check (JSON workflow files);
|
|
815
|
+
// a full schema validator is a follow-up.
|
|
816
|
+
name: 'workflow_validate',
|
|
817
|
+
description: 'Structurally validate a workflow definition file (JSON) — checks it has a steps/stages/tasks array and that each step names an agent. Use when native Read is wrong because you want a parsed, structured pass/fail with error/warning lists and step/agent counts rather than eyeballing the file. For just reading the file, native Read is fine. (Basic checks today — a full workflow-schema validator is a tracked follow-up.)',
|
|
818
|
+
category: 'workflow',
|
|
819
|
+
inputSchema: {
|
|
820
|
+
type: 'object',
|
|
821
|
+
properties: {
|
|
822
|
+
file: { type: 'string', description: 'Path to the workflow definition file' },
|
|
823
|
+
strict: { type: 'boolean', description: 'Treat warnings as errors' },
|
|
824
|
+
},
|
|
825
|
+
required: ['file'],
|
|
826
|
+
},
|
|
827
|
+
handler: async (input) => {
|
|
828
|
+
const file = String(input.file ?? '');
|
|
829
|
+
const errors = [];
|
|
830
|
+
const warnings = [];
|
|
831
|
+
let stages = 0;
|
|
832
|
+
let agents = 0;
|
|
833
|
+
try {
|
|
834
|
+
if (!file || !existsSync(file)) {
|
|
835
|
+
errors.push({ line: 0, message: `File not found: ${file || '(empty)'}`, severity: 'error' });
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
const raw = readFileSync(file, 'utf-8');
|
|
839
|
+
let doc = null;
|
|
840
|
+
if (/\.ya?ml$/i.test(file)) {
|
|
841
|
+
warnings.push({ line: 0, message: 'YAML workflow files are not schema-validated yet — only JSON is fully checked (#1916 follow-up)' });
|
|
842
|
+
try {
|
|
843
|
+
doc = JSON.parse(raw);
|
|
844
|
+
}
|
|
845
|
+
catch { /* not JSON; leave doc null */ }
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
doc = JSON.parse(raw);
|
|
849
|
+
}
|
|
850
|
+
const d = (doc ?? {});
|
|
851
|
+
const steps = (d.steps ?? d.stages ?? d.tasks);
|
|
852
|
+
if (!Array.isArray(steps)) {
|
|
853
|
+
errors.push({ line: 0, message: 'Workflow has no `steps` / `stages` / `tasks` array', severity: 'error' });
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
stages = steps.length;
|
|
857
|
+
const agentSet = new Set();
|
|
858
|
+
steps.forEach((s, i) => {
|
|
859
|
+
const step = (s ?? {});
|
|
860
|
+
const a = (step.agent ?? step.agentType ?? step.agent_type);
|
|
861
|
+
if (a)
|
|
862
|
+
agentSet.add(String(a));
|
|
863
|
+
else
|
|
864
|
+
warnings.push({ line: i + 1, message: `step ${i + 1} ("${step.name ?? step.id ?? i + 1}") names no agent` });
|
|
865
|
+
});
|
|
866
|
+
agents = agentSet.size;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
catch (e) {
|
|
871
|
+
errors.push({ line: 0, message: `Parse error: ${e.message}`, severity: 'error' });
|
|
872
|
+
}
|
|
873
|
+
const valid = errors.length === 0 && (!input.strict || warnings.length === 0);
|
|
874
|
+
return {
|
|
875
|
+
valid,
|
|
876
|
+
file,
|
|
877
|
+
errors,
|
|
878
|
+
warnings,
|
|
879
|
+
stats: { stages, agents, estimatedDuration: stages > 0 ? `~${stages * 30}s` : 'unknown' },
|
|
880
|
+
};
|
|
881
|
+
},
|
|
882
|
+
},
|
|
775
883
|
];
|
|
776
884
|
//# sourceMappingURL=workflow-tools.js.map
|
|
@@ -12,9 +12,9 @@ import { coverageRoute, coverageSuggest, coverageGaps, } from './coverage-router
|
|
|
12
12
|
* Uses ruvector's hooks_coverage_route when available.
|
|
13
13
|
*/
|
|
14
14
|
export const hooksCoverageRoute = {
|
|
15
|
-
name: '
|
|
15
|
+
name: 'hooks_coverage-route',
|
|
16
16
|
description: 'Route task to agents based on test coverage gaps (ruvector integration)',
|
|
17
|
-
category: '
|
|
17
|
+
category: 'hooks',
|
|
18
18
|
tags: ['coverage', 'routing', 'testing', 'ruvector'],
|
|
19
19
|
inputSchema: {
|
|
20
20
|
type: 'object',
|
|
@@ -57,9 +57,9 @@ export const hooksCoverageRoute = {
|
|
|
57
57
|
* Uses ruvector's hooks_coverage_suggest when available.
|
|
58
58
|
*/
|
|
59
59
|
export const hooksCoverageSuggest = {
|
|
60
|
-
name: '
|
|
60
|
+
name: 'hooks_coverage-suggest',
|
|
61
61
|
description: 'Suggest coverage improvements for a path (ruvector integration)',
|
|
62
|
-
category: '
|
|
62
|
+
category: 'hooks',
|
|
63
63
|
tags: ['coverage', 'suggestions', 'testing', 'ruvector'],
|
|
64
64
|
inputSchema: {
|
|
65
65
|
type: 'object',
|
|
@@ -107,9 +107,9 @@ export const hooksCoverageSuggest = {
|
|
|
107
107
|
* Lists all coverage gaps in the project with agent assignments.
|
|
108
108
|
*/
|
|
109
109
|
export const hooksCoverageGaps = {
|
|
110
|
-
name: '
|
|
110
|
+
name: 'hooks_coverage-gaps',
|
|
111
111
|
description: 'List all coverage gaps with priority scoring and agent assignments',
|
|
112
|
-
category: '
|
|
112
|
+
category: 'hooks',
|
|
113
113
|
tags: ['coverage', 'gaps', 'testing', 'analysis'],
|
|
114
114
|
inputSchema: {
|
|
115
115
|
type: 'object',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.7.0-alpha.
|
|
3
|
+
"version": "3.7.0-alpha.26",
|
|
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",
|