@multiclawcli/core 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/arch-design-reviewer.d.ts +4 -0
- package/dist/agents/architect.d.ts +4 -0
- package/dist/agents/backend-developer.d.ts +4 -0
- package/dist/agents/code-reviewer.d.ts +4 -0
- package/dist/agents/devops.d.ts +4 -0
- package/dist/agents/frontend-developer.d.ts +4 -0
- package/dist/agents/index.d.ts +26 -0
- package/dist/agents/leader.d.ts +4 -0
- package/dist/agents/product-manager.d.ts +4 -0
- package/dist/agents/reviewer.d.ts +4 -0
- package/dist/agents/tester.d.ts +4 -0
- package/dist/agents/ui-design-reviewer.d.ts +4 -0
- package/dist/agents/ui-designer.d.ts +4 -0
- package/dist/config/defineConfig.d.ts +2 -0
- package/dist/event/EventBus.d.ts +7 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +2207 -811
- package/dist/orchestrator/AgentRunner.d.ts +19 -0
- package/dist/orchestrator/ContextManager.d.ts +7 -0
- package/dist/orchestrator/Orchestrator.d.ts +109 -0
- package/dist/orchestrator/StageBuilder.d.ts +2 -0
- package/dist/providers/bridge.d.ts +29 -0
- package/dist/providers/catalog.d.ts +73 -0
- package/dist/providers/detect.d.ts +34 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/resolve.d.ts +26 -0
- package/dist/runtime/AcpRuntime.d.ts +17 -0
- package/dist/runtime/RuntimeBase.d.ts +6 -0
- package/dist/runtime/index.d.ts +12 -0
- package/dist/types/agent.d.ts +96 -0
- package/dist/types/config.d.ts +126 -0
- package/dist/types/event.d.ts +115 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/leader.d.ts +36 -0
- package/dist/types/runtime.d.ts +146 -0
- package/dist/utils/abort.d.ts +4 -0
- package/dist/utils/conversation.d.ts +29 -0
- package/dist/utils/errors.d.ts +16 -0
- package/dist/utils/json.d.ts +10 -0
- package/dist/utils/leaderDecision.d.ts +17 -0
- package/dist/utils/prompt.d.ts +1 -0
- package/dist/utils/resumePlan.d.ts +35 -0
- package/dist/utils/retry.d.ts +36 -0
- package/dist/utils/timeout.d.ts +1 -0
- package/dist/utils/toolSummary.d.ts +13 -0
- package/dist/utils/upstream.d.ts +69 -0
- package/package.json +18 -4
- package/src/agents/arch-design-reviewer.ts +0 -45
- package/src/agents/architect.ts +0 -72
- package/src/agents/backend-developer.ts +0 -54
- package/src/agents/code-reviewer.ts +0 -54
- package/src/agents/devops.ts +0 -59
- package/src/agents/frontend-developer.ts +0 -56
- package/src/agents/index.ts +0 -27
- package/src/agents/leader.ts +0 -37
- package/src/agents/product-manager.ts +0 -43
- package/src/agents/reviewer.ts +0 -47
- package/src/agents/tester.ts +0 -53
- package/src/agents/ui-design-reviewer.ts +0 -48
- package/src/agents/ui-designer.ts +0 -61
- package/src/config/defineConfig.ts +0 -4
- package/src/event/EventBus.ts +0 -19
- package/src/index.ts +0 -8
- package/src/orchestrator/AgentRunner.ts +0 -104
- package/src/orchestrator/ContextManager.ts +0 -11
- package/src/orchestrator/Orchestrator.ts +0 -180
- package/src/orchestrator/StageBuilder.ts +0 -30
- package/src/runtime/ClaudeRuntime.ts +0 -41
- package/src/runtime/RuntimeBase.ts +0 -7
- package/src/runtime/index.ts +0 -20
- package/src/types/agent.ts +0 -48
- package/src/types/config.ts +0 -26
- package/src/types/event.ts +0 -21
- package/src/types/index.ts +0 -4
- package/src/types/runtime.ts +0 -23
- package/src/utils/prompt.ts +0 -24
- package/src/utils/retry.ts +0 -24
- package/src/utils/timeout.ts +0 -16
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -10
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs"
|
|
2
|
-
import * as path from "path"
|
|
3
|
-
import type { AgentDefinition, AgentResult } from "../types/agent"
|
|
4
|
-
import { runtimeRegistry } from "../runtime"
|
|
5
|
-
import { EventBus } from "../event/EventBus"
|
|
6
|
-
import { ContextManager } from "./ContextManager"
|
|
7
|
-
import { withRetry } from "../utils/retry"
|
|
8
|
-
import { withTimeout } from "../utils/timeout"
|
|
9
|
-
import { renderPrompt } from "../utils/prompt"
|
|
10
|
-
|
|
11
|
-
export class AgentRunner {
|
|
12
|
-
constructor(
|
|
13
|
-
private eventBus: EventBus,
|
|
14
|
-
private contextManager: ContextManager,
|
|
15
|
-
private globalWorkDir: string
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
async run(agent: AgentDefinition): Promise<AgentResult> {
|
|
19
|
-
const startTime = new Date()
|
|
20
|
-
const workDir = agent.workDir
|
|
21
|
-
? path.resolve(this.globalWorkDir, agent.workDir)
|
|
22
|
-
: this.globalWorkDir
|
|
23
|
-
fs.mkdirSync(workDir, { recursive: true })
|
|
24
|
-
|
|
25
|
-
const runtime = runtimeRegistry.get(agent.runtime ?? "claude")
|
|
26
|
-
const retries = agent.retries ?? 0
|
|
27
|
-
const timeout = agent.timeout ?? 600_000
|
|
28
|
-
const tools = agent.tools ?? ["Read", "Write", "Bash"]
|
|
29
|
-
const model = agent.model ?? agent.models?.[agent.runtime ?? "claude"]
|
|
30
|
-
|
|
31
|
-
this.eventBus.emit({
|
|
32
|
-
type: "agent:start",
|
|
33
|
-
timestamp: new Date().toISOString(),
|
|
34
|
-
payload: { agentId: agent.id, agentName: agent.name, icon: agent.icon, model, taskTitle: agent.taskTitle },
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
let attempts = 0
|
|
38
|
-
let output = ""
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
output = await withTimeout(
|
|
42
|
-
(signal) => withRetry(async (attempt) => {
|
|
43
|
-
attempts = attempt
|
|
44
|
-
const prompt = await renderPrompt(
|
|
45
|
-
agent.taskPrompt,
|
|
46
|
-
this.contextManager.snapshot(),
|
|
47
|
-
this.globalWorkDir
|
|
48
|
-
)
|
|
49
|
-
return (await runtime.execute({
|
|
50
|
-
agentId: agent.id,
|
|
51
|
-
systemPrompt: agent.systemPrompt,
|
|
52
|
-
prompt, tools, workDir, timeout, signal,
|
|
53
|
-
model,
|
|
54
|
-
onOutput: (chunk) => {
|
|
55
|
-
this.eventBus.emit({
|
|
56
|
-
type: "agent:output",
|
|
57
|
-
timestamp: new Date().toISOString(),
|
|
58
|
-
payload: { agentId: agent.id, chunk },
|
|
59
|
-
})
|
|
60
|
-
},
|
|
61
|
-
})).output
|
|
62
|
-
}, retries, (attempt, err) => {
|
|
63
|
-
this.eventBus.emit({
|
|
64
|
-
type: "agent:retrying",
|
|
65
|
-
timestamp: new Date().toISOString(),
|
|
66
|
-
payload: {
|
|
67
|
-
agentId: agent.id,
|
|
68
|
-
agentName: agent.name,
|
|
69
|
-
attempt,
|
|
70
|
-
maxAttempts: retries + 1,
|
|
71
|
-
error: (err as Error).message,
|
|
72
|
-
},
|
|
73
|
-
})
|
|
74
|
-
}),
|
|
75
|
-
timeout,
|
|
76
|
-
agent.name
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
const endTime = new Date()
|
|
80
|
-
const result: AgentResult = {
|
|
81
|
-
agentId: agent.id, agentName: agent.name, status: "success",
|
|
82
|
-
output, duration: endTime.getTime() - startTime.getTime(),
|
|
83
|
-
attempts, startTime: startTime.toISOString(), endTime: endTime.toISOString(),
|
|
84
|
-
}
|
|
85
|
-
this.eventBus.emit({
|
|
86
|
-
type: "agent:complete", timestamp: new Date().toISOString(), payload: result,
|
|
87
|
-
})
|
|
88
|
-
return result
|
|
89
|
-
} catch (err) {
|
|
90
|
-
const endTime = new Date()
|
|
91
|
-
const result: AgentResult = {
|
|
92
|
-
agentId: agent.id, agentName: agent.name, status: "failed",
|
|
93
|
-
output, duration: endTime.getTime() - startTime.getTime(),
|
|
94
|
-
attempts, error: (err as Error).message,
|
|
95
|
-
startTime: startTime.toISOString(), endTime: endTime.toISOString(),
|
|
96
|
-
}
|
|
97
|
-
this.eventBus.emit({
|
|
98
|
-
type: "agent:failed", timestamp: new Date().toISOString(), payload: result,
|
|
99
|
-
})
|
|
100
|
-
return result
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export class ContextManager {
|
|
2
|
-
private store = new Map<string, string>()
|
|
3
|
-
constructor(initial?: Record<string, string>) {
|
|
4
|
-
if (initial) for (const [k, v] of Object.entries(initial)) this.store.set(k, v)
|
|
5
|
-
}
|
|
6
|
-
set(key: string, value: string) { this.store.set(key, value) }
|
|
7
|
-
get(key: string) { return this.store.get(key) }
|
|
8
|
-
snapshot(): Record<string, string> {
|
|
9
|
-
return Object.fromEntries(this.store.entries())
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs"
|
|
2
|
-
import * as path from "path"
|
|
3
|
-
import type { MultiClawConfig, OrchestratorResult } from "../types/config"
|
|
4
|
-
import type { AgentDefinition, AgentResult, TaskPlan } from "../types/agent"
|
|
5
|
-
import type { StageInfo } from "../types/event"
|
|
6
|
-
import { EventBus } from "../event/EventBus"
|
|
7
|
-
import { ContextManager } from "./ContextManager"
|
|
8
|
-
import { AgentRunner } from "./AgentRunner"
|
|
9
|
-
import { buildStages } from "./StageBuilder"
|
|
10
|
-
|
|
11
|
-
export class Orchestrator {
|
|
12
|
-
readonly eventBus = new EventBus()
|
|
13
|
-
private contextManager: ContextManager
|
|
14
|
-
private runner: AgentRunner
|
|
15
|
-
private workDir: string
|
|
16
|
-
|
|
17
|
-
constructor(private config: MultiClawConfig) {
|
|
18
|
-
this.workDir = path.resolve(config.workDir)
|
|
19
|
-
fs.mkdirSync(this.workDir, { recursive: true })
|
|
20
|
-
this.contextManager = new ContextManager(config.context)
|
|
21
|
-
this.runner = new AgentRunner(this.eventBus, this.contextManager, this.workDir)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async run(): Promise<OrchestratorResult> {
|
|
25
|
-
const startTime = new Date()
|
|
26
|
-
const agents = await this.resolveAgents()
|
|
27
|
-
this.contextManager.set(
|
|
28
|
-
"pipelineAgents",
|
|
29
|
-
agents.map((a) => `${a.id}: ${a.name}`).join("\n")
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
let stages = buildStages(agents)
|
|
33
|
-
|
|
34
|
-
const getModel = (agent: AgentDefinition): string | undefined => {
|
|
35
|
-
return agent.model ?? agent.models?.[agent.runtime ?? "claude"]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const stageInfos: StageInfo[] = stages.map((stage, i) => ({
|
|
39
|
-
stageIndex: i,
|
|
40
|
-
agents: stage.map((a) => ({ id: a.id, name: a.name, icon: a.icon, model: getModel(a), taskTitle: a.taskTitle })),
|
|
41
|
-
}))
|
|
42
|
-
|
|
43
|
-
this.eventBus.emit({
|
|
44
|
-
type: "orchestration:start",
|
|
45
|
-
timestamp: new Date().toISOString(),
|
|
46
|
-
payload: { name: this.config.name, requirement: this.config.context?.requirement, totalAgents: agents.length, stages: stageInfos },
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
const results: AgentResult[] = []
|
|
50
|
-
let allSuccess = true
|
|
51
|
-
|
|
52
|
-
for (let i = 0; i < stages.length; i++) {
|
|
53
|
-
const stage = stages[i]
|
|
54
|
-
|
|
55
|
-
this.eventBus.emit({
|
|
56
|
-
type: "stage:start", timestamp: new Date().toISOString(),
|
|
57
|
-
payload: { stageIndex: i, agentIds: stage.map((a) => a.id) },
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const stageResults = await this.runStage(stage)
|
|
61
|
-
results.push(...stageResults)
|
|
62
|
-
|
|
63
|
-
// After architect stage completes, apply task plan and rebuild stages
|
|
64
|
-
if (i === 0 && stage.some((a) => a.id === "architect") && stageResults.some((r) => r.status === "success")) {
|
|
65
|
-
await this.applyTaskPlan(agents)
|
|
66
|
-
stages = buildStages(agents)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
for (const r of stageResults) {
|
|
70
|
-
if (r.status !== "success") {
|
|
71
|
-
allSuccess = false
|
|
72
|
-
if (!this.config.continueOnError) break
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
this.eventBus.emit({
|
|
77
|
-
type: "stage:complete", timestamp: new Date().toISOString(),
|
|
78
|
-
payload: { stageIndex: i },
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
if (!allSuccess && !this.config.continueOnError) {
|
|
82
|
-
for (const remainingStage of stages.slice(i + 1)) {
|
|
83
|
-
for (const agent of remainingStage) {
|
|
84
|
-
this.eventBus.emit({
|
|
85
|
-
type: "agent:skipped",
|
|
86
|
-
timestamp: new Date().toISOString(),
|
|
87
|
-
payload: { agentId: agent.id, agentName: agent.name, icon: agent.icon },
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
break
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const endTime = new Date()
|
|
96
|
-
const result: OrchestratorResult = {
|
|
97
|
-
name: this.config.name, success: allSuccess,
|
|
98
|
-
totalDuration: endTime.getTime() - startTime.getTime(),
|
|
99
|
-
agentResults: results,
|
|
100
|
-
startTime: startTime.toISOString(), endTime: endTime.toISOString(),
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
this.eventBus.emit({
|
|
104
|
-
type: "orchestration:complete", timestamp: new Date().toISOString(), payload: result,
|
|
105
|
-
})
|
|
106
|
-
return result
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private async resolveAgents(): Promise<AgentDefinition[]> {
|
|
110
|
-
if (!this.config.leader || this.config.useLeader === false) return this.config.agents
|
|
111
|
-
|
|
112
|
-
this.contextManager.set(
|
|
113
|
-
"availableAgents",
|
|
114
|
-
this.config.agents.map((a) => `${a.id}: ${a.name}`).join("\n")
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
const result = await this.runner.run(this.config.leader)
|
|
118
|
-
if (result.status !== "success") return this.config.agents
|
|
119
|
-
|
|
120
|
-
const match = result.output.match(/\{[\s\S]*?"run"\s*:\s*\[[\s\S]*?\]\s*\}/)
|
|
121
|
-
if (!match) return this.config.agents
|
|
122
|
-
|
|
123
|
-
let plan: { run: string[] }
|
|
124
|
-
try { plan = JSON.parse(match[0]) } catch { return this.config.agents }
|
|
125
|
-
|
|
126
|
-
const runSet = new Set(plan.run)
|
|
127
|
-
const skippedIds = new Set(
|
|
128
|
-
this.config.agents.filter((a) => !runSet.has(a.id)).map((a) => a.id)
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
for (const agent of this.config.agents.filter((a) => skippedIds.has(a.id))) {
|
|
132
|
-
this.eventBus.emit({
|
|
133
|
-
type: "agent:skipped",
|
|
134
|
-
timestamp: new Date().toISOString(),
|
|
135
|
-
payload: { agentId: agent.id, agentName: agent.name, icon: agent.icon },
|
|
136
|
-
})
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return this.config.agents
|
|
140
|
-
.filter((a) => runSet.has(a.id))
|
|
141
|
-
.map((a) => ({
|
|
142
|
-
...a,
|
|
143
|
-
dependsOn: a.dependsOn?.filter((dep) => !skippedIds.has(dep)),
|
|
144
|
-
}))
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
private async runStage(agents: AgentDefinition[]): Promise<AgentResult[]> {
|
|
148
|
-
const { maxConcurrency } = this.config
|
|
149
|
-
if (!maxConcurrency || agents.length <= maxConcurrency) {
|
|
150
|
-
return Promise.all(agents.map((a) => this.runner.run(a)))
|
|
151
|
-
}
|
|
152
|
-
const results: AgentResult[] = []
|
|
153
|
-
for (let i = 0; i < agents.length; i += maxConcurrency) {
|
|
154
|
-
const batch = agents.slice(i, i + maxConcurrency)
|
|
155
|
-
results.push(...(await Promise.all(batch.map((a) => this.runner.run(a)))))
|
|
156
|
-
}
|
|
157
|
-
return results
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
private async applyTaskPlan(agents: AgentDefinition[]): Promise<void> {
|
|
161
|
-
try {
|
|
162
|
-
const taskPlanPath = path.join(this.workDir, "task-plan.json")
|
|
163
|
-
if (!fs.existsSync(taskPlanPath)) return
|
|
164
|
-
|
|
165
|
-
const content = fs.readFileSync(taskPlanPath, "utf-8")
|
|
166
|
-
const taskPlan: TaskPlan = JSON.parse(content)
|
|
167
|
-
|
|
168
|
-
const taskMap = new Map(taskPlan.tasks.map((t) => [t.id, t]))
|
|
169
|
-
for (const agent of agents) {
|
|
170
|
-
const task = taskMap.get(agent.id)
|
|
171
|
-
if (task) {
|
|
172
|
-
agent.taskTitle = task.title
|
|
173
|
-
agent.dependsOn = task.dependsOn
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
} catch {
|
|
177
|
-
// Silently ignore parsing errors
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { AgentDefinition } from "../types/agent"
|
|
2
|
-
|
|
3
|
-
export function buildStages(agents: AgentDefinition[]): AgentDefinition[][] {
|
|
4
|
-
const stages: AgentDefinition[][] = []
|
|
5
|
-
const completed = new Set<string>()
|
|
6
|
-
let remaining = [...agents]
|
|
7
|
-
|
|
8
|
-
const allIds = new Set(agents.map((a) => a.id))
|
|
9
|
-
for (const a of agents) {
|
|
10
|
-
for (const dep of a.dependsOn ?? []) {
|
|
11
|
-
if (!allIds.has(dep)) {
|
|
12
|
-
throw new Error(`Agent "${a.id}" depends on "${dep}" which does not exist`)
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
while (remaining.length > 0) {
|
|
18
|
-
const stage = remaining.filter((a) =>
|
|
19
|
-
(a.dependsOn ?? []).every((dep) => completed.has(dep))
|
|
20
|
-
)
|
|
21
|
-
if (stage.length === 0) {
|
|
22
|
-
const ids = remaining.map((a) => a.id).join(", ")
|
|
23
|
-
throw new Error(`Circular or unsatisfiable dependency detected: ${ids}`)
|
|
24
|
-
}
|
|
25
|
-
stages.push(stage)
|
|
26
|
-
stage.forEach((a) => completed.add(a.id))
|
|
27
|
-
remaining = remaining.filter((a) => !stage.includes(a))
|
|
28
|
-
}
|
|
29
|
-
return stages
|
|
30
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { spawn, execSync } from "child_process"
|
|
2
|
-
import { RuntimeBase } from "./RuntimeBase"
|
|
3
|
-
import type { AgentTask, AgentOutput } from "../types/runtime"
|
|
4
|
-
|
|
5
|
-
export class ClaudeRuntime extends RuntimeBase {
|
|
6
|
-
readonly name = "claude"
|
|
7
|
-
|
|
8
|
-
async checkAvailable(): Promise<boolean> {
|
|
9
|
-
try { execSync("claude --version", { stdio: "ignore" }); return true }
|
|
10
|
-
catch { return false }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async execute(task: AgentTask): Promise<AgentOutput> {
|
|
14
|
-
const args = [
|
|
15
|
-
"-p", task.prompt,
|
|
16
|
-
"--allowedTools", task.tools.join(","),
|
|
17
|
-
]
|
|
18
|
-
if (task.systemPrompt) args.push("--system-prompt", task.systemPrompt)
|
|
19
|
-
if (task.model) args.push("--model", task.model)
|
|
20
|
-
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
const child = spawn("claude", args, { cwd: task.workDir, env: process.env })
|
|
23
|
-
task.signal?.addEventListener("abort", () => child.kill())
|
|
24
|
-
|
|
25
|
-
let output = ""
|
|
26
|
-
let errorOutput = ""
|
|
27
|
-
child.stdout.on("data", (d: Buffer) => {
|
|
28
|
-
const chunk = d.toString(); output += chunk; task.onOutput?.(chunk)
|
|
29
|
-
})
|
|
30
|
-
child.stderr.on("data", (d: Buffer) => { errorOutput += d.toString() })
|
|
31
|
-
child.on("close", (code) => {
|
|
32
|
-
if (task.signal?.aborted) return
|
|
33
|
-
code === 0
|
|
34
|
-
? resolve({ output, exitCode: code })
|
|
35
|
-
: reject(new Error(`claude exited with code ${code}: ${errorOutput}`))
|
|
36
|
-
})
|
|
37
|
-
child.on("error", (err) => reject(new Error(`Failed to start claude: ${err.message}`)))
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { AgentRuntime, AgentTask, AgentOutput } from "../types/runtime"
|
|
2
|
-
|
|
3
|
-
export abstract class RuntimeBase implements AgentRuntime {
|
|
4
|
-
abstract readonly name: string
|
|
5
|
-
abstract checkAvailable(): Promise<boolean>
|
|
6
|
-
abstract execute(task: AgentTask): Promise<AgentOutput>
|
|
7
|
-
}
|
package/src/runtime/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { AgentRuntime } from "../types/runtime"
|
|
2
|
-
import { ClaudeRuntime } from "./ClaudeRuntime"
|
|
3
|
-
|
|
4
|
-
class RuntimeRegistry {
|
|
5
|
-
private runtimes = new Map<string, AgentRuntime>()
|
|
6
|
-
constructor() {
|
|
7
|
-
this.register(new ClaudeRuntime())
|
|
8
|
-
}
|
|
9
|
-
register(r: AgentRuntime) { this.runtimes.set(r.name, r) }
|
|
10
|
-
get(name: string): AgentRuntime {
|
|
11
|
-
const r = this.runtimes.get(name)
|
|
12
|
-
if (!r) throw new Error(`Runtime not found: "${name}". Available: ${[...this.runtimes.keys()].join(", ")}`)
|
|
13
|
-
return r
|
|
14
|
-
}
|
|
15
|
-
has(name: string) { return this.runtimes.has(name) }
|
|
16
|
-
list(): string[] { return [...this.runtimes.keys()] }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const runtimeRegistry = new RuntimeRegistry()
|
|
20
|
-
export { ClaudeRuntime }
|
package/src/types/agent.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export type ToolName =
|
|
2
|
-
| "Read" | "Write" | "Edit" | "MultiEdit"
|
|
3
|
-
| "Bash" | "Glob" | "Grep" | "WebFetch"
|
|
4
|
-
|
|
5
|
-
export interface AgentDefinition {
|
|
6
|
-
id: string
|
|
7
|
-
name: string
|
|
8
|
-
icon?: string
|
|
9
|
-
runtime?: string
|
|
10
|
-
model?: string
|
|
11
|
-
models?: Partial<Record<string, string>>
|
|
12
|
-
systemPrompt: string
|
|
13
|
-
taskPrompt: string
|
|
14
|
-
taskTitle?: string
|
|
15
|
-
tools?: ToolName[]
|
|
16
|
-
dependsOn?: string[]
|
|
17
|
-
timeout?: number
|
|
18
|
-
retries?: number
|
|
19
|
-
workDir?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type AgentStatus =
|
|
23
|
-
| "pending" | "running" | "retrying" | "success" | "failed" | "skipped"
|
|
24
|
-
|
|
25
|
-
export interface AgentResult {
|
|
26
|
-
agentId: string
|
|
27
|
-
agentName: string
|
|
28
|
-
status: AgentStatus
|
|
29
|
-
output: string
|
|
30
|
-
duration: number
|
|
31
|
-
attempts: number
|
|
32
|
-
error?: string
|
|
33
|
-
startTime: string
|
|
34
|
-
endTime: string
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface SubTask {
|
|
38
|
-
id: string // matches AgentDefinition.id
|
|
39
|
-
title: string
|
|
40
|
-
scope: string
|
|
41
|
-
dependsOn?: string[]
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface TaskPlan {
|
|
45
|
-
projectName: string
|
|
46
|
-
requirement: string
|
|
47
|
-
tasks: SubTask[]
|
|
48
|
-
}
|
package/src/types/config.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { AgentDefinition } from "./agent"
|
|
2
|
-
|
|
3
|
-
export interface MultiClawConfig {
|
|
4
|
-
name: string
|
|
5
|
-
workDir: string
|
|
6
|
-
leader?: AgentDefinition
|
|
7
|
-
agents: AgentDefinition[]
|
|
8
|
-
context?: Record<string, string>
|
|
9
|
-
logDir?: string
|
|
10
|
-
continueOnError?: boolean
|
|
11
|
-
maxConcurrency?: number
|
|
12
|
-
useLeader?: boolean
|
|
13
|
-
dashboard?: {
|
|
14
|
-
port?: number
|
|
15
|
-
autoOpen?: boolean
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface OrchestratorResult {
|
|
20
|
-
name: string
|
|
21
|
-
success: boolean
|
|
22
|
-
totalDuration: number
|
|
23
|
-
agentResults: import("./agent").AgentResult[]
|
|
24
|
-
startTime: string
|
|
25
|
-
endTime: string
|
|
26
|
-
}
|
package/src/types/event.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { AgentResult, OrchestratorResult } from "./index"
|
|
2
|
-
|
|
3
|
-
export interface StageInfo {
|
|
4
|
-
stageIndex: number
|
|
5
|
-
agents: Array<{ id: string; name: string; icon?: string; model?: string; taskTitle?: string }>
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export type MultiClawEvent =
|
|
9
|
-
| { type: "orchestration:start"; timestamp: string; payload: { name: string; requirement?: string; totalAgents: number; stages: StageInfo[] } }
|
|
10
|
-
| { type: "orchestration:complete"; timestamp: string; payload: OrchestratorResult }
|
|
11
|
-
| { type: "stage:start"; timestamp: string; payload: { stageIndex: number; agentIds: string[] } }
|
|
12
|
-
| { type: "stage:complete"; timestamp: string; payload: { stageIndex: number } }
|
|
13
|
-
| { type: "agent:start"; timestamp: string; payload: { agentId: string; agentName: string; icon?: string; model?: string; taskTitle?: string } }
|
|
14
|
-
| { type: "agent:output"; timestamp: string; payload: { agentId: string; chunk: string } }
|
|
15
|
-
| { type: "agent:complete"; timestamp: string; payload: AgentResult }
|
|
16
|
-
| { type: "agent:failed"; timestamp: string; payload: AgentResult }
|
|
17
|
-
| { type: "agent:skipped"; timestamp: string; payload: { agentId: string; agentName: string; icon?: string } }
|
|
18
|
-
| { type: "agent:retrying"; timestamp: string; payload: { agentId: string; agentName: string; attempt: number; maxAttempts: number; error: string } }
|
|
19
|
-
|
|
20
|
-
export type EventListener = (event: MultiClawEvent) => void
|
|
21
|
-
|
package/src/types/index.ts
DELETED
package/src/types/runtime.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export interface AgentTask {
|
|
2
|
-
agentId: string
|
|
3
|
-
systemPrompt: string
|
|
4
|
-
prompt: string
|
|
5
|
-
tools: string[]
|
|
6
|
-
workDir: string
|
|
7
|
-
timeout: number
|
|
8
|
-
signal?: AbortSignal
|
|
9
|
-
onOutput?: (chunk: string) => void
|
|
10
|
-
model?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface AgentOutput {
|
|
14
|
-
output: string
|
|
15
|
-
exitCode: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface AgentRuntime {
|
|
19
|
-
readonly name: string
|
|
20
|
-
checkAvailable(): Promise<boolean>
|
|
21
|
-
execute(task: AgentTask): Promise<AgentOutput>
|
|
22
|
-
}
|
|
23
|
-
|
package/src/utils/prompt.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs/promises"
|
|
2
|
-
import * as path from "path"
|
|
3
|
-
|
|
4
|
-
export async function renderPrompt(
|
|
5
|
-
template: string,
|
|
6
|
-
context: Record<string, string>,
|
|
7
|
-
workDir: string
|
|
8
|
-
): Promise<string> {
|
|
9
|
-
let result = template
|
|
10
|
-
const fileMatches = [...template.matchAll(/\{\{file:([^}]+)\}\}/g)]
|
|
11
|
-
for (const match of fileMatches) {
|
|
12
|
-
const filePath = path.resolve(workDir, match[1].trim())
|
|
13
|
-
try {
|
|
14
|
-
const content = await fs.readFile(filePath, "utf-8")
|
|
15
|
-
result = result.replace(match[0], content)
|
|
16
|
-
} catch {
|
|
17
|
-
result = result.replace(match[0], `[file not found: ${match[1]}]`)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
for (const [key, value] of Object.entries(context)) {
|
|
21
|
-
result = result.replaceAll(`{{${key}}}`, value)
|
|
22
|
-
}
|
|
23
|
-
return result
|
|
24
|
-
}
|
package/src/utils/retry.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export function sleep(ms: number): Promise<void> {
|
|
2
|
-
return new Promise((r) => setTimeout(r, ms))
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export async function withRetry<T>(
|
|
6
|
-
fn: (attempt: number) => Promise<T>,
|
|
7
|
-
retries: number,
|
|
8
|
-
onRetry?: (failedAttempt: number, error: unknown) => void,
|
|
9
|
-
delayMs = 2000
|
|
10
|
-
): Promise<T> {
|
|
11
|
-
let lastError: Error = new Error("Unknown error")
|
|
12
|
-
for (let attempt = 1; attempt <= retries + 1; attempt++) {
|
|
13
|
-
try { return await fn(attempt) }
|
|
14
|
-
catch (err) {
|
|
15
|
-
lastError = err as Error
|
|
16
|
-
if (attempt <= retries) {
|
|
17
|
-
onRetry?.(attempt, err)
|
|
18
|
-
await sleep(delayMs * attempt)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
throw lastError
|
|
23
|
-
}
|
|
24
|
-
|
package/src/utils/timeout.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export function withTimeout<T>(
|
|
2
|
-
factory: (signal: AbortSignal) => Promise<T>,
|
|
3
|
-
ms: number,
|
|
4
|
-
label: string
|
|
5
|
-
): Promise<T> {
|
|
6
|
-
const controller = new AbortController()
|
|
7
|
-
let timer: NodeJS.Timeout
|
|
8
|
-
const timeout = new Promise<never>((_, reject) => {
|
|
9
|
-
timer = setTimeout(() => {
|
|
10
|
-
controller.abort()
|
|
11
|
-
reject(new Error(`[${label}] timed out (${ms / 1000}s)`))
|
|
12
|
-
}, ms)
|
|
13
|
-
})
|
|
14
|
-
return Promise.race([factory(controller.signal), timeout]).finally(() => clearTimeout(timer))
|
|
15
|
-
}
|
|
16
|
-
|
package/tsconfig.build.json
DELETED
package/tsconfig.json
DELETED
package/tsup.config.ts
DELETED