@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
|
|
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);
|