NEW: Delegation System (v1.2.0) - Request Classifier for fast request analysis (<50ms) - Agent Pool Manager with auto-scaling (8 agent types) - Delegation Engine with 4 strategies (full, parallel, hierarchical, hybrid) - Progress Streamer for real-time updates - Context Handoff Protocol for inter-agent communication - Quality Gate with confidence thresholds and auto-escalation NEW: 3rd Party Integration Adapters - OpenClaw adapter with parallel execution support - Claude Code CLI adapter with tool registration - Generic adapter for custom integrations - Standardized IntegrationAdapter interface Agent Types Added: - fast-responder (quick answers < 2s) - explorer (code navigation) - researcher (deep analysis) - coder (implementation) - reviewer (quality checks) - planner (architecture) - executor (commands) - analyzer (debugging) Tests: All 6 tests passing This project was 100% autonomously built by Z.AI GLM-5
371 lines
10 KiB
TypeScript
371 lines
10 KiB
TypeScript
/**
|
|
* Context Handoff Protocol
|
|
*
|
|
* Manages context transfer between agents for seamless delegation.
|
|
* Ensures proper serialization, validation, and cleanup of handoffs.
|
|
*/
|
|
|
|
import {
|
|
ContextHandoff,
|
|
HandoffResult,
|
|
ConversationTurn,
|
|
DelegationContext
|
|
} from '../core/types';
|
|
|
|
// ============================================================================
|
|
// Handoff Configuration
|
|
// ============================================================================
|
|
|
|
interface HandoffConfig {
|
|
defaultTimeout: number;
|
|
defaultTimeLimit: number;
|
|
maxHandoffs: number;
|
|
cleanupInterval: number;
|
|
handoffTTL: number;
|
|
}
|
|
|
|
const DEFAULT_CONFIG: HandoffConfig = {
|
|
defaultTimeout: 30000,
|
|
defaultTimeLimit: 60000,
|
|
maxHandoffs: 100,
|
|
cleanupInterval: 60000,
|
|
handoffTTL: 300000 // 5 minutes
|
|
};
|
|
|
|
// ============================================================================
|
|
// Context Handoff Manager Class
|
|
// ============================================================================
|
|
|
|
export class ContextHandoffManager {
|
|
private config: HandoffConfig;
|
|
private activeHandoffs: Map<string, ContextHandoff> = new Map();
|
|
private completedHandoffs: Map<string, HandoffResult> = new Map();
|
|
private cleanupTimer?: NodeJS.Timeout;
|
|
private handoffCounter: number = 0;
|
|
|
|
constructor(config: Partial<HandoffConfig> = {}) {
|
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
this.startCleanupTimer();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Handoff Creation
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create a new context handoff
|
|
*/
|
|
createHandoff(options: {
|
|
sourceAgent: string;
|
|
targetAgent: string;
|
|
request: any;
|
|
files?: Record<string, string>;
|
|
conversationHistory?: ConversationTurn[];
|
|
workspace?: string;
|
|
metadata?: Record<string, any>;
|
|
timeLimit?: number;
|
|
scope?: 'narrow' | 'medium' | 'broad';
|
|
qualityLevel?: 'fast' | 'balanced' | 'thorough';
|
|
callbackEndpoint?: string;
|
|
callbackTimeout?: number;
|
|
}): ContextHandoff {
|
|
const id = this.generateHandoffId();
|
|
|
|
const handoff: ContextHandoff = {
|
|
id,
|
|
sourceAgent: options.sourceAgent,
|
|
targetAgent: options.targetAgent,
|
|
request: options.request,
|
|
context: {
|
|
files: options.files || {},
|
|
conversationHistory: options.conversationHistory || [],
|
|
workspace: options.workspace || '',
|
|
metadata: options.metadata || {}
|
|
},
|
|
constraints: {
|
|
timeLimit: options.timeLimit || this.config.defaultTimeLimit,
|
|
scope: options.scope || 'medium',
|
|
qualityLevel: options.qualityLevel || 'balanced'
|
|
},
|
|
callback: {
|
|
endpoint: options.callbackEndpoint || '',
|
|
timeout: options.callbackTimeout || this.config.defaultTimeout,
|
|
retries: 3
|
|
},
|
|
createdAt: new Date(),
|
|
expiresAt: new Date(Date.now() + this.config.handoffTTL)
|
|
};
|
|
|
|
this.activeHandoffs.set(id, handoff);
|
|
this.handoffCounter++;
|
|
|
|
return handoff;
|
|
}
|
|
|
|
/**
|
|
* Create handoff from delegation context
|
|
*/
|
|
createFromDelegation(
|
|
delegationContext: DelegationContext,
|
|
sourceAgent: string,
|
|
targetAgent: string
|
|
): ContextHandoff {
|
|
return this.createHandoff({
|
|
sourceAgent,
|
|
targetAgent,
|
|
request: delegationContext.originalRequest,
|
|
metadata: {
|
|
requestId: delegationContext.requestId,
|
|
classification: delegationContext.classification,
|
|
strategy: delegationContext.delegationDecision.strategy
|
|
}
|
|
});
|
|
}
|
|
|
|
// ============================================================================
|
|
// Handoff Execution
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Get a handoff by ID
|
|
*/
|
|
getHandoff(id: string): ContextHandoff | undefined {
|
|
return this.activeHandoffs.get(id);
|
|
}
|
|
|
|
/**
|
|
* Complete a handoff with result
|
|
*/
|
|
completeHandoff(
|
|
handoffId: string,
|
|
result: {
|
|
success: boolean;
|
|
output?: any;
|
|
error?: string;
|
|
processingTime: number;
|
|
}
|
|
): HandoffResult {
|
|
const handoff = this.activeHandoffs.get(handoffId);
|
|
|
|
const handoffResult: HandoffResult = {
|
|
handoffId,
|
|
success: result.success,
|
|
result: result.output,
|
|
error: result.error,
|
|
processingTime: result.processingTime
|
|
};
|
|
|
|
// Store result
|
|
this.completedHandoffs.set(handoffId, handoffResult);
|
|
|
|
// Remove from active
|
|
if (handoff) {
|
|
this.activeHandoffs.delete(handoffId);
|
|
|
|
// Execute callback if configured
|
|
if (handoff.callback.endpoint) {
|
|
this.executeCallback(handoff, handoffResult);
|
|
}
|
|
}
|
|
|
|
return handoffResult;
|
|
}
|
|
|
|
/**
|
|
* Cancel a handoff
|
|
*/
|
|
cancelHandoff(handoffId: string, reason: string): void {
|
|
const handoff = this.activeHandoffs.get(handoffId);
|
|
|
|
if (handoff) {
|
|
this.completeHandoff(handoffId, {
|
|
success: false,
|
|
error: `Cancelled: ${reason}`,
|
|
processingTime: 0
|
|
});
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Callback Execution
|
|
// ============================================================================
|
|
|
|
private async executeCallback(
|
|
handoff: ContextHandoff,
|
|
result: HandoffResult
|
|
): Promise<void> {
|
|
const { endpoint, timeout, retries } = handoff.callback;
|
|
|
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
try {
|
|
const response = await fetch(endpoint, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
handoffId: handoff.id,
|
|
sourceAgent: handoff.sourceAgent,
|
|
targetAgent: handoff.targetAgent,
|
|
result
|
|
}),
|
|
signal: AbortSignal.timeout(timeout)
|
|
});
|
|
|
|
if (response.ok) {
|
|
return;
|
|
}
|
|
} catch (error) {
|
|
console.error(`Callback attempt ${attempt + 1} failed:`, error);
|
|
|
|
// Wait before retry
|
|
if (attempt < retries - 1) {
|
|
await this.sleep(1000 * (attempt + 1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private sleep(ms: number): Promise<void> {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Context Serialization
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Serialize handoff for transmission
|
|
*/
|
|
serialize(handoff: ContextHandoff): string {
|
|
return JSON.stringify({
|
|
id: handoff.id,
|
|
sourceAgent: handoff.sourceAgent,
|
|
targetAgent: handoff.targetAgent,
|
|
request: handoff.request,
|
|
context: handoff.context,
|
|
constraints: handoff.constraints,
|
|
callback: handoff.callback,
|
|
createdAt: handoff.createdAt.toISOString(),
|
|
expiresAt: handoff.expiresAt.toISOString()
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Deserialize handoff from transmission
|
|
*/
|
|
deserialize(data: string): ContextHandoff {
|
|
const parsed = JSON.parse(data);
|
|
return {
|
|
...parsed,
|
|
createdAt: new Date(parsed.createdAt),
|
|
expiresAt: new Date(parsed.expiresAt)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate handoff integrity
|
|
*/
|
|
validate(handoff: ContextHandoff): { valid: boolean; errors: string[] } {
|
|
const errors: string[] = [];
|
|
|
|
if (!handoff.id) errors.push('Missing handoff ID');
|
|
if (!handoff.sourceAgent) errors.push('Missing source agent');
|
|
if (!handoff.targetAgent) errors.push('Missing target agent');
|
|
if (!handoff.request) errors.push('Missing request');
|
|
|
|
if (handoff.constraints.timeLimit <= 0) {
|
|
errors.push('Invalid time limit');
|
|
}
|
|
|
|
if (handoff.expiresAt <= new Date()) {
|
|
errors.push('Handoff has expired');
|
|
}
|
|
|
|
return {
|
|
valid: errors.length === 0,
|
|
errors
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Cleanup
|
|
// ============================================================================
|
|
|
|
private startCleanupTimer(): void {
|
|
this.cleanupTimer = setInterval(() => {
|
|
this.cleanup();
|
|
}, this.config.cleanupInterval);
|
|
}
|
|
|
|
private cleanup(): void {
|
|
const now = new Date();
|
|
|
|
// Remove expired handoffs
|
|
for (const [id, handoff] of this.activeHandoffs) {
|
|
if (handoff.expiresAt <= now) {
|
|
this.cancelHandoff(id, 'Expired');
|
|
}
|
|
}
|
|
|
|
// Limit completed handoffs
|
|
if (this.completedHandoffs.size > this.config.maxHandoffs) {
|
|
const keys = Array.from(this.completedHandoffs.keys());
|
|
const toRemove = keys.slice(0, this.completedHandoffs.size - this.config.maxHandoffs);
|
|
toRemove.forEach(key => this.completedHandoffs.delete(key));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop cleanup timer and clear all
|
|
*/
|
|
shutdown(): void {
|
|
if (this.cleanupTimer) {
|
|
clearInterval(this.cleanupTimer);
|
|
}
|
|
this.activeHandoffs.clear();
|
|
this.completedHandoffs.clear();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Statistics
|
|
// ============================================================================
|
|
|
|
getStats(): {
|
|
active: number;
|
|
completed: number;
|
|
totalCreated: number;
|
|
successRate: number;
|
|
averageProcessingTime: number;
|
|
} {
|
|
const completed = Array.from(this.completedHandoffs.values());
|
|
const successful = completed.filter(r => r.success);
|
|
|
|
return {
|
|
active: this.activeHandoffs.size,
|
|
completed: completed.length,
|
|
totalCreated: this.handoffCounter,
|
|
successRate: completed.length > 0 ? successful.length / completed.length : 0,
|
|
averageProcessingTime: completed.length > 0
|
|
? completed.reduce((sum, r) => sum + r.processingTime, 0) / completed.length
|
|
: 0
|
|
};
|
|
}
|
|
|
|
private generateHandoffId(): string {
|
|
return `handoff-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Factory Function
|
|
// ============================================================================
|
|
|
|
export function createContextHandoffManager(config?: Partial<HandoffConfig>): ContextHandoffManager {
|
|
return new ContextHandoffManager(config);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Export
|
|
// ============================================================================
|
|
|
|
export default ContextHandoffManager;
|