UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.

350 lines (310 loc) 10.5 kB
/** * Context Injector - Broadcast Message Builder * Constructs context messages for agent execution in CFN Loop * Implements iteration tracking, phase awareness, and success criteria injection * * Used by: cfn-loop-orchestration to broadcast agent context across Redis * Reference: helpers/context-injection.sh (142 LOC) - shell predecessor */ import { ExecutionMode } from '../types'; /** * Loop phase enumeration */ export type LoopPhase = 'loop3' | 'loop2' | 'product-owner' | 'iteration-prep'; /** * Success criteria for task execution */ export interface SuccessCriteria { criteria: string[]; testPassRate: number; consensusThreshold: number; } /** * Broadcast context message for agent execution */ export interface BroadcastContext { taskId: string; iteration: number; phase: LoopPhase; mode: ExecutionMode; agentIds?: string[] | undefined; successCriteria?: SuccessCriteria | undefined; taskDescription?: string | undefined; timestamp: string; contextVersion: string; } /** * Result of successful broadcast context construction */ export interface BroadcastResult { context: BroadcastContext; json: string; messageCount: number; } /** * Build broadcast context for agent execution * * @param params Context parameters * @returns BroadcastResult with JSON-formatted message * @throws Error if required fields are missing * * @example * ```typescript * const result = buildBroadcastContext({ * taskId: 'task-123', * iteration: 1, * phase: 'loop3', * mode: 'standard', * agentIds: ['agent-1', 'agent-2'] * }); * * console.log(result.json); // JSON string for Redis broadcast * ``` */ export function buildBroadcastContext(params: { taskId: string; iteration: number; phase: LoopPhase; mode: ExecutionMode; agentIds?: string[] | undefined; successCriteria?: SuccessCriteria | undefined; taskDescription?: string | undefined; }): BroadcastResult { // Validate required fields if (!params.taskId) { throw new Error('taskId is required for broadcast context'); } if (typeof params.iteration !== 'number' || params.iteration < 1) { throw new Error('iteration must be a positive number'); } if (!params.phase) { throw new Error('phase is required for broadcast context'); } if (!params.mode) { throw new Error('mode is required for broadcast context'); } // Validate phase value const validPhases: LoopPhase[] = ['loop3', 'loop2', 'product-owner', 'iteration-prep']; if (!validPhases.includes(params.phase)) { throw new Error(`Invalid phase: ${params.phase}. Must be one of: ${validPhases.join(', ')}`); } // Construct broadcast context const context: BroadcastContext = { taskId: params.taskId, iteration: params.iteration, phase: params.phase, mode: params.mode, timestamp: new Date().toISOString(), contextVersion: '3.0', }; // Add optional fields if provided if (params.agentIds && Array.isArray(params.agentIds) && params.agentIds.length > 0) { context.agentIds = params.agentIds; } if (params.successCriteria) { validateSuccessCriteria(params.successCriteria); context.successCriteria = params.successCriteria; } if (params.taskDescription) { context.taskDescription = params.taskDescription; } // Serialize to JSON const json = JSON.stringify(context, null, 2); return { context, json, messageCount: params.agentIds ? params.agentIds.length : 1, }; } /** * Build multiple broadcast messages for different agents * * @param baseContext Base context parameters * @param agentContexts Per-agent context overrides * @returns Array of broadcast contexts * * @example * ```typescript * const messages = buildBroadcastMessages( * { taskId: 'task-1', iteration: 1, phase: 'loop3', mode: 'standard' }, * [ * { agentId: 'loop3-backend-1', agentType: 'backend-engineer' }, * { agentId: 'loop3-frontend-1', agentType: 'react-frontend-engineer' } * ] * ); * ``` */ export function buildBroadcastMessages( baseContext: { taskId: string; iteration: number; phase: LoopPhase; mode: ExecutionMode; successCriteria?: SuccessCriteria; taskDescription?: string; }, agentContexts: Array<{ agentId: string; agentType: string; }> ): BroadcastContext[] { if (!Array.isArray(agentContexts) || agentContexts.length === 0) { throw new Error('agentContexts must be a non-empty array'); } // Validate base context by building it buildBroadcastContext({ ...baseContext, agentIds: agentContexts.map(ac => ac.agentId), }); // Build individual context for each agent return agentContexts.map(agentCtx => ({ taskId: baseContext.taskId, iteration: baseContext.iteration, phase: baseContext.phase, mode: baseContext.mode, agentIds: [agentCtx.agentId] as string[] | undefined, successCriteria: baseContext.successCriteria ?? undefined, taskDescription: baseContext.taskDescription ?? undefined, timestamp: new Date().toISOString(), contextVersion: '3.0', })); } /** * Build iteration-specific broadcast context for agent wake operations * * @param taskId Unique task identifier * @param iteration Current iteration number * @param mode Execution mode * @param feedback Optional feedback for next iteration * @returns BroadcastContext for iteration prep */ export function buildIterationContext( taskId: string, iteration: number, mode: ExecutionMode, feedback?: unknown ): BroadcastContext { const taskDescription: string | undefined = feedback ? JSON.stringify(feedback) : undefined; return buildBroadcastContext({ taskId, iteration, phase: 'iteration-prep', mode, taskDescription, }).context; } /** * Format context as JSON for Redis broadcast * * @param context Broadcast context * @param compact If true, removes whitespace * @returns JSON string */ export function formatContextJson(context: BroadcastContext, compact: boolean = false): string { if (compact) { return JSON.stringify(context); } return JSON.stringify(context, null, 2); } /** * Parse broadcast context from JSON * * @param json JSON string containing broadcast context * @returns Parsed BroadcastContext * @throws Error if JSON is invalid or missing required fields */ export function parseBroadcastContext(json: string): BroadcastContext { let parsed: unknown; try { parsed = JSON.parse(json); } catch (e) { throw new Error(`Invalid JSON for broadcast context: ${e instanceof Error ? e.message : String(e)}`); } if (typeof parsed !== 'object' || parsed === null) { throw new Error('Broadcast context must be a JSON object'); } const context = parsed as Record<string, unknown>; // Validate required fields if (!context.taskId || typeof context.taskId !== 'string') { throw new Error('taskId must be a non-empty string'); } if (typeof context.iteration !== 'number') { throw new Error('iteration must be a number'); } if (!context.phase || typeof context.phase !== 'string') { throw new Error('phase must be a non-empty string'); } if (!context.mode || typeof context.mode !== 'string') { throw new Error('mode must be a non-empty string'); } const agentIds: string[] | undefined = context.agentIds ? (context.agentIds as string[]) : undefined; const successCriteria: SuccessCriteria | undefined = context.successCriteria ? (context.successCriteria as SuccessCriteria) : undefined; const taskDescription: string | undefined = context.taskDescription ? (context.taskDescription as string) : undefined; return { taskId: context.taskId, iteration: context.iteration, phase: context.phase as LoopPhase, mode: context.mode as ExecutionMode, timestamp: (context.timestamp as string) || new Date().toISOString(), contextVersion: (context.contextVersion as string) || '3.0', agentIds, successCriteria, taskDescription, }; } /** * Validate success criteria structure * * @param criteria Success criteria to validate * @throws Error if structure is invalid */ function validateSuccessCriteria(criteria: SuccessCriteria): void { if (!Array.isArray(criteria.criteria) || criteria.criteria.length === 0) { throw new Error('successCriteria.criteria must be a non-empty array'); } if (typeof criteria.testPassRate !== 'number' || criteria.testPassRate < 0 || criteria.testPassRate > 1) { throw new Error('successCriteria.testPassRate must be a number between 0 and 1'); } if (typeof criteria.consensusThreshold !== 'number' || criteria.consensusThreshold < 0 || criteria.consensusThreshold > 1) { throw new Error('successCriteria.consensusThreshold must be a number between 0 and 1'); } } /** * Merge multiple broadcast contexts for multi-phase execution * * @param contexts Array of broadcast contexts * @returns Merged context with combined agentIds */ export function mergeBroadcastContexts(contexts: BroadcastContext[]): BroadcastContext { if (!Array.isArray(contexts) || contexts.length === 0) { throw new Error('contexts must be a non-empty array'); } const first = contexts[0]; if (!first) { throw new Error('First context is undefined'); } // Validate all contexts have same base properties for (const context of contexts) { if (context.taskId !== first.taskId) { throw new Error('All contexts must have the same taskId for merging'); } if (context.iteration !== first.iteration) { throw new Error('All contexts must have the same iteration for merging'); } } // Merge agent IDs from all contexts const mergedAgentIds = Array.from( new Set(contexts.flatMap(c => c.agentIds || [])) ); return { taskId: first.taskId, iteration: first.iteration, phase: first.phase, mode: first.mode, timestamp: new Date().toISOString(), contextVersion: first.contextVersion, agentIds: mergedAgentIds.length > 0 ? mergedAgentIds : undefined, successCriteria: first.successCriteria ?? undefined, taskDescription: first.taskDescription ?? undefined, }; }