@clawswarm/core 0.1.0-alpha → 0.2.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -1
- package/src/__tests__/integration.test.ts +0 -686
- package/src/agent.ts +0 -163
- package/src/chief.ts +0 -264
- package/src/clawswarm.ts +0 -257
- package/src/goal.ts +0 -183
- package/src/index.ts +0 -62
- package/src/swarm.ts +0 -225
- package/src/task.ts +0 -204
- package/src/types.ts +0 -240
- package/tsconfig.json +0 -11
- package/tsconfig.tsbuildinfo +0 -1
package/src/goal.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Goal decomposition and planning.
|
|
3
|
-
* @module @clawswarm/core/goal
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Goal, GoalStatus, CreateGoalInput, Task, AgentType, SwarmConfig } from './types.js';
|
|
7
|
-
import { TaskManager } from './task.js';
|
|
8
|
-
|
|
9
|
-
// ─── Goal Planner ─────────────────────────────────────────────────────────────
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Decomposes high-level goals into concrete, assignable tasks.
|
|
13
|
-
*
|
|
14
|
-
* The Planner analyzes a goal description and generates a task plan,
|
|
15
|
-
* assigns each task to the appropriate specialist agent, and sequences
|
|
16
|
-
* tasks based on their dependencies.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```typescript
|
|
20
|
-
* const planner = new GoalPlanner(swarmConfig);
|
|
21
|
-
* const tasks = await planner.decompose(goal, taskManager);
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
export class GoalPlanner {
|
|
25
|
-
constructor(private readonly config: SwarmConfig) {}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Decompose a goal into a list of tasks.
|
|
29
|
-
* Creates tasks via the TaskManager and returns them in execution order.
|
|
30
|
-
*/
|
|
31
|
-
async decompose(goal: Goal, taskManager: TaskManager): Promise<Task[]> {
|
|
32
|
-
const plan = await this._generatePlan(goal);
|
|
33
|
-
const tasks: Task[] = [];
|
|
34
|
-
|
|
35
|
-
// Create tasks from plan (maintaining dependency ordering)
|
|
36
|
-
for (const step of plan) {
|
|
37
|
-
const task = taskManager.create({
|
|
38
|
-
goalId: goal.id,
|
|
39
|
-
title: step.title,
|
|
40
|
-
description: step.description,
|
|
41
|
-
assignedTo: step.agentType,
|
|
42
|
-
dependsOn: step.dependsOnTitles
|
|
43
|
-
? tasks
|
|
44
|
-
.filter(t => step.dependsOnTitles!.includes(t.title))
|
|
45
|
-
.map(t => t.id)
|
|
46
|
-
: [],
|
|
47
|
-
});
|
|
48
|
-
tasks.push(task);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return tasks;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Generate a task plan for a goal.
|
|
56
|
-
* In production, this calls an LLM with the planner system prompt.
|
|
57
|
-
* Returns structured step definitions.
|
|
58
|
-
*/
|
|
59
|
-
private async _generatePlan(goal: Goal): Promise<PlanStep[]> {
|
|
60
|
-
// TODO: Replace with actual LLM call
|
|
61
|
-
// This stub returns a basic 3-step plan as an example
|
|
62
|
-
return [
|
|
63
|
-
{
|
|
64
|
-
title: `Research: ${goal.title}`,
|
|
65
|
-
description: `Research and gather background information for: ${goal.description}`,
|
|
66
|
-
agentType: 'research' as AgentType,
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
title: `Execute: ${goal.title}`,
|
|
70
|
-
description: `Based on research, implement the core work for: ${goal.description}`,
|
|
71
|
-
agentType: this._inferPrimaryAgent(goal),
|
|
72
|
-
dependsOnTitles: [`Research: ${goal.title}`],
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
title: `Review: ${goal.title}`,
|
|
76
|
-
description: `Verify and validate the output for: ${goal.description}`,
|
|
77
|
-
agentType: 'research' as AgentType,
|
|
78
|
-
dependsOnTitles: [`Execute: ${goal.title}`],
|
|
79
|
-
},
|
|
80
|
-
];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Infer the primary execution agent type from the goal description.
|
|
85
|
-
*/
|
|
86
|
-
private _inferPrimaryAgent(goal: Goal): AgentType {
|
|
87
|
-
const desc = `${goal.title} ${goal.description}`.toLowerCase();
|
|
88
|
-
|
|
89
|
-
if (/deploy|infrastructure|k8s|docker|ci\/cd|monitoring|server/.test(desc)) {
|
|
90
|
-
return 'ops';
|
|
91
|
-
}
|
|
92
|
-
if (/code|build|implement|function|api|test|debug|refactor/.test(desc)) {
|
|
93
|
-
return 'code';
|
|
94
|
-
}
|
|
95
|
-
if (/research|analyze|report|summarize|find|investigate/.test(desc)) {
|
|
96
|
-
return 'research';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return 'code'; // default
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ─── Goal Manager ─────────────────────────────────────────────────────────────
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Manages the lifecycle of goals in a ClawSwarm instance.
|
|
107
|
-
*/
|
|
108
|
-
export class GoalManager {
|
|
109
|
-
private goals: Map<string, Goal> = new Map();
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Create a new goal.
|
|
113
|
-
*/
|
|
114
|
-
create(input: CreateGoalInput): Goal {
|
|
115
|
-
const goal: Goal = {
|
|
116
|
-
...input,
|
|
117
|
-
id: `goal-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
118
|
-
status: 'created',
|
|
119
|
-
tasks: [],
|
|
120
|
-
deliverables: [],
|
|
121
|
-
priority: input.priority ?? 0,
|
|
122
|
-
tags: input.tags ?? [],
|
|
123
|
-
cost: {
|
|
124
|
-
totalTokens: 0,
|
|
125
|
-
estimatedCostUsd: 0,
|
|
126
|
-
byAgent: {},
|
|
127
|
-
},
|
|
128
|
-
createdAt: new Date().toISOString(),
|
|
129
|
-
};
|
|
130
|
-
this.goals.set(goal.id, goal);
|
|
131
|
-
return goal;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Get a goal by ID.
|
|
136
|
-
*/
|
|
137
|
-
get(goalId: string): Goal | undefined {
|
|
138
|
-
return this.goals.get(goalId);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Get all goals.
|
|
143
|
-
*/
|
|
144
|
-
getAll(): Goal[] {
|
|
145
|
-
return Array.from(this.goals.values());
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Update a goal's status.
|
|
150
|
-
*/
|
|
151
|
-
setStatus(goalId: string, status: GoalStatus): Goal {
|
|
152
|
-
const goal = this._getOrThrow(goalId);
|
|
153
|
-
goal.status = status;
|
|
154
|
-
if (status === 'completed') {
|
|
155
|
-
goal.completedAt = new Date().toISOString();
|
|
156
|
-
}
|
|
157
|
-
return goal;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Attach tasks to a goal.
|
|
162
|
-
*/
|
|
163
|
-
setTasks(goalId: string, tasks: Goal['tasks']): Goal {
|
|
164
|
-
const goal = this._getOrThrow(goalId);
|
|
165
|
-
goal.tasks = tasks;
|
|
166
|
-
return goal;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
private _getOrThrow(goalId: string): Goal {
|
|
170
|
-
const goal = this.goals.get(goalId);
|
|
171
|
-
if (!goal) throw new Error(`Goal not found: ${goalId}`);
|
|
172
|
-
return goal;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ─── Internal Types ───────────────────────────────────────────────────────────
|
|
177
|
-
|
|
178
|
-
interface PlanStep {
|
|
179
|
-
title: string;
|
|
180
|
-
description: string;
|
|
181
|
-
agentType: AgentType;
|
|
182
|
-
dependsOnTitles?: string[];
|
|
183
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @clawswarm/core — Public API
|
|
3
|
-
*
|
|
4
|
-
* The main entry point for the ClawSwarm framework.
|
|
5
|
-
* Import everything you need from this barrel export.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { ClawSwarm, Agent, GoalManager, TaskManager, ChiefReviewer } from '@clawswarm/core';
|
|
10
|
-
* ```
|
|
11
|
-
*
|
|
12
|
-
* @module @clawswarm/core
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// ─── Main Class ───────────────────────────────────────────────────────────────
|
|
16
|
-
export { ClawSwarm } from './clawswarm.js';
|
|
17
|
-
|
|
18
|
-
// ─── Agent ────────────────────────────────────────────────────────────────────
|
|
19
|
-
export { Agent } from './agent.js';
|
|
20
|
-
|
|
21
|
-
// ─── Goal ─────────────────────────────────────────────────────────────────────
|
|
22
|
-
export { GoalManager, GoalPlanner } from './goal.js';
|
|
23
|
-
|
|
24
|
-
// ─── Task ─────────────────────────────────────────────────────────────────────
|
|
25
|
-
export { TaskManager } from './task.js';
|
|
26
|
-
|
|
27
|
-
// ─── Chief Review ─────────────────────────────────────────────────────────────
|
|
28
|
-
export { ChiefReviewer } from './chief.js';
|
|
29
|
-
|
|
30
|
-
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
31
|
-
export type {
|
|
32
|
-
// Agent types
|
|
33
|
-
AgentType,
|
|
34
|
-
AgentStatus,
|
|
35
|
-
AgentConfig,
|
|
36
|
-
ModelId,
|
|
37
|
-
|
|
38
|
-
// Task types
|
|
39
|
-
TaskStatus,
|
|
40
|
-
Task,
|
|
41
|
-
Deliverable,
|
|
42
|
-
|
|
43
|
-
// Goal types
|
|
44
|
-
GoalStatus,
|
|
45
|
-
Goal,
|
|
46
|
-
CreateGoalInput,
|
|
47
|
-
|
|
48
|
-
// Review types
|
|
49
|
-
ReviewResult,
|
|
50
|
-
ChiefReviewConfig,
|
|
51
|
-
|
|
52
|
-
// Cost tracking
|
|
53
|
-
TokenUsage,
|
|
54
|
-
CostSummary,
|
|
55
|
-
|
|
56
|
-
// Events
|
|
57
|
-
SwarmEvents,
|
|
58
|
-
|
|
59
|
-
// Config
|
|
60
|
-
SwarmConfig,
|
|
61
|
-
GoalResult,
|
|
62
|
-
} from './types.js';
|
package/src/swarm.ts
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ClawSwarm — the top-level orchestrator.
|
|
3
|
-
* @module @clawswarm/core/swarm
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import EventEmitter from 'eventemitter3';
|
|
7
|
-
import {
|
|
8
|
-
SwarmConfig,
|
|
9
|
-
GoalResult,
|
|
10
|
-
CreateGoalInput,
|
|
11
|
-
Goal,
|
|
12
|
-
Task,
|
|
13
|
-
AgentType,
|
|
14
|
-
SwarmEvents,
|
|
15
|
-
} from './types.js';
|
|
16
|
-
import { Agent } from './agent.js';
|
|
17
|
-
import { TaskManager } from './task.js';
|
|
18
|
-
import { GoalManager, GoalPlanner } from './goal.js';
|
|
19
|
-
import { ChiefReviewer } from './chief.js';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* ClawSwarm — deploy and orchestrate a team of AI agents.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```typescript
|
|
26
|
-
* const swarm = new ClawSwarm({
|
|
27
|
-
* agents: [
|
|
28
|
-
* Agent.research({ model: 'claude-sonnet-4' }),
|
|
29
|
-
* Agent.code({ model: 'gpt-4o' }),
|
|
30
|
-
* Agent.ops({ model: 'gemini-pro' }),
|
|
31
|
-
* ],
|
|
32
|
-
* chiefReview: {
|
|
33
|
-
* autoApproveThreshold: 8,
|
|
34
|
-
* humanReviewThreshold: 5,
|
|
35
|
-
* },
|
|
36
|
-
* });
|
|
37
|
-
*
|
|
38
|
-
* swarm.on('task:completed', (task) => console.log('Done:', task.title));
|
|
39
|
-
*
|
|
40
|
-
* const goal = await swarm.createGoal({
|
|
41
|
-
* title: 'Write a blog post about AI',
|
|
42
|
-
* description: 'Research AI trends and write a 1000-word post',
|
|
43
|
-
* });
|
|
44
|
-
*
|
|
45
|
-
* const result = await swarm.execute(goal);
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export class ClawSwarm extends EventEmitter<SwarmEvents> {
|
|
49
|
-
private readonly config: SwarmConfig;
|
|
50
|
-
private readonly agents: Agent[];
|
|
51
|
-
private readonly taskManager: TaskManager;
|
|
52
|
-
private readonly goalManager: GoalManager;
|
|
53
|
-
private readonly planner: GoalPlanner;
|
|
54
|
-
private readonly reviewer: ChiefReviewer;
|
|
55
|
-
|
|
56
|
-
constructor(config: SwarmConfig) {
|
|
57
|
-
super();
|
|
58
|
-
this.config = config;
|
|
59
|
-
this.agents = config.agents.map(c => new Agent(c));
|
|
60
|
-
this.taskManager = new TaskManager();
|
|
61
|
-
this.goalManager = new GoalManager();
|
|
62
|
-
this.planner = new GoalPlanner(config);
|
|
63
|
-
this.reviewer = new ChiefReviewer(config.chiefReview);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Create a new goal. Does not start execution yet.
|
|
68
|
-
*
|
|
69
|
-
* @param input - Goal title, description, and optional metadata
|
|
70
|
-
* @returns The created goal
|
|
71
|
-
*/
|
|
72
|
-
async createGoal(input: CreateGoalInput): Promise<Goal> {
|
|
73
|
-
const goal = this.goalManager.create(input);
|
|
74
|
-
this.emit('goal:created', goal);
|
|
75
|
-
return goal;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Execute a goal: plan, assign, run, review, and collect results.
|
|
80
|
-
*
|
|
81
|
-
* @param goal - The goal to execute (from createGoal)
|
|
82
|
-
* @returns Final result with deliverables and cost summary
|
|
83
|
-
*/
|
|
84
|
-
async execute(goal: Goal): Promise<GoalResult> {
|
|
85
|
-
const startTime = Date.now();
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
// 1. Planning phase
|
|
89
|
-
this.goalManager.setStatus(goal.id, 'planning');
|
|
90
|
-
this.emit('goal:planning', goal);
|
|
91
|
-
|
|
92
|
-
const tasks = await this.planner.decompose(goal, this.taskManager);
|
|
93
|
-
this.goalManager.setTasks(goal.id, tasks);
|
|
94
|
-
|
|
95
|
-
// 2. Execution phase
|
|
96
|
-
this.goalManager.setStatus(goal.id, 'in_progress');
|
|
97
|
-
|
|
98
|
-
let hadHumanReview = false;
|
|
99
|
-
|
|
100
|
-
// Execute tasks in dependency order
|
|
101
|
-
while (!this.taskManager.isGoalDone(goal.id)) {
|
|
102
|
-
const readyTasks = this.taskManager.getReady(goal.id);
|
|
103
|
-
if (readyTasks.length === 0) break;
|
|
104
|
-
|
|
105
|
-
// Run all ready tasks (can parallelize)
|
|
106
|
-
await Promise.all(
|
|
107
|
-
readyTasks.map(async (task) => {
|
|
108
|
-
const result = await this._executeTask(task);
|
|
109
|
-
if (result.hadHumanReview) hadHumanReview = true;
|
|
110
|
-
})
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// 3. Collect results
|
|
115
|
-
const completedTasks = this.taskManager
|
|
116
|
-
.getByGoal(goal.id)
|
|
117
|
-
.filter(t => t.status === 'completed');
|
|
118
|
-
|
|
119
|
-
const deliverables = completedTasks.flatMap(t => t.deliverables);
|
|
120
|
-
|
|
121
|
-
const finalGoal = this.goalManager.setStatus(goal.id, 'completed');
|
|
122
|
-
this.emit('goal:completed', finalGoal);
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
goal: finalGoal,
|
|
126
|
-
deliverables,
|
|
127
|
-
cost: finalGoal.cost,
|
|
128
|
-
hadHumanReview,
|
|
129
|
-
durationMs: Date.now() - startTime,
|
|
130
|
-
};
|
|
131
|
-
} catch (error) {
|
|
132
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
133
|
-
const failedGoal = this.goalManager.setStatus(goal.id, 'failed');
|
|
134
|
-
this.emit('goal:failed', failedGoal, err);
|
|
135
|
-
throw err;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Get all agents in this swarm.
|
|
141
|
-
*/
|
|
142
|
-
getAgents(): Agent[] {
|
|
143
|
-
return [...this.agents];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Get an agent by type.
|
|
148
|
-
*/
|
|
149
|
-
getAgent(type: AgentType): Agent | undefined {
|
|
150
|
-
return this.agents.find(a => a.type === type && a.status === 'idle');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ─── Private ──────────────────────────────────────────────────────────────
|
|
154
|
-
|
|
155
|
-
private async _executeTask(task: Task): Promise<{ hadHumanReview: boolean }> {
|
|
156
|
-
let hadHumanReview = false;
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
// Assign to agent
|
|
160
|
-
const agentType = task.assignedTo ?? 'code';
|
|
161
|
-
const agent = this.getAgent(agentType);
|
|
162
|
-
|
|
163
|
-
if (!agent) {
|
|
164
|
-
throw new Error(`No available agent of type: ${agentType}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
this.taskManager.assign(task.id, agentType);
|
|
168
|
-
this.emit('task:assigned', task, agentType);
|
|
169
|
-
|
|
170
|
-
// Execute
|
|
171
|
-
agent.status = 'busy';
|
|
172
|
-
this.taskManager.start(task.id);
|
|
173
|
-
this.emit('task:started', task);
|
|
174
|
-
|
|
175
|
-
const deliverables = await agent.execute(task);
|
|
176
|
-
agent.status = 'idle';
|
|
177
|
-
|
|
178
|
-
// Submit for review
|
|
179
|
-
const updatedTask = this.taskManager.submitForReview(task.id, deliverables);
|
|
180
|
-
|
|
181
|
-
// Run through chief review
|
|
182
|
-
let approved = false;
|
|
183
|
-
while (!approved) {
|
|
184
|
-
const review = await this.reviewer.review(updatedTask);
|
|
185
|
-
this.emit('task:review', updatedTask, review);
|
|
186
|
-
|
|
187
|
-
if (review.decision === 'approved') {
|
|
188
|
-
this.taskManager.approve(task.id);
|
|
189
|
-
this.taskManager.complete(task.id);
|
|
190
|
-
this.emit('task:completed', updatedTask);
|
|
191
|
-
approved = true;
|
|
192
|
-
} else if (review.decision === 'human_review') {
|
|
193
|
-
hadHumanReview = true;
|
|
194
|
-
this.emit('human:review_required', updatedTask, review);
|
|
195
|
-
// In production: wait for human decision via webhook/callback
|
|
196
|
-
// For now, auto-approve after emitting
|
|
197
|
-
this.taskManager.approve(task.id);
|
|
198
|
-
this.taskManager.complete(task.id);
|
|
199
|
-
approved = true;
|
|
200
|
-
} else {
|
|
201
|
-
// Rejected — attempt rework
|
|
202
|
-
this.emit('task:rejected', updatedTask, review);
|
|
203
|
-
try {
|
|
204
|
-
this.taskManager.rework(task.id, review.feedback);
|
|
205
|
-
this.emit('task:rework', updatedTask, review);
|
|
206
|
-
// Re-run with agent
|
|
207
|
-
const reworkDeliverables = await agent.execute(updatedTask);
|
|
208
|
-
this.taskManager.submitForReview(task.id, reworkDeliverables);
|
|
209
|
-
} catch {
|
|
210
|
-
// Max rework exceeded
|
|
211
|
-
this.taskManager.reject(task.id, review.feedback);
|
|
212
|
-
this.emit('task:failed', updatedTask, new Error('Max rework cycles exceeded'));
|
|
213
|
-
approved = true; // exit loop
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
} catch (error) {
|
|
218
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
219
|
-
this.taskManager.fail(task.id, err);
|
|
220
|
-
this.emit('task:failed', task, err);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return { hadHumanReview };
|
|
224
|
-
}
|
|
225
|
-
}
|
package/src/task.ts
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Task lifecycle management.
|
|
3
|
-
* @module @clawswarm/core/task
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Task, TaskStatus, AgentType, Deliverable } from './types.js';
|
|
7
|
-
|
|
8
|
-
// ─── Task Manager ─────────────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Manages the lifecycle of tasks within a goal.
|
|
12
|
-
* Handles state transitions, rework cycles, and deliverable collection.
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```typescript
|
|
16
|
-
* const manager = new TaskManager();
|
|
17
|
-
* const task = manager.create({
|
|
18
|
-
* goalId: 'goal-123',
|
|
19
|
-
* title: 'Research AI trends',
|
|
20
|
-
* description: 'Find the top 5 AI trends in 2026',
|
|
21
|
-
* assignedTo: 'research',
|
|
22
|
-
* });
|
|
23
|
-
*
|
|
24
|
-
* manager.start(task.id);
|
|
25
|
-
* manager.complete(task.id, [{ type: 'text', label: 'Report', content: '...' }]);
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export class TaskManager {
|
|
29
|
-
private tasks: Map<string, Task> = new Map();
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Create a new task.
|
|
33
|
-
*/
|
|
34
|
-
create(input: Omit<Task, 'id' | 'status' | 'deliverables' | 'reworkCount' | 'maxReworkCycles' | 'createdAt' | 'updatedAt'>): Task {
|
|
35
|
-
const task: Task = {
|
|
36
|
-
...input,
|
|
37
|
-
id: `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
38
|
-
status: 'pending',
|
|
39
|
-
deliverables: [],
|
|
40
|
-
reworkCount: 0,
|
|
41
|
-
maxReworkCycles: 3,
|
|
42
|
-
dependsOn: input.dependsOn ?? [],
|
|
43
|
-
createdAt: new Date().toISOString(),
|
|
44
|
-
updatedAt: new Date().toISOString(),
|
|
45
|
-
};
|
|
46
|
-
this.tasks.set(task.id, task);
|
|
47
|
-
return task;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Get a task by ID.
|
|
52
|
-
*/
|
|
53
|
-
get(taskId: string): Task | undefined {
|
|
54
|
-
return this.tasks.get(taskId);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Get all tasks.
|
|
59
|
-
*/
|
|
60
|
-
getAll(): Task[] {
|
|
61
|
-
return Array.from(this.tasks.values());
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Get tasks by goal ID.
|
|
66
|
-
*/
|
|
67
|
-
getByGoal(goalId: string): Task[] {
|
|
68
|
-
return this.getAll().filter(t => t.goalId === goalId);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Get tasks that are ready to run (all dependencies completed).
|
|
73
|
-
*/
|
|
74
|
-
getReady(goalId: string): Task[] {
|
|
75
|
-
const tasks = this.getByGoal(goalId);
|
|
76
|
-
const completedIds = new Set(
|
|
77
|
-
tasks.filter(t => t.status === 'completed').map(t => t.id)
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
return tasks.filter(t =>
|
|
81
|
-
t.status === 'pending' &&
|
|
82
|
-
t.dependsOn.every(depId => completedIds.has(depId))
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Assign a task to an agent type.
|
|
88
|
-
*/
|
|
89
|
-
assign(taskId: string, agentType: AgentType): Task {
|
|
90
|
-
return this._transition(taskId, 'assigned', task => {
|
|
91
|
-
task.assignedTo = agentType;
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Mark a task as in progress.
|
|
97
|
-
*/
|
|
98
|
-
start(taskId: string): Task {
|
|
99
|
-
return this._transition(taskId, 'in_progress');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Submit a task for review with its deliverables.
|
|
104
|
-
*/
|
|
105
|
-
submitForReview(taskId: string, deliverables: Deliverable[]): Task {
|
|
106
|
-
return this._transition(taskId, 'review', task => {
|
|
107
|
-
task.deliverables = deliverables;
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Mark a task as approved.
|
|
113
|
-
*/
|
|
114
|
-
approve(taskId: string): Task {
|
|
115
|
-
return this._transition(taskId, 'approved');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Mark a task as completed (after approval and any post-processing).
|
|
120
|
-
*/
|
|
121
|
-
complete(taskId: string): Task {
|
|
122
|
-
return this._transition(taskId, 'completed');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Mark a task for rework. Increments rework counter.
|
|
127
|
-
* @throws If max rework cycles exceeded
|
|
128
|
-
*/
|
|
129
|
-
rework(taskId: string, feedback: string): Task {
|
|
130
|
-
const task = this._getOrThrow(taskId);
|
|
131
|
-
|
|
132
|
-
if (task.reworkCount >= task.maxReworkCycles) {
|
|
133
|
-
throw new Error(
|
|
134
|
-
`Task ${taskId} has exceeded max rework cycles (${task.maxReworkCycles}). Failing task.`
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return this._transition(taskId, 'rework', t => {
|
|
139
|
-
t.reworkCount += 1;
|
|
140
|
-
// Store feedback as a note in deliverables for the agent to reference
|
|
141
|
-
t.deliverables = [
|
|
142
|
-
...t.deliverables,
|
|
143
|
-
{
|
|
144
|
-
type: 'text',
|
|
145
|
-
label: `Rework Feedback (Cycle ${t.reworkCount})`,
|
|
146
|
-
content: feedback,
|
|
147
|
-
},
|
|
148
|
-
];
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Mark a task as rejected (max rework exceeded or explicitly rejected).
|
|
154
|
-
*/
|
|
155
|
-
reject(taskId: string, reason: string): Task {
|
|
156
|
-
return this._transition(taskId, 'rejected', task => {
|
|
157
|
-
task.deliverables = [
|
|
158
|
-
...task.deliverables,
|
|
159
|
-
{ type: 'text', label: 'Rejection Reason', content: reason },
|
|
160
|
-
];
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Mark a task as failed (unexpected error).
|
|
166
|
-
*/
|
|
167
|
-
fail(taskId: string, error: Error): Task {
|
|
168
|
-
return this._transition(taskId, 'failed', task => {
|
|
169
|
-
task.deliverables = [
|
|
170
|
-
...task.deliverables,
|
|
171
|
-
{ type: 'text', label: 'Error', content: error.message },
|
|
172
|
-
];
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Check if a goal has all tasks completed or failed.
|
|
178
|
-
*/
|
|
179
|
-
isGoalDone(goalId: string): boolean {
|
|
180
|
-
const tasks = this.getByGoal(goalId);
|
|
181
|
-
if (tasks.length === 0) return false;
|
|
182
|
-
return tasks.every(t => t.status === 'completed' || t.status === 'failed' || t.status === 'rejected');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ─── Private ──────────────────────────────────────────────────────────────
|
|
186
|
-
|
|
187
|
-
private _transition(
|
|
188
|
-
taskId: string,
|
|
189
|
-
status: TaskStatus,
|
|
190
|
-
mutate?: (task: Task) => void
|
|
191
|
-
): Task {
|
|
192
|
-
const task = this._getOrThrow(taskId);
|
|
193
|
-
task.status = status;
|
|
194
|
-
task.updatedAt = new Date().toISOString();
|
|
195
|
-
mutate?.(task);
|
|
196
|
-
return task;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private _getOrThrow(taskId: string): Task {
|
|
200
|
-
const task = this.tasks.get(taskId);
|
|
201
|
-
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
202
|
-
return task;
|
|
203
|
-
}
|
|
204
|
-
}
|