feat(P02): orchestrator enrichment — GitAgentContext, multi-phase, error recovery, timer cleanup, TEST stage
---ci---
phase: 2
milestone: v0.6
status: execute
decisions:
- id: D-001
decision: Pass GitAgentContext to agents instead of bare AgentContext
rationale: Agents need git-native context (gitContext, gitBranch, ciFiles, milestone) to operate autonomously
confidence: 0.95
- id: D-002
decision: Implement multi-phase iteration with totalPhases derived from ROADMAP.md
rationale: Milestones can span multiple phases; orchestrator must advance through all of them
confidence: 0.90
- id: D-003
decision: Add executeStageWithRecovery with retry + plan revision + escalation
rationale: Robust error recovery requires multiple fallback levels before giving up
confidence: 0.85
- id: D-004
decision: Add timer-to-escalation mapping in EscalationProtocol for proper cleanup
rationale: resolveEscalation must clearTimeout for the corresponding timer to prevent resource leaks
confidence: 0.90
- id: D-005
decision: Add dispose() to EscalationProtocol called in orchestrator finally block
rationale: Ensures all timers are cleaned up on orchestrator exit regardless of outcome
confidence: 0.95
- id: D-006
decision: Add mechanical TEST stage fallback running npm test via execSync
rationale: When no backend is available, tests can still be run mechanically
confidence: 0.85
---/ci---
This commit is contained in:
@@ -29,6 +29,7 @@ export class EscalationProtocol {
|
||||
private pendingEscalations: Map<string, Escalation>;
|
||||
private timeoutCallback: (escalation: Escalation, chosenOption: string) => void;
|
||||
private timers: NodeJS.Timeout[];
|
||||
private timerEscalationMap: Map<NodeJS.Timeout, string>;
|
||||
|
||||
constructor(
|
||||
config: CIAgentConfig,
|
||||
@@ -43,6 +44,7 @@ export class EscalationProtocol {
|
||||
this.pendingEscalations = new Map();
|
||||
this.timeoutCallback = timeoutCallback;
|
||||
this.timers = [];
|
||||
this.timerEscalationMap = new Map();
|
||||
}
|
||||
|
||||
setMilestone(milestone: string): void {
|
||||
@@ -102,6 +104,16 @@ export class EscalationProtocol {
|
||||
const escalation = this.pendingEscalations.get(escalationId);
|
||||
if (!escalation) return null;
|
||||
|
||||
for (let i = this.timers.length - 1; i >= 0; i--) {
|
||||
const timer = this.timers[i];
|
||||
const mappedId = this.timerEscalationMap.get(timer);
|
||||
if (mappedId === escalationId) {
|
||||
clearTimeout(timer);
|
||||
this.timerEscalationMap.delete(timer);
|
||||
this.timers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
escalation.resolution = resolution;
|
||||
escalation.resolved_at = new Date().toISOString();
|
||||
escalation.resolution_detail = `Chose option: ${chosenOptionId}`;
|
||||
@@ -139,11 +151,16 @@ export class EscalationProtocol {
|
||||
clearAllTimers(): void {
|
||||
for (const timer of this.timers) {
|
||||
clearTimeout(timer);
|
||||
this.timerEscalationMap.delete(timer);
|
||||
}
|
||||
this.timers = [];
|
||||
this.pendingEscalations.clear();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.clearAllTimers();
|
||||
}
|
||||
|
||||
formatEscalation(escalation: Escalation): string {
|
||||
const lines: string[] = [
|
||||
`⚠️ ESCALATION [${escalation.id}]`,
|
||||
@@ -200,9 +217,13 @@ export class EscalationProtocol {
|
||||
escalation.resolved_at = new Date().toISOString();
|
||||
escalation.resolution_detail = `Auto-proceeded with default: ${escalation.default_option_id}`;
|
||||
this.pendingEscalations.delete(escalation.id);
|
||||
this.timerEscalationMap.delete(timer);
|
||||
const idx = this.timers.indexOf(timer);
|
||||
if (idx >= 0) this.timers.splice(idx, 1);
|
||||
this.timeoutCallback(escalation, escalation.default_option_id);
|
||||
}
|
||||
}, timeout);
|
||||
this.timers.push(timer);
|
||||
this.timerEscalationMap.set(timer, escalation.id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user