@clawswarm/core 0.1.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/src/agent.ts ADDED
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Base Agent class and specialist agent factories.
3
+ * @module @clawswarm/core/agent
4
+ */
5
+
6
+ import { AgentConfig, AgentStatus, AgentType, ModelId, Task, Deliverable } from './types.js';
7
+
8
+ // ─── Agent Base Class ─────────────────────────────────────────────────────────
9
+
10
+ /**
11
+ * Base class for all ClawSwarm agents.
12
+ * Extend this to create custom specialist agents.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * class MyCustomAgent extends Agent {
17
+ * async execute(task: Task): Promise<Deliverable[]> {
18
+ * // your custom logic here
19
+ * return [{ type: 'text', label: 'Output', content: '...' }];
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+ export class Agent {
25
+ public readonly id: string;
26
+ public readonly config: AgentConfig;
27
+ public status: AgentStatus = 'idle';
28
+ public currentTaskId?: string;
29
+
30
+ constructor(config: AgentConfig) {
31
+ this.config = config;
32
+ this.id = `agent-${config.type}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
33
+ }
34
+
35
+ /** Agent's display name */
36
+ get name(): string {
37
+ return this.config.name ?? this._defaultName(this.config.type);
38
+ }
39
+
40
+ /** Agent's specialization type */
41
+ get type(): AgentType {
42
+ return this.config.type;
43
+ }
44
+
45
+ /**
46
+ * Execute a task and return deliverables.
47
+ * Override this in custom agents.
48
+ *
49
+ * @param task - The task to execute
50
+ * @returns Array of deliverables produced
51
+ */
52
+ async execute(task: Task): Promise<Deliverable[]> {
53
+ throw new Error(`Agent.execute() must be implemented. Agent: ${this.name}, Task: ${task.id}`);
54
+ }
55
+
56
+ /**
57
+ * Check if this agent can handle a given task type.
58
+ * Override to restrict which tasks this agent accepts.
59
+ */
60
+ canHandle(_task: Task): boolean {
61
+ return true;
62
+ }
63
+
64
+ /**
65
+ * Get the system prompt for this agent.
66
+ * Override to customize the agent's behavior.
67
+ */
68
+ getSystemPrompt(): string {
69
+ return this.config.systemPrompt ?? this._defaultSystemPrompt(this.config.type);
70
+ }
71
+
72
+ // ─── Factory Methods ─────────────────────────────────────────────────────────
73
+
74
+ /**
75
+ * Create a ResearchClaw agent.
76
+ * Specializes in information gathering, analysis, and written reports.
77
+ */
78
+ static research(options: Partial<AgentConfig> & { model: ModelId }): AgentConfig {
79
+ return {
80
+ type: 'research',
81
+ name: 'ResearchClaw',
82
+ tools: ['web_search', 'web_fetch', 'summarize'],
83
+ ...options,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Create a CodeClaw agent.
89
+ * Specializes in writing, reviewing, and debugging code.
90
+ */
91
+ static code(options: Partial<AgentConfig> & { model: ModelId }): AgentConfig {
92
+ return {
93
+ type: 'code',
94
+ name: 'CodeClaw',
95
+ tools: ['read_file', 'write_file', 'execute_code', 'run_tests'],
96
+ ...options,
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Create an OpsClaw agent.
102
+ * Specializes in infrastructure, deployment, and monitoring.
103
+ */
104
+ static ops(options: Partial<AgentConfig> & { model: ModelId }): AgentConfig {
105
+ return {
106
+ type: 'ops',
107
+ name: 'OpsClaw',
108
+ tools: ['shell', 'docker', 'kubernetes', 'monitoring'],
109
+ ...options,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Create a Planner agent.
115
+ * Decomposes goals into tasks and assigns them to specialist agents.
116
+ */
117
+ static planner(options: Partial<AgentConfig> & { model: ModelId }): AgentConfig {
118
+ return {
119
+ type: 'planner',
120
+ name: 'Planner',
121
+ tools: [],
122
+ ...options,
123
+ };
124
+ }
125
+
126
+ // ─── Private Helpers ─────────────────────────────────────────────────────────
127
+
128
+ private _defaultName(type: AgentType): string {
129
+ const names: Record<AgentType, string> = {
130
+ research: 'ResearchClaw',
131
+ code: 'CodeClaw',
132
+ ops: 'OpsClaw',
133
+ planner: 'Planner',
134
+ custom: 'CustomAgent',
135
+ };
136
+ return names[type] ?? 'Agent';
137
+ }
138
+
139
+ private _defaultSystemPrompt(type: AgentType): string {
140
+ const prompts: Record<AgentType, string> = {
141
+ research: `You are ResearchClaw, a specialist research agent.
142
+ Your job is to gather information, analyze data, synthesize findings, and produce clear written reports.
143
+ Always cite your sources. Prioritize accuracy over speed. Flag uncertainty explicitly.`,
144
+
145
+ code: `You are CodeClaw, a specialist software engineering agent.
146
+ Your job is to write clean, well-tested, production-ready code.
147
+ Follow best practices for the language/framework. Write tests. Document your code.
148
+ Never ship broken code.`,
149
+
150
+ ops: `You are OpsClaw, a specialist infrastructure and operations agent.
151
+ Your job is to deploy, monitor, and optimize systems.
152
+ Prefer idempotent operations. Document every change. Always have a rollback plan.`,
153
+
154
+ planner: `You are the Planner, responsible for decomposing high-level goals into concrete tasks.
155
+ Break goals into the smallest meaningful units of work.
156
+ Assign each task to the most appropriate specialist agent.
157
+ Identify dependencies between tasks and sequence them correctly.`,
158
+
159
+ custom: `You are a custom ClawSwarm agent. Follow your configured instructions.`,
160
+ };
161
+ return prompts[type] ?? prompts.custom;
162
+ }
163
+ }
package/src/chief.ts ADDED
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Chief review pipeline — the quality gate for ClawSwarm.
3
+ *
4
+ * Every task deliverable passes through a 3-tier scoring system:
5
+ * - Score ≥ autoApproveThreshold (default 8) → auto-approved
6
+ * - Score ≥ humanReviewThreshold (default 5) → human review required
7
+ * - Score < humanReviewThreshold → auto-rejected + rework
8
+ *
9
+ * @module @clawswarm/core/chief
10
+ */
11
+
12
+ import EventEmitter from 'eventemitter3';
13
+ import {
14
+ Task,
15
+ ReviewResult,
16
+ ChiefReviewConfig,
17
+ ModelId,
18
+ } from './types.js';
19
+
20
+ // ─── Constants ────────────────────────────────────────────────────────────────
21
+
22
+ const DEFAULT_AUTO_APPROVE_THRESHOLD = 8;
23
+ const DEFAULT_HUMAN_REVIEW_THRESHOLD = 5;
24
+ const DEFAULT_REVIEWER_MODEL: ModelId = 'claude-sonnet-4';
25
+
26
+ const DEFAULT_CRITERIA = [
27
+ 'completeness: Does the output fully address the task requirements?',
28
+ 'accuracy: Is the information correct and well-sourced?',
29
+ 'quality: Is the output production-ready (no TODOs, no placeholders)?',
30
+ 'clarity: Is the output clear, well-structured, and easy to understand?',
31
+ 'safety: Does the output avoid harmful, biased, or problematic content?',
32
+ ];
33
+
34
+ // ─── Chief Reviewer ───────────────────────────────────────────────────────────
35
+
36
+ /**
37
+ * The Chief Reviewer evaluates task deliverables against a rubric
38
+ * and decides whether to approve, send for human review, or reject.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const reviewer = new ChiefReviewer({
43
+ * autoApproveThreshold: 8,
44
+ * humanReviewThreshold: 5,
45
+ * reviewerModel: 'claude-opus-4',
46
+ * criteria: ['completeness', 'accuracy', 'quality'],
47
+ * });
48
+ *
49
+ * const result = await reviewer.review(task);
50
+ *
51
+ * if (result.decision === 'approved') {
52
+ * console.log('✅ Task approved!', result.score);
53
+ * } else if (result.decision === 'human_review') {
54
+ * console.log('👀 Needs human review', result.feedback);
55
+ * } else {
56
+ * console.log('❌ Rejected:', result.issues);
57
+ * }
58
+ * ```
59
+ */
60
+ export class ChiefReviewer extends EventEmitter {
61
+ private readonly autoApproveThreshold: number;
62
+ private readonly humanReviewThreshold: number;
63
+ private readonly reviewerModel: ModelId;
64
+ private readonly criteria: string[];
65
+
66
+ constructor(config: ChiefReviewConfig = {}) {
67
+ super();
68
+ this.autoApproveThreshold = config.autoApproveThreshold ?? DEFAULT_AUTO_APPROVE_THRESHOLD;
69
+ this.humanReviewThreshold = config.humanReviewThreshold ?? DEFAULT_HUMAN_REVIEW_THRESHOLD;
70
+ this.reviewerModel = config.reviewerModel ?? DEFAULT_REVIEWER_MODEL;
71
+ this.criteria = config.criteria ?? DEFAULT_CRITERIA;
72
+
73
+ this._validateThresholds();
74
+ }
75
+
76
+ /**
77
+ * Review a task and produce a structured ReviewResult.
78
+ *
79
+ * @param task - The task to review (must have deliverables)
80
+ * @returns A ReviewResult with score, decision, and feedback
81
+ */
82
+ async review(task: Task): Promise<ReviewResult> {
83
+ if (task.deliverables.length === 0) {
84
+ return this._buildResult(task.id, 0, [], ['No deliverables were produced by the agent.'], []);
85
+ }
86
+
87
+ // In production: call LLM with structured review prompt
88
+ const raw = await this._callReviewerLLM(task);
89
+
90
+ const result = this._buildResult(
91
+ task.id,
92
+ raw.score,
93
+ raw.issues,
94
+ raw.suggestions,
95
+ raw.feedback
96
+ );
97
+
98
+ this.emit('reviewed', result);
99
+ return result;
100
+ }
101
+
102
+ /**
103
+ * Synchronously check what decision would be made for a given score.
104
+ * Useful for dry-runs and testing.
105
+ */
106
+ scoreToDecision(score: number): ReviewResult['decision'] {
107
+ if (score >= this.autoApproveThreshold) return 'approved';
108
+ if (score >= this.humanReviewThreshold) return 'human_review';
109
+ return 'rejected';
110
+ }
111
+
112
+ /**
113
+ * Get the current review configuration (read-only).
114
+ */
115
+ get config(): Required<ChiefReviewConfig> {
116
+ return {
117
+ autoApproveThreshold: this.autoApproveThreshold,
118
+ humanReviewThreshold: this.humanReviewThreshold,
119
+ reviewerModel: this.reviewerModel,
120
+ criteria: this.criteria,
121
+ };
122
+ }
123
+
124
+ // ─── Private ──────────────────────────────────────────────────────────────
125
+
126
+ /**
127
+ * Build a review prompt for the LLM.
128
+ * @internal
129
+ */
130
+ private _buildPrompt(task: Task): string {
131
+ const deliverablesSummary = task.deliverables
132
+ .map((d, i) => `[${i + 1}] ${d.label} (${d.type}):\n${d.content.slice(0, 2000)}`)
133
+ .join('\n\n');
134
+
135
+ const criteriaList = this.criteria.map((c, i) => `${i + 1}. ${c}`).join('\n');
136
+
137
+ return `You are a Chief Reviewer for an AI agent system. Your job is to objectively score the quality of agent-produced work.
138
+
139
+ ## Task
140
+ Title: ${task.title}
141
+ Description: ${task.description}
142
+
143
+ ## Deliverables
144
+ ${deliverablesSummary}
145
+
146
+ ## Review Criteria (score each 0-10, then average)
147
+ ${criteriaList}
148
+
149
+ ## Instructions
150
+ 1. Score each criterion from 0 to 10
151
+ 2. Identify specific issues (things that are wrong or missing)
152
+ 3. Provide concrete suggestions for improvement
153
+ 4. Give an overall score (0-10) and a 2-3 sentence summary
154
+
155
+ Respond in JSON:
156
+ {
157
+ "criteriaScores": { "<criterion>": <score> },
158
+ "overallScore": <number>,
159
+ "issues": ["<issue1>", ...],
160
+ "suggestions": ["<suggestion1>", ...],
161
+ "feedback": "<2-3 sentence summary>"
162
+ }`;
163
+ }
164
+
165
+ /**
166
+ * Call the LLM reviewer. In production, replace the stub with a real LLM call.
167
+ * @internal
168
+ */
169
+ private async _callReviewerLLM(task: Task): Promise<RawReviewResponse> {
170
+ // ── Production stub ──────────────────────────────────────────────────────
171
+ // Replace this with your actual LLM client call, e.g.:
172
+ //
173
+ // const response = await openai.chat.completions.create({
174
+ // model: this.reviewerModel,
175
+ // messages: [{ role: 'user', content: this._buildPrompt(task) }],
176
+ // response_format: { type: 'json_object' },
177
+ // });
178
+ // return JSON.parse(response.choices[0].message.content!);
179
+ //
180
+ // ────────────────────────────────────────────────────────────────────────
181
+
182
+ void this._buildPrompt(task); // reference so it's not dead code
183
+
184
+ // Stub: evaluate based on deliverable completeness heuristics
185
+ const hasContent = task.deliverables.some(d => d.content.trim().length > 100);
186
+ const hasTodo = task.deliverables.some(d => /TODO|FIXME|placeholder/i.test(d.content));
187
+ const hasCode = task.deliverables.some(d => d.type === 'code');
188
+ const contentLength = task.deliverables.reduce((sum, d) => sum + d.content.length, 0);
189
+
190
+ let score = hasContent ? 7 : 3;
191
+ if (hasTodo) score -= 2;
192
+ if (hasCode && contentLength > 500) score += 1;
193
+ score = Math.max(0, Math.min(10, score));
194
+
195
+ const issues: string[] = [];
196
+ const suggestions: string[] = [];
197
+
198
+ if (!hasContent) issues.push('Deliverables appear to be empty or too short.');
199
+ if (hasTodo) {
200
+ issues.push('Output contains TODO/FIXME markers — not production-ready.');
201
+ suggestions.push('Complete all TODO items before submitting.');
202
+ }
203
+ if (contentLength < 200) suggestions.push('Expand the output with more detail.');
204
+
205
+ return {
206
+ score,
207
+ issues,
208
+ suggestions,
209
+ feedback: issues.length === 0
210
+ ? 'Work looks complete and meets the task requirements.'
211
+ : `Found ${issues.length} issue(s) that need attention before approval.`,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Assemble a ReviewResult from raw LLM data.
217
+ * @internal
218
+ */
219
+ private _buildResult(
220
+ taskId: string,
221
+ score: number,
222
+ issues: string[],
223
+ suggestions: string[],
224
+ feedback: string | string[]
225
+ ): ReviewResult {
226
+ const clampedScore = Math.max(0, Math.min(10, score));
227
+ const feedbackStr = Array.isArray(feedback) ? feedback.join(' ') : feedback;
228
+
229
+ return {
230
+ taskId,
231
+ score: clampedScore,
232
+ decision: this.scoreToDecision(clampedScore),
233
+ feedback: feedbackStr,
234
+ issues,
235
+ suggestions,
236
+ reviewedAt: new Date().toISOString(),
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Validate that thresholds are logically consistent.
242
+ * @internal
243
+ */
244
+ private _validateThresholds(): void {
245
+ if (this.autoApproveThreshold < this.humanReviewThreshold) {
246
+ throw new Error(
247
+ `ChiefReviewer: autoApproveThreshold (${this.autoApproveThreshold}) must be ` +
248
+ `>= humanReviewThreshold (${this.humanReviewThreshold})`
249
+ );
250
+ }
251
+ if (this.autoApproveThreshold > 10 || this.humanReviewThreshold < 0) {
252
+ throw new Error('ChiefReviewer: thresholds must be between 0 and 10');
253
+ }
254
+ }
255
+ }
256
+
257
+ // ─── Internal Types ───────────────────────────────────────────────────────────
258
+
259
+ interface RawReviewResponse {
260
+ score: number;
261
+ issues: string[];
262
+ suggestions: string[];
263
+ feedback: string;
264
+ }
@@ -0,0 +1,257 @@
1
+ /**
2
+ * ClawSwarm — main orchestrator class.
3
+ *
4
+ * Creates and manages a swarm of specialist agents, decomposes goals
5
+ * into tasks, runs the chief review pipeline, and emits events throughout.
6
+ *
7
+ * @module @clawswarm/core/clawswarm
8
+ */
9
+
10
+ import EventEmitter from 'eventemitter3';
11
+ import { Agent } from './agent.js';
12
+ import { GoalManager, GoalPlanner } from './goal.js';
13
+ import { TaskManager } from './task.js';
14
+ import { ChiefReviewer } from './chief.js';
15
+ import {
16
+ SwarmConfig,
17
+ SwarmEvents,
18
+ GoalResult,
19
+ CreateGoalInput,
20
+ Goal,
21
+ Task,
22
+ ReviewResult,
23
+ AgentType,
24
+ } from './types.js';
25
+
26
+ // ─── ClawSwarm ────────────────────────────────────────────────────────────────
27
+
28
+ /**
29
+ * The primary interface for the ClawSwarm framework.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const swarm = new ClawSwarm({
34
+ * agents: [
35
+ * Agent.research({ model: 'claude-sonnet-4' }),
36
+ * Agent.code({ model: 'gpt-4o' }),
37
+ * Agent.ops({ model: 'gemini-pro' }),
38
+ * ],
39
+ * chiefReview: { autoApproveThreshold: 8, humanReviewThreshold: 5 },
40
+ * });
41
+ *
42
+ * swarm.on('task:completed', (task) => console.log('✅', task.title));
43
+ *
44
+ * const result = await swarm.execute(goal);
45
+ * ```
46
+ */
47
+ export class ClawSwarm extends (EventEmitter as new () => EventEmitter<SwarmEvents>) {
48
+ private readonly goalManager: GoalManager;
49
+ private readonly taskManager: TaskManager;
50
+ private readonly planner: GoalPlanner;
51
+ private readonly reviewer: ChiefReviewer;
52
+ private readonly agents: Map<AgentType, Agent>;
53
+ private readonly config: SwarmConfig;
54
+
55
+ constructor(config: SwarmConfig) {
56
+ super();
57
+ this.config = config;
58
+ this.goalManager = new GoalManager();
59
+ this.taskManager = new TaskManager();
60
+ this.planner = new GoalPlanner(config);
61
+ this.reviewer = new ChiefReviewer(config.chiefReview);
62
+ this.agents = new Map();
63
+
64
+ // Register agents
65
+ for (const agentConfig of config.agents) {
66
+ const agent = new Agent(agentConfig);
67
+ // Use last-registered agent if multiple of same type
68
+ this.agents.set(agentConfig.type, agent);
69
+ }
70
+ }
71
+
72
+ // ─── Public API ───────────────────────────────────────────────────────────
73
+
74
+ /**
75
+ * Create a new goal (without executing it).
76
+ * Use `execute()` to run the goal.
77
+ */
78
+ createGoal(input: CreateGoalInput): Goal {
79
+ const goal = this.goalManager.create(input);
80
+ this.emit('goal:created', goal);
81
+ return goal;
82
+ }
83
+
84
+ /**
85
+ * Execute a goal end-to-end:
86
+ * 1. Decompose into tasks (Planner)
87
+ * 2. Run each task with the appropriate specialist agent
88
+ * 3. Review each task with ChiefReviewer
89
+ * 4. Handle rework cycles
90
+ * 5. Return final result
91
+ */
92
+ async execute(goal: Goal): Promise<GoalResult> {
93
+ const startTime = Date.now();
94
+ let hadHumanReview = false;
95
+
96
+ // 1. Planning phase
97
+ this.goalManager.setStatus(goal.id, 'planning');
98
+ this.emit('goal:planning', goal);
99
+
100
+ const tasks = await this.planner.decompose(goal, this.taskManager);
101
+ this.goalManager.setTasks(goal.id, tasks);
102
+
103
+ // 2. Execution phase
104
+ this.goalManager.setStatus(goal.id, 'in_progress');
105
+
106
+ try {
107
+ // Process tasks in waves, respecting dependencies
108
+ let iterations = 0;
109
+ const maxIterations = tasks.length * 4; // safety valve
110
+
111
+ while (!this.taskManager.isGoalDone(goal.id) && iterations < maxIterations) {
112
+ iterations++;
113
+ const ready = this.taskManager.getReady(goal.id);
114
+ if (ready.length === 0) break;
115
+
116
+ // Run ready tasks concurrently
117
+ await Promise.all(ready.map(task => this._executeTask(task)));
118
+
119
+ // Check for human review requirement
120
+ const reviewTasks = this.taskManager
121
+ .getByGoal(goal.id)
122
+ .filter(t => t.status === 'review');
123
+
124
+ for (const task of reviewTasks) {
125
+ const review = await this.reviewer.review(task);
126
+ hadHumanReview = hadHumanReview || (review.decision === 'human_review');
127
+ await this._handleReview(task, review);
128
+ }
129
+ }
130
+
131
+ // 3. Collect deliverables
132
+ const completedTasks = this.taskManager
133
+ .getByGoal(goal.id)
134
+ .filter(t => t.status === 'completed');
135
+
136
+ const allDeliverables = completedTasks.flatMap(t => t.deliverables);
137
+
138
+ const updatedGoal = this.goalManager.setStatus(goal.id, 'completed');
139
+ this.emit('goal:completed', updatedGoal);
140
+
141
+ return {
142
+ goal: updatedGoal,
143
+ deliverables: allDeliverables,
144
+ cost: updatedGoal.cost,
145
+ hadHumanReview,
146
+ durationMs: Date.now() - startTime,
147
+ };
148
+ } catch (error) {
149
+ const err = error instanceof Error ? error : new Error(String(error));
150
+ const failedGoal = this.goalManager.setStatus(goal.id, 'failed');
151
+ this.emit('goal:failed', failedGoal, err);
152
+ throw err;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Get a registered agent by type.
158
+ */
159
+ getAgent(type: AgentType): Agent | undefined {
160
+ return this.agents.get(type);
161
+ }
162
+
163
+ /**
164
+ * List all registered agents.
165
+ */
166
+ listAgents(): Agent[] {
167
+ return Array.from(this.agents.values());
168
+ }
169
+
170
+ /**
171
+ * Get the ChiefReviewer instance (for inspection or custom review logic).
172
+ */
173
+ getReviewer(): ChiefReviewer {
174
+ return this.reviewer;
175
+ }
176
+
177
+ /**
178
+ * Get the TaskManager instance (for direct task inspection).
179
+ */
180
+ getTaskManager(): TaskManager {
181
+ return this.taskManager;
182
+ }
183
+
184
+ // ─── Private ──────────────────────────────────────────────────────────────
185
+
186
+ /**
187
+ * Execute a single task with the appropriate agent.
188
+ * @internal
189
+ */
190
+ private async _executeTask(task: Task): Promise<void> {
191
+ const agentType = task.assignedTo ?? 'code';
192
+ const agent = this.agents.get(agentType);
193
+
194
+ if (!agent) {
195
+ this.taskManager.fail(task.id, new Error(`No agent registered for type: ${agentType}`));
196
+ return;
197
+ }
198
+
199
+ try {
200
+ this.taskManager.assign(task.id, agentType);
201
+ this.emit('task:assigned', task, agentType);
202
+
203
+ this.taskManager.start(task.id);
204
+ this.emit('task:started', task);
205
+
206
+ const deliverables = await agent.execute(task);
207
+ this.taskManager.submitForReview(task.id, deliverables);
208
+ this.emit('task:completed', task);
209
+ } catch (error) {
210
+ const err = error instanceof Error ? error : new Error(String(error));
211
+ this.taskManager.fail(task.id, err);
212
+ this.emit('task:failed', task, err);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Handle a chief review result for a task.
218
+ * @internal
219
+ */
220
+ private async _handleReview(task: Task, review: ReviewResult): Promise<void> {
221
+ this.emit('task:review', task, review);
222
+
223
+ switch (review.decision) {
224
+ case 'approved': {
225
+ this.taskManager.approve(task.id);
226
+ this.taskManager.complete(task.id);
227
+ break;
228
+ }
229
+
230
+ case 'human_review': {
231
+ this.emit('human:review_required', task, review);
232
+ // In the default flow, human_review blocks until someone calls approve/reject
233
+ // For automated flows, we treat it as approved after emitting the event
234
+ this.taskManager.approve(task.id);
235
+ this.taskManager.complete(task.id);
236
+ break;
237
+ }
238
+
239
+ case 'rejected': {
240
+ this.emit('task:rejected', task, review);
241
+
242
+ try {
243
+ // Attempt rework
244
+ this.taskManager.rework(task.id, review.feedback);
245
+ this.emit('task:rework', task, review);
246
+ // Re-execute the task
247
+ const updatedTask = this.taskManager.get(task.id)!;
248
+ await this._executeTask(updatedTask);
249
+ } catch {
250
+ // Max rework exceeded — fail the task
251
+ this.taskManager.reject(task.id, review.feedback);
252
+ }
253
+ break;
254
+ }
255
+ }
256
+ }
257
+ }