@phi-code-admin/phi-code 0.58.4 → 0.58.5

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.
@@ -193,7 +193,7 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
193
193
  });
194
194
  }
195
195
 
196
- // ─── Execute All Tasks ───────────────────────────────────────────
196
+ // ─── Execute All Tasks (parallel with dependency resolution) ─────
197
197
 
198
198
  async function executePlan(
199
199
  tasks: TaskDef[],
@@ -205,52 +205,133 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
205
205
  const progressPath = join(plansDir, progressFile);
206
206
  let progress = `# Progress: ${todoFile}\n\n`;
207
207
  progress += `**Started:** ${new Date().toLocaleString()}\n`;
208
- progress += `**Tasks:** ${tasks.length}\n\n`;
208
+ progress += `**Tasks:** ${tasks.length}\n**Mode:** parallel (dependency-aware)\n\n`;
209
209
  await writeFile(progressPath, progress, "utf-8");
210
210
 
211
- notify(`🚀 Executing ${tasks.length} tasks with sub-agents...`, "info");
212
-
211
+ // Build dependency graph
212
+ // Task indices are 1-based in the plan files
213
+ const completed = new Set<number>();
214
+ const failed = new Set<number>();
213
215
  const results: TaskResult[] = [];
214
216
 
215
- for (let i = 0; i < tasks.length; i++) {
216
- const task = tasks[i];
217
- const agentType = task.agent || "code";
218
- notify(`⏳ Task ${i + 1}/${tasks.length}: **${task.title}** [${agentType}]`, "info");
219
-
220
- const result = await executeTask(task, agentDefs, process.cwd());
221
- result.taskIndex = i + 1;
222
- results.push(result);
223
-
224
- const icon = result.status === "success" ? "✅" : "❌";
225
- const duration = (result.durationMs / 1000).toFixed(1);
226
- const outputPreview = result.output.length > 500 ? result.output.slice(0, 500) + "..." : result.output;
227
- notify(`${icon} Task ${i + 1}: **${task.title}** (${duration}s)\n${outputPreview}`,
228
- result.status === "success" ? "info" : "error");
229
-
230
- progress += `## Task ${i + 1}: ${task.title}\n\n`;
231
- progress += `- **Status:** ${result.status}\n`;
232
- progress += `- **Agent:** ${result.agent}\n`;
233
- progress += `- **Duration:** ${duration}s\n`;
234
- progress += `- **Output:**\n\n\`\`\`\n${result.output.slice(0, 3000)}\n\`\`\`\n\n`;
217
+ // Check which tasks can run (all dependencies completed successfully)
218
+ function getReadyTasks(): number[] {
219
+ const ready: number[] = [];
220
+ for (let i = 0; i < tasks.length; i++) {
221
+ const taskNum = i + 1;
222
+ if (completed.has(taskNum) || failed.has(taskNum)) continue;
223
+
224
+ const deps = tasks[i].dependencies || [];
225
+ const allDepsMet = deps.every(d => completed.has(d));
226
+ const anyDepFailed = deps.some(d => failed.has(d));
227
+
228
+ if (anyDepFailed) {
229
+ // Skip tasks whose dependencies failed
230
+ failed.add(taskNum);
231
+ results.push({
232
+ taskIndex: taskNum,
233
+ title: tasks[i].title,
234
+ agent: tasks[i].agent || "code",
235
+ status: "skipped",
236
+ output: `Skipped: dependency #${deps.find(d => failed.has(d))} failed`,
237
+ durationMs: 0,
238
+ });
239
+ notify(`⏭️ Task ${taskNum}: **${tasks[i].title}** — skipped (dependency failed)`, "warning");
240
+ } else if (allDepsMet) {
241
+ ready.push(i);
242
+ }
243
+ }
244
+ return ready;
245
+ }
246
+
247
+ const totalTasks = tasks.length;
248
+ let wave = 1;
249
+
250
+ notify(`🚀 Executing ${totalTasks} tasks with sub-agents (parallel mode)...`, "info");
251
+
252
+ // Execute in waves — each wave runs independent tasks in parallel
253
+ while (completed.size + failed.size < totalTasks) {
254
+ const readyIndices = getReadyTasks();
255
+
256
+ if (readyIndices.length === 0) {
257
+ // Deadlock or all done
258
+ break;
259
+ }
260
+
261
+ const parallelCount = readyIndices.length;
262
+ if (parallelCount > 1) {
263
+ notify(`\n🔄 **Wave ${wave}** — ${parallelCount} tasks in parallel`, "info");
264
+ }
265
+
266
+ for (const idx of readyIndices) {
267
+ const t = tasks[idx];
268
+ notify(`⏳ Task ${idx + 1}: **${t.title}** [${t.agent || "code"}]`, "info");
269
+ }
270
+
271
+ // Launch all ready tasks simultaneously
272
+ const promises = readyIndices.map(async (idx) => {
273
+ const task = tasks[idx];
274
+ const result = await executeTask(task, agentDefs, process.cwd());
275
+ result.taskIndex = idx + 1;
276
+ return result;
277
+ });
278
+
279
+ const waveResults = await Promise.all(promises);
280
+
281
+ // Process results
282
+ for (const result of waveResults) {
283
+ results.push(result);
284
+
285
+ if (result.status === "success") {
286
+ completed.add(result.taskIndex);
287
+ } else {
288
+ failed.add(result.taskIndex);
289
+ }
290
+
291
+ const icon = result.status === "success" ? "✅" : "❌";
292
+ const duration = (result.durationMs / 1000).toFixed(1);
293
+ const outputPreview = result.output.length > 500 ? result.output.slice(0, 500) + "..." : result.output;
294
+ notify(`${icon} Task ${result.taskIndex}: **${result.title}** (${duration}s)\n${outputPreview}`,
295
+ result.status === "success" ? "info" : "error");
296
+
297
+ progress += `## Task ${result.taskIndex}: ${result.title}\n\n`;
298
+ progress += `- **Status:** ${result.status}\n`;
299
+ progress += `- **Agent:** ${result.agent}\n`;
300
+ progress += `- **Wave:** ${wave}\n`;
301
+ progress += `- **Duration:** ${duration}s\n`;
302
+ progress += `- **Output:**\n\n\`\`\`\n${result.output.slice(0, 3000)}\n\`\`\`\n\n`;
303
+ }
304
+
235
305
  await writeFile(progressPath, progress, "utf-8");
306
+ wave++;
236
307
  }
237
308
 
238
- const succeeded = results.filter(r => r.status === "success").length;
239
- const failed = results.filter(r => r.status === "error").length;
309
+ // Sort results by task index for consistent reporting
310
+ results.sort((a, b) => a.taskIndex - b.taskIndex);
311
+
312
+ const succeededCount = results.filter(r => r.status === "success").length;
313
+ const failedCount = results.filter(r => r.status === "error").length;
314
+ const skippedCount = results.filter(r => r.status === "skipped").length;
240
315
  const totalTime = results.reduce((sum, r) => sum + r.durationMs, 0);
241
316
 
242
317
  progress += `---\n\n## Summary\n\n`;
243
318
  progress += `- **Completed:** ${new Date().toLocaleString()}\n`;
244
- progress += `- **Succeeded:** ${succeeded}/${results.length}\n`;
245
- progress += `- **Failed:** ${failed}\n`;
319
+ progress += `- **Waves:** ${wave - 1}\n`;
320
+ progress += `- **Succeeded:** ${succeededCount}/${results.length}\n`;
321
+ progress += `- **Failed:** ${failedCount}\n`;
322
+ progress += `- **Skipped:** ${skippedCount}\n`;
246
323
  progress += `- **Total time:** ${(totalTime / 1000).toFixed(1)}s\n`;
247
324
  await writeFile(progressPath, progress, "utf-8");
248
325
 
326
+ const statusParts = [`✅ ${succeededCount} succeeded`];
327
+ if (failedCount > 0) statusParts.push(`❌ ${failedCount} failed`);
328
+ if (skippedCount > 0) statusParts.push(`⏭️ ${skippedCount} skipped`);
329
+
249
330
  notify(
250
- `\n🏁 **Execution complete!**\n` +
251
- `✅ ${succeeded}/${results.length} succeeded | ${failed} failed | ⏱️ ${(totalTime / 1000).toFixed(1)}s\n` +
331
+ `\n🏁 **Execution complete!** (${wave - 1} waves)\n` +
332
+ statusParts.join(" | ") + ` | ⏱️ ${(totalTime / 1000).toFixed(1)}s\n` +
252
333
  `Progress: \`${progressFile}\``,
253
- failed === 0 ? "info" : "warning"
334
+ failedCount === 0 ? "info" : "warning"
254
335
  );
255
336
 
256
337
  return { results, progressFile };
@@ -317,13 +398,14 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
317
398
  pi.registerTool({
318
399
  name: "orchestrate",
319
400
  label: "Project Orchestrator",
320
- description: "Create a project plan AND automatically execute all tasks with sub-agents. Each agent gets its own isolated context, model, and system prompt. Call this after analyzing the user's project request.",
321
- promptSnippet: "Plan + execute projects. Creates spec/todo, then runs each task with an isolated sub-agent.",
401
+ description: "Create a project plan AND automatically execute all tasks with sub-agents in parallel. Each agent gets its own isolated context, model, and system prompt. Tasks without dependencies run simultaneously.",
402
+ promptSnippet: "Plan + execute projects. Creates spec/todo, then runs tasks in parallel waves with isolated sub-agents.",
322
403
  promptGuidelines: [
323
404
  "When asked to plan or build a project: analyze the request, then call orchestrate. It will plan AND execute automatically.",
324
405
  "Break tasks into small, actionable items. Each task is executed by an isolated sub-agent.",
325
406
  "Assign agent types: 'explore' (analysis), 'plan' (design), 'code' (implementation), 'test' (validation), 'review' (quality).",
326
- "Order tasks by dependency. Sub-agents execute sequentially, respecting the order.",
407
+ "Set dependencies carefully: tasks without dependencies run in parallel. Tasks with dependencies wait for their prerequisites.",
408
+ "Independent tasks run simultaneously (same wave). Dependent tasks wait for their prerequisites to complete.",
327
409
  ],
328
410
  parameters: Type.Object({
329
411
  title: Type.String({ description: "Project title" }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.58.4",
3
+ "version": "0.58.5",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {