swarm-control 0.1.3 → 0.1.4
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/dist/installer.js +2 -2
- package/dist/installer.js.map +1 -1
- package/package.json +1 -2
- package/src/plugin/config.ts +0 -56
- package/src/plugin/coordinator.ts +0 -195
- package/src/plugin/decomposer.ts +0 -87
- package/src/plugin/file-lock.ts +0 -66
- package/src/plugin/index.ts +0 -172
- package/src/plugin/state.ts +0 -81
- package/src/plugin/types.ts +0 -47
- package/src/plugin/worker.ts +0 -50
package/dist/installer.js
CHANGED
|
@@ -9,7 +9,7 @@ const __dirname = path.dirname(__filename);
|
|
|
9
9
|
const PACKAGE_ROOT = __dirname.includes('node_modules')
|
|
10
10
|
? path.join(__dirname, '..')
|
|
11
11
|
: path.join(__dirname, '..');
|
|
12
|
-
const
|
|
12
|
+
const PLUGIN_DIST_PATH = path.join(PACKAGE_ROOT, 'dist/plugin');
|
|
13
13
|
const PLUGIN_TEMPLATE = path.join(PACKAGE_ROOT, 'templates/swarm-control.template.ts');
|
|
14
14
|
const PLUGIN_TARGET = path.join(process.env.HOME, '.opencode/plugin/swarm-control.ts');
|
|
15
15
|
const OPENCODE_CONFIG_DIR = path.join(process.env.HOME, '.config/opencode');
|
|
@@ -22,7 +22,7 @@ export async function setupPlugin() {
|
|
|
22
22
|
const template = await fs.readFile(PLUGIN_TEMPLATE, 'utf-8');
|
|
23
23
|
// Replace placeholder with the actual package path
|
|
24
24
|
// Use .js extension since this is an ESM module
|
|
25
|
-
const packagePath = path.join(PACKAGE_ROOT, '
|
|
25
|
+
const packagePath = path.join(PACKAGE_ROOT, 'dist/plugin/index.js');
|
|
26
26
|
const pluginContent = template.replace('PLACEHOLDER_PACKAGE_PATH', packagePath);
|
|
27
27
|
// Ensure plugin directory exists
|
|
28
28
|
const pluginDir = path.dirname(PLUGIN_TARGET);
|
package/dist/installer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAA;AACzB,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAA;AACnC,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,GAAG,MAAM,KAAK,CAAA;AAErB,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACjD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;AAE1C,wEAAwE;AACxE,MAAM,YAAY,GAAG,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC;IACrD,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC;IAC5B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;AAE9B,MAAM,
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarm-control",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for multi-agent swarm coordination",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist/",
|
|
12
|
-
"src/plugin/",
|
|
13
12
|
"templates/",
|
|
14
13
|
"README.md",
|
|
15
14
|
"LICENSE"
|
package/src/plugin/config.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { SwarmConfigSchema, type SwarmConfig } from './types.js'
|
|
4
|
-
|
|
5
|
-
const OPENCODE_CONFIG_DIR = path.join(process.env.HOME!, '.config/opencode')
|
|
6
|
-
const OPENCODE_CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, 'opencode.json')
|
|
7
|
-
const SWARM_CONFIG_KEY = 'swarm'
|
|
8
|
-
|
|
9
|
-
export async function getConfig(): Promise<SwarmConfig | null> {
|
|
10
|
-
try {
|
|
11
|
-
const configExists = await fs.pathExists(OPENCODE_CONFIG_FILE)
|
|
12
|
-
if (!configExists) return null
|
|
13
|
-
|
|
14
|
-
const data = await fs.readFile(OPENCODE_CONFIG_FILE, 'utf-8')
|
|
15
|
-
const parsed = JSON.parse(data)
|
|
16
|
-
|
|
17
|
-
if (!parsed[SWARM_CONFIG_KEY]) return null
|
|
18
|
-
|
|
19
|
-
return SwarmConfigSchema.parse(parsed[SWARM_CONFIG_KEY])
|
|
20
|
-
} catch {
|
|
21
|
-
return null
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function setConfig(config: SwarmConfig): Promise<void> {
|
|
26
|
-
await fs.ensureDir(OPENCODE_CONFIG_DIR)
|
|
27
|
-
|
|
28
|
-
let existingConfig = {}
|
|
29
|
-
try {
|
|
30
|
-
const configExists = await fs.pathExists(OPENCODE_CONFIG_FILE)
|
|
31
|
-
if (configExists) {
|
|
32
|
-
existingConfig = await fs.readJson(OPENCODE_CONFIG_FILE)
|
|
33
|
-
}
|
|
34
|
-
} catch {
|
|
35
|
-
// Config doesn't exist or is invalid, start fresh
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const newConfig = {
|
|
39
|
-
...existingConfig,
|
|
40
|
-
[SWARM_CONFIG_KEY]: config
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
await fs.writeJson(OPENCODE_CONFIG_FILE, newConfig, { spaces: 2 })
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function clearConfig(): Promise<void> {
|
|
47
|
-
const configExists = await fs.pathExists(OPENCODE_CONFIG_FILE)
|
|
48
|
-
if (!configExists) return
|
|
49
|
-
|
|
50
|
-
const config = await fs.readJson(OPENCODE_CONFIG_FILE)
|
|
51
|
-
|
|
52
|
-
if (config[SWARM_CONFIG_KEY]) {
|
|
53
|
-
delete config[SWARM_CONFIG_KEY]
|
|
54
|
-
await fs.writeJson(OPENCODE_CONFIG_FILE, config, { spaces: 2 })
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import type { SwarmConfig, Subtask, SwarmTask, WorkerInfo } from './types.js'
|
|
2
|
-
import { lockManager } from './file-lock.js'
|
|
3
|
-
import { StateManager } from './state.js'
|
|
4
|
-
import { executeSubtask } from './worker.js'
|
|
5
|
-
|
|
6
|
-
export function createSwarmCoordinator(
|
|
7
|
-
client: any,
|
|
8
|
-
directory: string,
|
|
9
|
-
config: SwarmConfig
|
|
10
|
-
) {
|
|
11
|
-
const state = new StateManager()
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
async execute(task: string, decomposition: any, maxWorkers: number) {
|
|
15
|
-
const taskId = `task-${Date.now()}`
|
|
16
|
-
|
|
17
|
-
const swarmTask: SwarmTask = {
|
|
18
|
-
id: taskId,
|
|
19
|
-
description: task,
|
|
20
|
-
status: 'in_progress',
|
|
21
|
-
subtasks: decomposition.subtasks,
|
|
22
|
-
createdAt: Date.now()
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
await state.setCurrentTask(swarmTask)
|
|
26
|
-
|
|
27
|
-
// Create worker sessions (child sessions)
|
|
28
|
-
const workers: WorkerInfo[] = []
|
|
29
|
-
for (let i = 0; i < decomposition.numWorkers; i++) {
|
|
30
|
-
try {
|
|
31
|
-
const session = await client.session.create({
|
|
32
|
-
body: { title: `Swarm Worker ${i + 1}` }
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
workers.push({
|
|
36
|
-
id: `worker-${i}`,
|
|
37
|
-
sessionId: session.id,
|
|
38
|
-
status: 'idle'
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
await state.updateWorker(workers[i])
|
|
42
|
-
} catch (error) {
|
|
43
|
-
await client.app.log({
|
|
44
|
-
service: 'swarm-control',
|
|
45
|
-
level: 'error',
|
|
46
|
-
message: `Failed to create worker ${i}`,
|
|
47
|
-
extra: { error: error instanceof Error ? error.message : 'Unknown error' }
|
|
48
|
-
})
|
|
49
|
-
throw new Error(`Failed to create worker session: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Execute subtasks respecting dependencies
|
|
54
|
-
const results: string[] = []
|
|
55
|
-
const completedSubtasks = new Set<string>()
|
|
56
|
-
|
|
57
|
-
for (const subtask of decomposition.subtasks) {
|
|
58
|
-
// Wait for dependencies
|
|
59
|
-
if (subtask.dependsOn && subtask.dependsOn.length > 0) {
|
|
60
|
-
await this.waitForDependencies(state, taskId, subtask.dependsOn, completedSubtasks)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Acquire file locks
|
|
64
|
-
for (const file of subtask.files) {
|
|
65
|
-
const worker = this.findIdleWorker(workers)
|
|
66
|
-
if (!worker) {
|
|
67
|
-
throw new Error('No available workers')
|
|
68
|
-
}
|
|
69
|
-
await lockManager.acquireLock(file, worker.id)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Assign to worker
|
|
73
|
-
const worker = this.findIdleWorker(workers)
|
|
74
|
-
if (!worker) {
|
|
75
|
-
// Release locks and throw error
|
|
76
|
-
for (const file of subtask.files) {
|
|
77
|
-
lockManager.releaseLock(file)
|
|
78
|
-
}
|
|
79
|
-
throw new Error('No available workers')
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
worker.status = 'working'
|
|
83
|
-
worker.currentSubtaskId = subtask.id
|
|
84
|
-
await state.updateWorker(worker)
|
|
85
|
-
|
|
86
|
-
await state.updateSubtaskStatus(taskId, subtask.id, 'in_progress')
|
|
87
|
-
|
|
88
|
-
// Execute subtask
|
|
89
|
-
try {
|
|
90
|
-
const result = await executeSubtask(client, worker, subtask, config.model)
|
|
91
|
-
results.push(result)
|
|
92
|
-
|
|
93
|
-
await state.updateSubtaskStatus(taskId, subtask.id, 'completed', result)
|
|
94
|
-
completedSubtasks.add(subtask.id)
|
|
95
|
-
} catch (error) {
|
|
96
|
-
const errorMsg = error instanceof Error ? error.message : 'Unknown error'
|
|
97
|
-
await client.app.log({
|
|
98
|
-
service: 'swarm-control',
|
|
99
|
-
level: 'error',
|
|
100
|
-
message: `Subtask ${subtask.id} failed`,
|
|
101
|
-
extra: { error: errorMsg, subtaskId: subtask.id }
|
|
102
|
-
})
|
|
103
|
-
await state.updateSubtaskStatus(taskId, subtask.id, 'failed', undefined, errorMsg)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Release file locks
|
|
107
|
-
for (const file of subtask.files) {
|
|
108
|
-
lockManager.releaseLock(file)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
worker.status = 'idle'
|
|
112
|
-
worker.currentSubtaskId = undefined
|
|
113
|
-
await state.updateWorker(worker)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
swarmTask.status = 'completed'
|
|
117
|
-
swarmTask.completedAt = Date.now()
|
|
118
|
-
await state.setCurrentTask(swarmTask)
|
|
119
|
-
|
|
120
|
-
return this.formatSummary(swarmTask, decomposition)
|
|
121
|
-
},
|
|
122
|
-
|
|
123
|
-
findIdleWorker(workers: WorkerInfo[]): WorkerInfo | undefined {
|
|
124
|
-
return workers.find(w => w.status === 'idle')
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
async waitForDependencies(
|
|
128
|
-
state: StateManager,
|
|
129
|
-
taskId: string,
|
|
130
|
-
depIds: string[],
|
|
131
|
-
completedSubtasks: Set<string>
|
|
132
|
-
) {
|
|
133
|
-
let attempts = 0
|
|
134
|
-
const maxAttempts = 300 // 5 minutes timeout
|
|
135
|
-
|
|
136
|
-
while (attempts < maxAttempts) {
|
|
137
|
-
const task = await state.getCurrentTask()
|
|
138
|
-
if (!task) break
|
|
139
|
-
|
|
140
|
-
const allComplete = depIds.every(id => {
|
|
141
|
-
return completedSubtasks.has(id) || task.subtasks.find(s => s.id === id)?.status === 'completed'
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
if (allComplete) break
|
|
145
|
-
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
146
|
-
attempts++
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
|
|
150
|
-
formatSummary(task: SwarmTask, decomposition: any): string {
|
|
151
|
-
const completed = task.subtasks.filter((s: any) => s.status === 'completed').length
|
|
152
|
-
const failed = task.subtasks.filter((s: any) => s.status === 'failed').length
|
|
153
|
-
|
|
154
|
-
let summary = `## Swarm Task Completed\n\n`
|
|
155
|
-
summary += `**Task:** ${task.description}\n\n`
|
|
156
|
-
summary += `**Subtasks:** ${completed}/${task.subtasks.length} completed`
|
|
157
|
-
|
|
158
|
-
if (failed > 0) {
|
|
159
|
-
summary += ` (${failed} failed)`
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
summary += `\n\n`
|
|
163
|
-
|
|
164
|
-
if (decomposition.analysis) {
|
|
165
|
-
summary += `**Approach:** ${decomposition.analysis}\n\n`
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
summary += `**Details:**\n\n`
|
|
169
|
-
|
|
170
|
-
for (const subtask of task.subtasks) {
|
|
171
|
-
const statusEmoji = subtask.status === 'completed' ? '✅' :
|
|
172
|
-
subtask.status === 'failed' ? '❌' :
|
|
173
|
-
subtask.status === 'in_progress' ? '⏳' : '⏸️'
|
|
174
|
-
|
|
175
|
-
summary += `${statusEmoji} **${subtask.description}**\n`
|
|
176
|
-
|
|
177
|
-
if (subtask.files && subtask.files.length > 0) {
|
|
178
|
-
summary += ` Files: ${subtask.files.join(', ')}\n`
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (subtask.result) {
|
|
182
|
-
summary += ` Result: ${subtask.result}\n`
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (subtask.error) {
|
|
186
|
-
summary += ` Error: ${subtask.error}\n`
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
summary += '\n'
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return summary.trim()
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
package/src/plugin/decomposer.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
|
|
3
|
-
const DecompositionResponseSchema = z.object({
|
|
4
|
-
analysis: z.string(),
|
|
5
|
-
numWorkers: z.number().min(1).max(4),
|
|
6
|
-
subtasks: z.array(z.object({
|
|
7
|
-
id: z.string(),
|
|
8
|
-
description: z.string(),
|
|
9
|
-
files: z.array(z.string()),
|
|
10
|
-
dependsOn: z.array(z.string()).optional()
|
|
11
|
-
}))
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
export async function decomposeTask(
|
|
15
|
-
client: any,
|
|
16
|
-
model: string,
|
|
17
|
-
task: string,
|
|
18
|
-
context: any
|
|
19
|
-
) {
|
|
20
|
-
// Get file list from project
|
|
21
|
-
const filesResult = await client.find.files({
|
|
22
|
-
query: { type: 'file', limit: 200 }
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
const files = filesResult.data || []
|
|
26
|
-
const fileList = files.length > 0 ? files.join('\n') : 'No files found'
|
|
27
|
-
|
|
28
|
-
const prompt = `You are a task decomposition system. Analyze this programming task:
|
|
29
|
-
|
|
30
|
-
TASK: ${task}
|
|
31
|
-
|
|
32
|
-
PROJECT FILES:
|
|
33
|
-
${fileList}
|
|
34
|
-
|
|
35
|
-
Your goal:
|
|
36
|
-
1. Identify which files need to be modified
|
|
37
|
-
2. Group related files into logical subtasks (maximum 4 workers)
|
|
38
|
-
3. Determine if subtasks can be executed in parallel
|
|
39
|
-
4. Identify dependencies between subtasks
|
|
40
|
-
|
|
41
|
-
RESPONSE FORMAT (JSON only):
|
|
42
|
-
{
|
|
43
|
-
"analysis": "Brief explanation of approach",
|
|
44
|
-
"numWorkers": number (1-4),
|
|
45
|
-
"subtasks": [
|
|
46
|
-
{
|
|
47
|
-
"id": "unique_id",
|
|
48
|
-
"description": "Clear description",
|
|
49
|
-
"files": ["path/to/file1.ts"],
|
|
50
|
-
"dependsOn": ["id_of_dependency"]
|
|
51
|
-
}
|
|
52
|
-
]
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
IMPORTANT:
|
|
56
|
-
- Use the exact file paths from the PROJECT FILES list above
|
|
57
|
-
- Maximum 4 workers total
|
|
58
|
-
- Only include files that actually need to be modified
|
|
59
|
-
- Provide unique IDs for each subtask`
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const result = await client.session.prompt({
|
|
63
|
-
path: { id: context.sessionID },
|
|
64
|
-
body: {
|
|
65
|
-
model: {
|
|
66
|
-
providerID: model.split('/')[0],
|
|
67
|
-
modelID: model.split('/')[1]
|
|
68
|
-
},
|
|
69
|
-
parts: [{ type: 'text', text: prompt }]
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
const responseText = result.parts?.find((p: any) => p.type === 'text')?.text || '{}'
|
|
74
|
-
const jsonMatch = responseText.match(/\{[\s\S]*\}/)
|
|
75
|
-
const jsonStr = jsonMatch ? jsonMatch[0] : responseText
|
|
76
|
-
|
|
77
|
-
return DecompositionResponseSchema.parse(JSON.parse(jsonStr))
|
|
78
|
-
} catch (error) {
|
|
79
|
-
await client.app.log({
|
|
80
|
-
service: 'swarm-control',
|
|
81
|
-
level: 'error',
|
|
82
|
-
message: 'Decomposition failed',
|
|
83
|
-
extra: { error: error instanceof Error ? error.message : 'Unknown error', task }
|
|
84
|
-
})
|
|
85
|
-
throw new Error(`Failed to decompose task: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
86
|
-
}
|
|
87
|
-
}
|
package/src/plugin/file-lock.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { FileLock } from './types.js'
|
|
2
|
-
|
|
3
|
-
class FileLockManager {
|
|
4
|
-
private locks: Map<string, FileLock> = new Map()
|
|
5
|
-
private waitQueue: Map<string, Array<(released: boolean) => void>> = new Map()
|
|
6
|
-
|
|
7
|
-
async acquireLock(filePath: string, workerId: string): Promise<boolean> {
|
|
8
|
-
if (!this.locks.has(filePath)) {
|
|
9
|
-
this.locks.set(filePath, {
|
|
10
|
-
filePath,
|
|
11
|
-
lockedBy: workerId,
|
|
12
|
-
lockedAt: Date.now()
|
|
13
|
-
})
|
|
14
|
-
return true
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return new Promise((resolve) => {
|
|
18
|
-
if (!this.waitQueue.has(filePath)) {
|
|
19
|
-
this.waitQueue.set(filePath, [])
|
|
20
|
-
}
|
|
21
|
-
this.waitQueue.get(filePath)!.push(resolve)
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
releaseLock(filePath: string): void {
|
|
26
|
-
this.locks.delete(filePath)
|
|
27
|
-
|
|
28
|
-
const queue = this.waitQueue.get(filePath)
|
|
29
|
-
if (queue && queue.length > 0) {
|
|
30
|
-
const next = queue.shift()!
|
|
31
|
-
this.locks.set(filePath, {
|
|
32
|
-
filePath,
|
|
33
|
-
lockedBy: 'pending',
|
|
34
|
-
lockedAt: Date.now()
|
|
35
|
-
})
|
|
36
|
-
next(true)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (queue && queue.length === 0) {
|
|
40
|
-
this.waitQueue.delete(filePath)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
isLocked(filePath: string): boolean {
|
|
45
|
-
return this.locks.has(filePath)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
getLock(filePath: string): FileLock | undefined {
|
|
49
|
-
return this.locks.get(filePath)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
releaseAllLocksForWorker(workerId: string): void {
|
|
53
|
-
for (const [filePath, lock] of this.locks.entries()) {
|
|
54
|
-
if (lock.lockedBy === workerId) {
|
|
55
|
-
this.releaseLock(filePath)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
clearAll(): void {
|
|
61
|
-
this.locks.clear()
|
|
62
|
-
this.waitQueue.clear()
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const lockManager = new FileLockManager()
|
package/src/plugin/index.ts
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from '@opencode-ai/plugin'
|
|
2
|
-
import { tool } from '@opencode-ai/plugin/tool'
|
|
3
|
-
import type { ToolContext } from '@opencode-ai/plugin/tool'
|
|
4
|
-
import { getConfig, setConfig } from './config.js'
|
|
5
|
-
import { decomposeTask } from './decomposer.js'
|
|
6
|
-
import { createSwarmCoordinator } from './coordinator.js'
|
|
7
|
-
import { StateManager } from './state.js'
|
|
8
|
-
import { lockManager } from './file-lock.js'
|
|
9
|
-
|
|
10
|
-
export const SwarmControlPlugin: Plugin = async ({ client, directory }: any) => {
|
|
11
|
-
const state = new StateManager()
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
tool: {
|
|
15
|
-
swarm_model: tool({
|
|
16
|
-
description: 'Configure model to use for swarm task decomposition (required before using swarm_spawn)',
|
|
17
|
-
args: {
|
|
18
|
-
model: tool.schema.string().describe('Model identifier (e.g., anthropic/claude-3-5-sonnet-20241022)')
|
|
19
|
-
},
|
|
20
|
-
async execute({ model }: any) {
|
|
21
|
-
await setConfig({ model })
|
|
22
|
-
return `Swarm model set to: ${model}`
|
|
23
|
-
}
|
|
24
|
-
}),
|
|
25
|
-
|
|
26
|
-
swarm_spawn: tool({
|
|
27
|
-
description: 'Spawn a swarm of subagents to tackle a complex programming task',
|
|
28
|
-
args: {
|
|
29
|
-
task: tool.schema.string().describe('The task description to decompose and execute'),
|
|
30
|
-
maxWorkers: tool.schema.number().min(1).max(4).optional().describe('Maximum number of workers (default: 4)')
|
|
31
|
-
},
|
|
32
|
-
async execute({ task, maxWorkers = 4 }: any, context: ToolContext) {
|
|
33
|
-
// Check if model is configured
|
|
34
|
-
const config = await getConfig()
|
|
35
|
-
if (!config) {
|
|
36
|
-
throw new Error('Swarm model not configured. Please run /swarm_model <model> first to set the model for task decomposition.')
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Check if there's already a running task
|
|
40
|
-
const currentTask = await state.getCurrentTask()
|
|
41
|
-
if (currentTask && currentTask.status === 'in_progress') {
|
|
42
|
-
throw new Error(`A swarm task is already in progress. Task ID: ${currentTask.id}. Use /swarm_status to check progress or wait for it to complete.`)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Clear any stale locks
|
|
46
|
-
lockManager.clearAll()
|
|
47
|
-
|
|
48
|
-
let decompositionResult
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
// Decompose task
|
|
52
|
-
decompositionResult = await decomposeTask(client, config.model, task, context)
|
|
53
|
-
|
|
54
|
-
// Validate numWorkers doesn't exceed maxWorkers
|
|
55
|
-
const numWorkers = Math.min(decompositionResult.numWorkers, maxWorkers)
|
|
56
|
-
|
|
57
|
-
// Create and run coordinator
|
|
58
|
-
const coordinator = createSwarmCoordinator(client, directory, config)
|
|
59
|
-
const result = await coordinator.execute(task, {
|
|
60
|
-
...decompositionResult,
|
|
61
|
-
numWorkers
|
|
62
|
-
}, numWorkers)
|
|
63
|
-
|
|
64
|
-
return result
|
|
65
|
-
|
|
66
|
-
} catch (error) {
|
|
67
|
-
// Clean up on error
|
|
68
|
-
lockManager.clearAll()
|
|
69
|
-
await state.clearTask()
|
|
70
|
-
|
|
71
|
-
const errorMsg = error instanceof Error ? error.message : 'Unknown error'
|
|
72
|
-
throw new Error(`Swarm execution failed: ${errorMsg}`)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}),
|
|
76
|
-
|
|
77
|
-
swarm_status: tool({
|
|
78
|
-
description: 'Show status of current or recent swarm tasks',
|
|
79
|
-
args: {
|
|
80
|
-
taskId: tool.schema.string().optional().describe('Specific task ID to check (if omitted, shows current task)')
|
|
81
|
-
},
|
|
82
|
-
async execute({ taskId }: any) {
|
|
83
|
-
if (taskId) {
|
|
84
|
-
// For specific task ID, we'd need to implement task history
|
|
85
|
-
return `Task history not yet implemented. Use without arguments to check current task status.`
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const currentTask = await state.getCurrentTask()
|
|
89
|
-
|
|
90
|
-
if (!currentTask) {
|
|
91
|
-
return 'No active swarm task. Use /swarm_spawn to start a new task.'
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let status = `## Swarm Task Status\n\n`
|
|
95
|
-
status += `**Task ID:** ${currentTask.id}\n`
|
|
96
|
-
status += `**Description:** ${currentTask.description}\n`
|
|
97
|
-
status += `**Status:** ${currentTask.status.toUpperCase()}\n`
|
|
98
|
-
|
|
99
|
-
if (currentTask.completedAt) {
|
|
100
|
-
const duration = Math.round((currentTask.completedAt - currentTask.createdAt) / 1000)
|
|
101
|
-
status += `**Duration:** ${duration}s\n`
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
status += `\n**Subtasks:**\n\n`
|
|
105
|
-
|
|
106
|
-
const completed = currentTask.subtasks.filter((s: any) => s.status === 'completed').length
|
|
107
|
-
const failed = currentTask.subtasks.filter((s: any) => s.status === 'failed').length
|
|
108
|
-
const inProgress = currentTask.subtasks.filter((s: any) => s.status === 'in_progress').length
|
|
109
|
-
|
|
110
|
-
status += `- Total: ${currentTask.subtasks.length}\n`
|
|
111
|
-
status += `- ✅ Completed: ${completed}\n`
|
|
112
|
-
if (inProgress > 0) {
|
|
113
|
-
status += `- ⏳ In Progress: ${inProgress}\n`
|
|
114
|
-
}
|
|
115
|
-
if (failed > 0) {
|
|
116
|
-
status += `- ❌ Failed: ${failed}\n`
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
status += `\n**Details:**\n\n`
|
|
120
|
-
|
|
121
|
-
for (const subtask of currentTask.subtasks) {
|
|
122
|
-
const statusEmoji = subtask.status === 'completed' ? '✅' :
|
|
123
|
-
subtask.status === 'failed' ? '❌' :
|
|
124
|
-
subtask.status === 'in_progress' ? '⏳' : '⏸️'
|
|
125
|
-
|
|
126
|
-
status += `${statusEmoji} **${subtask.description}**\n`
|
|
127
|
-
|
|
128
|
-
if (subtask.files && subtask.files.length > 0) {
|
|
129
|
-
status += ` Files: ${subtask.files.join(', ')}\n`
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (subtask.workerId) {
|
|
133
|
-
status += ` Worker: ${subtask.workerId}\n`
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (subtask.result) {
|
|
137
|
-
status += ` Result: ${subtask.result}\n`
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (subtask.error) {
|
|
141
|
-
status += ` Error: ${subtask.error}\n`
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
status += '\n'
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Show worker status
|
|
148
|
-
const workers = await state.getWorkers()
|
|
149
|
-
if (workers.length > 0) {
|
|
150
|
-
status += `**Workers:**\n\n`
|
|
151
|
-
|
|
152
|
-
for (const worker of workers) {
|
|
153
|
-
const statusEmoji = worker.status === 'idle' ? '💤' :
|
|
154
|
-
worker.status === 'working' ? '🔨' :
|
|
155
|
-
worker.status === 'waiting_for_lock' ? '⏳' : '❌'
|
|
156
|
-
|
|
157
|
-
status += `${statusEmoji} **${worker.id}** (${worker.status})`
|
|
158
|
-
|
|
159
|
-
if (worker.currentSubtaskId) {
|
|
160
|
-
status += ` - Working on: ${worker.currentSubtaskId}`
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
status += '\n'
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return status.trim()
|
|
168
|
-
}
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
package/src/plugin/state.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import type { SwarmState, SwarmTask, WorkerInfo } from './types.js'
|
|
4
|
-
|
|
5
|
-
const STATE_DIR = path.join(process.env.HOME!, '.config/swarm-control')
|
|
6
|
-
const STATE_FILE = path.join(STATE_DIR, 'state.json')
|
|
7
|
-
|
|
8
|
-
export class StateManager {
|
|
9
|
-
async getState(): Promise<SwarmState> {
|
|
10
|
-
try {
|
|
11
|
-
const data = await fs.readFile(STATE_FILE, 'utf-8')
|
|
12
|
-
const parsed = JSON.parse(data)
|
|
13
|
-
return {
|
|
14
|
-
currentTask: parsed.currentTask,
|
|
15
|
-
workers: parsed.workers || [],
|
|
16
|
-
fileLocks: parsed.fileLocks || []
|
|
17
|
-
}
|
|
18
|
-
} catch {
|
|
19
|
-
return {
|
|
20
|
-
currentTask: null,
|
|
21
|
-
workers: [],
|
|
22
|
-
fileLocks: []
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async setState(state: SwarmState): Promise<void> {
|
|
28
|
-
await fs.ensureDir(STATE_DIR)
|
|
29
|
-
const tempFile = `${STATE_FILE}.tmp`
|
|
30
|
-
|
|
31
|
-
await fs.writeJSON(tempFile, state, { spaces: 2 })
|
|
32
|
-
await fs.move(tempFile, STATE_FILE, { overwrite: true })
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async getCurrentTask(): Promise<SwarmTask | null> {
|
|
36
|
-
const state = await this.getState()
|
|
37
|
-
return state.currentTask
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async setCurrentTask(task: SwarmTask | null): Promise<void> {
|
|
41
|
-
const state = await this.getState()
|
|
42
|
-
state.currentTask = task
|
|
43
|
-
await this.setState(state)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async updateSubtaskStatus(taskId: string, subtaskId: string, status: 'pending' | 'in_progress' | 'completed' | 'failed', result?: string, error?: string): Promise<void> {
|
|
47
|
-
const state = await this.getState()
|
|
48
|
-
if (!state.currentTask || state.currentTask.id !== taskId) return
|
|
49
|
-
|
|
50
|
-
const subtask = state.currentTask.subtasks.find(s => s.id === subtaskId)
|
|
51
|
-
if (subtask) {
|
|
52
|
-
subtask.status = status
|
|
53
|
-
if (result) subtask.result = result
|
|
54
|
-
if (error) subtask.error = error
|
|
55
|
-
await this.setState(state)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async getWorkers(): Promise<WorkerInfo[]> {
|
|
60
|
-
const state = await this.getState()
|
|
61
|
-
return state.workers
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async updateWorker(worker: WorkerInfo): Promise<void> {
|
|
65
|
-
const state = await this.getState()
|
|
66
|
-
const index = state.workers.findIndex((w: WorkerInfo) => w.id === worker.id)
|
|
67
|
-
if (index >= 0) {
|
|
68
|
-
state.workers[index] = worker
|
|
69
|
-
} else {
|
|
70
|
-
state.workers.push(worker)
|
|
71
|
-
}
|
|
72
|
-
await this.setState(state)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async clearTask(): Promise<void> {
|
|
76
|
-
const state = await this.getState()
|
|
77
|
-
state.currentTask = null
|
|
78
|
-
state.workers = []
|
|
79
|
-
await this.setState(state)
|
|
80
|
-
}
|
|
81
|
-
}
|
package/src/plugin/types.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
|
|
3
|
-
export const SwarmConfigSchema = z.object({
|
|
4
|
-
model: z.string().describe('Model to use for decomposition'),
|
|
5
|
-
})
|
|
6
|
-
|
|
7
|
-
export type SwarmConfig = z.infer<typeof SwarmConfigSchema>
|
|
8
|
-
|
|
9
|
-
export interface SwarmTask {
|
|
10
|
-
id: string
|
|
11
|
-
description: string
|
|
12
|
-
status: 'pending' | 'in_progress' | 'completed' | 'failed'
|
|
13
|
-
subtasks: Subtask[]
|
|
14
|
-
createdAt: number
|
|
15
|
-
completedAt?: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface Subtask {
|
|
19
|
-
id: string
|
|
20
|
-
description: string
|
|
21
|
-
files: string[]
|
|
22
|
-
status: 'pending' | 'in_progress' | 'completed' | 'failed'
|
|
23
|
-
workerId?: string
|
|
24
|
-
sessionId?: string
|
|
25
|
-
dependsOn?: string[]
|
|
26
|
-
result?: string
|
|
27
|
-
error?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface FileLock {
|
|
31
|
-
filePath: string
|
|
32
|
-
lockedBy: string
|
|
33
|
-
lockedAt: number
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface SwarmState {
|
|
37
|
-
currentTask: SwarmTask | null
|
|
38
|
-
workers: WorkerInfo[]
|
|
39
|
-
fileLocks: FileLock[]
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface WorkerInfo {
|
|
43
|
-
id: string
|
|
44
|
-
sessionId: string
|
|
45
|
-
status: 'idle' | 'working' | 'waiting_for_lock' | 'error'
|
|
46
|
-
currentSubtaskId?: string
|
|
47
|
-
}
|
package/src/plugin/worker.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { WorkerInfo, Subtask } from './types.js'
|
|
2
|
-
|
|
3
|
-
export async function executeSubtask(
|
|
4
|
-
client: any,
|
|
5
|
-
worker: WorkerInfo,
|
|
6
|
-
subtask: Subtask,
|
|
7
|
-
model: string
|
|
8
|
-
): Promise<string> {
|
|
9
|
-
const prompt = `You are a specialized worker agent. Execute this specific task:
|
|
10
|
-
|
|
11
|
-
${subtask.description}
|
|
12
|
-
|
|
13
|
-
Files to modify:
|
|
14
|
-
${subtask.files.map((f: string) => `- ${f}`).join('\n')}
|
|
15
|
-
|
|
16
|
-
Instructions:
|
|
17
|
-
1. Work on the specified files
|
|
18
|
-
2. Complete the task described above
|
|
19
|
-
3. Return a summary of changes made
|
|
20
|
-
4. If you encounter any errors, describe them clearly
|
|
21
|
-
|
|
22
|
-
IMPORTANT:
|
|
23
|
-
- Only modify the files listed above
|
|
24
|
-
- Be specific about what changes you made
|
|
25
|
-
- If a file doesn't exist, mention it in your response`
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const result = await client.session.prompt({
|
|
29
|
-
path: { id: worker.sessionId },
|
|
30
|
-
body: {
|
|
31
|
-
model: {
|
|
32
|
-
providerID: model.split('/')[0],
|
|
33
|
-
modelID: model.split('/')[1]
|
|
34
|
-
},
|
|
35
|
-
parts: [{ type: 'text', text: prompt }]
|
|
36
|
-
}
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const responseText = result.parts?.find((p: any) => p.type === 'text')?.text || ''
|
|
40
|
-
|
|
41
|
-
// Extract a concise summary
|
|
42
|
-
const lines = responseText.split('\n').filter((l: string) => l.trim())
|
|
43
|
-
const summary = lines.slice(0, 5).join(' ')
|
|
44
|
-
|
|
45
|
-
return summary || 'Task completed'
|
|
46
|
-
|
|
47
|
-
} catch (error) {
|
|
48
|
-
throw new Error(`Worker execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
49
|
-
}
|
|
50
|
-
}
|