claude-flow-novice 2.16.0 → 2.16.1
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.
- package/.claude/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +1 -1
- package/.claude/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +1 -1
- package/.claude/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +1 -1
- package/.claude/cfn-extras/skills/google-sheets-progress/SKILL.md +1 -1
- package/.claude/commands/CFN_LOOP_FRONTEND.md +1 -1
- package/.claude/commands/cfn-loop-cli.md +124 -46
- package/.claude/commands/cfn-loop-frontend.md +1 -1
- package/.claude/commands/cfn-loop-task.md +2 -2
- package/.claude/commands/deprecated/cfn-loop.md +2 -2
- package/.claude/hooks/cfn-invoke-post-edit.sh +31 -5
- package/.claude/hooks/cfn-post-edit.config.json +9 -2
- package/.claude/root-claude-distribute/CFN-CLAUDE.md +1 -1
- package/.claude/skills/cfn-backlog-management/SKILL.md +1 -1
- package/.claude/skills/cfn-loop-orchestration/NORTH_STAR_INDEX.md +1 -1
- package/claude-assets/agents/cfn-dev-team/analysts/root-cause-analyst.md +2 -2
- package/claude-assets/agents/cfn-dev-team/architecture/base-template-generator.md +1 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +2 -2
- package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +1 -1
- package/claude-assets/agents/cfn-dev-team/dev-ops/devops-engineer.md +1 -1
- package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +2 -2
- package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +2 -2
- package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +1 -1
- package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/documentation/pseudocode.md +1 -1
- package/claude-assets/agents/cfn-dev-team/product-owners/accessibility-advocate-persona.md +1 -1
- package/claude-assets/agents/cfn-dev-team/product-owners/cto-agent.md +1 -1
- package/claude-assets/agents/cfn-dev-team/product-owners/power-user-persona.md +1 -1
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/unit/tdd-london-unit-swarm.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/agent-builder.md +11 -0
- package/claude-assets/agents/cfn-dev-team/utility/analyst.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/claude-code-expert.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/researcher.md +1 -1
- package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +1 -1
- package/claude-assets/agents/custom/cfn-docker-expert.md +1 -0
- package/claude-assets/agents/custom/cfn-loops-cli-expert.md +326 -17
- package/claude-assets/agents/custom/cfn-redis-operations.md +529 -529
- package/claude-assets/agents/custom/cfn-system-expert.md +1 -1
- package/claude-assets/agents/custom/trigger-dev-expert.md +369 -0
- package/claude-assets/agents/docker-team/micro-sprint-planner.md +747 -747
- package/claude-assets/agents/project-only-agents/npm-package-specialist.md +1 -1
- package/claude-assets/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +1 -1
- package/claude-assets/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +1 -1
- package/claude-assets/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +1 -1
- package/claude-assets/cfn-extras/skills/google-sheets-progress/SKILL.md +1 -1
- package/claude-assets/commands/CFN_LOOP_FRONTEND.md +1 -1
- package/claude-assets/commands/cfn-loop-cli.md +124 -46
- package/claude-assets/commands/cfn-loop-frontend.md +1 -1
- package/claude-assets/commands/cfn-loop-task.md +2 -2
- package/claude-assets/commands/deprecated/cfn-loop.md +2 -2
- package/claude-assets/hooks/GIT-HOOKS-USAGE-EXAMPLES.md +116 -0
- package/claude-assets/hooks/README-GIT-HOOKS.md +443 -0
- package/claude-assets/hooks/cfn-invoke-post-edit.sh +31 -5
- package/claude-assets/hooks/cfn-post-edit.config.json +9 -2
- package/claude-assets/hooks/install-git-hooks.sh +243 -0
- package/claude-assets/hooks/subagent-start.sh +98 -0
- package/claude-assets/hooks/subagent-stop.sh +93 -0
- package/claude-assets/hooks/validators/credential-scanner.sh +172 -0
- package/claude-assets/root-claude-distribute/CFN-CLAUDE.md +1 -1
- package/claude-assets/skills/cfn-backlog-management/SKILL.md +1 -1
- package/claude-assets/skills/cfn-dependency-ingestion/SKILL.md +41 -13
- package/claude-assets/skills/cfn-dependency-ingestion/ingest.sh +237 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/cli-mode-dependencies.txt +73 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/shared-dependencies.txt +57 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/trigger-dev-dependencies.txt +82 -0
- package/claude-assets/skills/cfn-dependency-ingestion/manifests/trigger-mode-dependencies.txt +80 -0
- package/claude-assets/skills/cfn-environment-sanitization/sanitize-environment.sh +14 -4
- package/claude-assets/skills/cfn-loop-orchestration/NORTH_STAR_INDEX.md +1 -1
- package/claude-assets/skills/cfn-provider-routing/SKILL.md +23 -0
- package/claude-assets/skills/docker-build/build.sh +1 -1
- package/dist/agent/skill-mcp-selector.js +2 -1
- package/dist/agent/skill-mcp-selector.js.map +1 -1
- package/dist/agents/agent-loader.js +165 -146
- package/dist/agents/agent-loader.js.map +1 -1
- package/dist/cli/agent-executor.js +470 -26
- package/dist/cli/agent-executor.js.map +1 -1
- package/dist/cli/agent-prompt-builder.js +2 -2
- package/dist/cli/agent-prompt-builder.js.map +1 -1
- package/dist/cli/agent-spawn.js +7 -4
- package/dist/cli/agent-spawn.js.map +1 -1
- package/dist/cli/agent-spawner.js +51 -4
- package/dist/cli/agent-spawner.js.map +1 -1
- package/dist/cli/agent-token-manager.js +2 -1
- package/dist/cli/agent-token-manager.js.map +1 -1
- package/dist/cli/anthropic-client.js +117 -11
- package/dist/cli/anthropic-client.js.map +1 -1
- package/dist/cli/cfn-context.js +2 -1
- package/dist/cli/cfn-context.js.map +1 -1
- package/dist/cli/cfn-metrics.js +2 -1
- package/dist/cli/cfn-metrics.js.map +1 -1
- package/dist/cli/cfn-redis.js +2 -1
- package/dist/cli/cfn-redis.js.map +1 -1
- package/dist/cli/cli-agent-context.js +2 -0
- package/dist/cli/cli-agent-context.js.map +1 -1
- package/dist/cli/config-manager.js +4 -252
- package/dist/cli/config-manager.js.map +1 -1
- package/dist/cli/conversation-fork-cleanup.js +2 -1
- package/dist/cli/conversation-fork-cleanup.js.map +1 -1
- package/dist/cli/conversation-fork.js +2 -1
- package/dist/cli/conversation-fork.js.map +1 -1
- package/dist/cli/coordination/agent-messaging.js +415 -0
- package/dist/cli/coordination/agent-messaging.js.map +1 -0
- package/dist/cli/coordination/wait-for-threshold.js +232 -0
- package/dist/cli/coordination/wait-for-threshold.js.map +1 -0
- package/dist/cli/iteration-history.js +2 -1
- package/dist/cli/iteration-history.js.map +1 -1
- package/dist/cli/process-lifecycle.js +5 -1
- package/dist/cli/process-lifecycle.js.map +1 -1
- package/dist/cli/spawn-agent-cli.js +41 -6
- package/dist/cli/spawn-agent-cli.js.map +1 -1
- package/dist/coordination/redis-waiting-mode.js +4 -0
- package/dist/coordination/redis-waiting-mode.js.map +1 -1
- package/dist/lib/artifact-registry.js +4 -0
- package/dist/lib/artifact-registry.js.map +1 -1
- package/dist/lib/connection-pool.js +390 -0
- package/dist/lib/connection-pool.js.map +1 -0
- package/dist/lib/environment-contract.js +258 -0
- package/dist/lib/environment-contract.js.map +1 -0
- package/dist/lib/query-optimizer.js +388 -0
- package/dist/lib/query-optimizer.js.map +1 -0
- package/dist/lib/result-cache.js +285 -0
- package/dist/lib/result-cache.js.map +1 -0
- package/dist/mcp/auth-middleware.js +2 -1
- package/dist/mcp/auth-middleware.js.map +1 -1
- package/dist/mcp/playwright-mcp-server-auth.js +2 -1
- package/dist/mcp/playwright-mcp-server-auth.js.map +1 -1
- package/package.json +3 -1
- package/scripts/build-agent-image.sh +1 -1
- package/scripts/cost-allocation-tracker.sh +632 -0
- package/scripts/docker-rebuild-all-agents.sh +2 -2
- package/scripts/reorganize-tests.sh +280 -0
- package/scripts/trigger-dev-setup.sh +12 -0
- package/tests/README.md +45 -0
- package/.claude/commands/cost-savings-status.md +0 -34
- package/.claude/commands/metrics-summary.md +0 -58
- package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +0 -768
- package/claude-assets/agents/custom/test-mcp-access.md +0 -24
- package/claude-assets/commands/cost-savings-status.md +0 -34
- package/claude-assets/commands/metrics-summary.md +0 -58
- package/tests/test-memory-leak-task-mode.sh +0 -435
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/coordination/agent-messaging.ts"],"sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * Agent Messaging - Bidirectional Redis Communication\r\n *\r\n * Enables Main Chat to send commands to running CLI agents, and agents to process them.\r\n *\r\n * Main Chat → Agent: LPUSH cfn:agent:{taskId}:{agentId}:commands\r\n * Agent → Main Chat: SET cfn:agent:{taskId}:{agentId}:status (response)\r\n *\r\n * Supported Commands:\r\n * - status: Request agent status update\r\n * - redirect: Redirect agent to new task/context\r\n * - abort: Request clean agent abort\r\n * - pause: Request agent pause for N seconds\r\n *\r\n * Usage (Main Chat - send command):\r\n * npx tsx src/cli/coordination/agent-messaging.ts send \\\r\n * --task-id <id> --agent-id <aid> --command status\r\n *\r\n * Usage (Agent - process commands):\r\n * npx tsx src/cli/coordination/agent-messaging.ts listen \\\r\n * --task-id <id> --agent-id <aid> --poll-interval 5\r\n */\r\n\r\nimport { createClient, RedisClientType } from 'redis';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport type CommandType = 'status' | 'redirect' | 'abort' | 'pause' | 'custom';\r\n\r\nexport interface AgentCommand {\r\n id: string;\r\n type: CommandType;\r\n timestamp: string;\r\n payload?: Record<string, unknown>;\r\n replyTo?: string; // Optional key for response\r\n}\r\n\r\nexport interface AgentStatus {\r\n agentId: string;\r\n taskId: string;\r\n status: 'running' | 'paused' | 'aborting' | 'completed';\r\n timestamp: string;\r\n progress?: number;\r\n currentStep?: string;\r\n metadata?: Record<string, unknown>;\r\n}\r\n\r\nexport interface MessagingConfig {\r\n taskId: string;\r\n agentId: string;\r\n redisHost?: string;\r\n redisPort?: number;\r\n redisPassword?: string;\r\n}\r\n\r\n// ============================================================================\r\n// Redis Key Helpers\r\n// ============================================================================\r\n\r\nfunction getCommandsKey(taskId: string, agentId: string): string {\r\n return `cfn:agent:${taskId}:${agentId}:commands`;\r\n}\r\n\r\nfunction getStatusKey(taskId: string, agentId: string): string {\r\n return `cfn:agent:${taskId}:${agentId}:status`;\r\n}\r\n\r\nfunction getResponseKey(taskId: string, agentId: string, commandId: string): string {\r\n return `cfn:agent:${taskId}:${agentId}:response:${commandId}`;\r\n}\r\n\r\n// ============================================================================\r\n// Main Chat Side - Send Commands\r\n// ============================================================================\r\n\r\n/**\r\n * Send a command to an agent via Redis\r\n */\r\nexport async function sendCommand(\r\n config: MessagingConfig,\r\n command: Omit<AgentCommand, 'id' | 'timestamp'>\r\n): Promise<{ sent: boolean; commandId: string }> {\r\n const {\r\n taskId,\r\n agentId,\r\n redisHost = process.env.CFN_REDIS_HOST || 'localhost',\r\n redisPort = parseInt(process.env.CFN_REDIS_PORT || '6379', 10),\r\n redisPassword = process.env.CFN_REDIS_PASSWORD || undefined\r\n } = config;\r\n\r\n const client: RedisClientType = createClient({\r\n socket: { host: redisHost, port: redisPort },\r\n password: redisPassword || undefined\r\n });\r\n\r\n const commandId = `cmd-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;\r\n const fullCommand: AgentCommand = {\r\n ...command,\r\n id: commandId,\r\n timestamp: new Date().toISOString(),\r\n replyTo: getResponseKey(taskId, agentId, commandId)\r\n };\r\n\r\n try {\r\n await client.connect();\r\n\r\n const commandsKey = getCommandsKey(taskId, agentId);\r\n await client.lPush(commandsKey, JSON.stringify(fullCommand));\r\n\r\n console.log(`[agent-msg] Sent command ${command.type} to ${agentId}`);\r\n console.log(`[agent-msg] Commands key: ${commandsKey}`);\r\n console.log(`[agent-msg] Command ID: ${commandId}`);\r\n\r\n return { sent: true, commandId };\r\n } finally {\r\n await client.disconnect();\r\n }\r\n}\r\n\r\n/**\r\n * Wait for agent response to a command\r\n */\r\nexport async function waitForResponse(\r\n config: MessagingConfig,\r\n commandId: string,\r\n timeoutSeconds: number = 30\r\n): Promise<AgentStatus | null> {\r\n const {\r\n taskId,\r\n agentId,\r\n redisHost = process.env.CFN_REDIS_HOST || 'localhost',\r\n redisPort = parseInt(process.env.CFN_REDIS_PORT || '6379', 10),\r\n redisPassword = process.env.CFN_REDIS_PASSWORD || undefined\r\n } = config;\r\n\r\n const client: RedisClientType = createClient({\r\n socket: { host: redisHost, port: redisPort },\r\n password: redisPassword || undefined\r\n });\r\n\r\n try {\r\n await client.connect();\r\n\r\n const responseKey = getResponseKey(taskId, agentId, commandId);\r\n const result = await client.blPop(responseKey, timeoutSeconds);\r\n\r\n if (result) {\r\n return JSON.parse(result.element) as AgentStatus;\r\n }\r\n return null;\r\n } finally {\r\n await client.disconnect();\r\n }\r\n}\r\n\r\n/**\r\n * Get current agent status (non-blocking)\r\n */\r\nexport async function getAgentStatus(config: MessagingConfig): Promise<AgentStatus | null> {\r\n const {\r\n taskId,\r\n agentId,\r\n redisHost = process.env.CFN_REDIS_HOST || 'localhost',\r\n redisPort = parseInt(process.env.CFN_REDIS_PORT || '6379', 10),\r\n redisPassword = process.env.CFN_REDIS_PASSWORD || undefined\r\n } = config;\r\n\r\n const client: RedisClientType = createClient({\r\n socket: { host: redisHost, port: redisPort },\r\n password: redisPassword || undefined\r\n });\r\n\r\n try {\r\n await client.connect();\r\n\r\n const statusKey = getStatusKey(taskId, agentId);\r\n const status = await client.get(statusKey);\r\n\r\n if (status) {\r\n return JSON.parse(status) as AgentStatus;\r\n }\r\n return null;\r\n } finally {\r\n await client.disconnect();\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Agent Side - Process Commands\r\n// ============================================================================\r\n\r\nexport type CommandHandler = (command: AgentCommand) => Promise<AgentStatus | void>;\r\n\r\n/**\r\n * Agent command processor - polls for and processes commands\r\n */\r\nexport class AgentCommandProcessor {\r\n private client: RedisClientType | null = null;\r\n private running = false;\r\n private handlers: Map<CommandType, CommandHandler> = new Map();\r\n\r\n constructor(private config: MessagingConfig) {\r\n // Default handlers\r\n this.handlers.set('status', this.handleStatus.bind(this));\r\n this.handlers.set('abort', this.handleAbort.bind(this));\r\n this.handlers.set('pause', this.handlePause.bind(this));\r\n }\r\n\r\n /**\r\n * Register a custom command handler\r\n */\r\n onCommand(type: CommandType, handler: CommandHandler): void {\r\n this.handlers.set(type, handler);\r\n }\r\n\r\n /**\r\n * Start processing commands (non-blocking poll loop)\r\n */\r\n async start(pollIntervalSeconds: number = 5): Promise<void> {\r\n const {\r\n taskId,\r\n agentId,\r\n redisHost = process.env.CFN_REDIS_HOST || 'localhost',\r\n redisPort = parseInt(process.env.CFN_REDIS_PORT || '6379', 10),\r\n redisPassword = process.env.CFN_REDIS_PASSWORD || undefined\r\n } = this.config;\r\n\r\n this.client = createClient({\r\n socket: { host: redisHost, port: redisPort },\r\n password: redisPassword || undefined\r\n });\r\n\r\n await this.client.connect();\r\n this.running = true;\r\n\r\n const commandsKey = getCommandsKey(taskId, agentId);\r\n console.log(`[agent-processor] Started listening on ${commandsKey}`);\r\n\r\n // Non-blocking poll loop\r\n while (this.running) {\r\n try {\r\n // Short BLPOP to check for commands without blocking too long\r\n const result = await this.client.blPop(commandsKey, pollIntervalSeconds);\r\n\r\n if (result) {\r\n const command: AgentCommand = JSON.parse(result.element);\r\n console.log(`[agent-processor] Received command: ${command.type} (${command.id})`);\r\n\r\n // Process command\r\n const handler = this.handlers.get(command.type);\r\n if (handler) {\r\n const response = await handler(command);\r\n\r\n // Send response if handler returned status\r\n if (response && command.replyTo) {\r\n await this.client.lPush(command.replyTo, JSON.stringify(response));\r\n console.log(`[agent-processor] Sent response to ${command.replyTo}`);\r\n }\r\n } else {\r\n console.warn(`[agent-processor] No handler for command type: ${command.type}`);\r\n }\r\n }\r\n } catch (err) {\r\n if (this.running) {\r\n console.error(`[agent-processor] Error processing command:`, err);\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Stop processing commands\r\n */\r\n async stop(): Promise<void> {\r\n this.running = false;\r\n if (this.client) {\r\n await this.client.disconnect();\r\n this.client = null;\r\n }\r\n console.log(`[agent-processor] Stopped`);\r\n }\r\n\r\n /**\r\n * Update agent status in Redis\r\n */\r\n async updateStatus(status: Partial<AgentStatus>): Promise<void> {\r\n if (!this.client) return;\r\n\r\n const { taskId, agentId } = this.config;\r\n const fullStatus: AgentStatus = {\r\n agentId,\r\n taskId,\r\n status: 'running',\r\n timestamp: new Date().toISOString(),\r\n ...status\r\n };\r\n\r\n const statusKey = getStatusKey(taskId, agentId);\r\n await this.client.set(statusKey, JSON.stringify(fullStatus) as string);\r\n // Expire after 1 hour\r\n await this.client.expire(statusKey, 3600);\r\n }\r\n\r\n // Default handlers\r\n\r\n private async handleStatus(command: AgentCommand): Promise<AgentStatus> {\r\n const { taskId, agentId } = this.config;\r\n return {\r\n agentId,\r\n taskId,\r\n status: 'running',\r\n timestamp: new Date().toISOString(),\r\n metadata: { respondingTo: command.id }\r\n };\r\n }\r\n\r\n private async handleAbort(_command: AgentCommand): Promise<AgentStatus> {\r\n const { taskId, agentId } = this.config;\r\n console.log(`[agent-processor] Abort requested, shutting down...`);\r\n\r\n // Schedule stop after response\r\n setTimeout(() => this.stop(), 100);\r\n\r\n return {\r\n agentId,\r\n taskId,\r\n status: 'aborting',\r\n timestamp: new Date().toISOString()\r\n };\r\n }\r\n\r\n private async handlePause(command: AgentCommand): Promise<AgentStatus> {\r\n const { taskId, agentId } = this.config;\r\n const pauseSeconds = (command.payload?.seconds as number) || 10;\r\n\r\n console.log(`[agent-processor] Pausing for ${pauseSeconds}s...`);\r\n await new Promise(resolve => setTimeout(resolve, pauseSeconds * 1000));\r\n console.log(`[agent-processor] Resumed after pause`);\r\n\r\n return {\r\n agentId,\r\n taskId,\r\n status: 'running',\r\n timestamp: new Date().toISOString(),\r\n metadata: { pausedFor: pauseSeconds }\r\n };\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// CLI Entry Point\r\n// ============================================================================\r\n\r\nfunction printHelp(): void {\r\n console.log(`\r\nAgent Messaging - Bidirectional Redis Communication\r\n\r\nUSAGE:\r\n # Send command to agent (Main Chat side)\r\n npx tsx src/cli/coordination/agent-messaging.ts send \\\\\r\n --task-id <id> --agent-id <aid> --command <type> [--payload <json>]\r\n\r\n # Listen for commands (Agent side)\r\n npx tsx src/cli/coordination/agent-messaging.ts listen \\\\\r\n --task-id <id> --agent-id <aid> [--poll-interval <seconds>]\r\n\r\n # Get agent status (Main Chat side)\r\n npx tsx src/cli/coordination/agent-messaging.ts status \\\\\r\n --task-id <id> --agent-id <aid>\r\n\r\nCOMMANDS:\r\n status Request agent status update\r\n redirect Redirect agent to new task (include --payload)\r\n abort Request clean agent abort\r\n pause Pause agent (include --payload '{\"seconds\": 10}')\r\n custom Custom command (include --payload)\r\n\r\nEXAMPLES:\r\n # Request status from agent\r\n npx tsx src/cli/coordination/agent-messaging.ts send \\\\\r\n --task-id cfn-cli-123 --agent-id backend-dev-456 --command status\r\n\r\n # Abort agent\r\n npx tsx src/cli/coordination/agent-messaging.ts send \\\\\r\n --task-id cfn-cli-123 --agent-id backend-dev-456 --command abort\r\n\r\n # Pause agent for 30 seconds\r\n npx tsx src/cli/coordination/agent-messaging.ts send \\\\\r\n --task-id cfn-cli-123 --agent-id backend-dev-456 \\\\\r\n --command pause --payload '{\"seconds\": 30}'\r\n\r\n # Redirect agent to new context\r\n npx tsx src/cli/coordination/agent-messaging.ts send \\\\\r\n --task-id cfn-cli-123 --agent-id backend-dev-456 \\\\\r\n --command redirect --payload '{\"newTask\": \"Focus on security tests\"}'\r\n\r\n # Start listening for commands (agent side)\r\n npx tsx src/cli/coordination/agent-messaging.ts listen \\\\\r\n --task-id cfn-cli-123 --agent-id backend-dev-456 --poll-interval 5\r\n`);\r\n}\r\n\r\nasync function main(): Promise<void> {\r\n const args = process.argv.slice(2);\r\n const action = args[0];\r\n\r\n if (!action || action === '--help' || action === '-h') {\r\n printHelp();\r\n process.exit(0);\r\n }\r\n\r\n // Parse common args\r\n let taskId: string | undefined;\r\n let agentId: string | undefined;\r\n let command: CommandType | undefined;\r\n let payload: Record<string, unknown> | undefined;\r\n let pollInterval = 5;\r\n\r\n for (let i = 1; i < args.length; i++) {\r\n const arg = args[i];\r\n const value = args[i + 1];\r\n\r\n switch (arg) {\r\n case '--task-id':\r\n case '-t':\r\n taskId = value;\r\n i++;\r\n break;\r\n case '--agent-id':\r\n case '-a':\r\n agentId = value;\r\n i++;\r\n break;\r\n case '--command':\r\n case '-c':\r\n command = value as CommandType;\r\n i++;\r\n break;\r\n case '--payload':\r\n case '-p':\r\n try {\r\n payload = JSON.parse(value);\r\n } catch {\r\n console.error('Invalid JSON payload');\r\n process.exit(1);\r\n }\r\n i++;\r\n break;\r\n case '--poll-interval':\r\n pollInterval = parseInt(value, 10);\r\n i++;\r\n break;\r\n }\r\n }\r\n\r\n if (!taskId || !agentId) {\r\n console.error('Error: --task-id and --agent-id are required');\r\n process.exit(1);\r\n }\r\n\r\n const config: MessagingConfig = { taskId, agentId };\r\n\r\n switch (action) {\r\n case 'send': {\r\n if (!command) {\r\n console.error('Error: --command is required for send action');\r\n process.exit(1);\r\n }\r\n const result = await sendCommand(config, { type: command, payload });\r\n console.log(JSON.stringify(result, null, 2));\r\n\r\n // Wait for response\r\n console.log('\\n[agent-msg] Waiting for response (30s timeout)...');\r\n const response = await waitForResponse(config, result.commandId, 30);\r\n if (response) {\r\n console.log('[agent-msg] Response received:');\r\n console.log(JSON.stringify(response, null, 2));\r\n } else {\r\n console.log('[agent-msg] No response received (timeout)');\r\n }\r\n break;\r\n }\r\n\r\n case 'listen': {\r\n const processor = new AgentCommandProcessor(config);\r\n\r\n // Handle shutdown signals\r\n process.on('SIGINT', async () => {\r\n console.log('\\n[agent-msg] Received SIGINT, stopping...');\r\n await processor.stop();\r\n process.exit(0);\r\n });\r\n\r\n await processor.start(pollInterval);\r\n break;\r\n }\r\n\r\n case 'status': {\r\n const status = await getAgentStatus(config);\r\n if (status) {\r\n console.log(JSON.stringify(status, null, 2));\r\n } else {\r\n console.log('No status available for agent');\r\n }\r\n break;\r\n }\r\n\r\n default:\r\n console.error(`Unknown action: ${action}`);\r\n printHelp();\r\n process.exit(1);\r\n }\r\n}\r\n\r\n// Run if called directly\r\nif (import.meta.url.endsWith(process.argv[1]?.replace(/\\\\/g, '/') || '')) {\r\n main().catch(err => {\r\n console.error('[agent-msg] Fatal error:', err);\r\n process.exit(2);\r\n });\r\n}\r\n"],"names":["createClient","getCommandsKey","taskId","agentId","getStatusKey","getResponseKey","commandId","sendCommand","config","command","redisHost","process","env","CFN_REDIS_HOST","redisPort","parseInt","CFN_REDIS_PORT","redisPassword","CFN_REDIS_PASSWORD","undefined","client","socket","host","port","password","Date","now","Math","random","toString","substring","fullCommand","id","timestamp","toISOString","replyTo","connect","commandsKey","lPush","JSON","stringify","console","log","type","sent","disconnect","waitForResponse","timeoutSeconds","responseKey","result","blPop","parse","element","getAgentStatus","statusKey","status","get","AgentCommandProcessor","running","handlers","Map","set","handleStatus","bind","handleAbort","handlePause","onCommand","handler","start","pollIntervalSeconds","response","warn","err","error","stop","updateStatus","fullStatus","expire","metadata","respondingTo","_command","setTimeout","pauseSeconds","payload","seconds","Promise","resolve","pausedFor","printHelp","main","args","argv","slice","action","exit","pollInterval","i","length","arg","value","processor","on","url","endsWith","replace","catch"],"mappings":"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"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Wait for Threshold Completion - Parallel Agent Coordination
|
|
4
|
+
*
|
|
5
|
+
* Waits for N/M agents to complete (e.g., 3/4 = 75% threshold) before continuing.
|
|
6
|
+
* Enables parallel agent spawning with graceful degradation on partial completion.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx tsx src/cli/coordination/wait-for-threshold.ts \
|
|
10
|
+
* --task-id <id> \
|
|
11
|
+
* --total-agents <n> \
|
|
12
|
+
* --threshold <0.0-1.0> \
|
|
13
|
+
* --timeout <seconds>
|
|
14
|
+
*
|
|
15
|
+
* Example:
|
|
16
|
+
* # Wait for 3/4 agents (75%) with 120s timeout
|
|
17
|
+
* npx tsx src/cli/coordination/wait-for-threshold.ts \
|
|
18
|
+
* --task-id cfn-cli-12345 \
|
|
19
|
+
* --total-agents 4 \
|
|
20
|
+
* --threshold 0.75 \
|
|
21
|
+
* --timeout 120
|
|
22
|
+
*/ import { createClient } from 'redis';
|
|
23
|
+
/**
|
|
24
|
+
* Wait for threshold completion of agents
|
|
25
|
+
*
|
|
26
|
+
* Uses Redis BLPOP with short timeouts to poll for completion signals
|
|
27
|
+
* while tracking progress toward the threshold.
|
|
28
|
+
*/ export async function waitForThreshold(config) {
|
|
29
|
+
const { taskId, totalAgents, threshold, timeoutSeconds, redisHost = process.env.CFN_REDIS_HOST || 'localhost', redisPort = parseInt(process.env.CFN_REDIS_PORT || '6379', 10), redisPassword = process.env.CFN_REDIS_PASSWORD || undefined } = config;
|
|
30
|
+
const requiredCount = Math.ceil(totalAgents * threshold);
|
|
31
|
+
const signalKey = `cfn-completion:${taskId}`;
|
|
32
|
+
const completed = [];
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
35
|
+
// Connect to Redis
|
|
36
|
+
const client = createClient({
|
|
37
|
+
socket: {
|
|
38
|
+
host: redisHost,
|
|
39
|
+
port: redisPort
|
|
40
|
+
},
|
|
41
|
+
password: redisPassword || undefined
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
await client.connect();
|
|
45
|
+
console.log(`[wait-threshold] Connected to Redis at ${redisHost}:${redisPort}`);
|
|
46
|
+
console.log(`[wait-threshold] Waiting for ${requiredCount}/${totalAgents} agents (${(threshold * 100).toFixed(0)}% threshold)`);
|
|
47
|
+
console.log(`[wait-threshold] Signal key: ${signalKey}`);
|
|
48
|
+
console.log(`[wait-threshold] Timeout: ${timeoutSeconds}s`);
|
|
49
|
+
// Poll loop with short BLPOP timeouts
|
|
50
|
+
const pollIntervalSeconds = 5; // Check every 5 seconds
|
|
51
|
+
while(completed.length < requiredCount){
|
|
52
|
+
const elapsed = Date.now() - startTime;
|
|
53
|
+
// Check overall timeout
|
|
54
|
+
if (elapsed >= timeoutMs) {
|
|
55
|
+
console.log(`[wait-threshold] Timeout reached after ${(elapsed / 1000).toFixed(1)}s`);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
// Calculate remaining time for this poll
|
|
59
|
+
const remainingMs = timeoutMs - elapsed;
|
|
60
|
+
const pollTimeout = Math.min(pollIntervalSeconds, Math.ceil(remainingMs / 1000));
|
|
61
|
+
try {
|
|
62
|
+
// BLPOP with short timeout - returns null on timeout
|
|
63
|
+
const result = await client.blPop(signalKey, pollTimeout);
|
|
64
|
+
if (result) {
|
|
65
|
+
try {
|
|
66
|
+
const signal = JSON.parse(result.element);
|
|
67
|
+
completed.push(signal);
|
|
68
|
+
console.log(`[wait-threshold] Received signal ${completed.length}/${requiredCount}: ${signal.agentId} (${signal.status})`);
|
|
69
|
+
// Check if threshold met
|
|
70
|
+
if (completed.length >= requiredCount) {
|
|
71
|
+
console.log(`[wait-threshold] Threshold met! ${completed.length}/${totalAgents} agents completed`);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
} catch (parseErr) {
|
|
75
|
+
console.warn(`[wait-threshold] Failed to parse signal: ${result.element}`);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
// Timeout on BLPOP - no signal received, continue polling
|
|
79
|
+
const elapsedSec = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
80
|
+
console.log(`[wait-threshold] Polling... ${completed.length}/${requiredCount} completed (${elapsedSec}s elapsed)`);
|
|
81
|
+
}
|
|
82
|
+
} catch (blpopErr) {
|
|
83
|
+
// Redis error during BLPOP
|
|
84
|
+
console.error(`[wait-threshold] BLPOP error:`, blpopErr);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const elapsedMs = Date.now() - startTime;
|
|
89
|
+
const thresholdMet = completed.length >= requiredCount;
|
|
90
|
+
return {
|
|
91
|
+
success: thresholdMet,
|
|
92
|
+
completed,
|
|
93
|
+
timedOut: !thresholdMet && Date.now() - startTime >= timeoutMs,
|
|
94
|
+
thresholdMet,
|
|
95
|
+
completedCount: completed.length,
|
|
96
|
+
requiredCount,
|
|
97
|
+
totalAgents,
|
|
98
|
+
elapsedMs
|
|
99
|
+
};
|
|
100
|
+
} finally{
|
|
101
|
+
await client.disconnect();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Parse CLI arguments
|
|
106
|
+
*/ function parseArgs(args) {
|
|
107
|
+
const config = {
|
|
108
|
+
threshold: 0.75,
|
|
109
|
+
timeoutSeconds: 120
|
|
110
|
+
};
|
|
111
|
+
for(let i = 0; i < args.length; i++){
|
|
112
|
+
const arg = args[i];
|
|
113
|
+
const value = args[i + 1];
|
|
114
|
+
switch(arg){
|
|
115
|
+
case '--task-id':
|
|
116
|
+
case '-t':
|
|
117
|
+
config.taskId = value;
|
|
118
|
+
i++;
|
|
119
|
+
break;
|
|
120
|
+
case '--total-agents':
|
|
121
|
+
case '-n':
|
|
122
|
+
config.totalAgents = parseInt(value, 10);
|
|
123
|
+
i++;
|
|
124
|
+
break;
|
|
125
|
+
case '--threshold':
|
|
126
|
+
config.threshold = parseFloat(value);
|
|
127
|
+
i++;
|
|
128
|
+
break;
|
|
129
|
+
case '--timeout':
|
|
130
|
+
config.timeoutSeconds = parseInt(value, 10);
|
|
131
|
+
i++;
|
|
132
|
+
break;
|
|
133
|
+
case '--redis-host':
|
|
134
|
+
config.redisHost = value;
|
|
135
|
+
i++;
|
|
136
|
+
break;
|
|
137
|
+
case '--redis-port':
|
|
138
|
+
config.redisPort = parseInt(value, 10);
|
|
139
|
+
i++;
|
|
140
|
+
break;
|
|
141
|
+
case '--help':
|
|
142
|
+
case '-h':
|
|
143
|
+
printHelp();
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Validate required fields
|
|
148
|
+
if (!config.taskId) {
|
|
149
|
+
console.error('Error: --task-id is required');
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
if (!config.totalAgents || config.totalAgents < 1) {
|
|
153
|
+
console.error('Error: --total-agents must be a positive integer');
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
if (config.threshold < 0 || config.threshold > 1) {
|
|
157
|
+
console.error('Error: --threshold must be between 0.0 and 1.0');
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return config;
|
|
161
|
+
}
|
|
162
|
+
function printHelp() {
|
|
163
|
+
console.log(`
|
|
164
|
+
Wait for Threshold Completion - Parallel Agent Coordination
|
|
165
|
+
|
|
166
|
+
USAGE:
|
|
167
|
+
npx tsx src/cli/coordination/wait-for-threshold.ts [OPTIONS]
|
|
168
|
+
|
|
169
|
+
OPTIONS:
|
|
170
|
+
-t, --task-id <id> Task ID for coordination (required)
|
|
171
|
+
-n, --total-agents <n> Total number of agents spawned (required)
|
|
172
|
+
--threshold <0.0-1.0> Completion threshold (default: 0.75 = 75%)
|
|
173
|
+
--timeout <seconds> Overall timeout (default: 120)
|
|
174
|
+
--redis-host <host> Redis host (default: localhost)
|
|
175
|
+
--redis-port <port> Redis port (default: 6379)
|
|
176
|
+
-h, --help Show this help message
|
|
177
|
+
|
|
178
|
+
EXAMPLES:
|
|
179
|
+
# Wait for 3/4 agents (75%) with 120s timeout
|
|
180
|
+
npx tsx src/cli/coordination/wait-for-threshold.ts \\
|
|
181
|
+
--task-id cfn-cli-12345 \\
|
|
182
|
+
--total-agents 4 \\
|
|
183
|
+
--threshold 0.75 \\
|
|
184
|
+
--timeout 120
|
|
185
|
+
|
|
186
|
+
# Wait for all agents (100%) with 300s timeout
|
|
187
|
+
npx tsx src/cli/coordination/wait-for-threshold.ts \\
|
|
188
|
+
--task-id cfn-cli-12345 \\
|
|
189
|
+
--total-agents 4 \\
|
|
190
|
+
--threshold 1.0 \\
|
|
191
|
+
--timeout 300
|
|
192
|
+
|
|
193
|
+
OUTPUT:
|
|
194
|
+
JSON result with completion status:
|
|
195
|
+
{
|
|
196
|
+
"success": true,
|
|
197
|
+
"thresholdMet": true,
|
|
198
|
+
"completedCount": 3,
|
|
199
|
+
"requiredCount": 3,
|
|
200
|
+
"totalAgents": 4,
|
|
201
|
+
"elapsedMs": 45000,
|
|
202
|
+
"completed": [...]
|
|
203
|
+
}
|
|
204
|
+
`);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* CLI entry point
|
|
208
|
+
*/ async function main() {
|
|
209
|
+
const config = parseArgs(process.argv.slice(2));
|
|
210
|
+
if (!config) {
|
|
211
|
+
console.error('Use --help for usage information');
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const result = await waitForThreshold(config);
|
|
216
|
+
// Output result as JSON for scripting
|
|
217
|
+
console.log('\n[wait-threshold] Result:');
|
|
218
|
+
console.log(JSON.stringify(result, null, 2));
|
|
219
|
+
// Exit with appropriate code
|
|
220
|
+
process.exit(result.success ? 0 : 1);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error('[wait-threshold] Fatal error:', error);
|
|
223
|
+
process.exit(2);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Run if called directly
|
|
227
|
+
if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, '/') || '')) {
|
|
228
|
+
main();
|
|
229
|
+
}
|
|
230
|
+
export { parseArgs };
|
|
231
|
+
|
|
232
|
+
//# sourceMappingURL=wait-for-threshold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/coordination/wait-for-threshold.ts"],"sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * Wait for Threshold Completion - Parallel Agent Coordination\r\n *\r\n * Waits for N/M agents to complete (e.g., 3/4 = 75% threshold) before continuing.\r\n * Enables parallel agent spawning with graceful degradation on partial completion.\r\n *\r\n * Usage:\r\n * npx tsx src/cli/coordination/wait-for-threshold.ts \\\r\n * --task-id <id> \\\r\n * --total-agents <n> \\\r\n * --threshold <0.0-1.0> \\\r\n * --timeout <seconds>\r\n *\r\n * Example:\r\n * # Wait for 3/4 agents (75%) with 120s timeout\r\n * npx tsx src/cli/coordination/wait-for-threshold.ts \\\r\n * --task-id cfn-cli-12345 \\\r\n * --total-agents 4 \\\r\n * --threshold 0.75 \\\r\n * --timeout 120\r\n */\r\n\r\nimport { createClient, RedisClientType } from 'redis';\r\n\r\nexport interface ThresholdConfig {\r\n taskId: string;\r\n totalAgents: number;\r\n threshold: number; // 0.75 for 3/4\r\n timeoutSeconds: number;\r\n redisHost?: string;\r\n redisPort?: number;\r\n redisPassword?: string;\r\n}\r\n\r\nexport interface CompletionSignal {\r\n agentId: string;\r\n taskId: string;\r\n status: 'completed' | 'failed' | 'timeout';\r\n timestamp: string;\r\n confidence?: number;\r\n metadata?: Record<string, unknown>;\r\n}\r\n\r\nexport interface ThresholdResult {\r\n success: boolean;\r\n completed: CompletionSignal[];\r\n timedOut: boolean;\r\n thresholdMet: boolean;\r\n completedCount: number;\r\n requiredCount: number;\r\n totalAgents: number;\r\n elapsedMs: number;\r\n}\r\n\r\n/**\r\n * Wait for threshold completion of agents\r\n *\r\n * Uses Redis BLPOP with short timeouts to poll for completion signals\r\n * while tracking progress toward the threshold.\r\n */\r\nexport async function waitForThreshold(config: ThresholdConfig): Promise<ThresholdResult> {\r\n const {\r\n taskId,\r\n totalAgents,\r\n threshold,\r\n timeoutSeconds,\r\n redisHost = process.env.CFN_REDIS_HOST || 'localhost',\r\n redisPort = parseInt(process.env.CFN_REDIS_PORT || '6379', 10),\r\n redisPassword = process.env.CFN_REDIS_PASSWORD || undefined\r\n } = config;\r\n\r\n const requiredCount = Math.ceil(totalAgents * threshold);\r\n const signalKey = `cfn-completion:${taskId}`;\r\n const completed: CompletionSignal[] = [];\r\n const startTime = Date.now();\r\n const timeoutMs = timeoutSeconds * 1000;\r\n\r\n // Connect to Redis\r\n const client: RedisClientType = createClient({\r\n socket: { host: redisHost, port: redisPort },\r\n password: redisPassword || undefined\r\n });\r\n\r\n try {\r\n await client.connect();\r\n console.log(`[wait-threshold] Connected to Redis at ${redisHost}:${redisPort}`);\r\n console.log(`[wait-threshold] Waiting for ${requiredCount}/${totalAgents} agents (${(threshold * 100).toFixed(0)}% threshold)`);\r\n console.log(`[wait-threshold] Signal key: ${signalKey}`);\r\n console.log(`[wait-threshold] Timeout: ${timeoutSeconds}s`);\r\n\r\n // Poll loop with short BLPOP timeouts\r\n const pollIntervalSeconds = 5; // Check every 5 seconds\r\n\r\n while (completed.length < requiredCount) {\r\n const elapsed = Date.now() - startTime;\r\n\r\n // Check overall timeout\r\n if (elapsed >= timeoutMs) {\r\n console.log(`[wait-threshold] Timeout reached after ${(elapsed / 1000).toFixed(1)}s`);\r\n break;\r\n }\r\n\r\n // Calculate remaining time for this poll\r\n const remainingMs = timeoutMs - elapsed;\r\n const pollTimeout = Math.min(pollIntervalSeconds, Math.ceil(remainingMs / 1000));\r\n\r\n try {\r\n // BLPOP with short timeout - returns null on timeout\r\n const result = await client.blPop(signalKey, pollTimeout);\r\n\r\n if (result) {\r\n try {\r\n const signal: CompletionSignal = JSON.parse(result.element);\r\n completed.push(signal);\r\n\r\n console.log(`[wait-threshold] Received signal ${completed.length}/${requiredCount}: ${signal.agentId} (${signal.status})`);\r\n\r\n // Check if threshold met\r\n if (completed.length >= requiredCount) {\r\n console.log(`[wait-threshold] Threshold met! ${completed.length}/${totalAgents} agents completed`);\r\n break;\r\n }\r\n } catch (parseErr) {\r\n console.warn(`[wait-threshold] Failed to parse signal: ${result.element}`);\r\n }\r\n } else {\r\n // Timeout on BLPOP - no signal received, continue polling\r\n const elapsedSec = ((Date.now() - startTime) / 1000).toFixed(1);\r\n console.log(`[wait-threshold] Polling... ${completed.length}/${requiredCount} completed (${elapsedSec}s elapsed)`);\r\n }\r\n } catch (blpopErr) {\r\n // Redis error during BLPOP\r\n console.error(`[wait-threshold] BLPOP error:`, blpopErr);\r\n break;\r\n }\r\n }\r\n\r\n const elapsedMs = Date.now() - startTime;\r\n const thresholdMet = completed.length >= requiredCount;\r\n\r\n return {\r\n success: thresholdMet,\r\n completed,\r\n timedOut: !thresholdMet && (Date.now() - startTime) >= timeoutMs,\r\n thresholdMet,\r\n completedCount: completed.length,\r\n requiredCount,\r\n totalAgents,\r\n elapsedMs\r\n };\r\n\r\n } finally {\r\n await client.disconnect();\r\n }\r\n}\r\n\r\n/**\r\n * Parse CLI arguments\r\n */\r\nfunction parseArgs(args: string[]): ThresholdConfig | null {\r\n const config: Partial<ThresholdConfig> = {\r\n threshold: 0.75,\r\n timeoutSeconds: 120\r\n };\r\n\r\n for (let i = 0; i < args.length; i++) {\r\n const arg = args[i];\r\n const value = args[i + 1];\r\n\r\n switch (arg) {\r\n case '--task-id':\r\n case '-t':\r\n config.taskId = value;\r\n i++;\r\n break;\r\n case '--total-agents':\r\n case '-n':\r\n config.totalAgents = parseInt(value, 10);\r\n i++;\r\n break;\r\n case '--threshold':\r\n config.threshold = parseFloat(value);\r\n i++;\r\n break;\r\n case '--timeout':\r\n config.timeoutSeconds = parseInt(value, 10);\r\n i++;\r\n break;\r\n case '--redis-host':\r\n config.redisHost = value;\r\n i++;\r\n break;\r\n case '--redis-port':\r\n config.redisPort = parseInt(value, 10);\r\n i++;\r\n break;\r\n case '--help':\r\n case '-h':\r\n printHelp();\r\n process.exit(0);\r\n }\r\n }\r\n\r\n // Validate required fields\r\n if (!config.taskId) {\r\n console.error('Error: --task-id is required');\r\n return null;\r\n }\r\n if (!config.totalAgents || config.totalAgents < 1) {\r\n console.error('Error: --total-agents must be a positive integer');\r\n return null;\r\n }\r\n if (config.threshold! < 0 || config.threshold! > 1) {\r\n console.error('Error: --threshold must be between 0.0 and 1.0');\r\n return null;\r\n }\r\n\r\n return config as ThresholdConfig;\r\n}\r\n\r\nfunction printHelp(): void {\r\n console.log(`\r\nWait for Threshold Completion - Parallel Agent Coordination\r\n\r\nUSAGE:\r\n npx tsx src/cli/coordination/wait-for-threshold.ts [OPTIONS]\r\n\r\nOPTIONS:\r\n -t, --task-id <id> Task ID for coordination (required)\r\n -n, --total-agents <n> Total number of agents spawned (required)\r\n --threshold <0.0-1.0> Completion threshold (default: 0.75 = 75%)\r\n --timeout <seconds> Overall timeout (default: 120)\r\n --redis-host <host> Redis host (default: localhost)\r\n --redis-port <port> Redis port (default: 6379)\r\n -h, --help Show this help message\r\n\r\nEXAMPLES:\r\n # Wait for 3/4 agents (75%) with 120s timeout\r\n npx tsx src/cli/coordination/wait-for-threshold.ts \\\\\r\n --task-id cfn-cli-12345 \\\\\r\n --total-agents 4 \\\\\r\n --threshold 0.75 \\\\\r\n --timeout 120\r\n\r\n # Wait for all agents (100%) with 300s timeout\r\n npx tsx src/cli/coordination/wait-for-threshold.ts \\\\\r\n --task-id cfn-cli-12345 \\\\\r\n --total-agents 4 \\\\\r\n --threshold 1.0 \\\\\r\n --timeout 300\r\n\r\nOUTPUT:\r\n JSON result with completion status:\r\n {\r\n \"success\": true,\r\n \"thresholdMet\": true,\r\n \"completedCount\": 3,\r\n \"requiredCount\": 3,\r\n \"totalAgents\": 4,\r\n \"elapsedMs\": 45000,\r\n \"completed\": [...]\r\n }\r\n`);\r\n}\r\n\r\n/**\r\n * CLI entry point\r\n */\r\nasync function main(): Promise<void> {\r\n const config = parseArgs(process.argv.slice(2));\r\n\r\n if (!config) {\r\n console.error('Use --help for usage information');\r\n process.exit(1);\r\n }\r\n\r\n try {\r\n const result = await waitForThreshold(config);\r\n\r\n // Output result as JSON for scripting\r\n console.log('\\n[wait-threshold] Result:');\r\n console.log(JSON.stringify(result, null, 2));\r\n\r\n // Exit with appropriate code\r\n process.exit(result.success ? 0 : 1);\r\n } catch (error) {\r\n console.error('[wait-threshold] Fatal error:', error);\r\n process.exit(2);\r\n }\r\n}\r\n\r\n// Run if called directly\r\nif (import.meta.url.endsWith(process.argv[1]?.replace(/\\\\/g, '/') || '')) {\r\n main();\r\n}\r\n\r\nexport { parseArgs };\r\n"],"names":["createClient","waitForThreshold","config","taskId","totalAgents","threshold","timeoutSeconds","redisHost","process","env","CFN_REDIS_HOST","redisPort","parseInt","CFN_REDIS_PORT","redisPassword","CFN_REDIS_PASSWORD","undefined","requiredCount","Math","ceil","signalKey","completed","startTime","Date","now","timeoutMs","client","socket","host","port","password","connect","console","log","toFixed","pollIntervalSeconds","length","elapsed","remainingMs","pollTimeout","min","result","blPop","signal","JSON","parse","element","push","agentId","status","parseErr","warn","elapsedSec","blpopErr","error","elapsedMs","thresholdMet","success","timedOut","completedCount","disconnect","parseArgs","args","i","arg","value","parseFloat","printHelp","exit","main","argv","slice","stringify","url","endsWith","replace"],"mappings":"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"}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* Sprint 3 - Phase 2 Implementation
|
|
12
12
|
*/ import { execSync } from 'child_process';
|
|
13
13
|
// Bug #6 Fix: Read Redis connection parameters from process.env
|
|
14
|
-
|
|
14
|
+
// FIX: Default to 'localhost' for CLI mode (host execution), not 'cfn-redis' (Docker)
|
|
15
|
+
const redisHost = process.env.CFN_REDIS_HOST || 'localhost';
|
|
15
16
|
const redisPort = process.env.CFN_REDIS_PORT || '6379';
|
|
16
17
|
/**
|
|
17
18
|
* Load iteration history for an agent from Redis
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/iteration-history.ts"],"sourcesContent":["/**\r\n * Iteration History Management\r\n *\r\n * Loads and formats iteration history from Redis for CLI-spawned agents.\r\n * Enables agents to learn from previous attempts and feedback.\r\n *\r\n * Storage Pattern:\r\n * swarm:${TASK_ID}:${AGENT_ID}:result:iteration-${N} → Result text + confidence\r\n * swarm:${TASK_ID}:${AGENT_ID}:feedback:iteration-${N} → Validator feedback\r\n *\r\n * Sprint 3 - Phase 2 Implementation\r\n */\r\n\r\nimport { execSync } from 'child_process';\r\n\r\n// Bug #6 Fix: Read Redis connection parameters from process.env\r\nconst redisHost = process.env.CFN_REDIS_HOST || 'cfn-redis';\r\nconst redisPort = process.env.CFN_REDIS_PORT || '6379';\r\n\r\nexport interface IterationResult {\r\n iteration: number;\r\n result: string;\r\n confidence: number;\r\n timestamp: string;\r\n feedback?: string;\r\n}\r\n\r\n/**\r\n * Load iteration history for an agent from Redis\r\n *\r\n * @param taskId - Task identifier\r\n * @param agentId - Agent identifier\r\n * @param currentIteration - Current iteration number (loads 1 to N-1)\r\n * @returns Array of iteration results\r\n */\r\nexport async function loadIterationHistory(\r\n taskId: string,\r\n agentId: string,\r\n currentIteration: number\r\n): Promise<IterationResult[]> {\r\n const history: IterationResult[] = [];\r\n\r\n // Load previous iterations (1 to currentIteration - 1)\r\n for (let i = 1; i < currentIteration; i++) {\r\n try {\r\n // Load result data\r\n const resultKey = `swarm:${taskId}:${agentId}:result:iteration-${i}`;\r\n const resultJson = execSync(`redis-cli -h ${redisHost} -p ${redisPort} get \"${resultKey}\"`, { encoding: 'utf8' }).trim();\r\n\r\n if (resultJson === '(nil)' || !resultJson) {\r\n // No result for this iteration (shouldn't happen in normal flow)\r\n continue;\r\n }\r\n\r\n const resultData = JSON.parse(resultJson);\r\n\r\n // Load feedback data (may not exist for all iterations)\r\n const feedbackKey = `swarm:${taskId}:${agentId}:feedback:iteration-${i}`;\r\n let feedback: string | undefined;\r\n\r\n try {\r\n const feedbackJson = execSync(`redis-cli -h ${redisHost} -p ${redisPort} get \"${feedbackKey}\"`, { encoding: 'utf8' }).trim();\r\n if (feedbackJson !== '(nil)' && feedbackJson) {\r\n const feedbackData = JSON.parse(feedbackJson);\r\n feedback = feedbackData.feedback || feedbackData.comments;\r\n }\r\n } catch (err) {\r\n // Feedback may not exist for this iteration\r\n feedback = undefined;\r\n }\r\n\r\n history.push({\r\n iteration: i,\r\n result: resultData.result || resultData.output || '',\r\n confidence: resultData.confidence || 0,\r\n timestamp: resultData.timestamp || new Date().toISOString(),\r\n feedback\r\n });\r\n } catch (err) {\r\n console.error(`[iteration-history] Failed to load iteration ${i}:`, err);\r\n // Continue loading other iterations\r\n }\r\n }\r\n\r\n return history;\r\n}\r\n\r\n/**\r\n * Store iteration result in Redis\r\n *\r\n * @param taskId - Task identifier\r\n * @param agentId - Agent identifier\r\n * @param iteration - Iteration number\r\n * @param result - Result text/output\r\n * @param confidence - Confidence score (0.0-1.0)\r\n */\r\nexport async function storeIterationResult(\r\n taskId: string,\r\n agentId: string,\r\n iteration: number,\r\n result: string,\r\n confidence: number\r\n): Promise<void> {\r\n const resultKey = `swarm:${taskId}:${agentId}:result:iteration-${iteration}`;\r\n const resultData = {\r\n result,\r\n confidence,\r\n timestamp: new Date().toISOString(),\r\n iteration\r\n };\r\n\r\n const resultJson = JSON.stringify(resultData);\r\n\r\n try {\r\n // Store with 24 hour TTL\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} setex \"${resultKey}\" 86400 '${resultJson.replace(/'/g, \"'\\\\''\")}'`, {\r\n encoding: 'utf8'\r\n });\r\n console.log(`[iteration-history] Stored result for iteration ${iteration}`);\r\n } catch (err) {\r\n console.error(`[iteration-history] Failed to store result:`, err);\r\n throw err;\r\n }\r\n}\r\n\r\n/**\r\n * Format iteration history as markdown for system prompt\r\n *\r\n * @param history - Array of iteration results\r\n * @param currentIteration - Current iteration number\r\n * @returns Formatted markdown string\r\n */\r\nexport function formatIterationHistory(\r\n history: IterationResult[],\r\n currentIteration: number\r\n): string {\r\n if (history.length === 0) {\r\n return `## Current Iteration: ${currentIteration}\r\n\r\nThis is your first attempt at this task. No previous iteration history available.\r\n`;\r\n }\r\n\r\n const sections: string[] = [];\r\n\r\n sections.push('## Iteration History');\r\n sections.push('');\r\n sections.push('Learn from your previous attempts and feedback:');\r\n sections.push('');\r\n\r\n // Format each iteration\r\n for (const iter of history) {\r\n sections.push(`### Iteration ${iter.iteration}`);\r\n sections.push('');\r\n\r\n sections.push('**Result:**');\r\n sections.push(iter.result.substring(0, 500)); // Truncate to 500 chars\r\n if (iter.result.length > 500) {\r\n sections.push('... (truncated)');\r\n }\r\n sections.push('');\r\n\r\n if (iter.feedback) {\r\n sections.push('**Feedback from Validators:**');\r\n sections.push(iter.feedback);\r\n sections.push('');\r\n }\r\n\r\n sections.push(`**Confidence:** ${iter.confidence.toFixed(2)}`);\r\n sections.push(`**Timestamp:** ${iter.timestamp}`);\r\n sections.push('');\r\n sections.push('---');\r\n sections.push('');\r\n }\r\n\r\n // Add current iteration context\r\n sections.push(`## Current Iteration: ${currentIteration}`);\r\n sections.push('');\r\n\r\n if (history.length > 0) {\r\n const lastIteration = history[history.length - 1];\r\n if (lastIteration.feedback) {\r\n sections.push('**Your Task:** Address the feedback from the previous iteration:');\r\n sections.push('');\r\n sections.push(lastIteration.feedback);\r\n sections.push('');\r\n } else {\r\n sections.push(`**Your Task:** Improve upon iteration ${lastIteration.iteration} (confidence: ${lastIteration.confidence.toFixed(2)})`);\r\n sections.push('');\r\n }\r\n }\r\n\r\n return sections.join('\\n');\r\n}\r\n\r\n/**\r\n * Check if iteration history exists for an agent\r\n *\r\n * @param taskId - Task identifier\r\n * @param agentId - Agent identifier\r\n * @returns True if any iteration history exists\r\n */\r\nexport async function hasIterationHistory(\r\n taskId: string,\r\n agentId: string\r\n): Promise<boolean> {\r\n try {\r\n const pattern = `swarm:${taskId}:${agentId}:result:iteration-*`;\r\n const keys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} --scan --pattern \"${pattern}\"`, { encoding: 'utf8' }).trim();\r\n return keys.length > 0;\r\n } catch (err) {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Get the latest iteration number for an agent\r\n *\r\n * @param taskId - Task identifier\r\n * @param agentId - Agent identifier\r\n * @returns Latest iteration number (0 if no history)\r\n */\r\nexport async function getLatestIteration(\r\n taskId: string,\r\n agentId: string\r\n): Promise<number> {\r\n try {\r\n const pattern = `swarm:${taskId}:${agentId}:result:iteration-*`;\r\n const keys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} --scan --pattern \"${pattern}\"`, { encoding: 'utf8' })\r\n .trim()\r\n .split('\\n')\r\n .filter((k) => k.length > 0);\r\n\r\n if (keys.length === 0) return 0;\r\n\r\n // Extract iteration numbers and find max\r\n const iterations = keys.map((key) => {\r\n const match = key.match(/iteration-(\\d+)$/);\r\n return match ? parseInt(match[1], 10) : 0;\r\n });\r\n\r\n return Math.max(...iterations);\r\n } catch (err) {\r\n return 0;\r\n }\r\n}\r\n"],"names":["execSync","redisHost","process","env","CFN_REDIS_HOST","redisPort","CFN_REDIS_PORT","loadIterationHistory","taskId","agentId","currentIteration","history","i","resultKey","resultJson","encoding","trim","resultData","JSON","parse","feedbackKey","feedback","feedbackJson","feedbackData","comments","err","undefined","push","iteration","result","output","confidence","timestamp","Date","toISOString","console","error","storeIterationResult","stringify","replace","log","formatIterationHistory","length","sections","iter","substring","toFixed","lastIteration","join","hasIterationHistory","pattern","keys","getLatestIteration","split","filter","k","iterations","map","key","match","parseInt","Math","max"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/iteration-history.ts"],"sourcesContent":["/**\r\n * Iteration History Management\r\n *\r\n * Loads and formats iteration history from Redis for CLI-spawned agents.\r\n * Enables agents to learn from previous attempts and feedback.\r\n *\r\n * Storage Pattern:\r\n * swarm:${TASK_ID}:${AGENT_ID}:result:iteration-${N} → Result text + confidence\r\n * swarm:${TASK_ID}:${AGENT_ID}:feedback:iteration-${N} → Validator feedback\r\n *\r\n * Sprint 3 - Phase 2 Implementation\r\n */\r\n\r\nimport { execSync } from 'child_process';\r\n\r\n// Bug #6 Fix: Read Redis connection parameters from process.env\r\n// FIX: Default to 'localhost' for CLI mode (host execution), not 'cfn-redis' (Docker)\r\nconst redisHost = process.env.CFN_REDIS_HOST || 'localhost';\r\nconst redisPort = process.env.CFN_REDIS_PORT || '6379';\r\n\r\nexport interface IterationResult {\r\n iteration: number;\r\n result: string;\r\n confidence: number;\r\n timestamp: string;\r\n feedback?: string;\r\n}\r\n\r\n/**\r\n * Load iteration history for an agent from Redis\r\n *\r\n * @param taskId - Task identifier\r\n * @param agentId - Agent identifier\r\n * @param currentIteration - Current iteration number (loads 1 to N-1)\r\n * @returns Array of iteration results\r\n */\r\nexport async function loadIterationHistory(\r\n taskId: string,\r\n agentId: string,\r\n currentIteration: number\r\n): Promise<IterationResult[]> {\r\n const history: IterationResult[] = [];\r\n\r\n // Load previous iterations (1 to currentIteration - 1)\r\n for (let i = 1; i < currentIteration; i++) {\r\n try {\r\n // Load result data\r\n const resultKey = `swarm:${taskId}:${agentId}:result:iteration-${i}`;\r\n const resultJson = execSync(`redis-cli -h ${redisHost} -p ${redisPort} get \"${resultKey}\"`, { encoding: 'utf8' }).trim();\r\n\r\n if (resultJson === '(nil)' || !resultJson) {\r\n // No result for this iteration (shouldn't happen in normal flow)\r\n continue;\r\n }\r\n\r\n const resultData = JSON.parse(resultJson);\r\n\r\n // Load feedback data (may not exist for all iterations)\r\n const feedbackKey = `swarm:${taskId}:${agentId}:feedback:iteration-${i}`;\r\n let feedback: string | undefined;\r\n\r\n try {\r\n const feedbackJson = execSync(`redis-cli -h ${redisHost} -p ${redisPort} get \"${feedbackKey}\"`, { encoding: 'utf8' }).trim();\r\n if (feedbackJson !== '(nil)' && feedbackJson) {\r\n const feedbackData = JSON.parse(feedbackJson);\r\n feedback = feedbackData.feedback || feedbackData.comments;\r\n }\r\n } catch (err) {\r\n // Feedback may not exist for this iteration\r\n feedback = undefined;\r\n }\r\n\r\n history.push({\r\n iteration: i,\r\n result: resultData.result || resultData.output || '',\r\n confidence: resultData.confidence || 0,\r\n timestamp: resultData.timestamp || new Date().toISOString(),\r\n feedback\r\n });\r\n } catch (err) {\r\n console.error(`[iteration-history] Failed to load iteration ${i}:`, err);\r\n // Continue loading other iterations\r\n }\r\n }\r\n\r\n return history;\r\n}\r\n\r\n/**\r\n * Store iteration result in Redis\r\n *\r\n * @param taskId - Task identifier\r\n * @param agentId - Agent identifier\r\n * @param iteration - Iteration number\r\n * @param result - Result text/output\r\n * @param confidence - Confidence score (0.0-1.0)\r\n */\r\nexport async function storeIterationResult(\r\n taskId: string,\r\n agentId: string,\r\n iteration: number,\r\n result: string,\r\n confidence: number\r\n): Promise<void> {\r\n const resultKey = `swarm:${taskId}:${agentId}:result:iteration-${iteration}`;\r\n const resultData = {\r\n result,\r\n confidence,\r\n timestamp: new Date().toISOString(),\r\n iteration\r\n };\r\n\r\n const resultJson = JSON.stringify(resultData);\r\n\r\n try {\r\n // Store with 24 hour TTL\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} setex \"${resultKey}\" 86400 '${resultJson.replace(/'/g, \"'\\\\''\")}'`, {\r\n encoding: 'utf8'\r\n });\r\n console.log(`[iteration-history] Stored result for iteration ${iteration}`);\r\n } catch (err) {\r\n console.error(`[iteration-history] Failed to store result:`, err);\r\n throw err;\r\n }\r\n}\r\n\r\n/**\r\n * Format iteration history as markdown for system prompt\r\n *\r\n * @param history - Array of iteration results\r\n * @param currentIteration - Current iteration number\r\n * @returns Formatted markdown string\r\n */\r\nexport function formatIterationHistory(\r\n history: IterationResult[],\r\n currentIteration: number\r\n): string {\r\n if (history.length === 0) {\r\n return `## Current Iteration: ${currentIteration}\r\n\r\nThis is your first attempt at this task. No previous iteration history available.\r\n`;\r\n }\r\n\r\n const sections: string[] = [];\r\n\r\n sections.push('## Iteration History');\r\n sections.push('');\r\n sections.push('Learn from your previous attempts and feedback:');\r\n sections.push('');\r\n\r\n // Format each iteration\r\n for (const iter of history) {\r\n sections.push(`### Iteration ${iter.iteration}`);\r\n sections.push('');\r\n\r\n sections.push('**Result:**');\r\n sections.push(iter.result.substring(0, 500)); // Truncate to 500 chars\r\n if (iter.result.length > 500) {\r\n sections.push('... (truncated)');\r\n }\r\n sections.push('');\r\n\r\n if (iter.feedback) {\r\n sections.push('**Feedback from Validators:**');\r\n sections.push(iter.feedback);\r\n sections.push('');\r\n }\r\n\r\n sections.push(`**Confidence:** ${iter.confidence.toFixed(2)}`);\r\n sections.push(`**Timestamp:** ${iter.timestamp}`);\r\n sections.push('');\r\n sections.push('---');\r\n sections.push('');\r\n }\r\n\r\n // Add current iteration context\r\n sections.push(`## Current Iteration: ${currentIteration}`);\r\n sections.push('');\r\n\r\n if (history.length > 0) {\r\n const lastIteration = history[history.length - 1];\r\n if (lastIteration.feedback) {\r\n sections.push('**Your Task:** Address the feedback from the previous iteration:');\r\n sections.push('');\r\n sections.push(lastIteration.feedback);\r\n sections.push('');\r\n } else {\r\n sections.push(`**Your Task:** Improve upon iteration ${lastIteration.iteration} (confidence: ${lastIteration.confidence.toFixed(2)})`);\r\n sections.push('');\r\n }\r\n }\r\n\r\n return sections.join('\\n');\r\n}\r\n\r\n/**\r\n * Check if iteration history exists for an agent\r\n *\r\n * @param taskId - Task identifier\r\n * @param agentId - Agent identifier\r\n * @returns True if any iteration history exists\r\n */\r\nexport async function hasIterationHistory(\r\n taskId: string,\r\n agentId: string\r\n): Promise<boolean> {\r\n try {\r\n const pattern = `swarm:${taskId}:${agentId}:result:iteration-*`;\r\n const keys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} --scan --pattern \"${pattern}\"`, { encoding: 'utf8' }).trim();\r\n return keys.length > 0;\r\n } catch (err) {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Get the latest iteration number for an agent\r\n *\r\n * @param taskId - Task identifier\r\n * @param agentId - Agent identifier\r\n * @returns Latest iteration number (0 if no history)\r\n */\r\nexport async function getLatestIteration(\r\n taskId: string,\r\n agentId: string\r\n): Promise<number> {\r\n try {\r\n const pattern = `swarm:${taskId}:${agentId}:result:iteration-*`;\r\n const keys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} --scan --pattern \"${pattern}\"`, { encoding: 'utf8' })\r\n .trim()\r\n .split('\\n')\r\n .filter((k) => k.length > 0);\r\n\r\n if (keys.length === 0) return 0;\r\n\r\n // Extract iteration numbers and find max\r\n const iterations = keys.map((key) => {\r\n const match = key.match(/iteration-(\\d+)$/);\r\n return match ? parseInt(match[1], 10) : 0;\r\n });\r\n\r\n return Math.max(...iterations);\r\n } catch (err) {\r\n return 0;\r\n }\r\n}\r\n"],"names":["execSync","redisHost","process","env","CFN_REDIS_HOST","redisPort","CFN_REDIS_PORT","loadIterationHistory","taskId","agentId","currentIteration","history","i","resultKey","resultJson","encoding","trim","resultData","JSON","parse","feedbackKey","feedback","feedbackJson","feedbackData","comments","err","undefined","push","iteration","result","output","confidence","timestamp","Date","toISOString","console","error","storeIterationResult","stringify","replace","log","formatIterationHistory","length","sections","iter","substring","toFixed","lastIteration","join","hasIterationHistory","pattern","keys","getLatestIteration","split","filter","k","iterations","map","key","match","parseInt","Math","max"],"mappings":"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"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { EventEmitter } from "events";
|
|
2
2
|
import { readFile } from "fs/promises";
|
|
3
|
-
import { join } from "path";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
// ESM-compatible __dirname
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
4
8
|
export var ProcessType = /*#__PURE__*/ function(ProcessType) {
|
|
5
9
|
ProcessType["EVENT_BUS"] = "EVENT_BUS";
|
|
6
10
|
ProcessType["ORCHESTRATOR"] = "ORCHESTRATOR";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/process-lifecycle.ts"],"sourcesContent":["import { EventEmitter } from \"events\";\nimport { readFile } from \"fs/promises\";\nimport { join } from \"path\";\n\nexport enum ProcessType {\n EVENT_BUS = \"EVENT_BUS\",\n ORCHESTRATOR = \"ORCHESTRATOR\",\n MEMORY_MANAGER = \"MEMORY_MANAGER\",\n TERMINAL_POOL = \"TERMINAL_POOL\",\n COORDINATOR = \"COORDINATOR\",\n MCP_SERVER = \"MCP_SERVER\",\n}\n\nexport enum ProcessStatus {\n STOPPED = \"STOPPED\",\n STARTING = \"STARTING\",\n RUNNING = \"RUNNING\",\n STOPPING = \"STOPPING\",\n ERROR = \"ERROR\",\n}\n\nexport interface ProcessInfo {\n id: string;\n name: string;\n type: ProcessType;\n status: ProcessStatus;\n startTime?: number;\n pid?: number;\n metrics?: ProcessMetrics;\n}\n\nexport interface ProcessMetrics {\n lastError?: string;\n uptime?: number;\n memoryUsage?: number;\n cpuUsage?: number;\n}\n\nexport interface SystemStats {\n totalProcesses: number;\n runningProcesses: number;\n stoppedProcesses: number;\n errorProcesses: number;\n systemUptime: number;\n totalMemory: number;\n totalCpu: number;\n}\n\nexport interface ProcessLifecycleConfig {\n processTypes: {\n [key in ProcessType]: {\n dependencies: ProcessType[];\n restartPolicy: \"always\" | \"on-failure\" | \"never\";\n };\n };\n monitoringConfig: {\n pollingInterval: number;\n healthCheckTimeout: number;\n metricCollectionEnabled: boolean;\n };\n}\n\nexport class ProcessLifecycleManager extends EventEmitter {\n private processes: Map<string, ProcessInfo> = new Map();\n private config: ProcessLifecycleConfig;\n private configPath: string;\n\n constructor(\n configPath: string = join(\n __dirname,\n \"..\",\n \"..\",\n \".claude\",\n \"skills\",\n \"process-lifecycle\",\n \"config.json\",\n ),\n ) {\n super();\n this.configPath = configPath;\n }\n\n async initialize(): Promise<void> {\n try {\n const configData = await readFile(this.configPath, \"utf-8\");\n this.config = JSON.parse(configData);\n this.initializeProcesses();\n this.emit(\"initialized\", { config: this.config });\n } catch (error) {\n this.emit(\"error\", { component: \"ProcessLifecycleManager\", error });\n throw error;\n }\n }\n\n private initializeProcesses(): void {\n const processDefinitions: ProcessInfo[] = Object.entries(\n this.config.processTypes,\n ).map(([type, details]) => ({\n id: type,\n name: this.formatProcessName(type),\n type: type as ProcessType,\n status: ProcessStatus.STOPPED,\n }));\n\n for (const process of processDefinitions) {\n this.processes.set(process.id, process);\n }\n }\n\n private formatProcessName(type: string): string {\n return type\n .split(\"_\")\n .map((word) => word[0] + word.slice(1).toLowerCase())\n .join(\" \");\n }\n\n async startProcess(processId: string): Promise<void> {\n const process = this.processes.get(processId);\n if (!process) {\n throw new Error(`Unknown process: ${processId}`);\n }\n\n if (process.status === ProcessStatus.RUNNING) {\n throw new Error(`Process ${processId} is already running`);\n }\n\n // Validate dependencies\n const processConfig = this.config.processTypes[process.type as ProcessType];\n const missingDependencies = processConfig.dependencies.filter(\n (dep) => this.processes.get(dep)?.status !== ProcessStatus.RUNNING,\n );\n\n if (missingDependencies.length > 0) {\n throw new Error(\n `Missing dependencies for ${processId}: ${missingDependencies.join(\", \")}`,\n );\n }\n\n this.updateProcessStatus(processId, ProcessStatus.STARTING);\n\n try {\n // Simulated process start - in real implementation, this would start actual processes\n process.startTime = Date.now();\n process.pid = process.id.charCodeAt(0); // Simulated PID\n\n this.updateProcessStatus(processId, ProcessStatus.RUNNING);\n this.emit(\"processStarted\", { processId, process });\n } catch (error) {\n this.updateProcessStatus(processId, ProcessStatus.ERROR);\n process.metrics = {\n ...process.metrics,\n lastError: (error as Error).message,\n };\n this.emit(\"processError\", { processId, error });\n throw error;\n }\n }\n\n async stopProcess(processId: string): Promise<void> {\n const process = this.processes.get(processId);\n if (!process || process.status !== ProcessStatus.RUNNING) {\n throw new Error(`Process ${processId} is not running`);\n }\n\n // Check if any running processes depend on this one\n const dependentProcesses = Array.from(this.processes.values()).filter(\n (p) =>\n this.config.processTypes[p.type as ProcessType].dependencies.includes(\n process.type as ProcessType,\n ) && p.status === ProcessStatus.RUNNING,\n );\n\n if (dependentProcesses.length > 0) {\n throw new Error(\n `Cannot stop ${processId}, dependent processes are running: ${dependentProcesses.map((p) => p.id).join(\", \")}`,\n );\n }\n\n this.updateProcessStatus(processId, ProcessStatus.STOPPING);\n\n try {\n // Simulated process stop\n this.updateProcessStatus(processId, ProcessStatus.STOPPED);\n this.emit(\"processStopped\", { processId });\n } catch (error) {\n this.updateProcessStatus(processId, ProcessStatus.ERROR);\n this.emit(\"processError\", { processId, error });\n throw error;\n }\n }\n\n async restartProcess(processId: string): Promise<void> {\n await this.stopProcess(processId);\n await new Promise((resolve) => setTimeout(resolve, 1000)); // Brief delay\n await this.startProcess(processId);\n }\n\n async startAll(): Promise<void> {\n // Start in dependency order\n const startOrder = Object.values(ProcessType);\n\n for (const processId of startOrder) {\n try {\n await this.startProcess(processId);\n } catch (error) {\n console.error(\n `Failed to start ${processId}:`,\n (error as Error).message,\n );\n // Continue with other processes\n }\n }\n }\n\n async stopAll(): Promise<void> {\n // Stop in reverse dependency order\n const stopOrder = Object.values(ProcessType).reverse();\n\n for (const processId of stopOrder) {\n const process = this.processes.get(processId);\n if (process && process.status === ProcessStatus.RUNNING) {\n try {\n await this.stopProcess(processId);\n } catch (error) {\n console.error(\n `Failed to stop ${processId}:`,\n (error as Error).message,\n );\n }\n }\n }\n }\n\n getProcess(processId: string): ProcessInfo | undefined {\n return this.processes.get(processId);\n }\n\n getAllProcesses(): ProcessInfo[] {\n return Array.from(this.processes.values());\n }\n\n getSystemStats(): SystemStats {\n const processes = this.getAllProcesses();\n const runningProcesses = processes.filter(\n (p) => p.status === ProcessStatus.RUNNING,\n );\n const stoppedProcesses = processes.filter(\n (p) => p.status === ProcessStatus.STOPPED,\n );\n const errorProcesses = processes.filter(\n (p) => p.status === ProcessStatus.ERROR,\n );\n\n return {\n totalProcesses: processes.length,\n runningProcesses: runningProcesses.length,\n stoppedProcesses: stoppedProcesses.length,\n errorProcesses: errorProcesses.length,\n systemUptime: this.getSystemUptime(),\n totalMemory: this.getTotalMemoryUsage(),\n totalCpu: this.getTotalCpuUsage(),\n };\n }\n\n private updateProcessStatus(processId: string, status: ProcessStatus): void {\n const process = this.processes.get(processId);\n if (process) {\n process.status = status;\n this.emit(\"statusChanged\", { processId, status });\n }\n }\n\n private getSystemUptime(): number {\n const firstRunningProcess = Array.from(this.processes.values()).find(\n (p) => p.status === ProcessStatus.RUNNING && p.startTime,\n );\n\n return firstRunningProcess && firstRunningProcess.startTime\n ? Date.now() - firstRunningProcess.startTime\n : 0;\n }\n\n private getTotalMemoryUsage(): number {\n // Placeholder for actual memory monitoring\n return 0;\n }\n\n private getTotalCpuUsage(): number {\n // Placeholder for actual CPU monitoring\n return 0;\n }\n\n async getProcessLogs(\n processId: string,\n lines: number = 50,\n ): Promise<string[]> {\n // Placeholder for actual logging system\n return [\n `[${new Date().toISOString()}] Process ${processId} started`,\n `[${new Date().toISOString()}] Process ${processId} is running normally`,\n ];\n }\n}\n"],"names":["EventEmitter","readFile","join","ProcessType","ProcessStatus","ProcessLifecycleManager","processes","Map","config","configPath","__dirname","initialize","configData","JSON","parse","initializeProcesses","emit","error","component","processDefinitions","Object","entries","processTypes","map","type","details","id","name","formatProcessName","status","process","set","split","word","slice","toLowerCase","startProcess","processId","get","Error","processConfig","missingDependencies","dependencies","filter","dep","length","updateProcessStatus","startTime","Date","now","pid","charCodeAt","metrics","lastError","message","stopProcess","dependentProcesses","Array","from","values","p","includes","restartProcess","Promise","resolve","setTimeout","startAll","startOrder","console","stopAll","stopOrder","reverse","getProcess","getAllProcesses","getSystemStats","runningProcesses","stoppedProcesses","errorProcesses","totalProcesses","systemUptime","getSystemUptime","totalMemory","getTotalMemoryUsage","totalCpu","getTotalCpuUsage","firstRunningProcess","find","getProcessLogs","lines","toISOString"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/process-lifecycle.ts"],"sourcesContent":["import { EventEmitter } from \"events\";\nimport { readFile } from \"fs/promises\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\n\n// ESM-compatible __dirname\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nexport enum ProcessType {\n EVENT_BUS = \"EVENT_BUS\",\n ORCHESTRATOR = \"ORCHESTRATOR\",\n MEMORY_MANAGER = \"MEMORY_MANAGER\",\n TERMINAL_POOL = \"TERMINAL_POOL\",\n COORDINATOR = \"COORDINATOR\",\n MCP_SERVER = \"MCP_SERVER\",\n}\n\nexport enum ProcessStatus {\n STOPPED = \"STOPPED\",\n STARTING = \"STARTING\",\n RUNNING = \"RUNNING\",\n STOPPING = \"STOPPING\",\n ERROR = \"ERROR\",\n}\n\nexport interface ProcessInfo {\n id: string;\n name: string;\n type: ProcessType;\n status: ProcessStatus;\n startTime?: number;\n pid?: number;\n metrics?: ProcessMetrics;\n}\n\nexport interface ProcessMetrics {\n lastError?: string;\n uptime?: number;\n memoryUsage?: number;\n cpuUsage?: number;\n}\n\nexport interface SystemStats {\n totalProcesses: number;\n runningProcesses: number;\n stoppedProcesses: number;\n errorProcesses: number;\n systemUptime: number;\n totalMemory: number;\n totalCpu: number;\n}\n\nexport interface ProcessLifecycleConfig {\n processTypes: {\n [key in ProcessType]: {\n dependencies: ProcessType[];\n restartPolicy: \"always\" | \"on-failure\" | \"never\";\n };\n };\n monitoringConfig: {\n pollingInterval: number;\n healthCheckTimeout: number;\n metricCollectionEnabled: boolean;\n };\n}\n\nexport class ProcessLifecycleManager extends EventEmitter {\n private processes: Map<string, ProcessInfo> = new Map();\n private config: ProcessLifecycleConfig;\n private configPath: string;\n\n constructor(\n configPath: string = join(\n __dirname,\n \"..\",\n \"..\",\n \".claude\",\n \"skills\",\n \"process-lifecycle\",\n \"config.json\",\n ),\n ) {\n super();\n this.configPath = configPath;\n }\n\n async initialize(): Promise<void> {\n try {\n const configData = await readFile(this.configPath, \"utf-8\");\n this.config = JSON.parse(configData);\n this.initializeProcesses();\n this.emit(\"initialized\", { config: this.config });\n } catch (error) {\n this.emit(\"error\", { component: \"ProcessLifecycleManager\", error });\n throw error;\n }\n }\n\n private initializeProcesses(): void {\n const processDefinitions: ProcessInfo[] = Object.entries(\n this.config.processTypes,\n ).map(([type, details]) => ({\n id: type,\n name: this.formatProcessName(type),\n type: type as ProcessType,\n status: ProcessStatus.STOPPED,\n }));\n\n for (const process of processDefinitions) {\n this.processes.set(process.id, process);\n }\n }\n\n private formatProcessName(type: string): string {\n return type\n .split(\"_\")\n .map((word) => word[0] + word.slice(1).toLowerCase())\n .join(\" \");\n }\n\n async startProcess(processId: string): Promise<void> {\n const process = this.processes.get(processId);\n if (!process) {\n throw new Error(`Unknown process: ${processId}`);\n }\n\n if (process.status === ProcessStatus.RUNNING) {\n throw new Error(`Process ${processId} is already running`);\n }\n\n // Validate dependencies\n const processConfig = this.config.processTypes[process.type as ProcessType];\n const missingDependencies = processConfig.dependencies.filter(\n (dep) => this.processes.get(dep)?.status !== ProcessStatus.RUNNING,\n );\n\n if (missingDependencies.length > 0) {\n throw new Error(\n `Missing dependencies for ${processId}: ${missingDependencies.join(\", \")}`,\n );\n }\n\n this.updateProcessStatus(processId, ProcessStatus.STARTING);\n\n try {\n // Simulated process start - in real implementation, this would start actual processes\n process.startTime = Date.now();\n process.pid = process.id.charCodeAt(0); // Simulated PID\n\n this.updateProcessStatus(processId, ProcessStatus.RUNNING);\n this.emit(\"processStarted\", { processId, process });\n } catch (error) {\n this.updateProcessStatus(processId, ProcessStatus.ERROR);\n process.metrics = {\n ...process.metrics,\n lastError: (error as Error).message,\n };\n this.emit(\"processError\", { processId, error });\n throw error;\n }\n }\n\n async stopProcess(processId: string): Promise<void> {\n const process = this.processes.get(processId);\n if (!process || process.status !== ProcessStatus.RUNNING) {\n throw new Error(`Process ${processId} is not running`);\n }\n\n // Check if any running processes depend on this one\n const dependentProcesses = Array.from(this.processes.values()).filter(\n (p) =>\n this.config.processTypes[p.type as ProcessType].dependencies.includes(\n process.type as ProcessType,\n ) && p.status === ProcessStatus.RUNNING,\n );\n\n if (dependentProcesses.length > 0) {\n throw new Error(\n `Cannot stop ${processId}, dependent processes are running: ${dependentProcesses.map((p) => p.id).join(\", \")}`,\n );\n }\n\n this.updateProcessStatus(processId, ProcessStatus.STOPPING);\n\n try {\n // Simulated process stop\n this.updateProcessStatus(processId, ProcessStatus.STOPPED);\n this.emit(\"processStopped\", { processId });\n } catch (error) {\n this.updateProcessStatus(processId, ProcessStatus.ERROR);\n this.emit(\"processError\", { processId, error });\n throw error;\n }\n }\n\n async restartProcess(processId: string): Promise<void> {\n await this.stopProcess(processId);\n await new Promise((resolve) => setTimeout(resolve, 1000)); // Brief delay\n await this.startProcess(processId);\n }\n\n async startAll(): Promise<void> {\n // Start in dependency order\n const startOrder = Object.values(ProcessType);\n\n for (const processId of startOrder) {\n try {\n await this.startProcess(processId);\n } catch (error) {\n console.error(\n `Failed to start ${processId}:`,\n (error as Error).message,\n );\n // Continue with other processes\n }\n }\n }\n\n async stopAll(): Promise<void> {\n // Stop in reverse dependency order\n const stopOrder = Object.values(ProcessType).reverse();\n\n for (const processId of stopOrder) {\n const process = this.processes.get(processId);\n if (process && process.status === ProcessStatus.RUNNING) {\n try {\n await this.stopProcess(processId);\n } catch (error) {\n console.error(\n `Failed to stop ${processId}:`,\n (error as Error).message,\n );\n }\n }\n }\n }\n\n getProcess(processId: string): ProcessInfo | undefined {\n return this.processes.get(processId);\n }\n\n getAllProcesses(): ProcessInfo[] {\n return Array.from(this.processes.values());\n }\n\n getSystemStats(): SystemStats {\n const processes = this.getAllProcesses();\n const runningProcesses = processes.filter(\n (p) => p.status === ProcessStatus.RUNNING,\n );\n const stoppedProcesses = processes.filter(\n (p) => p.status === ProcessStatus.STOPPED,\n );\n const errorProcesses = processes.filter(\n (p) => p.status === ProcessStatus.ERROR,\n );\n\n return {\n totalProcesses: processes.length,\n runningProcesses: runningProcesses.length,\n stoppedProcesses: stoppedProcesses.length,\n errorProcesses: errorProcesses.length,\n systemUptime: this.getSystemUptime(),\n totalMemory: this.getTotalMemoryUsage(),\n totalCpu: this.getTotalCpuUsage(),\n };\n }\n\n private updateProcessStatus(processId: string, status: ProcessStatus): void {\n const process = this.processes.get(processId);\n if (process) {\n process.status = status;\n this.emit(\"statusChanged\", { processId, status });\n }\n }\n\n private getSystemUptime(): number {\n const firstRunningProcess = Array.from(this.processes.values()).find(\n (p) => p.status === ProcessStatus.RUNNING && p.startTime,\n );\n\n return firstRunningProcess && firstRunningProcess.startTime\n ? Date.now() - firstRunningProcess.startTime\n : 0;\n }\n\n private getTotalMemoryUsage(): number {\n // Placeholder for actual memory monitoring\n return 0;\n }\n\n private getTotalCpuUsage(): number {\n // Placeholder for actual CPU monitoring\n return 0;\n }\n\n async getProcessLogs(\n processId: string,\n lines: number = 50,\n ): Promise<string[]> {\n // Placeholder for actual logging system\n return [\n `[${new Date().toISOString()}] Process ${processId} started`,\n `[${new Date().toISOString()}] Process ${processId} is running normally`,\n ];\n }\n}\n"],"names":["EventEmitter","readFile","join","dirname","fileURLToPath","__filename","url","__dirname","ProcessType","ProcessStatus","ProcessLifecycleManager","processes","Map","config","configPath","initialize","configData","JSON","parse","initializeProcesses","emit","error","component","processDefinitions","Object","entries","processTypes","map","type","details","id","name","formatProcessName","status","process","set","split","word","slice","toLowerCase","startProcess","processId","get","Error","processConfig","missingDependencies","dependencies","filter","dep","length","updateProcessStatus","startTime","Date","now","pid","charCodeAt","metrics","lastError","message","stopProcess","dependentProcesses","Array","from","values","p","includes","restartProcess","Promise","resolve","setTimeout","startAll","startOrder","console","stopAll","stopOrder","reverse","getProcess","getAllProcesses","getSystemStats","runningProcesses","stoppedProcesses","errorProcesses","totalProcesses","systemUptime","getSystemUptime","totalMemory","getTotalMemoryUsage","totalCpu","getTotalCpuUsage","firstRunningProcess","find","getProcessLogs","lines","toISOString"],"mappings":"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"}
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
* spawn-agent-cli --version
|
|
12
12
|
*/ import { AgentSpawner } from './agent-spawner';
|
|
13
13
|
import { readFileSync } from 'fs';
|
|
14
|
-
import { resolve } from 'path';
|
|
14
|
+
import { resolve, dirname } from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
// ESM-compatible __dirname
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
15
19
|
/**
|
|
16
20
|
* Parse command-line arguments
|
|
17
21
|
*/ function parseArgs(args) {
|
|
@@ -40,6 +44,8 @@ import { resolve } from 'path';
|
|
|
40
44
|
parsed.provider = args[++i];
|
|
41
45
|
} else if (arg === '--model') {
|
|
42
46
|
parsed.model = args[++i];
|
|
47
|
+
} else if (arg === '--prompt') {
|
|
48
|
+
parsed.prompt = args[++i];
|
|
43
49
|
} else if (!arg.startsWith('-')) {
|
|
44
50
|
if (!parsed.agentType) {
|
|
45
51
|
parsed.agentType = arg;
|
|
@@ -66,6 +72,7 @@ OPTIONS:
|
|
|
66
72
|
-m, --mode <mode> Mode: mvp, standard, enterprise (default: standard)
|
|
67
73
|
-p, --provider <p> Provider: zai, anthropic, etc. (default: auto-detect)
|
|
68
74
|
--model <model> Explicit model name (default: auto-detect)
|
|
75
|
+
--prompt <text> Task prompt/description to pass to agent
|
|
69
76
|
--foreground Run in foreground (default: background)
|
|
70
77
|
--background Run in background (default)
|
|
71
78
|
--json Output result as JSON
|
|
@@ -101,6 +108,31 @@ ENVIRONMENT VARIABLES:
|
|
|
101
108
|
console.log('spawn-agent-cli v1.0.0');
|
|
102
109
|
}
|
|
103
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Phase 1: Mode Prefix Function for CLI/Trigger.dev Collision Mitigation
|
|
113
|
+
*
|
|
114
|
+
* Generates task ID with mode prefix to prevent Redis key collisions between
|
|
115
|
+
* CLI mode and Trigger.dev Docker mode. Both modes share identical Redis coordination
|
|
116
|
+
* patterns and must use isolated namespaces.
|
|
117
|
+
*
|
|
118
|
+
* @param rawTaskId - Original task ID without prefix
|
|
119
|
+
* @param mode - Execution mode: 'cli' for CLI mode, 'trigger' for Trigger.dev
|
|
120
|
+
* @returns Prefixed task ID in format "MODE:rawTaskId"
|
|
121
|
+
*
|
|
122
|
+
* Example:
|
|
123
|
+
* generateTaskId('task-123', 'cli') => 'cli:task-123'
|
|
124
|
+
* generateTaskId('task-123', 'trigger') => 'trigger:task-123'
|
|
125
|
+
*
|
|
126
|
+
* Redis Key Isolation (After):
|
|
127
|
+
* CLI: cfn:task:cli:task-123:status
|
|
128
|
+
* Trigger: cfn:task:trigger:task-123:status
|
|
129
|
+
*/ function generateTaskId(rawTaskId, mode) {
|
|
130
|
+
// Don't add prefix if task ID already has a namespace prefix
|
|
131
|
+
if (/^[a-z]+:/.test(rawTaskId)) {
|
|
132
|
+
return rawTaskId;
|
|
133
|
+
}
|
|
134
|
+
return `${mode}:${rawTaskId}`;
|
|
135
|
+
}
|
|
104
136
|
/**
|
|
105
137
|
* Validate CLI arguments
|
|
106
138
|
*/ function validateArgs(args) {
|
|
@@ -112,7 +144,8 @@ ENVIRONMENT VARIABLES:
|
|
|
112
144
|
if (!taskId) {
|
|
113
145
|
errors.push('Task ID is required (--task-id or TASK_ID env var)');
|
|
114
146
|
}
|
|
115
|
-
|
|
147
|
+
// Allow optional namespace prefix (e.g., "cli:", "task:") for coordination routing
|
|
148
|
+
if (taskId && !/^([a-z]+:)?[a-zA-Z0-9_.-]{1,64}$/.test(taskId)) {
|
|
116
149
|
errors.push('Invalid task ID format');
|
|
117
150
|
}
|
|
118
151
|
if (args.iteration && (args.iteration < 1 || !Number.isInteger(args.iteration))) {
|
|
@@ -173,17 +206,19 @@ ENVIRONMENT VARIABLES:
|
|
|
173
206
|
// Create spawner
|
|
174
207
|
const projectRoot = process.env.PROJECT_ROOT || process.cwd();
|
|
175
208
|
const spawner = new AgentSpawner(projectRoot);
|
|
176
|
-
// Build config
|
|
209
|
+
// Build config with CLI mode prefix for Redis key isolation (Phase 1)
|
|
210
|
+
const prefixedTaskId = generateTaskId(taskId, 'cli');
|
|
177
211
|
const config = {
|
|
178
212
|
agentType: args.agentType,
|
|
179
|
-
taskId:
|
|
213
|
+
taskId: prefixedTaskId,
|
|
180
214
|
iteration: args.iteration || 1,
|
|
181
215
|
mode: args.mode || 'standard',
|
|
182
216
|
provider: args.provider,
|
|
183
217
|
model: args.model,
|
|
218
|
+
prompt: args.prompt,
|
|
184
219
|
background: args.background !== false,
|
|
185
220
|
env: {
|
|
186
|
-
TASK_ID:
|
|
221
|
+
TASK_ID: prefixedTaskId
|
|
187
222
|
}
|
|
188
223
|
};
|
|
189
224
|
try {
|
|
@@ -204,6 +239,6 @@ main().catch((error)=>{
|
|
|
204
239
|
console.error('Unexpected error:', error);
|
|
205
240
|
process.exit(2);
|
|
206
241
|
});
|
|
207
|
-
export { parseArgs, validateArgs, formatOutput };
|
|
242
|
+
export { parseArgs, validateArgs, formatOutput, generateTaskId };
|
|
208
243
|
|
|
209
244
|
//# sourceMappingURL=spawn-agent-cli.js.map
|