@multiclawcli/core 0.3.1 → 0.4.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/index.d.ts +720 -0
- package/dist/index.js +1909 -811
- 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,61 +0,0 @@
|
|
|
1
|
-
import type { AgentDefinition } from "../types/agent"
|
|
2
|
-
|
|
3
|
-
type Overrides = Partial<AgentDefinition>
|
|
4
|
-
|
|
5
|
-
const SYSTEM = `You are a UI/UX designer. Your job is to translate architecture and requirements into a detailed, developer-ready UI design specification.
|
|
6
|
-
|
|
7
|
-
Responsibilities:
|
|
8
|
-
- Define the component inventory: every page, section, and reusable component
|
|
9
|
-
- Specify layouts, spacing principles, and responsive breakpoints
|
|
10
|
-
- Define interaction patterns: loading states, empty states, error states, transitions
|
|
11
|
-
- Specify design tokens: color palette, typography scale, spacing scale, shadow levels
|
|
12
|
-
- Describe accessibility requirements: ARIA roles, keyboard navigation, focus management
|
|
13
|
-
- Do not write implementation code — produce specification documents only
|
|
14
|
-
|
|
15
|
-
Deliverables (save to the working directory):
|
|
16
|
-
- ui-design.md — full UI design specification: component inventory, page layouts, interaction flows, design tokens, accessibility requirements
|
|
17
|
-
|
|
18
|
-
Principles: Be specific enough that a developer can implement without guessing. Every component gets a name, purpose, props/variants, and states. Flag anything that depends on backend data.`
|
|
19
|
-
|
|
20
|
-
const TASK = `## Task Scope
|
|
21
|
-
{{file:task-plan.json}}
|
|
22
|
-
|
|
23
|
-
Find the entry where id == "ui-designer" and treat its scope as the primary guide for your work.
|
|
24
|
-
If task-plan.json is not found, determine your scope from the architecture design and API specification below.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Project
|
|
29
|
-
{{projectName}}
|
|
30
|
-
|
|
31
|
-
## Requirement
|
|
32
|
-
{{requirement}}
|
|
33
|
-
|
|
34
|
-
## Architecture Design
|
|
35
|
-
{{file:architecture.md}}
|
|
36
|
-
|
|
37
|
-
## API Specification
|
|
38
|
-
{{file:api-spec.md}}
|
|
39
|
-
|
|
40
|
-
## Task
|
|
41
|
-
Create ui-design.md covering:
|
|
42
|
-
1. **Component Inventory** — list every page and reusable component; for each: name, purpose, variants/states, props
|
|
43
|
-
2. **Page Layouts** — describe the visual structure of each page (header, main, sidebar, footer zones); include responsive behaviour
|
|
44
|
-
3. **Interaction Flows** — key user flows with step-by-step state transitions (loading → success → error)
|
|
45
|
-
4. **Design Tokens** — color palette (primary, neutral, semantic), typography scale, spacing scale
|
|
46
|
-
5. **Accessibility** — ARIA roles/labels for interactive components, keyboard nav, focus order
|
|
47
|
-
|
|
48
|
-
Be concrete. Reference API endpoints where components bind to data. Flag any UX decision that needs product clarification.`
|
|
49
|
-
|
|
50
|
-
export function uiDesigner(overrides?: Overrides): AgentDefinition {
|
|
51
|
-
return {
|
|
52
|
-
id: "ui-designer",
|
|
53
|
-
name: "UI Designer",
|
|
54
|
-
icon: "▣",
|
|
55
|
-
models: { claude: "sonnet" },
|
|
56
|
-
systemPrompt: SYSTEM,
|
|
57
|
-
taskPrompt: TASK,
|
|
58
|
-
tools: ["Read", "Write"],
|
|
59
|
-
...overrides,
|
|
60
|
-
}
|
|
61
|
-
}
|
package/src/event/EventBus.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { MultiClawEvent, EventListener } from "../types/event"
|
|
2
|
-
|
|
3
|
-
export class EventBus {
|
|
4
|
-
private listeners = new Set<EventListener>()
|
|
5
|
-
|
|
6
|
-
subscribe(listener: EventListener): () => void {
|
|
7
|
-
this.listeners.add(listener)
|
|
8
|
-
return () => this.listeners.delete(listener)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
emit(event: MultiClawEvent): void {
|
|
12
|
-
for (const listener of this.listeners) {
|
|
13
|
-
try { listener(event) }
|
|
14
|
-
catch (err) { console.error("EventBus listener error:", err) }
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
clear(): void { this.listeners.clear() }
|
|
19
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export * from "./types"
|
|
2
|
-
export { EventBus } from "./event/EventBus"
|
|
3
|
-
export { Orchestrator } from "./orchestrator/Orchestrator"
|
|
4
|
-
export { buildStages } from "./orchestrator/StageBuilder"
|
|
5
|
-
export { runtimeRegistry, ClaudeRuntime } from "./runtime"
|
|
6
|
-
export { RuntimeBase } from "./runtime/RuntimeBase"
|
|
7
|
-
export { defineConfig } from "./config/defineConfig"
|
|
8
|
-
export { agents } from "./agents"
|
|
@@ -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
|
-
|