@phi-code-admin/phi-code 0.63.0 → 0.63.1

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.
@@ -450,23 +450,61 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
450
450
 
451
451
  // ─── Orchestration State ─────────────────────────────────────────
452
452
 
453
+ interface AgentDef {
454
+ name: string;
455
+ tools: string[];
456
+ systemPrompt: string;
457
+ }
458
+
453
459
  interface OrchestratorPhase {
454
460
  key: string;
455
461
  label: string;
456
462
  model: string;
457
463
  fallback: string;
464
+ agent: AgentDef | null;
458
465
  instruction: string;
459
466
  }
460
467
 
461
468
  let phaseQueue: OrchestratorPhase[] = [];
462
469
  let orchestrationActive = false;
463
470
  let idlePollTimer: ReturnType<typeof setInterval> | null = null;
471
+ let activeAgentPrompt: string | null = null;
472
+ let activeAgentTools: string[] | null = null;
473
+ let savedTools: string[] | null = null;
474
+
475
+ /**
476
+ * Parse agent .md file with YAML frontmatter
477
+ */
478
+ function loadAgentDef(name: string): AgentDef | null {
479
+ const dirs = [
480
+ join(process.cwd(), ".phi", "agents"),
481
+ join(homedir(), ".phi", "agent", "agents"),
482
+ ];
483
+ for (const dir of dirs) {
484
+ const filePath = join(dir, `${name}.md`);
485
+ try {
486
+ const content = readFileSync(filePath, "utf-8");
487
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
488
+ if (!fmMatch) continue;
489
+ const fields: Record<string, string> = {};
490
+ for (const line of fmMatch[1].split("\n")) {
491
+ const m = line.match(/^(\w+):\s*(.*)$/);
492
+ if (m) fields[m[1]] = m[2].trim();
493
+ }
494
+ return {
495
+ name: fields.name || name,
496
+ tools: (fields.tools || "").split(",").map(t => t.trim()).filter(Boolean),
497
+ systemPrompt: fmMatch[2].trim(),
498
+ };
499
+ } catch { continue; }
500
+ }
501
+ return null;
502
+ }
464
503
 
465
504
  /**
466
- * Load routing config and build phase queue with model assignments.
505
+ * Load routing config and build phase queue with model assignments + agent definitions.
467
506
  */
