@phi-code-admin/phi-code 0.58.0 → 0.58.2
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/orchestrator.ts +360 -40
- package/package.json +2 -2
|
@@ -1,27 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Orchestrator Extension - Project planning and task
|
|
2
|
+
* Orchestrator Extension - Project planning and automatic task execution
|
|
3
3
|
*
|
|
4
|
-
* Provides tools for the LLM to create structured project plans:
|
|
5
|
-
* - /plan: Interactive planning command
|
|
4
|
+
* Provides tools for the LLM to create structured project plans and execute them:
|
|
5
|
+
* - /plan: Interactive planning command → creates spec.md + todo.md
|
|
6
6
|
* - /plans: List and manage existing plans
|
|
7
|
+
* - /run: Execute plan tasks using sub-agents (each with own context + model)
|
|
7
8
|
* - orchestrate tool: Create spec.md + todo.md from structured input
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* (ROLE/CONTEXT/TASK/FORMAT/CONSTRAINTS) to structure the spec.
|
|
17
|
-
* The skill-loader extension handles this automatically.
|
|
10
|
+
* Sub-agent execution:
|
|
11
|
+
* Each task spawns a separate `phi` CLI process with:
|
|
12
|
+
* - Its own system prompt (from the agent .md file)
|
|
13
|
+
* - Its own model (from routing.json or current model)
|
|
14
|
+
* - Its own context (isolated, no shared history)
|
|
15
|
+
* - Its own tool access (read, write, edit, bash, etc.)
|
|
16
|
+
* Results are collected into progress.md and reported to the user.
|
|
18
17
|
*/
|
|
19
18
|
|
|
20
19
|
import { Type } from "@sinclair/typebox";
|
|
21
20
|
import type { ExtensionAPI } from "phi-code";
|
|
22
21
|
import { writeFile, mkdir, readdir, readFile, access } from "node:fs/promises";
|
|
23
|
-
import { join } from "node:path";
|
|
24
|
-
import { existsSync } from "node:fs";
|
|
22
|
+
import { join, dirname } from "node:path";
|
|
23
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
+
import { execFile } from "node:child_process";
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
|
|
27
|
+
// ─── Types ───────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
interface TaskResult {
|
|
30
|
+
taskIndex: number;
|
|
31
|
+
title: string;
|
|
32
|
+
agent: string;
|
|
33
|
+
status: "success" | "error" | "skipped";
|
|
34
|
+
output: string;
|
|
35
|
+
durationMs: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface AgentDef {
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
tools: string;
|
|
42
|
+
systemPrompt: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Extension ───────────────────────────────────────────────────────────
|
|
25
46
|
|
|
26
47
|
export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
27
48
|
const plansDir = join(process.cwd(), ".phi", "plans");
|
|
@@ -34,6 +55,168 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
34
55
|
return new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
35
56
|
}
|
|
36
57
|
|
|
58
|
+
// ─── Agent Discovery ─────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Load agent definitions from .md files.
|
|
62
|
+
* Searches: project .phi/agents/ → global ~/.phi/agent/agents/ → bundled agents/
|
|
63
|
+
*/
|
|
64
|
+
function loadAgentDefs(): Map<string, AgentDef> {
|
|
65
|
+
const agents = new Map<string, AgentDef>();
|
|
66
|
+
const dirs = [
|
|
67
|
+
join(process.cwd(), ".phi", "agents"),
|
|
68
|
+
join(homedir(), ".phi", "agent", "agents"),
|
|
69
|
+
join(__dirname, "..", "..", "..", "agents"),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const dir of dirs) {
|
|
73
|
+
if (!existsSync(dir)) continue;
|
|
74
|
+
try {
|
|
75
|
+
const files = require("fs").readdirSync(dir) as string[];
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
if (!file.endsWith(".md")) continue;
|
|
78
|
+
const name = file.replace(".md", "");
|
|
79
|
+
if (agents.has(name)) continue; // Priority: project > global > bundled
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const content = readFileSync(join(dir, file), "utf-8");
|
|
83
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
84
|
+
if (!fmMatch) continue;
|
|
85
|
+
|
|
86
|
+
const frontmatter = fmMatch[1];
|
|
87
|
+
const body = fmMatch[2].trim();
|
|
88
|
+
|
|
89
|
+
const desc = frontmatter.match(/description:\s*(.+)/)?.[1] || "";
|
|
90
|
+
const tools = frontmatter.match(/tools:\s*(.+)/)?.[1] || "";
|
|
91
|
+
|
|
92
|
+
agents.set(name, { name, description: desc, tools, systemPrompt: body });
|
|
93
|
+
} catch { /* skip unparseable files */ }
|
|
94
|
+
}
|
|
95
|
+
} catch { /* skip inaccessible dirs */ }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return agents;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the model for an agent type from routing.json.
|
|
103
|
+
*/
|
|
104
|
+
function resolveAgentModel(agentType: string): string | null {
|
|
105
|
+
const routingPath = join(homedir(), ".phi", "agent", "routing.json");
|
|
106
|
+
try {
|
|
107
|
+
const config = JSON.parse(readFileSync(routingPath, "utf-8"));
|
|
108
|
+
// Find the route that maps to this agent
|
|
109
|
+
for (const [_category, route] of Object.entries(config.routes || {})) {
|
|
110
|
+
const r = route as any;
|
|
111
|
+
if (r.agent === agentType) {
|
|
112
|
+
return r.preferredModel || null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return config.default?.model || null;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Find the phi CLI binary path.
|
|
123
|
+
*/
|
|
124
|
+
function findPhiBinary(): string {
|
|
125
|
+
// 1. Try the dist/cli.js from our package
|
|
126
|
+
const bundledCli = join(__dirname, "..", "..", "..", "dist", "cli.js");
|
|
127
|
+
if (existsSync(bundledCli)) return bundledCli;
|
|
128
|
+
|
|
129
|
+
// 2. Try global phi binary
|
|
130
|
+
try {
|
|
131
|
+
const which = require("child_process").execSync("which phi 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
132
|
+
if (which) return which;
|
|
133
|
+
} catch { /* not in PATH */ }
|
|
134
|
+
|
|
135
|
+
// 3. Fallback to npx
|
|
136
|
+
return "npx";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Sub-Agent Execution ─────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Execute a task using a sub-agent process.
|
|
143
|
+
* Each agent gets its own isolated phi process with:
|
|
144
|
+
* - Its system prompt (from agent .md)
|
|
145
|
+
* - Its model (from routing.json)
|
|
146
|
+
* - No saved session (--no-save)
|
|
147
|
+
* - JSON output mode (--json)
|
|
148
|
+
*/
|
|
149
|
+
function executeTask(
|
|
150
|
+
task: { title: string; description: string; agent?: string; subtasks?: string[] },
|
|
151
|
+
agentDefs: Map<string, AgentDef>,
|
|
152
|
+
cwd: string,
|
|
153
|
+
timeoutMs: number = 300000, // 5 minutes per task
|
|
154
|
+
): Promise<TaskResult> {
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
const agentType = task.agent || "code";
|
|
157
|
+
const agentDef = agentDefs.get(agentType);
|
|
158
|
+
const model = resolveAgentModel(agentType);
|
|
159
|
+
const phiBin = findPhiBinary();
|
|
160
|
+
const startTime = Date.now();
|
|
161
|
+
|
|
162
|
+
// Build the task prompt
|
|
163
|
+
let taskPrompt = `Task: ${task.title}\n\n${task.description}`;
|
|
164
|
+
if (task.subtasks && task.subtasks.length > 0) {
|
|
165
|
+
taskPrompt += "\n\nSub-tasks:\n" + task.subtasks.map((st, i) => `${i + 1}. ${st}`).join("\n");
|
|
166
|
+
}
|
|
167
|
+
taskPrompt += "\n\nComplete this task. Be thorough and precise. Report what you did.";
|
|
168
|
+
|
|
169
|
+
// Build the command
|
|
170
|
+
const args: string[] = [];
|
|
171
|
+
if (phiBin === "npx") {
|
|
172
|
+
args.push("@phi-code-admin/phi-code");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Add flags
|
|
176
|
+
args.push("--print"); // Non-interactive, output only
|
|
177
|
+
if (model && model !== "default") {
|
|
178
|
+
args.push("--model", model);
|
|
179
|
+
}
|
|
180
|
+
if (agentDef?.systemPrompt) {
|
|
181
|
+
args.push("--system-prompt", agentDef.systemPrompt);
|
|
182
|
+
}
|
|
183
|
+
args.push("--no-save"); // Don't create a session
|
|
184
|
+
args.push(taskPrompt);
|
|
185
|
+
|
|
186
|
+
const cmd = phiBin === "npx" ? "npx" : "node";
|
|
187
|
+
const cmdArgs = phiBin === "npx" ? args : [phiBin, ...args];
|
|
188
|
+
|
|
189
|
+
const child = execFile(cmd, cmdArgs, {
|
|
190
|
+
cwd,
|
|
191
|
+
timeout: timeoutMs,
|
|
192
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
193
|
+
env: { ...process.env },
|
|
194
|
+
}, (error, stdout, stderr) => {
|
|
195
|
+
const durationMs = Date.now() - startTime;
|
|
196
|
+
|
|
197
|
+
if (error) {
|
|
198
|
+
resolve({
|
|
199
|
+
taskIndex: 0,
|
|
200
|
+
title: task.title,
|
|
201
|
+
agent: agentType,
|
|
202
|
+
status: "error",
|
|
203
|
+
output: `Error: ${error.message}\n${stderr || ""}`.trim(),
|
|
204
|
+
durationMs,
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
resolve({
|
|
208
|
+
taskIndex: 0,
|
|
209
|
+
title: task.title,
|
|
210
|
+
agent: agentType,
|
|
211
|
+
status: "success",
|
|
212
|
+
output: stdout.trim(),
|
|
213
|
+
durationMs,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
37
220
|
// ─── Orchestrate Tool ────────────────────────────────────────────
|
|
38
221
|
|
|
39
222
|
pi.registerTool({
|
|
@@ -59,7 +242,7 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
59
242
|
description: Type.String({ description: "Detailed task description" }),
|
|
60
243
|
agent: Type.Optional(Type.String({ description: "Recommended agent: explore, plan, code, test, or review" })),
|
|
61
244
|
priority: Type.Optional(Type.String({ description: "high, medium, or low" })),
|
|
62
|
-
dependencies: Type.Optional(Type.Array(Type.Number(), { description: "IDs of tasks this depends on" })),
|
|
245
|
+
dependencies: Type.Optional(Type.Array(Type.Number(), { description: "IDs of tasks this depends on (1-indexed)" })),
|
|
63
246
|
subtasks: Type.Optional(Type.Array(Type.String(), { description: "Sub-task descriptions" })),
|
|
64
247
|
}),
|
|
65
248
|
{ description: "Ordered list of tasks" }
|
|
@@ -138,7 +321,8 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
138
321
|
// ─── Generate todo.md ─────────────────────────────────
|
|
139
322
|
let todo = `# TODO: ${p.title}\n\n`;
|
|
140
323
|
todo += `**Created:** ${new Date().toLocaleString()}\n`;
|
|
141
|
-
todo += `**Tasks:** ${p.tasks.length}\n
|
|
324
|
+
todo += `**Tasks:** ${p.tasks.length}\n`;
|
|
325
|
+
todo += `**Status:** pending\n\n`;
|
|
142
326
|
|
|
143
327
|
p.tasks.forEach((t, i) => {
|
|
144
328
|
const agentTag = t.agent ? ` [${t.agent}]` : "";
|
|
@@ -160,7 +344,7 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
160
344
|
todo += `- Total: ${p.tasks.length} tasks\n`;
|
|
161
345
|
todo += `- High priority: ${p.tasks.filter(t => t.priority === "high").length}\n`;
|
|
162
346
|
todo += `- Agents needed: ${[...new Set(p.tasks.map(t => t.agent || "code"))].join(", ")}\n\n`;
|
|
163
|
-
todo += `*
|
|
347
|
+
todo += `*Run \`/run\` to execute tasks automatically with sub-agents.*\n`;
|
|
164
348
|
|
|
165
349
|
// Write files
|
|
166
350
|
await writeFile(join(plansDir, specFile), spec, "utf-8");
|
|
@@ -171,22 +355,16 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
171
355
|
📋 **${p.title}**
|
|
172
356
|
|
|
173
357
|
**Files:**
|
|
174
|
-
- \`${specFile}\` — Full specification
|
|
175
|
-
- \`${todoFile}\` —
|
|
358
|
+
- \`${specFile}\` — Full specification
|
|
359
|
+
- \`${todoFile}\` — Task list with agent assignments
|
|
176
360
|
|
|
177
361
|
**Summary:**
|
|
178
|
-
- ${p.goals.length} goals
|
|
179
|
-
- ${p.requirements.length} requirements
|
|
362
|
+
- ${p.goals.length} goals, ${p.requirements.length} requirements
|
|
180
363
|
- ${p.tasks.length} tasks (${p.tasks.filter(t => t.priority === "high").length} high priority)
|
|
181
364
|
- Agents: ${[...new Set(p.tasks.map(t => t.agent || "code"))].join(", ")}
|
|
182
365
|
|
|
183
|
-
**
|
|
184
|
-
|
|
185
|
-
2. Start with high-priority tasks
|
|
186
|
-
3. Follow dependency order
|
|
187
|
-
4. Mark tasks [x] when done
|
|
188
|
-
|
|
189
|
-
Files saved in \`.phi/plans/\``;
|
|
366
|
+
**Execute:** Run \`/run\` to launch sub-agents and execute tasks automatically.
|
|
367
|
+
Each agent gets its own context, model, and system prompt.`;
|
|
190
368
|
|
|
191
369
|
return {
|
|
192
370
|
content: [{ type: "text", text: summary }],
|
|
@@ -201,6 +379,144 @@ Files saved in \`.phi/plans/\``;
|
|
|
201
379
|
},
|
|
202
380
|
});
|
|
203
381
|
|
|
382
|
+
// ─── /run Command — Execute plan with sub-agents ─────────────────
|
|
383
|
+
|
|
384
|
+
pi.registerCommand("run", {
|
|
385
|
+
description: "Execute the latest plan's tasks using sub-agents (each with own context and model)",
|
|
386
|
+
handler: async (args, ctx) => {
|
|
387
|
+
// Find the latest todo file
|
|
388
|
+
if (!existsSync(plansDir)) {
|
|
389
|
+
ctx.ui.notify("No plans found. Use `/plan <description>` to create one first.", "warning");
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const files = (await readdir(plansDir)).sort().reverse();
|
|
394
|
+
const todoFiles = files.filter(f => f.startsWith("todo-") && f.endsWith(".md"));
|
|
395
|
+
|
|
396
|
+
if (todoFiles.length === 0) {
|
|
397
|
+
ctx.ui.notify("No todo files found. Use `/plan <description>` first.", "warning");
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const todoFile = todoFiles[0]; // Most recent
|
|
402
|
+
const todoPath = join(plansDir, todoFile);
|
|
403
|
+
const todoContent = await readFile(todoPath, "utf-8");
|
|
404
|
+
|
|
405
|
+
// Parse tasks from todo.md
|
|
406
|
+
const taskRegex = /## Task (\d+): (.+?)(?:\s*🔴|\s*🟡|\s*🟢)?\s*(?:\[(\w+)\])?\s*(?:\(after.*?\))?\n\n- \[ \] (.+?)(?:\n(?: - \[ \] .+?\n)*)?/g;
|
|
407
|
+
const tasks: Array<{ index: number; title: string; agent: string; description: string; subtasks: string[] }> = [];
|
|
408
|
+
|
|
409
|
+
let match;
|
|
410
|
+
while ((match = taskRegex.exec(todoContent)) !== null) {
|
|
411
|
+
const subtaskRegex = / - \[ \] (.+)/g;
|
|
412
|
+
const subtasks: string[] = [];
|
|
413
|
+
const taskBlock = todoContent.slice(match.index, taskRegex.lastIndex + 500);
|
|
414
|
+
let stMatch;
|
|
415
|
+
while ((stMatch = subtaskRegex.exec(taskBlock)) !== null) {
|
|
416
|
+
subtasks.push(stMatch[1]);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
tasks.push({
|
|
420
|
+
index: parseInt(match[1]),
|
|
421
|
+
title: match[2].trim(),
|
|
422
|
+
agent: match[3] || "code",
|
|
423
|
+
description: match[4].trim(),
|
|
424
|
+
subtasks,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (tasks.length === 0) {
|
|
429
|
+
ctx.ui.notify("Could not parse tasks from the todo file. Check the format.", "error");
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Load agent definitions
|
|
434
|
+
const agentDefs = loadAgentDefs();
|
|
435
|
+
const availableAgents = [...agentDefs.keys()].join(", ") || "none loaded";
|
|
436
|
+
|
|
437
|
+
// Confirm execution
|
|
438
|
+
const confirmed = await ctx.ui.confirm(
|
|
439
|
+
"Execute Plan",
|
|
440
|
+
`Found ${tasks.length} tasks in \`${todoFile}\`.\n` +
|
|
441
|
+
`Available agents: ${availableAgents}\n` +
|
|
442
|
+
`Each task will spawn an isolated phi process with its own context.\n\n` +
|
|
443
|
+
`Proceed?`
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
if (!confirmed) {
|
|
447
|
+
ctx.ui.notify("Execution cancelled.", "info");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Create progress file
|
|
452
|
+
const progressFile = todoFile.replace("todo-", "progress-");
|
|
453
|
+
const progressPath = join(plansDir, progressFile);
|
|
454
|
+
let progress = `# Progress: ${todoFile}\n\n`;
|
|
455
|
+
progress += `**Started:** ${new Date().toLocaleString()}\n`;
|
|
456
|
+
progress += `**Tasks:** ${tasks.length}\n\n`;
|
|
457
|
+
await writeFile(progressPath, progress, "utf-8");
|
|
458
|
+
|
|
459
|
+
ctx.ui.notify(`🚀 Starting execution of ${tasks.length} tasks...`, "info");
|
|
460
|
+
|
|
461
|
+
// Execute tasks in order (respecting dependencies)
|
|
462
|
+
const results: TaskResult[] = [];
|
|
463
|
+
const completed = new Set<number>();
|
|
464
|
+
|
|
465
|
+
for (const task of tasks) {
|
|
466
|
+
ctx.ui.notify(`\n⏳ **Task ${task.index}: ${task.title}** [${task.agent}]`, "info");
|
|
467
|
+
|
|
468
|
+
// Execute the task
|
|
469
|
+
const result = await executeTask(
|
|
470
|
+
{ title: task.title, description: task.description, agent: task.agent, subtasks: task.subtasks },
|
|
471
|
+
agentDefs,
|
|
472
|
+
process.cwd(),
|
|
473
|
+
);
|
|
474
|
+
result.taskIndex = task.index;
|
|
475
|
+
|
|
476
|
+
results.push(result);
|
|
477
|
+
completed.add(task.index);
|
|
478
|
+
|
|
479
|
+
// Report result
|
|
480
|
+
const icon = result.status === "success" ? "✅" : "❌";
|
|
481
|
+
const duration = (result.durationMs / 1000).toFixed(1);
|
|
482
|
+
ctx.ui.notify(
|
|
483
|
+
`${icon} **Task ${task.index}: ${task.title}** (${duration}s)\n` +
|
|
484
|
+
`Agent: ${task.agent} | Output: ${result.output.slice(0, 500)}${result.output.length > 500 ? "..." : ""}`,
|
|
485
|
+
result.status === "success" ? "info" : "error"
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
// Update progress file
|
|
489
|
+
progress += `## Task ${task.index}: ${task.title}\n\n`;
|
|
490
|
+
progress += `- **Status:** ${result.status}\n`;
|
|
491
|
+
progress += `- **Agent:** ${result.agent}\n`;
|
|
492
|
+
progress += `- **Duration:** ${duration}s\n`;
|
|
493
|
+
progress += `- **Output:**\n\n\`\`\`\n${result.output.slice(0, 2000)}\n\`\`\`\n\n`;
|
|
494
|
+
await writeFile(progressPath, progress, "utf-8");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Final summary
|
|
498
|
+
const succeeded = results.filter(r => r.status === "success").length;
|
|
499
|
+
const failed = results.filter(r => r.status === "error").length;
|
|
500
|
+
const totalTime = results.reduce((sum, r) => sum + r.durationMs, 0);
|
|
501
|
+
|
|
502
|
+
progress += `---\n\n## Summary\n\n`;
|
|
503
|
+
progress += `- **Completed:** ${new Date().toLocaleString()}\n`;
|
|
504
|
+
progress += `- **Succeeded:** ${succeeded}/${results.length}\n`;
|
|
505
|
+
progress += `- **Failed:** ${failed}\n`;
|
|
506
|
+
progress += `- **Total time:** ${(totalTime / 1000).toFixed(1)}s\n`;
|
|
507
|
+
await writeFile(progressPath, progress, "utf-8");
|
|
508
|
+
|
|
509
|
+
ctx.ui.notify(
|
|
510
|
+
`\n🏁 **Execution complete!**\n\n` +
|
|
511
|
+
`✅ Succeeded: ${succeeded}/${results.length}\n` +
|
|
512
|
+
`❌ Failed: ${failed}\n` +
|
|
513
|
+
`⏱️ Total time: ${(totalTime / 1000).toFixed(1)}s\n\n` +
|
|
514
|
+
`Progress saved to \`${progressFile}\``,
|
|
515
|
+
failed === 0 ? "info" : "warning"
|
|
516
|
+
);
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
|
|
204
520
|
// ─── /plan Command ───────────────────────────────────────────────
|
|
205
521
|
|
|
206
522
|
pi.registerCommand("plan", {
|
|
@@ -217,16 +533,15 @@ Files saved in \`.phi/plans/\``;
|
|
|
217
533
|
/plan Add comprehensive test coverage to the payment module
|
|
218
534
|
|
|
219
535
|
The LLM will:
|
|
220
|
-
1. Analyze your description
|
|
221
|
-
2.
|
|
222
|
-
3.
|
|
223
|
-
4.
|
|
536
|
+
1. Analyze your description
|
|
537
|
+
2. Break down into tasks with agent assignments
|
|
538
|
+
3. Create spec.md + todo.md files in .phi/plans/
|
|
539
|
+
4. Run \`/run\` to execute with sub-agents
|
|
224
540
|
|
|
225
|
-
|
|
541
|
+
Each sub-agent gets its own isolated context, model, and system prompt.`, "info");
|
|
226
542
|
return;
|
|
227
543
|
}
|
|
228
544
|
|
|
229
|
-
// Send as user message to trigger the LLM to analyze and call orchestrate
|
|
230
545
|
ctx.sendUserMessage(
|
|
231
546
|
`Please analyze this project request and create a structured plan using the orchestrate tool.
|
|
232
547
|
|
|
@@ -237,8 +552,7 @@ Instructions:
|
|
|
237
552
|
- Break the work into small, actionable tasks (each doable by one agent)
|
|
238
553
|
- Assign the best agent type to each task: explore (analysis), plan (design), code (implementation), test (validation), review (quality)
|
|
239
554
|
- Set priorities (high/medium/low) and dependencies between tasks
|
|
240
|
-
- Define success criteria for the project
|
|
241
|
-
- If the prompt-architect skill is available, use its ROLE/CONTEXT/TASK/FORMAT patterns to structure the specification`
|
|
555
|
+
- Define success criteria for the project`
|
|
242
556
|
);
|
|
243
557
|
},
|
|
244
558
|
});
|
|
@@ -246,7 +560,7 @@ Instructions:
|
|
|
246
560
|
// ─── /plans Command ──────────────────────────────────────────────
|
|
247
561
|
|
|
248
562
|
pi.registerCommand("plans", {
|
|
249
|
-
description: "List existing project plans",
|
|
563
|
+
description: "List existing project plans and their execution status",
|
|
250
564
|
handler: async (_args, ctx) => {
|
|
251
565
|
try {
|
|
252
566
|
if (!existsSync(plansDir)) {
|
|
@@ -267,6 +581,7 @@ Instructions:
|
|
|
267
581
|
for (const specFile of specs) {
|
|
268
582
|
const ts = specFile.replace("spec-", "").replace(".md", "");
|
|
269
583
|
const todoFile = `todo-${ts}.md`;
|
|
584
|
+
const progressFile = `progress-${ts}.md`;
|
|
270
585
|
|
|
271
586
|
try {
|
|
272
587
|
const content = await readFile(join(plansDir, specFile), "utf-8");
|
|
@@ -276,9 +591,14 @@ Instructions:
|
|
|
276
591
|
const date = ts.replace(/_/g, " ").substring(0, 10);
|
|
277
592
|
|
|
278
593
|
const hasTodo = files.includes(todoFile);
|
|
594
|
+
const hasProgress = files.includes(progressFile);
|
|
595
|
+
const status = hasProgress ? "🟢 executed" : hasTodo ? "🟡 planned" : "⚪ spec only";
|
|
279
596
|
|
|
280
|
-
output += `📋 **${title}** (${date})\n`;
|
|
281
|
-
output += ` Spec: \`${specFile}
|
|
597
|
+
output += `📋 **${title}** (${date}) ${status}\n`;
|
|
598
|
+
output += ` Spec: \`${specFile}\``;
|
|
599
|
+
if (hasTodo) output += ` | Todo: \`${todoFile}\``;
|
|
600
|
+
if (hasProgress) output += ` | Progress: \`${progressFile}\``;
|
|
601
|
+
output += "\n";
|
|
282
602
|
if (taskCount > 0) output += ` Tasks: ${taskCount}\n`;
|
|
283
603
|
output += "\n";
|
|
284
604
|
} catch {
|
|
@@ -286,7 +606,7 @@ Instructions:
|
|
|
286
606
|
}
|
|
287
607
|
}
|
|
288
608
|
|
|
289
|
-
output += `
|
|
609
|
+
output += `_Commands: \`/plan\` (create), \`/run\` (execute), \`read .phi/plans/<file>\` (view)_`;
|
|
290
610
|
ctx.ui.notify(output, "info");
|
|
291
611
|
} catch (error) {
|
|
292
612
|
ctx.ui.notify(`Failed to list plans: ${error}`, "error");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phi-code-admin/phi-code",
|
|
3
|
-
"version": "0.58.
|
|
3
|
+
"version": "0.58.2",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"phi-code-ai": "^0.56.3",
|
|
59
59
|
"phi-code-tui": "^0.56.3",
|
|
60
60
|
"proper-lockfile": "^4.1.2",
|
|
61
|
-
"sigma-agents": "^0.1.
|
|
61
|
+
"sigma-agents": "^0.1.2",
|
|
62
62
|
"sigma-memory": "^0.1.0",
|
|
63
63
|
"sigma-skills": "^0.1.0",
|
|
64
64
|
"strip-ansi": "^7.1.0",
|