@phi-code-admin/phi-code 0.63.4 → 0.64.0

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.
@@ -462,10 +462,7 @@ _Edit this file to customize Phi Code's behavior for your project._
462
462
  ctx.ui.notify(` ${status} ${p.name}${tag}${modelCount}`, "info");
463
463
  }
464
464
 
465
- // If no cloud providers have keys, prompt for setup
466
- if (cloudConfigured.length === 0) {
467
- ctx.ui.notify("\n⚠️ No cloud API keys configured.\n", "warning");
468
- }
465
+ // No warning needed the wizard itself handles configuration
469
466
 
470
467
  // Always offer to add a provider via select menu
471
468
  const providerOptions = [
@@ -471,6 +471,7 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
471
471
  let activeAgentPrompt: string | null = null;
472
472
  let activeAgentTools: string[] | null = null;
473
473
  let savedTools: string[] | null = null;
474
+ let phasePending = false; // true while waiting for a phase to complete
474
475
 
475
476
  /**
476
477
  * Parse agent .md file with YAML frontmatter
@@ -607,24 +608,55 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
607
608
  /**
608
609
  * Send the next phase in the queue.
609
610
  */
611
+ function setOrchestrationActive(active: boolean) {
612
+ orchestrationActive = active;
613
+ (globalThis as any).__phiOrchestrationActive = active;
614
+ }
615
+
610
616
  function sendNextPhase(ctx: any) {
611
617
  if (phaseQueue.length === 0) {
612
- orchestrationActive = false;
618
+ setOrchestrationActive(false);
619
+ phasePending = false;
613
620
  deactivateAgent();
614
621
  ctx.ui.notify(`\n✅ **All 5 phases complete!**`, "info");
615
622
  return;
616
623
  }
617
624
 
618
625
  const phase = phaseQueue.shift()!;
626
+ phasePending = true;
619
627
 
620
628
  switchModelForPhase(phase, ctx).then((modelId) => {
621
629
  activateAgent(phase, ctx);
622
630
  const agentName = phase.agent?.name || phase.key;
623
631
  ctx.ui.notify(`\n${phase.label} → \`${modelId}\` (agent: ${agentName})`, "info");
624
- setTimeout(() => pi.sendUserMessage(phase.instruction), 200);
632
+ // Wait for agent to be idle, then send the phase instruction
633
+ waitForIdleThenSend(phase.instruction, ctx);
625
634
  });
626
635
  }
627
636
 
637
+ /**
638
+ * Reliable phase sending: poll isIdle() then sendUserMessage.
639
+ * Retries up to 240 attempts (120 seconds).
640
+ */
641
+ function waitForIdleThenSend(message: string, ctx: any) {
642
+ let attempts = 0;
643
+ const timer = setInterval(() => {
644
+ attempts++;
645
+ if (attempts > 240) {
646
+ clearInterval(timer);
647
+ ctx.ui.notify("⚠️ Orchestrator timeout — agent never became idle.", "warning");
648
+ setOrchestrationActive(false);
649
+ phasePending = false;
650
+ deactivateAgent();
651
+ return;
652
+ }
653
+ if (ctx.isIdle()) {
654
+ clearInterval(timer);
655
+ setTimeout(() => pi.sendUserMessage(message), 300);
656
+ }
657
+ }, 500);
658
+ }
659
+
628
660
  // ─── System Prompt Injection — Agent personas ────────────────────
629
661
 
630
662
  pi.on("before_agent_start", async (event, _ctx) => {
@@ -636,33 +668,41 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
636
668
  });
637
669
 
638
670
  // ─── Output Event — Phase Chaining ───────────────────────────────
671
+ // Fires on each output chunk. We use it to detect when a phase completes.
672
+ // The key fix: only start ONE idle-check timer per phase, not one per chunk.
639
673
 
640
674
  pi.on("output", async (_event, ctx) => {
641
- if (!orchestrationActive || phaseQueue.length === 0) return;
675
+ if (!orchestrationActive || !phasePending) return;
676
+ if (phaseQueue.length === 0 && phasePending) {
677
+ // Last phase in progress — wait for it to finish, then complete
678
+ }
642
679
 
680
+ // Debounce: clear any existing timer, start fresh
643
681
  if (idlePollTimer) {
644
682
  clearInterval(idlePollTimer);
645
683
  idlePollTimer = null;
646
684
  }
647
685
 
648
- let attempts = 0;
649
- idlePollTimer = setInterval(() => {
650
- attempts++;
651
- if (attempts > 120) {
652
- clearInterval(idlePollTimer!);
653
- idlePollTimer = null;
654
- ctx.ui.notify("⚠️ Orchestrator timeout phase took too long.", "warning");
655
- orchestrationActive = false;
656
- deactivateAgent();
657
- return;
658
- }
659
- if (ctx.isIdle()) {
660
- clearInterval(idlePollTimer!);
661
- idlePollTimer = null;
662
- sendNextPhase(ctx);
663
- }
664
- }, 500);
665
- });
686
+ // After output, wait a bit then check if the agent is truly idle
687
+ // Use a longer initial delay (2 seconds) to let tool calls complete
688
+ idlePollTimer = setTimeout(() => {
689
+ let attempts = 0;
690
+ idlePollTimer = setInterval(() => {
691
+ attempts++;
692
+ if (attempts > 60) { // 30 seconds after last output
693
+ clearInterval(idlePollTimer!);
694
+ idlePollTimer = null;
695
+ // Don't timeout — the phase might still have tool calls
696
+ return;
697
+ }
698
+ if (ctx.isIdle()) {
699
+ clearInterval(idlePollTimer!);
700
+ idlePollTimer = null;
701
+ phasePending = false;
702
+ sendNextPhase(ctx);
703
+ }
704
+ }, 500) as any;
705
+ }, 2000) as any;
666
706
 
667
707
  // ─── /plan Command — Full workflow ───────────────────────────────
668
708
 
@@ -693,7 +733,8 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
693
733
  // Build phases with model assignments + agent definitions
694
734
  const phases = buildPhases(description);
695
735
  phaseQueue = phases.slice(1); // Queue phases 2-5
696
- orchestrationActive = true;
736
+ setOrchestrationActive(true);
737
+ phasePending = true;
697
738
  const firstPhase = phases[0];
698
739
 
699
740
  ctx.ui.notify(`📋 **Orchestrator started** — 5 phases with model routing + agent roles\n`, "info");
@@ -77,6 +77,11 @@ export default function smartRouterExtension(pi: ExtensionAPI) {
77
77
  return { action: "continue" };
78
78
  }
79
79
 
80
+ // Do NOT route during orchestration — the orchestrator manages models
81
+ if ((globalThis as any).__phiOrchestrationActive) {
82
+ return { action: "continue" };
83
+ }
84
+
80
85
  const recommendation = router.getRecommendation(event.text);
81
86
 
82
87
  if (recommendation.category !== "general") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.63.4",
3
+ "version": "0.64.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {