pixelcrew 0.2.2 → 0.2.6
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/README.md +389 -315
- package/bin/pixel-agents.js +306 -149
- package/package.json +3 -1
- package/skill/SKILL.md +185 -0
- package/skill/references/anti-ai-rubric.md +30 -0
- package/skill/references/anti-ai-slop-encyclopedia.md +181 -0
- package/skill/references/commands-reference.md +135 -0
- package/skill/references/testing-and-qa-strategy.md +25 -0
- package/skill/references/token-efficiency.md +20 -0
- package/src/adapters/adapter.interface.js +77 -0
- package/src/adapters/antigravity.js +138 -0
- package/src/adapters/claude-code.js +149 -0
- package/src/adapters/codex.js +131 -0
- package/src/adapters/cursor.js +133 -0
- package/src/adapters/generic.js +63 -0
- package/src/adapters/index.js +15 -0
- package/src/adapters/kiro.js +134 -0
- package/src/adapters/registry.js +150 -0
- package/src/backend/analyzer/complexity-analyzer.js +104 -0
- package/src/backend/analyzer/requirement-analyzer.js +213 -0
- package/src/backend/analyzer/threat-analyzer.js +122 -0
- package/src/backend/api/api-designer.js +90 -0
- package/src/backend/api/error-contract.js +132 -0
- package/src/backend/api/route-generator.js +92 -0
- package/src/backend/api/schema-generator.js +64 -0
- package/src/backend/architecture/adr-generator.js +62 -0
- package/src/backend/architecture/architecture-reasoner.js +119 -0
- package/src/backend/architecture/architecture-selector.js +57 -0
- package/src/backend/architecture/tradeoff-analyzer.js +92 -0
- package/src/backend/data/data-modeler.js +114 -0
- package/src/backend/data/index-analyzer.js +93 -0
- package/src/backend/data/query-analyzer.js +41 -0
- package/src/backend/data/schema-generator.js +92 -0
- package/src/backend/data/transaction-analyzer.js +49 -0
- package/src/backend/index.js +67 -0
- package/src/backend/observability/logging-engine.js +80 -0
- package/src/backend/observability/metrics-engine.js +31 -0
- package/src/backend/observability/tracing-engine.js +16 -0
- package/src/backend/patterns/pattern-registry.js +74 -0
- package/src/backend/patterns/pattern-selector.js +39 -0
- package/src/backend/patterns/tradeoff-engine.js +17 -0
- package/src/backend/performance/cache-analyzer.js +31 -0
- package/src/backend/performance/concurrency-analyzer.js +34 -0
- package/src/backend/performance/performance-engine.js +98 -0
- package/src/backend/performance/query-optimizer.js +16 -0
- package/src/backend/reliability/failure-analyzer.js +22 -0
- package/src/backend/reliability/idempotency-engine.js +56 -0
- package/src/backend/reliability/resilience-engine.js +141 -0
- package/src/backend/reliability/retry-analyzer.js +15 -0
- package/src/backend/security/auth-selector.js +33 -0
- package/src/backend/security/authorization-engine.js +90 -0
- package/src/backend/security/policy-engine.js +49 -0
- package/src/backend/security/secret-analyzer.js +39 -0
- package/src/backend/security/security-engine.js +122 -0
- package/src/backend/security/threat-model.js +59 -0
- package/src/backend/synthesis/backend-synthesizer.js +98 -0
- package/src/backend/synthesis/infrastructure-synthesizer.js +108 -0
- package/src/backend/synthesis/module-synthesizer.js +262 -0
- package/src/backend/validation/architecture-validator.js +70 -0
- package/src/backend/validation/backend-quality-validator.js +71 -0
- package/src/backend/validation/dependency-validator.js +20 -0
- package/src/backend/validation/performance-validator.js +38 -0
- package/src/backend/validation/security-validator.js +73 -0
- package/src/commands/adapt.js +105 -0
- package/src/commands/add.js +78 -0
- package/src/commands/animate.js +48 -0
- package/src/commands/assemble.js +57 -0
- package/src/commands/audit.js +93 -0
- package/src/commands/backend.js +125 -0
- package/src/commands/bento.js +25 -0
- package/src/commands/blueprint.js +65 -0
- package/src/commands/bolder.js +48 -0
- package/src/commands/boss-fight.js +60 -0
- package/src/commands/build.js +39 -0
- package/src/commands/calibrate.js +25 -0
- package/src/commands/chromatic.js +25 -0
- package/src/commands/clarify.js +52 -0
- package/src/commands/colorize.js +57 -0
- package/src/commands/command.interface.js +28 -0
- package/src/commands/commands.js +84 -0
- package/src/commands/craft.js +57 -0
- package/src/commands/crew.js +66 -0
- package/src/commands/critique.js +25 -0
- package/src/commands/de-slop.js +25 -0
- package/src/commands/delight.js +46 -0
- package/src/commands/deploy.js +42 -0
- package/src/commands/distill.js +48 -0
- package/src/commands/doctor.js +89 -0
- package/src/commands/document.js +83 -0
- package/src/commands/eight-bit.js +25 -0
- package/src/commands/extract.js +75 -0
- package/src/commands/fix.js +46 -0
- package/src/commands/harden.js +85 -0
- package/src/commands/index.js +67 -0
- package/src/commands/install.js +75 -0
- package/src/commands/layout.js +48 -0
- package/src/commands/live.js +44 -0
- package/src/commands/manifest.js +25 -0
- package/src/commands/office.js +25 -0
- package/src/commands/onboard.js +46 -0
- package/src/commands/oneshot.js +60 -0
- package/src/commands/optimize.js +46 -0
- package/src/commands/overdrive.js +46 -0
- package/src/commands/parser.js +143 -0
- package/src/commands/pixelcrew.js +96 -0
- package/src/commands/plan.js +66 -0
- package/src/commands/polish.js +45 -0
- package/src/commands/quieter.js +48 -0
- package/src/commands/recap.js +358 -0
- package/src/commands/registry.js +353 -0
- package/src/commands/render.js +26 -0
- package/src/commands/retrofit.js +25 -0
- package/src/commands/review.js +118 -0
- package/src/commands/roster.js +25 -0
- package/src/commands/sentinel.js +25 -0
- package/src/commands/shape.js +66 -0
- package/src/commands/skills.js +57 -0
- package/src/commands/status.js +48 -0
- package/src/commands/stop.js +50 -0
- package/src/commands/sync.js +58 -0
- package/src/commands/typeset.js +58 -0
- package/src/commands/warp.js +25 -0
- package/src/core/agent-runtime.js +94 -0
- package/src/core/event-bus.js +98 -0
- package/src/core/index.js +9 -0
- package/src/core/orchestrator.js +234 -0
- package/src/core/scheduler.js +124 -0
- package/src/core/task-graph.js +185 -0
- package/src/dashboard/app.js +1172 -30
- package/src/dashboard/index.html +223 -23
- package/src/dashboard/styles.css +870 -0
- package/src/index.js +16 -0
- package/src/orchestrator/artifact-graph.js +104 -0
- package/src/orchestrator/ast-normalizer.js +147 -0
- package/src/orchestrator/code-generator.js +846 -0
- package/src/orchestrator/design-engine.js +203 -0
- package/src/orchestrator/engine.js +318 -39
- package/src/orchestrator/oneshot.js +351 -1561
- package/src/orchestrator/ontology.js +394 -0
- package/src/orchestrator/planner.js +302 -0
- package/src/orchestrator/repair-planner.js +67 -0
- package/src/orchestrator/requirement-contract.js +177 -0
- package/src/orchestrator/requirement-validator.js +39 -0
- package/src/orchestrator/semantic-engine.js +582 -0
- package/src/orchestrator/semantic-reviewer.js +179 -0
- package/src/orchestrator/skills-registry.js +194 -0
- package/src/orchestrator/task-planner.js +111 -0
- package/src/orchestrator/task-queue.js +128 -0
- package/src/orchestrator/telemetry.js +209 -0
- package/src/orchestrator/ui-synthesizer.js +245 -0
- package/src/orchestrator/visual-critic.js +106 -0
- package/src/policies/architecture/layering.policy.js +14 -0
- package/src/policies/performance/indexing.policy.js +13 -0
- package/src/policies/reliability/resilience.policy.js +13 -0
- package/src/policies/security/auth.policy.js +31 -0
- package/src/protocol/agent.js +124 -0
- package/src/protocol/event.js +78 -0
- package/src/protocol/index.js +8 -0
- package/src/protocol/skill.js +54 -0
- package/src/protocol/task.js +100 -0
- package/src/scaffold/claude-generator.js +60 -0
- package/src/scaffold/commands-catalog.js +321 -0
- package/src/scaffold/cursor-generator.js +74 -0
- package/src/scaffold/ide-rules.js +133 -0
- package/src/scaffold/init.js +415 -91
- package/src/scaffold/installer.js +571 -0
- package/src/scaffold/kiro-generator.js +86 -0
- package/src/scaffold/skills-bundle.js +520 -0
- package/src/scaffold/templates.js +57 -4
- package/src/scaffold/workstations.js +364 -0
- package/src/server/server.js +292 -42
- package/src/utils/fs-safe.js +168 -0
- package/src/utils/markdown-report.js +175 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PIXEL CREW — Decoupled Universal Core Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates multi-agent pipelines, task graph scheduling, provider routing,
|
|
5
|
+
* and real-time state telemetry completely decoupled from specific IDEs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { EventEmitter } from 'node:events';
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { TaskGraph } from './task-graph.js';
|
|
12
|
+
import { Scheduler } from './scheduler.js';
|
|
13
|
+
import { AgentRuntime } from './agent-runtime.js';
|
|
14
|
+
import { EventBus } from './event-bus.js';
|
|
15
|
+
import { AGENT_STATES, AGENT_EXPRESSIONS, ORCHESTRATOR_EXPRESSIONS } from '../protocol/agent.js';
|
|
16
|
+
import { EVENT_TYPES } from '../protocol/event.js';
|
|
17
|
+
import { TASK_STATUS } from '../protocol/task.js';
|
|
18
|
+
|
|
19
|
+
export class CoreOrchestrator extends EventEmitter {
|
|
20
|
+
constructor(rootDir = process.cwd(), options = {}) {
|
|
21
|
+
super();
|
|
22
|
+
this.rootDir = rootDir;
|
|
23
|
+
this.options = options;
|
|
24
|
+
|
|
25
|
+
// Filesystem compatibility paths (.pixel-crew primary, .pixel-agents legacy fallback)
|
|
26
|
+
this.crewDir = path.join(rootDir, '.pixel-crew');
|
|
27
|
+
this.agentsDir = path.join(rootDir, '.pixel-agents');
|
|
28
|
+
this.activeDir = this.crewDir;
|
|
29
|
+
|
|
30
|
+
this.eventBus = new EventBus();
|
|
31
|
+
this.agentRuntime = new AgentRuntime({ eventBus: this.eventBus });
|
|
32
|
+
this.scheduler = new Scheduler({
|
|
33
|
+
maxConcurrent: options.maxConcurrent || 4,
|
|
34
|
+
agentRuntime: this.agentRuntime
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this.providerRegistry = null;
|
|
38
|
+
this.skillRegistry = null;
|
|
39
|
+
this.config = null;
|
|
40
|
+
this.state = null;
|
|
41
|
+
this.activeTaskGraph = null;
|
|
42
|
+
this.activeSprintAbort = null;
|
|
43
|
+
|
|
44
|
+
// Forward events from eventBus to orchestrator listeners
|
|
45
|
+
this.eventBus.on('event', (evt) => {
|
|
46
|
+
this.emit('event', evt);
|
|
47
|
+
this.emit('agent_event', evt);
|
|
48
|
+
this.syncStateFromEvent(evt);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setProviderRegistry(registry) {
|
|
53
|
+
this.providerRegistry = registry;
|
|
54
|
+
this.scheduler.setProviderRegistry(registry);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
setSkillRegistry(registry) {
|
|
58
|
+
this.skillRegistry = registry;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async initialize() {
|
|
62
|
+
// Check if .pixel-crew exists, fallback to .pixel-agents
|
|
63
|
+
let hasCrewDir = false;
|
|
64
|
+
try {
|
|
65
|
+
await fs.access(this.crewDir);
|
|
66
|
+
hasCrewDir = true;
|
|
67
|
+
this.activeDir = this.crewDir;
|
|
68
|
+
} catch {
|
|
69
|
+
try {
|
|
70
|
+
await fs.access(this.agentsDir);
|
|
71
|
+
this.activeDir = this.agentsDir;
|
|
72
|
+
} catch {
|
|
73
|
+
this.activeDir = this.crewDir;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const configPath = path.join(this.activeDir, 'config.json');
|
|
78
|
+
const statePath = path.join(this.activeDir, 'state.json');
|
|
79
|
+
const eventsPath = path.join(this.activeDir, 'events.jsonl');
|
|
80
|
+
|
|
81
|
+
// Register log paths on event bus
|
|
82
|
+
this.eventBus.addLogFilePath(eventsPath);
|
|
83
|
+
if (this.activeDir !== this.agentsDir) {
|
|
84
|
+
// Also register legacy path if .pixel-agents exists
|
|
85
|
+
try {
|
|
86
|
+
await fs.access(this.agentsDir);
|
|
87
|
+
this.eventBus.addLogFilePath(path.join(this.agentsDir, 'events.jsonl'));
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Load Config
|
|
92
|
+
try {
|
|
93
|
+
const raw = await fs.readFile(configPath, 'utf-8');
|
|
94
|
+
this.config = JSON.parse(raw);
|
|
95
|
+
} catch {
|
|
96
|
+
this.config = {
|
|
97
|
+
project: path.basename(this.rootDir),
|
|
98
|
+
orchestrator: { enabled: true, maxConcurrentAgents: 4, runtimeStrategy: 'auto' },
|
|
99
|
+
agents: {},
|
|
100
|
+
dashboard: { enabled: true, port: 4747 }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Load State
|
|
105
|
+
try {
|
|
106
|
+
const raw = await fs.readFile(statePath, 'utf-8');
|
|
107
|
+
this.state = JSON.parse(raw);
|
|
108
|
+
} catch {
|
|
109
|
+
this.state = {
|
|
110
|
+
status: 'READY',
|
|
111
|
+
activeTask: 'Waiting for tasks',
|
|
112
|
+
startedAt: null,
|
|
113
|
+
completedAt: null,
|
|
114
|
+
orchestrator: { state: AGENT_STATES.IDLE, expression: ORCHESTRATOR_EXPRESSIONS.IDLE, activeSubtasks: 0, totalSubtasks: 0, progress: 0 },
|
|
115
|
+
agents: {}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Load Events
|
|
120
|
+
await this.eventBus.loadFromDisk(eventsPath);
|
|
121
|
+
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getState() {
|
|
126
|
+
return this.state;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getConfig() {
|
|
130
|
+
return this.config;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getEvents() {
|
|
134
|
+
return this.eventBus.getHistory();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async emitEvent(eventData) {
|
|
138
|
+
return await this.eventBus.emitEvent(eventData);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
syncStateFromEvent(event) {
|
|
142
|
+
if (!this.state) return;
|
|
143
|
+
|
|
144
|
+
if (event.type === EVENT_TYPES.ORCHESTRATION_STARTED) {
|
|
145
|
+
this.state.status = 'RUNNING';
|
|
146
|
+
this.state.activeTask = event.taskName || event.message;
|
|
147
|
+
this.state.startedAt = event.timestamp;
|
|
148
|
+
this.state.completedAt = null;
|
|
149
|
+
this.state.orchestrator = {
|
|
150
|
+
state: AGENT_STATES.WORKING,
|
|
151
|
+
expression: ORCHESTRATOR_EXPRESSIONS.COORDINATING,
|
|
152
|
+
progress: 5
|
|
153
|
+
};
|
|
154
|
+
} else if (event.type === EVENT_TYPES.ORCHESTRATION_COMPLETED) {
|
|
155
|
+
this.state.status = 'COMPLETED';
|
|
156
|
+
this.state.completedAt = event.timestamp;
|
|
157
|
+
this.state.orchestrator = {
|
|
158
|
+
state: AGENT_STATES.COMPLETED,
|
|
159
|
+
expression: ORCHESTRATOR_EXPRESSIONS.COMPLETED,
|
|
160
|
+
progress: 100
|
|
161
|
+
};
|
|
162
|
+
} else if (event.type === EVENT_TYPES.AGENT_STARTED) {
|
|
163
|
+
if (event.agent && this.state.agents) {
|
|
164
|
+
this.state.agents[event.agent] = {
|
|
165
|
+
state: AGENT_STATES.WORKING,
|
|
166
|
+
expression: AGENT_EXPRESSIONS.WORKING,
|
|
167
|
+
currentTask: event.taskName || event.message,
|
|
168
|
+
provider: event.provider
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
} else if (event.type === EVENT_TYPES.TASK_COMPLETED) {
|
|
172
|
+
if (event.agent && this.state.agents) {
|
|
173
|
+
this.state.agents[event.agent] = {
|
|
174
|
+
state: AGENT_STATES.COMPLETED,
|
|
175
|
+
expression: AGENT_EXPRESSIONS.COMPLETED,
|
|
176
|
+
currentTask: `Completed: ${event.taskName || ''}`,
|
|
177
|
+
provider: event.provider
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.emit('state_change', { state: this.state });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Dispatches and executes a TaskGraph
|
|
187
|
+
*/
|
|
188
|
+
async executeTaskGraph(taskGraph, options = {}) {
|
|
189
|
+
this.activeTaskGraph = taskGraph;
|
|
190
|
+
|
|
191
|
+
await this.emitEvent({
|
|
192
|
+
agent: 'orchestrator',
|
|
193
|
+
type: EVENT_TYPES.ORCHESTRATION_STARTED,
|
|
194
|
+
taskName: options.title || 'Multi-Agent Task Sprint',
|
|
195
|
+
message: `Orchestrator dispatching DAG task graph (${taskGraph.tasks.size} tasks)`
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const result = await this.scheduler.execute(taskGraph, {
|
|
200
|
+
strategy: options.strategy || this.config?.orchestrator?.runtimeStrategy || 'auto',
|
|
201
|
+
...options
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await this.emitEvent({
|
|
205
|
+
agent: 'orchestrator',
|
|
206
|
+
type: EVENT_TYPES.ORCHESTRATION_COMPLETED,
|
|
207
|
+
taskName: options.title || 'Multi-Agent Task Sprint',
|
|
208
|
+
message: `Orchestration completed successfully (${result.completed}/${result.total} tasks resolved)`
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return result;
|
|
212
|
+
} catch (err) {
|
|
213
|
+
await this.emitEvent({
|
|
214
|
+
agent: 'orchestrator',
|
|
215
|
+
type: EVENT_TYPES.ORCHESTRATION_FAILED,
|
|
216
|
+
taskName: options.title || 'Multi-Agent Task Sprint',
|
|
217
|
+
message: `Orchestration sprint failed: ${err.message}`,
|
|
218
|
+
data: { error: err.message }
|
|
219
|
+
});
|
|
220
|
+
throw err;
|
|
221
|
+
} finally {
|
|
222
|
+
this.activeTaskGraph = null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
cancelActiveExecution() {
|
|
227
|
+
this.scheduler.cancel();
|
|
228
|
+
if (this.state) {
|
|
229
|
+
this.state.status = 'READY';
|
|
230
|
+
this.state.orchestrator.state = AGENT_STATES.IDLE;
|
|
231
|
+
this.state.orchestrator.expression = ORCHESTRATOR_EXPRESSIONS.IDLE;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PIXEL CREW — Core Scheduler & Concurrency Dispatcher
|
|
3
|
+
*
|
|
4
|
+
* Schedules task batches from a TaskGraph to Worker Slots respecting dependencies,
|
|
5
|
+
* concurrency limits, and provider routing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { EventEmitter } from 'node:events';
|
|
9
|
+
import { TASK_STATUS } from '../protocol/task.js';
|
|
10
|
+
|
|
11
|
+
export class Scheduler extends EventEmitter {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
super();
|
|
14
|
+
this.maxConcurrent = options.maxConcurrent || 4;
|
|
15
|
+
this.agentRuntime = options.agentRuntime || null;
|
|
16
|
+
this.providerRegistry = options.providerRegistry || null;
|
|
17
|
+
this.abortController = null;
|
|
18
|
+
this.activeWorkers = new Set();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setAgentRuntime(runtime) {
|
|
22
|
+
this.agentRuntime = runtime;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setProviderRegistry(registry) {
|
|
26
|
+
this.providerRegistry = registry;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Executes a TaskGraph until completion or error
|
|
31
|
+
*/
|
|
32
|
+
async execute(taskGraph, options = {}) {
|
|
33
|
+
this.abortController = new AbortController();
|
|
34
|
+
const runtimeStrategy = options.strategy || 'auto';
|
|
35
|
+
const handler = options.executeTaskHandler || null;
|
|
36
|
+
|
|
37
|
+
if (taskGraph.hasCycle()) {
|
|
38
|
+
throw new Error('Deadlock detected in TaskGraph: Cyclic dependencies present');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const runningPromises = new Map();
|
|
42
|
+
|
|
43
|
+
while (!taskGraph.isCompleted() && !taskGraph.hasFailed()) {
|
|
44
|
+
if (this.abortController.signal.aborted) {
|
|
45
|
+
throw new Error('Scheduler execution aborted');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const readyTasks = taskGraph.getReadyTasks();
|
|
49
|
+
|
|
50
|
+
// If no tasks ready and none running, graph is stuck
|
|
51
|
+
if (readyTasks.length === 0 && runningPromises.size === 0) {
|
|
52
|
+
if (!taskGraph.isCompleted()) {
|
|
53
|
+
throw new Error('TaskGraph stalled: unresolvable dependencies or missing prerequisite tasks');
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Fill available concurrency slots
|
|
59
|
+
const availableSlots = this.maxConcurrent - runningPromises.size;
|
|
60
|
+
const tasksToDispatch = readyTasks.slice(0, Math.max(0, availableSlots));
|
|
61
|
+
|
|
62
|
+
for (const task of tasksToDispatch) {
|
|
63
|
+
taskGraph.markRunning(task.id);
|
|
64
|
+
|
|
65
|
+
const taskPromise = (async () => {
|
|
66
|
+
let adapter = null;
|
|
67
|
+
if (this.providerRegistry) {
|
|
68
|
+
adapter = await this.providerRegistry.getBestAgent(task, runtimeStrategy);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (this.agentRuntime) {
|
|
72
|
+
return await this.agentRuntime.executeTask(task, adapter, {
|
|
73
|
+
abortController: this.abortController,
|
|
74
|
+
executeTaskHandler: handler,
|
|
75
|
+
...options
|
|
76
|
+
});
|
|
77
|
+
} else if (handler) {
|
|
78
|
+
return await handler(task);
|
|
79
|
+
} else {
|
|
80
|
+
await new Promise(r => setTimeout(r, 200));
|
|
81
|
+
return { success: true };
|
|
82
|
+
}
|
|
83
|
+
})()
|
|
84
|
+
.then((result) => {
|
|
85
|
+
taskGraph.markCompleted(task.id, result);
|
|
86
|
+
this.emit('task_completed', { task, result });
|
|
87
|
+
})
|
|
88
|
+
.catch((err) => {
|
|
89
|
+
taskGraph.markFailed(task.id, err);
|
|
90
|
+
this.emit('task_failed', { task, error: err });
|
|
91
|
+
throw err;
|
|
92
|
+
})
|
|
93
|
+
.finally(() => {
|
|
94
|
+
runningPromises.delete(task.id);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
runningPromises.set(task.id, taskPromise);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (runningPromises.size > 0) {
|
|
101
|
+
// Wait for at least one running task to finish before next loop iteration
|
|
102
|
+
await Promise.race(runningPromises.values());
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Wait for any remaining tasks to settle
|
|
107
|
+
if (runningPromises.size > 0) {
|
|
108
|
+
await Promise.all(runningPromises.values());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
success: !taskGraph.hasFailed(),
|
|
113
|
+
completed: taskGraph.completedTaskIds.size,
|
|
114
|
+
total: taskGraph.tasks.size,
|
|
115
|
+
tasks: taskGraph.getAllTasks()
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
cancel() {
|
|
120
|
+
if (this.abortController) {
|
|
121
|
+
this.abortController.abort();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PIXEL CREW — Dynamic Directed Acyclic Graph (DAG) Task Engine
|
|
3
|
+
*
|
|
4
|
+
* Manages task graph construction, topological sorting, cycle detection,
|
|
5
|
+
* and parallel dependency resolution.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createTask, TASK_STATUS, validateTask } from '../protocol/task.js';
|
|
9
|
+
|
|
10
|
+
export class TaskGraph {
|
|
11
|
+
constructor(tasks = []) {
|
|
12
|
+
this.tasks = new Map();
|
|
13
|
+
this.completedTaskIds = new Set();
|
|
14
|
+
this.runningTaskIds = new Set();
|
|
15
|
+
this.failedTaskIds = new Set();
|
|
16
|
+
|
|
17
|
+
for (const t of tasks) {
|
|
18
|
+
this.addTask(t);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Adds a task to the graph
|
|
24
|
+
*/
|
|
25
|
+
addTask(taskInput) {
|
|
26
|
+
const task = taskInput.id && taskInput.title ? taskInput : createTask(taskInput);
|
|
27
|
+
validateTask(task);
|
|
28
|
+
this.tasks.set(task.id, task);
|
|
29
|
+
if (task.status === TASK_STATUS.COMPLETED) {
|
|
30
|
+
this.completedTaskIds.add(task.id);
|
|
31
|
+
} else if (task.status === TASK_STATUS.RUNNING) {
|
|
32
|
+
this.runningTaskIds.add(task.id);
|
|
33
|
+
} else if (task.status === TASK_STATUS.FAILED) {
|
|
34
|
+
this.failedTaskIds.add(task.id);
|
|
35
|
+
}
|
|
36
|
+
return task;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getTask(id) {
|
|
40
|
+
return this.tasks.get(id) || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getAllTasks() {
|
|
44
|
+
return Array.from(this.tasks.values());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Detects cycles in the task graph using DFS
|
|
49
|
+
*/
|
|
50
|
+
hasCycle() {
|
|
51
|
+
const visited = new Set();
|
|
52
|
+
const recursionStack = new Set();
|
|
53
|
+
|
|
54
|
+
const dfs = (taskId) => {
|
|
55
|
+
visited.add(taskId);
|
|
56
|
+
recursionStack.add(taskId);
|
|
57
|
+
|
|
58
|
+
const task = this.tasks.get(taskId);
|
|
59
|
+
if (task && Array.isArray(task.dependencies)) {
|
|
60
|
+
for (const depId of task.dependencies) {
|
|
61
|
+
if (!this.tasks.has(depId)) {
|
|
62
|
+
// Unresolved external dependency
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!visited.has(depId)) {
|
|
66
|
+
if (dfs(depId)) return true;
|
|
67
|
+
} else if (recursionStack.has(depId)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
recursionStack.delete(taskId);
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
for (const taskId of this.tasks.keys()) {
|
|
78
|
+
if (!visited.has(taskId)) {
|
|
79
|
+
if (dfs(taskId)) return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Returns topological ordering of tasks
|
|
87
|
+
*/
|
|
88
|
+
getTopologicalOrder() {
|
|
89
|
+
if (this.hasCycle()) {
|
|
90
|
+
throw new Error('Cannot compute topological order: Cycle detected in task graph');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const visited = new Set();
|
|
94
|
+
const order = [];
|
|
95
|
+
|
|
96
|
+
const visit = (taskId) => {
|
|
97
|
+
if (visited.has(taskId)) return;
|
|
98
|
+
visited.add(taskId);
|
|
99
|
+
|
|
100
|
+
const task = this.tasks.get(taskId);
|
|
101
|
+
if (task && Array.isArray(task.dependencies)) {
|
|
102
|
+
for (const depId of task.dependencies) {
|
|
103
|
+
if (this.tasks.has(depId)) {
|
|
104
|
+
visit(depId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
order.push(task);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
for (const taskId of this.tasks.keys()) {
|
|
112
|
+
visit(taskId);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return order;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Returns all tasks ready to run (dependencies completed and task is queued/pending)
|
|
120
|
+
*/
|
|
121
|
+
getReadyTasks() {
|
|
122
|
+
const ready = [];
|
|
123
|
+
for (const [id, task] of this.tasks.entries()) {
|
|
124
|
+
if (task.status === TASK_STATUS.QUEUED || task.status === TASK_STATUS.PENDING) {
|
|
125
|
+
const depsMet = task.dependencies.every(depId => this.completedTaskIds.has(depId));
|
|
126
|
+
if (depsMet) {
|
|
127
|
+
ready.push(task);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return ready;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
markRunning(taskId) {
|
|
135
|
+
const task = this.tasks.get(taskId);
|
|
136
|
+
if (!task) return;
|
|
137
|
+
task.status = TASK_STATUS.RUNNING;
|
|
138
|
+
task.updatedAt = Date.now();
|
|
139
|
+
this.runningTaskIds.add(taskId);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
markCompleted(taskId, result = null) {
|
|
143
|
+
const task = this.tasks.get(taskId);
|
|
144
|
+
if (!task) return;
|
|
145
|
+
task.status = TASK_STATUS.COMPLETED;
|
|
146
|
+
task.result = result;
|
|
147
|
+
task.updatedAt = Date.now();
|
|
148
|
+
this.runningTaskIds.delete(taskId);
|
|
149
|
+
this.completedTaskIds.add(taskId);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
markFailed(taskId, error = null) {
|
|
153
|
+
const task = this.tasks.get(taskId);
|
|
154
|
+
if (!task) return;
|
|
155
|
+
task.status = TASK_STATUS.FAILED;
|
|
156
|
+
task.error = typeof error === 'string' ? error : (error?.message || 'Unknown error');
|
|
157
|
+
task.updatedAt = Date.now();
|
|
158
|
+
this.runningTaskIds.delete(taskId);
|
|
159
|
+
this.failedTaskIds.add(taskId);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
isCompleted() {
|
|
163
|
+
return this.tasks.size > 0 && this.tasks.size === this.completedTaskIds.size;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
hasFailed() {
|
|
167
|
+
return this.failedTaskIds.size > 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
reset() {
|
|
171
|
+
this.completedTaskIds.clear();
|
|
172
|
+
this.runningTaskIds.clear();
|
|
173
|
+
this.failedTaskIds.clear();
|
|
174
|
+
for (const task of this.tasks.values()) {
|
|
175
|
+
task.status = TASK_STATUS.QUEUED;
|
|
176
|
+
task.result = null;
|
|
177
|
+
task.error = null;
|
|
178
|
+
task.updatedAt = Date.now();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
toJSON() {
|
|
183
|
+
return Array.from(this.tasks.values());
|
|
184
|
+
}
|
|
185
|
+
}
|