@phi-code-admin/phi-code 0.72.0 → 0.73.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.
@@ -471,6 +471,10 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
471
471
  let activeAgentTools: string[] | null = null;
472
472
  let savedTools: string[] | null = null;
473
473
  let phasePending = false; // true while waiting for a phase to complete
474
+ let phaseTimeoutId: ReturnType<typeof setTimeout> | null = null;
475
+ const MAX_PHASE_DURATION_MS = 10 * 60 * 1000; // 10 minutes per phase
476
+ const MAX_TOOL_CALLS_PER_PHASE = 60; // Safety limit
477
+ let phaseStartTime: number | null = null;
474
478
 
475
479
  /**
476
480
  * Parse agent .md file with YAML frontmatter
@@ -541,6 +545,9 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
541
545
  **Project Request:** ${description}
542
546
 
543
547
  **Your tasks:**
548
+
549
+ **Parallelization:** When making multiple tool calls that don't depend on each other (e.g., memory_search + ontology_query, or reading 2+ files), call them IN PARALLEL in the same response. This is faster.
550
+
544
551
  1. Call \`memory_search\` with project-relevant keywords (MANDATORY)
545
552
  2. List all existing files and read key ones
546
553
  3. Identify tech stack, patterns, and constraints
@@ -555,6 +562,7 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
555
562
  **LAST ACTION (MANDATORY):** Call \`memory_write\` to save your exploration findings for downstream agents.
556
563
 
557
564
  **Knowledge Graph:**
565
+ // TODO: ontology_batch_add for reducing API calls (currently single-item only)
558
566
  After your analysis, use \`ontology_add\` to save key project entities AND their relations:
559
567
  - Add entities for: the project, each major library, each module/directory
560
568
  - Add relations between them: "uses", "contains", "depends_on", "implements"
@@ -721,6 +729,8 @@ After implementation, use \`memory_write\` to save a summary of what was built,
721
729
  - On Linux/Mac fallback: \`lsof -ti:PORT | xargs kill -9\`
722
730
  - Always clean up after tests: kill background processes, remove temp files
723
731
 
732
+ **Anti-loop rule:** If the SAME test fails 3 times in a row with the same error after your fixes, STOP trying to fix it. Write the failure in your test report as "UNRESOLVED" and move on. Do not waste more than 3 iterations on the same issue.
733
+
724
734
  After testing, use \`memory_write\` to save test results, bugs found, and lessons learned.` + runtimeInfo,
725
735
  },
726
736
  {
@@ -780,7 +790,12 @@ After your review, use \`memory_write\` ONCE to save:
780
790
  - Common mistakes to avoid in future projects
781
791
  Tag the note with relevant keywords for vector search.
782
792
 
783
- **Important:** Write lessons-learned ONCE. Do not call memory_write twice with the same filename or duplicate content.` + runtimeInfo,
793
+ **Important:** Write lessons-learned ONCE. Do not call memory_write twice with the same filename or duplicate content.
794
+
795
+ **Ontology enrichment:** After your review, use \`ontology_add\` to save your key findings:
796
+ - Add a "review-report" entity with type "Document"
797
+ - Add relations to the project: "reviews" → project, quality score as entity property
798
+ - Save any new architectural decisions or patterns discovered` + runtimeInfo,
784
799
  },
785
800
  ];
786
801
  }
@@ -852,6 +867,16 @@ Tag the note with relevant keywords for vector search.
852
867
  setOrchestrationActive(false);
853
868
  phasePending = false;
854
869
  deactivateAgent();
870
+ if (phaseTimeoutId) { clearTimeout(phaseTimeoutId); phaseTimeoutId = null; }
871
+ // Generate global final summary
872
+ const totalPhases = 5; // always 5
873
+ const elapsed = phaseStartTime ? Math.round((Date.now() - phaseStartTime) / 1000) : 0;
874
+ const minutes = Math.floor(elapsed / 60);
875
+ const seconds = elapsed % 60;
876
+ ctx.ui.notify(`\nšŸ“Š **Orchestration Summary**\n` +
877
+ ` Phases: ${totalPhases}/5 completed\n` +
878
+ ` Duration: ${minutes}m ${seconds}s\n` +
879
+ ` Check \`.phi/plans/\` for all reports`, "info");
855
880
  try {
856
881
  ctx.ui.notify(`\nāœ… **All 5 phases complete!**`, "info");
857
882
  } catch {
@@ -870,6 +895,15 @@ Tag the note with relevant keywords for vector search.
870
895
  ctx.ui.notify(`\n${phase.label} → \`${modelId}\` (agent: ${agentName})`, "info");
871
896
  // Small delay to let the model switch settle, then send instruction
872
897
  setTimeout(() => pi.sendUserMessage(phase.instruction), 500);
898
+ // Set phase timeout — abort if phase takes too long
899
+ if (phaseTimeoutId) clearTimeout(phaseTimeoutId);
900
+ phaseTimeoutId = setTimeout(() => {
901
+ if (orchestrationActive && phasePending) {
902
+ ctx.ui.notify(`\nā° **Phase timed out** (${MAX_PHASE_DURATION_MS / 60000} min limit). Skipping to next phase.`, "warning");
903
+ phasePending = false;
904
+ sendNextPhase(ctx);
905
+ }
906
+ }, MAX_PHASE_DURATION_MS);
873
907
  });
874
908
  }
875
909
 
@@ -902,6 +936,9 @@ Tag the note with relevant keywords for vector search.
902
936
  return;
903
937
  }
904
938
 
939
+ // Clear phase timeout on normal completion
940
+ if (phaseTimeoutId) { clearTimeout(phaseTimeoutId); phaseTimeoutId = null; }
941
+
905
942
  // Build a structured summary of what happened in this phase
906
943
  // Instead of raw LLM text, extract concrete actions: files created/modified,
907
944
  // errors encountered, test results. This gives the next phase actionable context.
@@ -930,8 +967,12 @@ Tag the note with relevant keywords for vector search.
930
967
  const match = content.match(/edited (.+)/) || content.match(/in (.+)/);
931
968
  if (match) filesEdited.push(match[1]);
932
969
  }
933
- // Track errors
934
- if (content.includes('ERR:') || content.includes('Error:') || content.includes('FAIL')) {
970
+ // Track errors — but filter out edit retries (old_text mismatch = normal retry, not error)
971
+ if ((content.includes('ERR:') || content.includes('Error:') || content.includes('FAIL'))
972
+ && !content.includes('old text must match')
973
+ && !content.includes('The old text')
974
+ && !content.includes('oldText not found')
975
+ && !content.includes('old_text not found')) {
935
976
  const preview = content.slice(0, 150).replace(/\n/g, ' ');
936
977
  errorsHit.push(`${name}: ${preview}`);
937
978
  }
@@ -943,13 +984,42 @@ Tag the note with relevant keywords for vector search.
943
984
  }
944
985
  }
945
986
 
987
+ // Detect API errors (401, auth failures) — abort workflow if found
988
+ const hasAuthError = messages.some((msg: any) => {
989
+ const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content || '');
990
+ return content.includes('401') && (content.includes('invalid access token') || content.includes('token expired') || content.includes('Unauthorized'));
991
+ });
992
+ if (hasAuthError || (toolCallCount === 0 && messages.length > 0)) {
993
+ const errorMsg = hasAuthError ? 'API authentication error (401)' : 'Phase produced 0 tool calls — possible API or model error';
994
+ ctx.ui.notify(`\nāŒ **Orchestrator aborted:** ${errorMsg}\nCheck your API key and model configuration.`, "error");
995
+ setOrchestrationActive(false);
996
+ phasePending = false;
997
+ deactivateAgent();
998
+ if (phaseTimeoutId) { clearTimeout(phaseTimeoutId); phaseTimeoutId = null; }
999
+ return;
1000
+ }
1001
+
946
1002
  // Build the summary
947
1003
  const summaryParts: string[] = [];
948
1004
  summaryParts.push(`Tool calls: ${toolCallCount}`);
1005
+ // Anti-loop guard: warn if tool calls are excessive
1006
+ if (toolCallCount > MAX_TOOL_CALLS_PER_PHASE) {
1007
+ summaryParts.push(`āš ļø WARNING: Phase used ${toolCallCount} tool calls (limit: ${MAX_TOOL_CALLS_PER_PHASE}). Possible loop detected.`);
1008
+ }
949
1009
  if (filesWritten.length > 0) summaryParts.push(`Files created/written: ${filesWritten.join(', ')}`);
950
1010
  if (filesEdited.length > 0) summaryParts.push(`Files edited: ${filesEdited.join(', ')}`);
951
1011
  if (testResults.length > 0) summaryParts.push(`Test results:\n${testResults.join('\n')}`);
952
1012
  if (errorsHit.length > 0) summaryParts.push(`Errors encountered: ${errorsHit.length}\n${errorsHit.slice(0, 5).join('\n')}`);
1013
+
1014
+ // Verify mandatory tool usage
1015
+ const toolNames = messages
1016
+ .filter((m: any) => m.role === 'tool' || m.role === 'function' || m.role === 'toolResult')
1017
+ .map((m: any) => (m as any).name || (m as any).toolName || '');
1018
+ const hasMemorySearch = toolNames.includes('memory_search');
1019
+ const hasMemoryWrite = toolNames.includes('memory_write');
1020
+ if (!hasMemorySearch) summaryParts.push(`āš ļø Phase did NOT call memory_search (mandatory)`);
1021
+ if (!hasMemoryWrite) summaryParts.push(`āš ļø Phase did NOT call memory_write (mandatory)`);
1022
+
953
1023
  const phaseSummary = summaryParts.join('\n');
954
1024
 
955
1025
  // Inject structured summary into next phase
@@ -1005,6 +1075,8 @@ Tag the note with relevant keywords for vector search.
1005
1075
  }
1006
1076
  ctx.ui.notify("", "info");
1007
1077
 
1078
+ // Record orchestration start time for final summary
1079
+ phaseStartTime = Date.now();
1008
1080
  // Switch model and activate agent for first phase
1009
1081
  const modelId = await switchModelForPhase(firstPhase, ctx);
1010
1082
  activateAgent(firstPhase, ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.72.0",
3
+ "version": "0.73.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {