@phi-code-admin/phi-code 0.58.3 → 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.
- package/extensions/phi/init.ts +22 -16
- package/extensions/phi/orchestrator.ts +116 -34
- package/package.json +1 -1
package/extensions/phi/init.ts
CHANGED
|
@@ -149,12 +149,12 @@ function getAllAvailableModels(providers: DetectedProvider[]): string[] {
|
|
|
149
149
|
// ─── Routing Presets ─────────────────────────────────────────────────────
|
|
150
150
|
|
|
151
151
|
const TASK_ROLES = [
|
|
152
|
-
{ key: "code", label: "Code Generation", desc: "Writing and modifying code", agent: "code", defaultModel: "
|
|
153
|
-
{ key: "debug", label: "Debugging", desc: "Finding and fixing bugs", agent: "code", defaultModel: "
|
|
154
|
-
{ key: "plan", label: "Planning", desc: "Architecture and design", agent: "plan", defaultModel: "
|
|
155
|
-
{ key: "explore", label: "Exploration", desc: "Code reading and analysis", agent: "explore", defaultModel: "
|
|
156
|
-
{ key: "test", label: "Testing", desc: "Running and writing tests", agent: "test", defaultModel: "
|
|
157
|
-
{ key: "review", label: "Code Review", desc: "Quality and security review", agent: "review", defaultModel: "
|
|
152
|
+
{ key: "code", label: "Code Generation", desc: "Writing and modifying code", agent: "code", defaultModel: "default" },
|
|
153
|
+
{ key: "debug", label: "Debugging", desc: "Finding and fixing bugs", agent: "code", defaultModel: "default" },
|
|
154
|
+
{ key: "plan", label: "Planning", desc: "Architecture and design", agent: "plan", defaultModel: "default" },
|
|
155
|
+
{ key: "explore", label: "Exploration", desc: "Code reading and analysis", agent: "explore", defaultModel: "default" },
|
|
156
|
+
{ key: "test", label: "Testing", desc: "Running and writing tests", agent: "test", defaultModel: "default" },
|
|
157
|
+
{ key: "review", label: "Code Review", desc: "Quality and security review", agent: "review", defaultModel: "default" },
|
|
158
158
|
] as const;
|
|
159
159
|
|
|
160
160
|
const KEYWORDS: Record<string, string[]> = {
|
|
@@ -455,16 +455,12 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
455
455
|
// ─── MODE: Benchmark ─────────────────────────────────────────────
|
|
456
456
|
|
|
457
457
|
async function benchmarkMode(availableModels: string[], ctx: any): Promise<Record<string, { preferred: string; fallback: string }>> {
|
|
458
|
-
ctx.ui.notify("🧪 Benchmark mode: running tests on available models...", "info");
|
|
459
|
-
ctx.ui.notify("This will test each model with 6 coding tasks. It may take 10-15 minutes.", "info");
|
|
460
|
-
ctx.ui.notify("💡 Tip: You can run `/benchmark all` separately and use `/benchmark results` to see rankings.\n", "info");
|
|
461
|
-
|
|
462
458
|
// Check if benchmark results already exist
|
|
463
459
|
const benchmarkPath = join(phiDir, "benchmark", "results.json");
|
|
464
460
|
let existingResults: any = null;
|
|
465
461
|
try {
|
|
466
462
|
await access(benchmarkPath);
|
|
467
|
-
const content = await
|
|
463
|
+
const content = await readFile(benchmarkPath, "utf-8");
|
|
468
464
|
existingResults = JSON.parse(content);
|
|
469
465
|
} catch {
|
|
470
466
|
// No existing results
|
|
@@ -473,18 +469,28 @@ _Edit this file to customize Phi Code's behavior for your project._
|
|
|
473
469
|
if (existingResults?.results?.length > 0) {
|
|
474
470
|
const useExisting = await ctx.ui.confirm(
|
|
475
471
|
"Use existing benchmarks?",
|
|
476
|
-
`Found ${existingResults.results.length}
|
|
472
|
+
`Found ${existingResults.results.length} benchmark results from a previous run. Use them?`
|
|
477
473
|
);
|
|
478
474
|
if (useExisting) {
|
|
475
|
+
ctx.ui.notify("📊 Using existing benchmark results for model assignment.\n", "info");
|
|
479
476
|
return assignFromBenchmark(existingResults.results, availableModels);
|
|
480
477
|
}
|
|
481
478
|
}
|
|
482
479
|
|
|
483
|
-
//
|
|
484
|
-
ctx.ui.notify("
|
|
485
|
-
ctx.ui.notify("
|
|
480
|
+
// No existing results or user declined — run benchmarks now
|
|
481
|
+
ctx.ui.notify("🧪 Benchmark mode: launching model tests...", "info");
|
|
482
|
+
ctx.ui.notify("This tests each model with 6 coding tasks via real API calls.", "info");
|
|
483
|
+
ctx.ui.notify("⏱️ Estimated time: 2-3 minutes per model.\n", "info");
|
|
484
|
+
|
|
485
|
+
// Trigger benchmark via sendUserMessage — this runs /benchmark all
|
|
486
|
+
// which saves results to the same results.json path
|
|
487
|
+
ctx.sendUserMessage("/benchmark all");
|
|
488
|
+
ctx.ui.notify("⏳ Benchmarks started. Once complete, run `/phi-init` again and select benchmark mode to use the results.\n", "info");
|
|
489
|
+
ctx.ui.notify("💡 The benchmark runs in the background. You'll see live results in the terminal.\n", "info");
|
|
486
490
|
|
|
487
|
-
//
|
|
491
|
+
// Return auto mode assignments as temporary defaults
|
|
492
|
+
// (will be overwritten when user re-runs /phi-init with benchmark results)
|
|
493
|
+
ctx.ui.notify("📋 Setting auto-mode defaults while benchmarks run...\n", "info");
|
|
488
494
|
return autoMode(availableModels, ctx);
|
|
489
495
|
}
|
|
490
496
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
239
|
-
|
|
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 += `- **
|
|
245
|
-
progress += `- **
|
|
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
|
|
251
|
-
|
|
331
|
+
`\n🏁 **Execution complete!** (${wave - 1} waves)\n` +
|
|
332
|
+
statusParts.join(" | ") + ` | ⏱️ ${(totalTime / 1000).toFixed(1)}s\n` +
|
|
252
333
|
`Progress: \`${progressFile}\``,
|
|
253
|
-
|
|
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.
|
|
321
|
-
promptSnippet: "Plan + execute projects. Creates spec/todo, then runs
|
|
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
|
-
"
|
|
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" }),
|