468
507
  function buildPhases(description: string): OrchestratorPhase[] {
469
- // Read routing.json for model assignments
470
508
  const routingPath = join(homedir(), ".phi", "agent", "routing.json");
471
509
  let routing: any = { routes: {}, default: { model: "default" } };
472
510
  try {
@@ -490,22 +528,27 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
490
528
  return [
491
529
  {
492
530
  key: "explore", label: "🔍 Phase 1 — EXPLORE", model: explore.preferred, fallback: explore.fallback,
531
+ agent: loadAgentDef("explore"),
493
532
  instruction: `Analyze the project requirements and existing codebase. Identify what exists, what's needed, and any constraints.\n\n**Project:** ${description}\n\nList files, read key ones, check dependencies. Return a structured summary.`,
494
533
  },
495
534
  {
496
535
  key: "plan", label: "📐 Phase 2 — PLAN", model: plan.preferred, fallback: plan.fallback,
536
+ agent: loadAgentDef("plan"),
497
537
  instruction: `Design the architecture for this project. Define file structure, tech choices, and implementation approach.\n\n**Project:** ${description}\n\nBe specific: list every file to create with its purpose.`,
498
538
  },
499
539
  {
500
540
  key: "code", label: "💻 Phase 3 — CODE", model: code.preferred, fallback: code.fallback,
541
+ agent: loadAgentDef("code"),
501
542
  instruction: `Implement the COMPLETE project. Create ALL files with production-quality code.\n\n**Project:** ${description}\n\n**Rules:**\n- Create every file needed\n- No placeholders, no TODOs, no stubs\n- Every function must be fully implemented\n- Follow the architecture from the previous planning phase`,
502
543
  },
503
544
  {
504
545
  key: "test", label: "🧪 Phase 4 — TEST", model: test.preferred, fallback: test.fallback,
546
+ agent: loadAgentDef("test"),
505
547
  instruction: `Test the implementation. Run the code, check for errors, verify it works.\n\n**Project:** ${description}\n\nFix any errors you find. Ensure the project runs correctly.`,
506
548
  },
507
549
  {
508
550
  key: "review", label: "🔍 Phase 5 — REVIEW", model: review.preferred, fallback: review.fallback,
551
+ agent: loadAgentDef("review"),
509
552
  instruction: `Review the code quality, security, and performance. Fix any issues.\n\n**Project:** ${description}\n\nCheck: error handling, edge cases, code style, documentation.`,
510
553
  },
511
554
  ];
@@ -513,7 +556,6 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
513
556
 
514
557
  /**
515
558
  * Switch model for the current phase.
516
- * Tries preferred model first, then fallback.
517
559
  */
518
560
  async function switchModelForPhase(phase: OrchestratorPhase, ctx: any): Promise<string> {
519
561
  const available = ctx.modelRegistry?.getAvailable?.() || [];
@@ -528,47 +570,90 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
528
570
  return ctx.model?.id || phase.model;
529
571
  }
530
572
 
573
+ /**
574
+ * Activate agent for a phase: set system prompt + restrict tools.
575
+ */
576
+ function activateAgent(phase: OrchestratorPhase, ctx: any) {
577
+ if (phase.agent) {
578
+ // Save current tools for restoration
579
+ if (!savedTools) {
580
+ savedTools = pi.getActiveTools();
581
+ }
582
+ // Set agent's system prompt (will be injected via before_agent_start)
583
+ activeAgentPrompt = phase.agent.systemPrompt;
584
+ // Restrict tools to agent's allowed tools
585
+ if (phase.agent.tools.length > 0) {
586
+ activeAgentTools = phase.agent.tools;
587
+ pi.setActiveTools(phase.agent.tools);
588
+ }
589
+ } else {
590
+ activeAgentPrompt = null;
591
+ activeAgentTools = null;
592
+ }
593
+ }
594
+
595
+ /**
596
+ * Deactivate agent: restore tools, clear prompt override.
597
+ */
598
+ function deactivateAgent() {
599
+ activeAgentPrompt = null;
600
+ activeAgentTools = null;
601
+ if (savedTools) {
602
+ pi.setActiveTools(savedTools);
603
+ savedTools = null;
604
+ }
605
+ }
606
+
531
607
  /**
532
608
  * Send the next phase in the queue.
533
- * Called after the agent goes idle (previous phase complete).
534
609
  */
535
610
  function sendNextPhase(ctx: any) {
536
611
  if (phaseQueue.length === 0) {
537
- // All phases done
538
612
  orchestrationActive = false;
613
+ deactivateAgent();
539
614
  ctx.ui.notify(`\n✅ **All 5 phases complete!**`, "info");
540
615
  return;
541
616
  }
542
617
 
543
618
  const phase = phaseQueue.shift()!;
544
619
 
545
- // Switch model and send
546
620
  switchModelForPhase(phase, ctx).then((modelId) => {
547
- ctx.ui.notify(`\n${phase.label} → \`${modelId}\``, "info");
621
+ activateAgent(phase, ctx);
622
+ const agentName = phase.agent?.name || phase.key;
623
+ ctx.ui.notify(`\n${phase.label} → \`${modelId}\` (agent: ${agentName})`, "info");
548
624
  setTimeout(() => pi.sendUserMessage(phase.instruction), 200);
549
625
  });
550
626
  }
551
627
 
628
+ // ─── System Prompt Injection — Agent personas ────────────────────
629
+
630
+ pi.on("before_agent_start", async (event, _ctx) => {
631
+ if (!orchestrationActive || !activeAgentPrompt) {
632
+ return { };
633
+ }
634
+ // Replace system prompt with the active agent's prompt
635
+ return { systemPrompt: activeAgentPrompt };
636
+ });
637
+
552
638
  // ─── Output Event — Phase Chaining ───────────────────────────────
553
639
 
554
640
  pi.on("output", async (_event, ctx) => {
555
641
  if (!orchestrationActive || phaseQueue.length === 0) return;
556
642
 
557
- // Debounce: clear any existing poll timer
558
643
  if (idlePollTimer) {
559
644
  clearInterval(idlePollTimer);
560
645
  idlePollTimer = null;
561
646
  }
562
647
 
563
- // Poll for idle state (agent finishes tool calls + response)
564
648
  let attempts = 0;
565
649
  idlePollTimer = setInterval(() => {
566
650
  attempts++;
567
- if (attempts > 120) { // 60 seconds max
651
+ if (attempts > 120) {
568
652
  clearInterval(idlePollTimer!);
569
653
  idlePollTimer = null;
570
654
  ctx.ui.notify("⚠️ Orchestrator timeout — phase took too long.", "warning");
571
655
  orchestrationActive = false;
656
+ deactivateAgent();
572
657
  return;
573
658
  }
574
659
  if (ctx.isIdle()) {
@@ -605,23 +690,27 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
605
690
  const specFile = `spec-${ts}.md`;
606
691
  await writeFile(join(plansDir, specFile), `# ${description}\n\n**Created:** ${new Date().toLocaleString()}\n`, "utf-8");
607
692
 
608
- // Build phases with model assignments from routing.json
693
+ // Build phases with model assignments + agent definitions
609
694
  const phases = buildPhases(description);
610
695
  phaseQueue = phases.slice(1); // Queue phases 2-5
611
696
  orchestrationActive = true;
612
697
  const firstPhase = phases[0];
613
698
 
614
- ctx.ui.notify(`📋 **Orchestrator started** — 5 phases with model routing\n`, "info");
699
+ ctx.ui.notify(`📋 **Orchestrator started** — 5 phases with model routing + agent roles\n`, "info");
615
700
 
616
701
  // Show the plan
617
702
  for (const p of phases) {
618
- ctx.ui.notify(` ${p.label} \`${p.model}\``, "info");
703
+ const agentName = p.agent?.name || p.key;
704
+ const toolCount = p.agent?.tools.length || 0;
705
+ ctx.ui.notify(` ${p.label} → \`${p.model}\` (agent: ${agentName}, ${toolCount} tools)`, "info");
619
706
  }
620
707
  ctx.ui.notify("", "info");
621
708
 
622
- // Switch model and send first phase after handler returns
709
+ // Switch model and activate agent for first phase
623
710
  const modelId = await switchModelForPhase(firstPhase, ctx);
624
- ctx.ui.notify(`${firstPhase.label} → \`${modelId}\``, "info");
711
+ activateAgent(firstPhase, ctx);
712
+ const agentName = firstPhase.agent?.name || firstPhase.key;
713
+ ctx.ui.notify(`${firstPhase.label} → \`${modelId}\` (agent: ${agentName})`, "info");
625
714
  setTimeout(() => pi.sendUserMessage(firstPhase.instruction), 200);
626
715
  },
627
716
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.63.0",
3
+ "version": "0.63.1",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {