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.
Files changed (154) hide show
  1. package/.claude/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +1 -1
  2. package/.claude/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +1 -1
  3. package/.claude/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +1 -1
  4. package/.claude/cfn-extras/skills/google-sheets-progress/SKILL.md +1 -1
  5. package/.claude/commands/CFN_LOOP_FRONTEND.md +1 -1
  6. package/.claude/commands/cfn-loop-cli.md +124 -46
  7. package/.claude/commands/cfn-loop-frontend.md +1 -1
  8. package/.claude/commands/cfn-loop-task.md +2 -2
  9. package/.claude/commands/deprecated/cfn-loop.md +2 -2
  10. package/.claude/hooks/cfn-invoke-post-edit.sh +31 -5
  11. package/.claude/hooks/cfn-post-edit.config.json +9 -2
  12. package/.claude/root-claude-distribute/CFN-CLAUDE.md +1 -1
  13. package/.claude/skills/cfn-backlog-management/SKILL.md +1 -1
  14. package/.claude/skills/cfn-loop-orchestration/NORTH_STAR_INDEX.md +1 -1
  15. package/claude-assets/agents/cfn-dev-team/analysts/root-cause-analyst.md +2 -2
  16. package/claude-assets/agents/cfn-dev-team/architecture/base-template-generator.md +1 -1
  17. package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +2 -2
  18. package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +1 -1
  19. package/claude-assets/agents/cfn-dev-team/dev-ops/devops-engineer.md +1 -1
  20. package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +2 -2
  21. package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +2 -2
  22. package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +1 -1
  23. package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +1 -1
  24. package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +1 -1
  25. package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +1 -1
  26. package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +1 -1
  27. package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +1 -1
  28. package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +1 -1
  29. package/claude-assets/agents/cfn-dev-team/documentation/pseudocode.md +1 -1
  30. package/claude-assets/agents/cfn-dev-team/product-owners/accessibility-advocate-persona.md +1 -1
  31. package/claude-assets/agents/cfn-dev-team/product-owners/cto-agent.md +1 -1
  32. package/claude-assets/agents/cfn-dev-team/product-owners/power-user-persona.md +1 -1
  33. package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +1 -1
  34. package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +1 -1
  35. package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +1 -1
  36. package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +1 -1
  37. package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -1
  38. package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +1 -1
  39. package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +1 -1
  40. package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +1 -1
  41. package/claude-assets/agents/cfn-dev-team/testers/unit/tdd-london-unit-swarm.md +1 -1
  42. package/claude-assets/agents/cfn-dev-team/utility/agent-builder.md +11 -0
  43. package/claude-assets/agents/cfn-dev-team/utility/analyst.md +1 -1
  44. package/claude-assets/agents/cfn-dev-team/utility/claude-code-expert.md +1 -1
  45. package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +1 -1
  46. package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +1 -1
  47. package/claude-assets/agents/cfn-dev-team/utility/researcher.md +1 -1
  48. package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +1 -1
  49. package/claude-assets/agents/custom/cfn-docker-expert.md +1 -0
  50. package/claude-assets/agents/custom/cfn-loops-cli-expert.md +326 -17
  51. package/claude-assets/agents/custom/cfn-redis-operations.md +529 -529
  52. package/claude-assets/agents/custom/cfn-system-expert.md +1 -1
  53. package/claude-assets/agents/custom/trigger-dev-expert.md +369 -0
  54. package/claude-assets/agents/docker-team/micro-sprint-planner.md +747 -747
  55. package/claude-assets/agents/project-only-agents/npm-package-specialist.md +1 -1
  56. package/claude-assets/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +1 -1
  57. package/claude-assets/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +1 -1
  58. package/claude-assets/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +1 -1
  59. package/claude-assets/cfn-extras/skills/google-sheets-progress/SKILL.md +1 -1
  60. package/claude-assets/commands/CFN_LOOP_FRONTEND.md +1 -1
  61. package/claude-assets/commands/cfn-loop-cli.md +124 -46
  62. package/claude-assets/commands/cfn-loop-frontend.md +1 -1
  63. package/claude-assets/commands/cfn-loop-task.md +2 -2
  64. package/claude-assets/commands/deprecated/cfn-loop.md +2 -2
  65. package/claude-assets/hooks/GIT-HOOKS-USAGE-EXAMPLES.md +116 -0
  66. package/claude-assets/hooks/README-GIT-HOOKS.md +443 -0
  67. package/claude-assets/hooks/cfn-invoke-post-edit.sh +31 -5
  68. package/claude-assets/hooks/cfn-post-edit.config.json +9 -2
  69. package/claude-assets/hooks/install-git-hooks.sh +243 -0
  70. package/claude-assets/hooks/subagent-start.sh +98 -0
  71. package/claude-assets/hooks/subagent-stop.sh +93 -0
  72. package/claude-assets/hooks/validators/credential-scanner.sh +172 -0
  73. package/claude-assets/root-claude-distribute/CFN-CLAUDE.md +1 -1
  74. package/claude-assets/skills/cfn-backlog-management/SKILL.md +1 -1
  75. package/claude-assets/skills/cfn-dependency-ingestion/SKILL.md +41 -13
  76. package/claude-assets/skills/cfn-dependency-ingestion/ingest.sh +237 -0
  77. package/claude-assets/skills/cfn-dependency-ingestion/manifests/cli-mode-dependencies.txt +73 -0
  78. package/claude-assets/skills/cfn-dependency-ingestion/manifests/shared-dependencies.txt +57 -0
  79. package/claude-assets/skills/cfn-dependency-ingestion/manifests/trigger-dev-dependencies.txt +82 -0
  80. package/claude-assets/skills/cfn-dependency-ingestion/manifests/trigger-mode-dependencies.txt +80 -0
  81. package/claude-assets/skills/cfn-environment-sanitization/sanitize-environment.sh +14 -4
  82. package/claude-assets/skills/cfn-loop-orchestration/NORTH_STAR_INDEX.md +1 -1
  83. package/claude-assets/skills/cfn-provider-routing/SKILL.md +23 -0
  84. package/claude-assets/skills/docker-build/build.sh +1 -1
  85. package/dist/agent/skill-mcp-selector.js +2 -1
  86. package/dist/agent/skill-mcp-selector.js.map +1 -1
  87. package/dist/agents/agent-loader.js +165 -146
  88. package/dist/agents/agent-loader.js.map +1 -1
  89. package/dist/cli/agent-executor.js +470 -26
  90. package/dist/cli/agent-executor.js.map +1 -1
  91. package/dist/cli/agent-prompt-builder.js +2 -2
  92. package/dist/cli/agent-prompt-builder.js.map +1 -1
  93. package/dist/cli/agent-spawn.js +7 -4
  94. package/dist/cli/agent-spawn.js.map +1 -1
  95. package/dist/cli/agent-spawner.js +51 -4
  96. package/dist/cli/agent-spawner.js.map +1 -1
  97. package/dist/cli/agent-token-manager.js +2 -1
  98. package/dist/cli/agent-token-manager.js.map +1 -1
  99. package/dist/cli/anthropic-client.js +117 -11
  100. package/dist/cli/anthropic-client.js.map +1 -1
  101. package/dist/cli/cfn-context.js +2 -1
  102. package/dist/cli/cfn-context.js.map +1 -1
  103. package/dist/cli/cfn-metrics.js +2 -1
  104. package/dist/cli/cfn-metrics.js.map +1 -1
  105. package/dist/cli/cfn-redis.js +2 -1
  106. package/dist/cli/cfn-redis.js.map +1 -1
  107. package/dist/cli/cli-agent-context.js +2 -0
  108. package/dist/cli/cli-agent-context.js.map +1 -1
  109. package/dist/cli/config-manager.js +4 -252
  110. package/dist/cli/config-manager.js.map +1 -1
  111. package/dist/cli/conversation-fork-cleanup.js +2 -1
  112. package/dist/cli/conversation-fork-cleanup.js.map +1 -1
  113. package/dist/cli/conversation-fork.js +2 -1
  114. package/dist/cli/conversation-fork.js.map +1 -1
  115. package/dist/cli/coordination/agent-messaging.js +415 -0
  116. package/dist/cli/coordination/agent-messaging.js.map +1 -0
  117. package/dist/cli/coordination/wait-for-threshold.js +232 -0
  118. package/dist/cli/coordination/wait-for-threshold.js.map +1 -0
  119. package/dist/cli/iteration-history.js +2 -1
  120. package/dist/cli/iteration-history.js.map +1 -1
  121. package/dist/cli/process-lifecycle.js +5 -1
  122. package/dist/cli/process-lifecycle.js.map +1 -1
  123. package/dist/cli/spawn-agent-cli.js +41 -6
  124. package/dist/cli/spawn-agent-cli.js.map +1 -1
  125. package/dist/coordination/redis-waiting-mode.js +4 -0
  126. package/dist/coordination/redis-waiting-mode.js.map +1 -1
  127. package/dist/lib/artifact-registry.js +4 -0
  128. package/dist/lib/artifact-registry.js.map +1 -1
  129. package/dist/lib/connection-pool.js +390 -0
  130. package/dist/lib/connection-pool.js.map +1 -0
  131. package/dist/lib/environment-contract.js +258 -0
  132. package/dist/lib/environment-contract.js.map +1 -0
  133. package/dist/lib/query-optimizer.js +388 -0
  134. package/dist/lib/query-optimizer.js.map +1 -0
  135. package/dist/lib/result-cache.js +285 -0
  136. package/dist/lib/result-cache.js.map +1 -0
  137. package/dist/mcp/auth-middleware.js +2 -1
  138. package/dist/mcp/auth-middleware.js.map +1 -1
  139. package/dist/mcp/playwright-mcp-server-auth.js +2 -1
  140. package/dist/mcp/playwright-mcp-server-auth.js.map +1 -1
  141. package/package.json +3 -1
  142. package/scripts/build-agent-image.sh +1 -1
  143. package/scripts/cost-allocation-tracker.sh +632 -0
  144. package/scripts/docker-rebuild-all-agents.sh +2 -2
  145. package/scripts/reorganize-tests.sh +280 -0
  146. package/scripts/trigger-dev-setup.sh +12 -0
  147. package/tests/README.md +45 -0
  148. package/.claude/commands/cost-savings-status.md +0 -34
  149. package/.claude/commands/metrics-summary.md +0 -58
  150. package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +0 -768
  151. package/claude-assets/agents/custom/test-mcp-access.md +0 -24
  152. package/claude-assets/commands/cost-savings-status.md +0 -34
  153. package/claude-assets/commands/metrics-summary.md +0 -58
  154. package/tests/test-memory-leak-task-mode.sh +0 -435
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/conversation-fork-cleanup.ts"],"sourcesContent":["/**\r\n * Conversation Fork Cleanup Utilities\r\n *\r\n * Provides memory leak prevention for Task Mode by:\r\n * 1. Setting TTL on message lists (24h default)\r\n * 2. Cleaning up completed task messages\r\n * 3. Limiting message history size\r\n * 4. Auto-cleanup of orphaned forks\r\n */\r\n\r\nimport { execSync } from 'child_process';\r\n\r\nconst redisHost = process.env.CFN_REDIS_HOST || 'cfn-redis';\r\nconst redisPort = process.env.CFN_REDIS_PORT || '6379';\r\n\r\nexport interface CleanupOptions {\r\n messageTTL?: number; // TTL for message lists (seconds, default: 86400 = 24h)\r\n maxMessagesPerAgent?: number; // Max messages to keep per agent (default: 100)\r\n autoCleanupForks?: boolean; // Auto-cleanup orphaned forks (default: true)\r\n}\r\n\r\nconst DEFAULT_OPTIONS: Required<CleanupOptions> = {\r\n messageTTL: 86400, // 24 hours\r\n maxMessagesPerAgent: 100, // 100 messages = ~50 iterations\r\n autoCleanupForks: true,\r\n};\r\n\r\n/**\r\n * Set TTL on message list to prevent indefinite accumulation\r\n */\r\nexport function setMessageListTTL(\r\n taskId: string,\r\n agentId: string,\r\n ttlSeconds: number = DEFAULT_OPTIONS.messageTTL\r\n): void {\r\n const key = `swarm:${taskId}:${agentId}:messages`;\r\n\r\n try {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} expire \"${key}\" ${ttlSeconds}`, {\r\n encoding: 'utf8'\r\n });\r\n console.log(`[conversation-cleanup] Set TTL ${ttlSeconds}s on ${key}`);\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed to set TTL:`, error);\r\n }\r\n}\r\n\r\n/**\r\n * Trim message list to max size (FIFO - keep recent messages)\r\n */\r\nexport function trimMessageList(\r\n taskId: string,\r\n agentId: string,\r\n maxMessages: number = DEFAULT_OPTIONS.maxMessagesPerAgent\r\n): void {\r\n const key = `swarm:${taskId}:${agentId}:messages`;\r\n\r\n try {\r\n // Keep only the last N messages (0 = oldest, -N = keep last N)\r\n const start = -maxMessages;\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} ltrim \"${key}\" ${start} -1`, {\r\n encoding: 'utf8'\r\n });\r\n console.log(`[conversation-cleanup] Trimmed ${key} to ${maxMessages} messages`);\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed to trim messages:`, error);\r\n }\r\n}\r\n\r\n/**\r\n * Clean up all messages and forks for a completed task\r\n */\r\nexport function cleanupTaskMessages(taskId: string, agentId: string): void {\r\n try {\r\n // Delete main message list\r\n const messagesKey = `swarm:${taskId}:${agentId}:messages`;\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del \"${messagesKey}\"`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n // Delete all fork message lists\r\n const forkPattern = `swarm:${taskId}:${agentId}:fork:*:messages`;\r\n const forkKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${forkPattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n if (forkKeys.length > 0) {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${forkKeys.join(' ')}`, {\r\n encoding: 'utf8'\r\n });\r\n }\r\n\r\n // Delete all fork metadata\r\n const forkMetaPattern = `swarm:${taskId}:${agentId}:fork:*:meta`;\r\n const metaKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${forkMetaPattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n if (metaKeys.length > 0) {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${metaKeys.join(' ')}`, {\r\n encoding: 'utf8'\r\n });\r\n }\r\n\r\n // Delete current fork pointer\r\n const currentForkKey = `swarm:${taskId}:${agentId}:current-fork`;\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del \"${currentForkKey}\"`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n console.log(`[conversation-cleanup] Cleaned up task ${taskId} agent ${agentId}`);\r\n console.log(`[conversation-cleanup] Removed: 1 message list, ${forkKeys.length} fork snapshots, ${metaKeys.length} fork metadata`);\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed to cleanup task messages:`, error);\r\n }\r\n}\r\n\r\n/**\r\n * Clean up orphaned forks (metadata expired but messages remain)\r\n */\r\nexport function cleanupOrphanedForks(taskId: string, agentId: string): void {\r\n try {\r\n // Find all fork message keys\r\n const forkPattern = `swarm:${taskId}:${agentId}:fork:*:messages`;\r\n const forkMessageKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${forkPattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n if (forkMessageKeys.length === 0) {\r\n return;\r\n }\r\n\r\n const orphanedKeys: string[] = [];\r\n\r\n for (const messageKey of forkMessageKeys) {\r\n // Extract fork ID from key: swarm:task:agent:fork:FORK_ID:messages\r\n const parts = messageKey.split(':');\r\n const forkId = parts[parts.length - 2];\r\n\r\n // Check if metadata exists\r\n const metaKey = `swarm:${taskId}:${agentId}:fork:${forkId}:meta`;\r\n const metaExists = execSync(`redis-cli -h ${redisHost} -p ${redisPort} exists \"${metaKey}\"`, {\r\n encoding: 'utf8'\r\n }).trim();\r\n\r\n if (metaExists === '0') {\r\n // Metadata expired but messages remain = orphaned\r\n orphanedKeys.push(messageKey);\r\n }\r\n }\r\n\r\n if (orphanedKeys.length > 0) {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${orphanedKeys.join(' ')}`, {\r\n encoding: 'utf8'\r\n });\r\n console.log(`[conversation-cleanup] Removed ${orphanedKeys.length} orphaned fork snapshots`);\r\n }\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed to cleanup orphaned forks:`, error);\r\n }\r\n}\r\n\r\n/**\r\n * Get memory usage statistics for a task\r\n */\r\nexport function getTaskMemoryStats(taskId: string, agentId: string): {\r\n messageCount: number;\r\n forkCount: number;\r\n estimatedSizeKB: number;\r\n} {\r\n try {\r\n // Count messages in main list\r\n const messagesKey = `swarm:${taskId}:${agentId}:messages`;\r\n const messageCount = parseInt(execSync(`redis-cli -h ${redisHost} -p ${redisPort} llen \"${messagesKey}\"`, {\r\n encoding: 'utf8'\r\n }).trim(), 10);\r\n\r\n // Count forks\r\n const forkPattern = `swarm:${taskId}:${agentId}:fork:*:messages`;\r\n const forkKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${forkPattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n // Estimate size (average message ~5KB)\r\n const estimatedSizeKB = messageCount * 5;\r\n\r\n return {\r\n messageCount,\r\n forkCount: forkKeys.length,\r\n estimatedSizeKB,\r\n };\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed to get memory stats:`, error);\r\n return {\r\n messageCount: 0,\r\n forkCount: 0,\r\n estimatedSizeKB: 0,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Configure automatic cleanup options for a task\r\n */\r\nexport function configureAutoCleanup(\r\n taskId: string,\r\n agentId: string,\r\n options: CleanupOptions = {}\r\n): void {\r\n const config = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n // Set TTL on message list\r\n setMessageListTTL(taskId, agentId, config.messageTTL);\r\n\r\n // Trim to max size\r\n trimMessageList(taskId, agentId, config.maxMessagesPerAgent);\r\n\r\n // Cleanup orphaned forks\r\n if (config.autoCleanupForks) {\r\n cleanupOrphanedForks(taskId, agentId);\r\n }\r\n\r\n console.log(`[conversation-cleanup] Auto-cleanup configured for ${taskId}/${agentId}`);\r\n console.log(`[conversation-cleanup] - TTL: ${config.messageTTL}s`);\r\n console.log(`[conversation-cleanup] - Max messages: ${config.maxMessagesPerAgent}`);\r\n console.log(`[conversation-cleanup] - Auto-cleanup forks: ${config.autoCleanupForks}`);\r\n}\r\n\r\n/**\r\n * Emergency cleanup - remove all conversation data for all tasks\r\n * USE WITH CAUTION: This will delete ALL conversation history\r\n */\r\nexport function emergencyCleanupAll(): void {\r\n try {\r\n const patterns = [\r\n 'swarm:*:*:messages',\r\n 'swarm:*:*:fork:*:messages',\r\n 'swarm:*:*:fork:*:meta',\r\n 'swarm:*:*:current-fork',\r\n ];\r\n\r\n let totalDeleted = 0;\r\n\r\n for (const pattern of patterns) {\r\n const keys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${pattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n if (keys.length > 0) {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${keys.join(' ')}`, {\r\n encoding: 'utf8'\r\n });\r\n totalDeleted += keys.length;\r\n }\r\n }\r\n\r\n console.log(`[conversation-cleanup] EMERGENCY CLEANUP: Deleted ${totalDeleted} conversation keys`);\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed emergency cleanup:`, error);\r\n }\r\n}\r\n"],"names":["execSync","redisHost","process","env","CFN_REDIS_HOST","redisPort","CFN_REDIS_PORT","DEFAULT_OPTIONS","messageTTL","maxMessagesPerAgent","autoCleanupForks","setMessageListTTL","taskId","agentId","ttlSeconds","key","encoding","console","log","error","trimMessageList","maxMessages","start","cleanupTaskMessages","messagesKey","forkPattern","forkKeys","trim","split","filter","k","length","join","forkMetaPattern","metaKeys","currentForkKey","cleanupOrphanedForks","forkMessageKeys","orphanedKeys","messageKey","parts","forkId","metaKey","metaExists","push","getTaskMemoryStats","messageCount","parseInt","estimatedSizeKB","forkCount","configureAutoCleanup","options","config","emergencyCleanupAll","patterns","totalDeleted","pattern","keys"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/cli/conversation-fork-cleanup.ts"],"sourcesContent":["/**\r\n * Conversation Fork Cleanup Utilities\r\n *\r\n * Provides memory leak prevention for Task Mode by:\r\n * 1. Setting TTL on message lists (24h default)\r\n * 2. Cleaning up completed task messages\r\n * 3. Limiting message history size\r\n * 4. Auto-cleanup of orphaned forks\r\n */\r\n\r\nimport { execSync } from 'child_process';\r\n\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 CleanupOptions {\r\n messageTTL?: number; // TTL for message lists (seconds, default: 86400 = 24h)\r\n maxMessagesPerAgent?: number; // Max messages to keep per agent (default: 100)\r\n autoCleanupForks?: boolean; // Auto-cleanup orphaned forks (default: true)\r\n}\r\n\r\nconst DEFAULT_OPTIONS: Required<CleanupOptions> = {\r\n messageTTL: 86400, // 24 hours\r\n maxMessagesPerAgent: 100, // 100 messages = ~50 iterations\r\n autoCleanupForks: true,\r\n};\r\n\r\n/**\r\n * Set TTL on message list to prevent indefinite accumulation\r\n */\r\nexport function setMessageListTTL(\r\n taskId: string,\r\n agentId: string,\r\n ttlSeconds: number = DEFAULT_OPTIONS.messageTTL\r\n): void {\r\n const key = `swarm:${taskId}:${agentId}:messages`;\r\n\r\n try {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} expire \"${key}\" ${ttlSeconds}`, {\r\n encoding: 'utf8'\r\n });\r\n console.log(`[conversation-cleanup] Set TTL ${ttlSeconds}s on ${key}`);\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed to set TTL:`, error);\r\n }\r\n}\r\n\r\n/**\r\n * Trim message list to max size (FIFO - keep recent messages)\r\n */\r\nexport function trimMessageList(\r\n taskId: string,\r\n agentId: string,\r\n maxMessages: number = DEFAULT_OPTIONS.maxMessagesPerAgent\r\n): void {\r\n const key = `swarm:${taskId}:${agentId}:messages`;\r\n\r\n try {\r\n // Keep only the last N messages (0 = oldest, -N = keep last N)\r\n const start = -maxMessages;\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} ltrim \"${key}\" ${start} -1`, {\r\n encoding: 'utf8'\r\n });\r\n console.log(`[conversation-cleanup] Trimmed ${key} to ${maxMessages} messages`);\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed to trim messages:`, error);\r\n }\r\n}\r\n\r\n/**\r\n * Clean up all messages and forks for a completed task\r\n */\r\nexport function cleanupTaskMessages(taskId: string, agentId: string): void {\r\n try {\r\n // Delete main message list\r\n const messagesKey = `swarm:${taskId}:${agentId}:messages`;\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del \"${messagesKey}\"`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n // Delete all fork message lists\r\n const forkPattern = `swarm:${taskId}:${agentId}:fork:*:messages`;\r\n const forkKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${forkPattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n if (forkKeys.length > 0) {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${forkKeys.join(' ')}`, {\r\n encoding: 'utf8'\r\n });\r\n }\r\n\r\n // Delete all fork metadata\r\n const forkMetaPattern = `swarm:${taskId}:${agentId}:fork:*:meta`;\r\n const metaKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${forkMetaPattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n if (metaKeys.length > 0) {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${metaKeys.join(' ')}`, {\r\n encoding: 'utf8'\r\n });\r\n }\r\n\r\n // Delete current fork pointer\r\n const currentForkKey = `swarm:${taskId}:${agentId}:current-fork`;\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del \"${currentForkKey}\"`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n console.log(`[conversation-cleanup] Cleaned up task ${taskId} agent ${agentId}`);\r\n console.log(`[conversation-cleanup] Removed: 1 message list, ${forkKeys.length} fork snapshots, ${metaKeys.length} fork metadata`);\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed to cleanup task messages:`, error);\r\n }\r\n}\r\n\r\n/**\r\n * Clean up orphaned forks (metadata expired but messages remain)\r\n */\r\nexport function cleanupOrphanedForks(taskId: string, agentId: string): void {\r\n try {\r\n // Find all fork message keys\r\n const forkPattern = `swarm:${taskId}:${agentId}:fork:*:messages`;\r\n const forkMessageKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${forkPattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n if (forkMessageKeys.length === 0) {\r\n return;\r\n }\r\n\r\n const orphanedKeys: string[] = [];\r\n\r\n for (const messageKey of forkMessageKeys) {\r\n // Extract fork ID from key: swarm:task:agent:fork:FORK_ID:messages\r\n const parts = messageKey.split(':');\r\n const forkId = parts[parts.length - 2];\r\n\r\n // Check if metadata exists\r\n const metaKey = `swarm:${taskId}:${agentId}:fork:${forkId}:meta`;\r\n const metaExists = execSync(`redis-cli -h ${redisHost} -p ${redisPort} exists \"${metaKey}\"`, {\r\n encoding: 'utf8'\r\n }).trim();\r\n\r\n if (metaExists === '0') {\r\n // Metadata expired but messages remain = orphaned\r\n orphanedKeys.push(messageKey);\r\n }\r\n }\r\n\r\n if (orphanedKeys.length > 0) {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${orphanedKeys.join(' ')}`, {\r\n encoding: 'utf8'\r\n });\r\n console.log(`[conversation-cleanup] Removed ${orphanedKeys.length} orphaned fork snapshots`);\r\n }\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed to cleanup orphaned forks:`, error);\r\n }\r\n}\r\n\r\n/**\r\n * Get memory usage statistics for a task\r\n */\r\nexport function getTaskMemoryStats(taskId: string, agentId: string): {\r\n messageCount: number;\r\n forkCount: number;\r\n estimatedSizeKB: number;\r\n} {\r\n try {\r\n // Count messages in main list\r\n const messagesKey = `swarm:${taskId}:${agentId}:messages`;\r\n const messageCount = parseInt(execSync(`redis-cli -h ${redisHost} -p ${redisPort} llen \"${messagesKey}\"`, {\r\n encoding: 'utf8'\r\n }).trim(), 10);\r\n\r\n // Count forks\r\n const forkPattern = `swarm:${taskId}:${agentId}:fork:*:messages`;\r\n const forkKeys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${forkPattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n // Estimate size (average message ~5KB)\r\n const estimatedSizeKB = messageCount * 5;\r\n\r\n return {\r\n messageCount,\r\n forkCount: forkKeys.length,\r\n estimatedSizeKB,\r\n };\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed to get memory stats:`, error);\r\n return {\r\n messageCount: 0,\r\n forkCount: 0,\r\n estimatedSizeKB: 0,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Configure automatic cleanup options for a task\r\n */\r\nexport function configureAutoCleanup(\r\n taskId: string,\r\n agentId: string,\r\n options: CleanupOptions = {}\r\n): void {\r\n const config = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n // Set TTL on message list\r\n setMessageListTTL(taskId, agentId, config.messageTTL);\r\n\r\n // Trim to max size\r\n trimMessageList(taskId, agentId, config.maxMessagesPerAgent);\r\n\r\n // Cleanup orphaned forks\r\n if (config.autoCleanupForks) {\r\n cleanupOrphanedForks(taskId, agentId);\r\n }\r\n\r\n console.log(`[conversation-cleanup] Auto-cleanup configured for ${taskId}/${agentId}`);\r\n console.log(`[conversation-cleanup] - TTL: ${config.messageTTL}s`);\r\n console.log(`[conversation-cleanup] - Max messages: ${config.maxMessagesPerAgent}`);\r\n console.log(`[conversation-cleanup] - Auto-cleanup forks: ${config.autoCleanupForks}`);\r\n}\r\n\r\n/**\r\n * Emergency cleanup - remove all conversation data for all tasks\r\n * USE WITH CAUTION: This will delete ALL conversation history\r\n */\r\nexport function emergencyCleanupAll(): void {\r\n try {\r\n const patterns = [\r\n 'swarm:*:*:messages',\r\n 'swarm:*:*:fork:*:messages',\r\n 'swarm:*:*:fork:*:meta',\r\n 'swarm:*:*:current-fork',\r\n ];\r\n\r\n let totalDeleted = 0;\r\n\r\n for (const pattern of patterns) {\r\n const keys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${pattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n if (keys.length > 0) {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del ${keys.join(' ')}`, {\r\n encoding: 'utf8'\r\n });\r\n totalDeleted += keys.length;\r\n }\r\n }\r\n\r\n console.log(`[conversation-cleanup] EMERGENCY CLEANUP: Deleted ${totalDeleted} conversation keys`);\r\n } catch (error) {\r\n console.error(`[conversation-cleanup] Failed emergency cleanup:`, error);\r\n }\r\n}\r\n"],"names":["execSync","redisHost","process","env","CFN_REDIS_HOST","redisPort","CFN_REDIS_PORT","DEFAULT_OPTIONS","messageTTL","maxMessagesPerAgent","autoCleanupForks","setMessageListTTL","taskId","agentId","ttlSeconds","key","encoding","console","log","error","trimMessageList","maxMessages","start","cleanupTaskMessages","messagesKey","forkPattern","forkKeys","trim","split","filter","k","length","join","forkMetaPattern","metaKeys","currentForkKey","cleanupOrphanedForks","forkMessageKeys","orphanedKeys","messageKey","parts","forkId","metaKey","metaExists","push","getTaskMemoryStats","messageCount","parseInt","estimatedSizeKB","forkCount","configureAutoCleanup","options","config","emergencyCleanupAll","patterns","totalDeleted","pattern","keys"],"mappings":"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"}
@@ -8,7 +8,8 @@
8
8
  */ import { execSync } from 'child_process';
9
9
  import { randomBytes } from 'crypto';
10
10
  // Bug #6 Fix: Read Redis connection parameters from process.env
11
- const redisHost = process.env.CFN_REDIS_HOST || 'cfn-redis';
11
+ // FIX: Default to 'localhost' for CLI mode (host execution), not 'cfn-redis' (Docker)
12
+ const redisHost = process.env.CFN_REDIS_HOST || 'localhost';
12
13
  const redisPort = process.env.CFN_REDIS_PORT || '6379';
13
14
  /**
14
15
  * Store a message in conversation history
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/conversation-fork.ts"],"sourcesContent":["/**\r\n * Conversation Fork Management\r\n *\r\n * Implements application-level conversation forking for CFN Loop iterations.\r\n * Stores conversation history in Redis and allows branching at specific points.\r\n *\r\n * Sprint 4: Conversation Forking (v2.7.0)\r\n */\r\n\r\nimport { execSync } from 'child_process';\r\nimport { randomBytes } from 'crypto';\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 Message {\r\n role: 'user' | 'assistant';\r\n content: string;\r\n iteration: number;\r\n timestamp: string;\r\n}\r\n\r\nexport interface ForkMetadata {\r\n forkId: string;\r\n taskId: string;\r\n agentId: string;\r\n createdAt: string;\r\n parentIteration: number;\r\n messageCount: number;\r\n}\r\n\r\n/**\r\n * Store a message in conversation history\r\n * MEMORY LEAK FIX: Now sets TTL on message list to prevent indefinite accumulation\r\n */\r\nexport async function storeMessage(\r\n taskId: string,\r\n agentId: string,\r\n message: Message\r\n): Promise<void> {\r\n const key = `swarm:${taskId}:${agentId}:messages`;\r\n const messageJson = JSON.stringify(message);\r\n\r\n try {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} rpush \"${key}\" '${messageJson.replace(/'/g, \"'\\\\''\")}'`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n // MEMORY LEAK FIX: Set TTL on message list (24h default)\r\n // This prevents messages from accumulating indefinitely across multiple tasks\r\n const messageTTL = parseInt(process.env.CFN_MESSAGE_TTL || '86400', 10); // 24 hours\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} expire \"${key}\" ${messageTTL}`, {\r\n encoding: 'utf8'\r\n });\r\n } catch (error) {\r\n console.error(`[conversation-fork] Failed to store message:`, error);\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Load all messages from conversation history\r\n */\r\nexport async function loadMessages(\r\n taskId: string,\r\n agentId: string,\r\n forkId?: string\r\n): Promise<Message[]> {\r\n const key = forkId\r\n ? `swarm:${taskId}:${agentId}:fork:${forkId}:messages`\r\n : `swarm:${taskId}:${agentId}:messages`;\r\n\r\n try {\r\n const output = execSync(`redis-cli -h ${redisHost} -p ${redisPort} lrange \"${key}\" 0 -1`, {\r\n encoding: 'utf8'\r\n }).trim();\r\n\r\n if (!output || output === '(empty array)') {\r\n return [];\r\n }\r\n\r\n // Redis returns each message on a new line\r\n const lines = output.split('\\n');\r\n return lines.map(line => JSON.parse(line));\r\n } catch (error) {\r\n console.error(`[conversation-fork] Failed to load messages:`, error);\r\n return [];\r\n }\r\n}\r\n\r\n/**\r\n * Create a fork from current conversation state\r\n * Copies all messages up to current iteration\r\n * MEMORY LEAK FIX: Now sets TTL on fork message list matching metadata TTL\r\n */\r\nexport async function createFork(\r\n taskId: string,\r\n agentId: string,\r\n currentIteration: number\r\n): Promise<string> {\r\n // Generate unique fork ID\r\n const forkId = `fork-${currentIteration}-${randomBytes(4).toString('hex')}`;\r\n\r\n // Load messages up to current iteration\r\n const messages = await loadMessages(taskId, agentId);\r\n const forkMessages = messages.filter(m => m.iteration <= currentIteration);\r\n\r\n if (forkMessages.length === 0) {\r\n throw new Error(`No messages found for iteration ${currentIteration}`);\r\n }\r\n\r\n // Store fork snapshot\r\n const forkKey = `swarm:${taskId}:${agentId}:fork:${forkId}:messages`;\r\n const forkTTL = parseInt(process.env.CFN_FORK_TTL || '86400', 10); // 24 hours\r\n\r\n for (const message of forkMessages) {\r\n const messageJson = JSON.stringify(message);\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} rpush \"${forkKey}\" '${messageJson.replace(/'/g, \"'\\\\''\")}'`, {\r\n encoding: 'utf8'\r\n });\r\n }\r\n\r\n // MEMORY LEAK FIX: Set TTL on fork messages (was missing before)\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} expire \"${forkKey}\" ${forkTTL}`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n // Store fork metadata\r\n const metadata: ForkMetadata = {\r\n forkId,\r\n taskId,\r\n agentId,\r\n createdAt: new Date().toISOString(),\r\n parentIteration: currentIteration,\r\n messageCount: forkMessages.length\r\n };\r\n\r\n const metaKey = `swarm:${taskId}:${agentId}:fork:${forkId}:meta`;\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} setex \"${metaKey}\" ${forkTTL} '${JSON.stringify(metadata)}'`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n // Set as current fork\r\n const currentForkKey = `swarm:${taskId}:${agentId}:current-fork`;\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} setex \"${currentForkKey}\" ${forkTTL} \"${forkId}\"`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n console.log(`[conversation-fork] Created fork ${forkId} with ${forkMessages.length} messages (TTL: ${forkTTL}s)`);\r\n\r\n return forkId;\r\n}\r\n\r\n/**\r\n * Get current active fork ID\r\n */\r\nexport async function getCurrentFork(\r\n taskId: string,\r\n agentId: string\r\n): Promise<string | null> {\r\n const key = `swarm:${taskId}:${agentId}:current-fork`;\r\n\r\n try {\r\n const forkId = execSync(`redis-cli -h ${redisHost} -p ${redisPort} get \"${key}\"`, {\r\n encoding: 'utf8'\r\n }).trim();\r\n\r\n if (forkId === '(nil)' || !forkId) {\r\n return null;\r\n }\r\n\r\n return forkId;\r\n } catch (error) {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Get fork metadata\r\n */\r\nexport async function getForkMetadata(\r\n taskId: string,\r\n agentId: string,\r\n forkId: string\r\n): Promise<ForkMetadata | null> {\r\n const key = `swarm:${taskId}:${agentId}:fork:${forkId}:meta`;\r\n\r\n try {\r\n const metaJson = execSync(`redis-cli -h ${redisHost} -p ${redisPort} get \"${key}\"`, {\r\n encoding: 'utf8'\r\n }).trim();\r\n\r\n if (metaJson === '(nil)' || !metaJson) {\r\n return null;\r\n }\r\n\r\n return JSON.parse(metaJson);\r\n } catch (error) {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * List all forks for an agent\r\n */\r\nexport async function listForks(\r\n taskId: string,\r\n agentId: string\r\n): Promise<ForkMetadata[]> {\r\n const pattern = `swarm:${taskId}:${agentId}:fork:*:meta`;\r\n\r\n try {\r\n const keys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${pattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n const forks: ForkMetadata[] = [];\r\n\r\n for (const key of keys) {\r\n const metaJson = execSync(`redis-cli -h ${redisHost} -p ${redisPort} get \"${key}\"`, {\r\n encoding: 'utf8'\r\n }).trim();\r\n\r\n if (metaJson && metaJson !== '(nil)') {\r\n forks.push(JSON.parse(metaJson));\r\n }\r\n }\r\n\r\n return forks.sort((a, b) =>\r\n new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()\r\n );\r\n } catch (error) {\r\n console.error(`[conversation-fork] Failed to list forks:`, error);\r\n return [];\r\n }\r\n}\r\n\r\n/**\r\n * Delete a fork and its messages\r\n */\r\nexport async function deleteFork(\r\n taskId: string,\r\n agentId: string,\r\n forkId: string\r\n): Promise<void> {\r\n const messagesKey = `swarm:${taskId}:${agentId}:fork:${forkId}:messages`;\r\n const metaKey = `swarm:${taskId}:${agentId}:fork:${forkId}:meta`;\r\n\r\n try {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del \"${messagesKey}\" \"${metaKey}\"`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n console.log(`[conversation-fork] Deleted fork ${forkId}`);\r\n } catch (error) {\r\n console.error(`[conversation-fork] Failed to delete fork:`, error);\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Clear current fork (start fresh conversation)\r\n */\r\nexport async function clearCurrentFork(\r\n taskId: string,\r\n agentId: string\r\n): Promise<void> {\r\n const key = `swarm:${taskId}:${agentId}:current-fork`;\r\n\r\n try {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del \"${key}\"`, {\r\n encoding: 'utf8'\r\n });\r\n } catch (error) {\r\n console.error(`[conversation-fork] Failed to clear current fork:`, error);\r\n }\r\n}\r\n\r\n/**\r\n * Format messages for Anthropic API\r\n */\r\nexport function formatMessagesForAPI(messages: Message[]): Array<{role: string, content: string}> {\r\n return messages.map(m => ({\r\n role: m.role,\r\n content: m.content\r\n }));\r\n}\r\n\r\n/**\r\n * Get conversation statistics\r\n */\r\nexport async function getConversationStats(\r\n taskId: string,\r\n agentId: string,\r\n forkId?: string\r\n): Promise<{\r\n messageCount: number;\r\n userMessages: number;\r\n assistantMessages: number;\r\n iterations: number;\r\n firstMessage: string | null;\r\n lastMessage: string | null;\r\n}> {\r\n const messages = await loadMessages(taskId, agentId, forkId);\r\n\r\n if (messages.length === 0) {\r\n return {\r\n messageCount: 0,\r\n userMessages: 0,\r\n assistantMessages: 0,\r\n iterations: 0,\r\n firstMessage: null,\r\n lastMessage: null\r\n };\r\n }\r\n\r\n const userMessages = messages.filter(m => m.role === 'user').length;\r\n const assistantMessages = messages.filter(m => m.role === 'assistant').length;\r\n const iterations = Math.max(...messages.map(m => m.iteration));\r\n\r\n return {\r\n messageCount: messages.length,\r\n userMessages,\r\n assistantMessages,\r\n iterations,\r\n firstMessage: messages[0].timestamp,\r\n lastMessage: messages[messages.length - 1].timestamp\r\n };\r\n}\r\n"],"names":["execSync","randomBytes","redisHost","process","env","CFN_REDIS_HOST","redisPort","CFN_REDIS_PORT","storeMessage","taskId","agentId","message","key","messageJson","JSON","stringify","replace","encoding","messageTTL","parseInt","CFN_MESSAGE_TTL","error","console","loadMessages","forkId","output","trim","lines","split","map","line","parse","createFork","currentIteration","toString","messages","forkMessages","filter","m","iteration","length","Error","forkKey","forkTTL","CFN_FORK_TTL","metadata","createdAt","Date","toISOString","parentIteration","messageCount","metaKey","currentForkKey","log","getCurrentFork","getForkMetadata","metaJson","listForks","pattern","keys","k","forks","push","sort","a","b","getTime","deleteFork","messagesKey","clearCurrentFork","formatMessagesForAPI","role","content","getConversationStats","userMessages","assistantMessages","iterations","firstMessage","lastMessage","Math","max","timestamp"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/cli/conversation-fork.ts"],"sourcesContent":["/**\r\n * Conversation Fork Management\r\n *\r\n * Implements application-level conversation forking for CFN Loop iterations.\r\n * Stores conversation history in Redis and allows branching at specific points.\r\n *\r\n * Sprint 4: Conversation Forking (v2.7.0)\r\n */\r\n\r\nimport { execSync } from 'child_process';\r\nimport { randomBytes } from 'crypto';\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 Message {\r\n role: 'user' | 'assistant';\r\n content: string;\r\n iteration: number;\r\n timestamp: string;\r\n}\r\n\r\nexport interface ForkMetadata {\r\n forkId: string;\r\n taskId: string;\r\n agentId: string;\r\n createdAt: string;\r\n parentIteration: number;\r\n messageCount: number;\r\n}\r\n\r\n/**\r\n * Store a message in conversation history\r\n * MEMORY LEAK FIX: Now sets TTL on message list to prevent indefinite accumulation\r\n */\r\nexport async function storeMessage(\r\n taskId: string,\r\n agentId: string,\r\n message: Message\r\n): Promise<void> {\r\n const key = `swarm:${taskId}:${agentId}:messages`;\r\n const messageJson = JSON.stringify(message);\r\n\r\n try {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} rpush \"${key}\" '${messageJson.replace(/'/g, \"'\\\\''\")}'`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n // MEMORY LEAK FIX: Set TTL on message list (24h default)\r\n // This prevents messages from accumulating indefinitely across multiple tasks\r\n const messageTTL = parseInt(process.env.CFN_MESSAGE_TTL || '86400', 10); // 24 hours\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} expire \"${key}\" ${messageTTL}`, {\r\n encoding: 'utf8'\r\n });\r\n } catch (error) {\r\n console.error(`[conversation-fork] Failed to store message:`, error);\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Load all messages from conversation history\r\n */\r\nexport async function loadMessages(\r\n taskId: string,\r\n agentId: string,\r\n forkId?: string\r\n): Promise<Message[]> {\r\n const key = forkId\r\n ? `swarm:${taskId}:${agentId}:fork:${forkId}:messages`\r\n : `swarm:${taskId}:${agentId}:messages`;\r\n\r\n try {\r\n const output = execSync(`redis-cli -h ${redisHost} -p ${redisPort} lrange \"${key}\" 0 -1`, {\r\n encoding: 'utf8'\r\n }).trim();\r\n\r\n if (!output || output === '(empty array)') {\r\n return [];\r\n }\r\n\r\n // Redis returns each message on a new line\r\n const lines = output.split('\\n');\r\n return lines.map(line => JSON.parse(line));\r\n } catch (error) {\r\n console.error(`[conversation-fork] Failed to load messages:`, error);\r\n return [];\r\n }\r\n}\r\n\r\n/**\r\n * Create a fork from current conversation state\r\n * Copies all messages up to current iteration\r\n * MEMORY LEAK FIX: Now sets TTL on fork message list matching metadata TTL\r\n */\r\nexport async function createFork(\r\n taskId: string,\r\n agentId: string,\r\n currentIteration: number\r\n): Promise<string> {\r\n // Generate unique fork ID\r\n const forkId = `fork-${currentIteration}-${randomBytes(4).toString('hex')}`;\r\n\r\n // Load messages up to current iteration\r\n const messages = await loadMessages(taskId, agentId);\r\n const forkMessages = messages.filter(m => m.iteration <= currentIteration);\r\n\r\n if (forkMessages.length === 0) {\r\n throw new Error(`No messages found for iteration ${currentIteration}`);\r\n }\r\n\r\n // Store fork snapshot\r\n const forkKey = `swarm:${taskId}:${agentId}:fork:${forkId}:messages`;\r\n const forkTTL = parseInt(process.env.CFN_FORK_TTL || '86400', 10); // 24 hours\r\n\r\n for (const message of forkMessages) {\r\n const messageJson = JSON.stringify(message);\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} rpush \"${forkKey}\" '${messageJson.replace(/'/g, \"'\\\\''\")}'`, {\r\n encoding: 'utf8'\r\n });\r\n }\r\n\r\n // MEMORY LEAK FIX: Set TTL on fork messages (was missing before)\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} expire \"${forkKey}\" ${forkTTL}`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n // Store fork metadata\r\n const metadata: ForkMetadata = {\r\n forkId,\r\n taskId,\r\n agentId,\r\n createdAt: new Date().toISOString(),\r\n parentIteration: currentIteration,\r\n messageCount: forkMessages.length\r\n };\r\n\r\n const metaKey = `swarm:${taskId}:${agentId}:fork:${forkId}:meta`;\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} setex \"${metaKey}\" ${forkTTL} '${JSON.stringify(metadata)}'`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n // Set as current fork\r\n const currentForkKey = `swarm:${taskId}:${agentId}:current-fork`;\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} setex \"${currentForkKey}\" ${forkTTL} \"${forkId}\"`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n console.log(`[conversation-fork] Created fork ${forkId} with ${forkMessages.length} messages (TTL: ${forkTTL}s)`);\r\n\r\n return forkId;\r\n}\r\n\r\n/**\r\n * Get current active fork ID\r\n */\r\nexport async function getCurrentFork(\r\n taskId: string,\r\n agentId: string\r\n): Promise<string | null> {\r\n const key = `swarm:${taskId}:${agentId}:current-fork`;\r\n\r\n try {\r\n const forkId = execSync(`redis-cli -h ${redisHost} -p ${redisPort} get \"${key}\"`, {\r\n encoding: 'utf8'\r\n }).trim();\r\n\r\n if (forkId === '(nil)' || !forkId) {\r\n return null;\r\n }\r\n\r\n return forkId;\r\n } catch (error) {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Get fork metadata\r\n */\r\nexport async function getForkMetadata(\r\n taskId: string,\r\n agentId: string,\r\n forkId: string\r\n): Promise<ForkMetadata | null> {\r\n const key = `swarm:${taskId}:${agentId}:fork:${forkId}:meta`;\r\n\r\n try {\r\n const metaJson = execSync(`redis-cli -h ${redisHost} -p ${redisPort} get \"${key}\"`, {\r\n encoding: 'utf8'\r\n }).trim();\r\n\r\n if (metaJson === '(nil)' || !metaJson) {\r\n return null;\r\n }\r\n\r\n return JSON.parse(metaJson);\r\n } catch (error) {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * List all forks for an agent\r\n */\r\nexport async function listForks(\r\n taskId: string,\r\n agentId: string\r\n): Promise<ForkMetadata[]> {\r\n const pattern = `swarm:${taskId}:${agentId}:fork:*:meta`;\r\n\r\n try {\r\n const keys = execSync(`redis-cli -h ${redisHost} -p ${redisPort} keys \"${pattern}\"`, {\r\n encoding: 'utf8'\r\n }).trim().split('\\n').filter(k => k);\r\n\r\n const forks: ForkMetadata[] = [];\r\n\r\n for (const key of keys) {\r\n const metaJson = execSync(`redis-cli -h ${redisHost} -p ${redisPort} get \"${key}\"`, {\r\n encoding: 'utf8'\r\n }).trim();\r\n\r\n if (metaJson && metaJson !== '(nil)') {\r\n forks.push(JSON.parse(metaJson));\r\n }\r\n }\r\n\r\n return forks.sort((a, b) =>\r\n new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()\r\n );\r\n } catch (error) {\r\n console.error(`[conversation-fork] Failed to list forks:`, error);\r\n return [];\r\n }\r\n}\r\n\r\n/**\r\n * Delete a fork and its messages\r\n */\r\nexport async function deleteFork(\r\n taskId: string,\r\n agentId: string,\r\n forkId: string\r\n): Promise<void> {\r\n const messagesKey = `swarm:${taskId}:${agentId}:fork:${forkId}:messages`;\r\n const metaKey = `swarm:${taskId}:${agentId}:fork:${forkId}:meta`;\r\n\r\n try {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del \"${messagesKey}\" \"${metaKey}\"`, {\r\n encoding: 'utf8'\r\n });\r\n\r\n console.log(`[conversation-fork] Deleted fork ${forkId}`);\r\n } catch (error) {\r\n console.error(`[conversation-fork] Failed to delete fork:`, error);\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Clear current fork (start fresh conversation)\r\n */\r\nexport async function clearCurrentFork(\r\n taskId: string,\r\n agentId: string\r\n): Promise<void> {\r\n const key = `swarm:${taskId}:${agentId}:current-fork`;\r\n\r\n try {\r\n execSync(`redis-cli -h ${redisHost} -p ${redisPort} del \"${key}\"`, {\r\n encoding: 'utf8'\r\n });\r\n } catch (error) {\r\n console.error(`[conversation-fork] Failed to clear current fork:`, error);\r\n }\r\n}\r\n\r\n/**\r\n * Format messages for Anthropic API\r\n */\r\nexport function formatMessagesForAPI(messages: Message[]): Array<{role: string, content: string}> {\r\n return messages.map(m => ({\r\n role: m.role,\r\n content: m.content\r\n }));\r\n}\r\n\r\n/**\r\n * Get conversation statistics\r\n */\r\nexport async function getConversationStats(\r\n taskId: string,\r\n agentId: string,\r\n forkId?: string\r\n): Promise<{\r\n messageCount: number;\r\n userMessages: number;\r\n assistantMessages: number;\r\n iterations: number;\r\n firstMessage: string | null;\r\n lastMessage: string | null;\r\n}> {\r\n const messages = await loadMessages(taskId, agentId, forkId);\r\n\r\n if (messages.length === 0) {\r\n return {\r\n messageCount: 0,\r\n userMessages: 0,\r\n assistantMessages: 0,\r\n iterations: 0,\r\n firstMessage: null,\r\n lastMessage: null\r\n };\r\n }\r\n\r\n const userMessages = messages.filter(m => m.role === 'user').length;\r\n const assistantMessages = messages.filter(m => m.role === 'assistant').length;\r\n const iterations = Math.max(...messages.map(m => m.iteration));\r\n\r\n return {\r\n messageCount: messages.length,\r\n userMessages,\r\n assistantMessages,\r\n iterations,\r\n firstMessage: messages[0].timestamp,\r\n lastMessage: messages[messages.length - 1].timestamp\r\n };\r\n}\r\n"],"names":["execSync","randomBytes","redisHost","process","env","CFN_REDIS_HOST","redisPort","CFN_REDIS_PORT","storeMessage","taskId","agentId","message","key","messageJson","JSON","stringify","replace","encoding","messageTTL","parseInt","CFN_MESSAGE_TTL","error","console","loadMessages","forkId","output","trim","lines","split","map","line","parse","createFork","currentIteration","toString","messages","forkMessages","filter","m","iteration","length","Error","forkKey","forkTTL","CFN_FORK_TTL","metadata","createdAt","Date","toISOString","parentIteration","messageCount","metaKey","currentForkKey","log","getCurrentFork","getForkMetadata","metaJson","listForks","pattern","keys","k","forks","push","sort","a","b","getTime","deleteFork","messagesKey","clearCurrentFork","formatMessagesForAPI","role","content","getConversationStats","userMessages","assistantMessages","iterations","firstMessage","lastMessage","Math","max","timestamp"],"mappings":"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"}
@@ -0,0 +1,415 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Agent Messaging - Bidirectional Redis Communication
4
+ *
5
+ * Enables Main Chat to send commands to running CLI agents, and agents to process them.
6
+ *
7
+ * Main Chat → Agent: LPUSH cfn:agent:{taskId}:{agentId}:commands
8
+ * Agent → Main Chat: SET cfn:agent:{taskId}:{agentId}:status (response)
9
+ *
10
+ * Supported Commands:
11
+ * - status: Request agent status update
12
+ * - redirect: Redirect agent to new task/context
13
+ * - abort: Request clean agent abort
14
+ * - pause: Request agent pause for N seconds
15
+ *
16
+ * Usage (Main Chat - send command):
17
+ * npx tsx src/cli/coordination/agent-messaging.ts send \
18
+ * --task-id <id> --agent-id <aid> --command status
19
+ *
20
+ * Usage (Agent - process commands):
21
+ * npx tsx src/cli/coordination/agent-messaging.ts listen \
22
+ * --task-id <id> --agent-id <aid> --poll-interval 5
23
+ */ import { createClient } from 'redis';
24
+ // ============================================================================
25
+ // Redis Key Helpers
26
+ // ============================================================================
27
+ function getCommandsKey(taskId, agentId) {
28
+ return `cfn:agent:${taskId}:${agentId}:commands`;
29
+ }
30
+ function getStatusKey(taskId, agentId) {
31
+ return `cfn:agent:${taskId}:${agentId}:status`;
32
+ }
33
+ function getResponseKey(taskId, agentId, commandId) {
34
+ return `cfn:agent:${taskId}:${agentId}:response:${commandId}`;
35
+ }
36
+ // ============================================================================
37
+ // Main Chat Side - Send Commands
38
+ // ============================================================================
39
+ /**
40
+ * Send a command to an agent via Redis
41
+ */ export async function sendCommand(config, command) {
42
+ const { taskId, agentId, redisHost = process.env.CFN_REDIS_HOST || 'localhost', redisPort = parseInt(process.env.CFN_REDIS_PORT || '6379', 10), redisPassword = process.env.CFN_REDIS_PASSWORD || undefined } = config;
43
+ const client = createClient({
44
+ socket: {
45
+ host: redisHost,
46
+ port: redisPort
47
+ },
48
+ password: redisPassword || undefined
49
+ });
50
+ const commandId = `cmd-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
51
+ const fullCommand = {
52
+ ...command,
53
+ id: commandId,
54
+ timestamp: new Date().toISOString(),
55
+ replyTo: getResponseKey(taskId, agentId, commandId)
56
+ };
57
+ try {
58
+ await client.connect();
59
+ const commandsKey = getCommandsKey(taskId, agentId);
60
+ await client.lPush(commandsKey, JSON.stringify(fullCommand));
61
+ console.log(`[agent-msg] Sent command ${command.type} to ${agentId}`);
62
+ console.log(`[agent-msg] Commands key: ${commandsKey}`);
63
+ console.log(`[agent-msg] Command ID: ${commandId}`);
64
+ return {
65
+ sent: true,
66
+ commandId
67
+ };
68
+ } finally{
69
+ await client.disconnect();
70
+ }
71
+ }
72
+ /**
73
+ * Wait for agent response to a command
74
+ */ export async function waitForResponse(config, commandId, timeoutSeconds = 30) {
75
+ const { taskId, agentId, redisHost = process.env.CFN_REDIS_HOST || 'localhost', redisPort = parseInt(process.env.CFN_REDIS_PORT || '6379', 10), redisPassword = process.env.CFN_REDIS_PASSWORD || undefined } = config;
76
+ const client = createClient({
77
+ socket: {
78
+ host: redisHost,
79
+ port: redisPort
80
+ },
81
+ password: redisPassword || undefined
82
+ });
83
+ try {
84
+ await client.connect();
85
+ const responseKey = getResponseKey(taskId, agentId, commandId);
86
+ const result = await client.blPop(responseKey, timeoutSeconds);
87
+ if (result) {
88
+ return JSON.parse(result.element);
89
+ }
90
+ return null;
91
+ } finally{
92
+ await client.disconnect();
93
+ }
94
+ }
95
+ /**
96
+ * Get current agent status (non-blocking)
97
+ */ export async function getAgentStatus(config) {
98
+ const { taskId, agentId, redisHost = process.env.CFN_REDIS_HOST || 'localhost', redisPort = parseInt(process.env.CFN_REDIS_PORT || '6379', 10), redisPassword = process.env.CFN_REDIS_PASSWORD || undefined } = config;
99
+ const client = createClient({
100
+ socket: {
101
+ host: redisHost,
102
+ port: redisPort
103
+ },
104
+ password: redisPassword || undefined
105
+ });
106
+ try {
107
+ await client.connect();
108
+ const statusKey = getStatusKey(taskId, agentId);
109
+ const status = await client.get(statusKey);
110
+ if (status) {
111
+ return JSON.parse(status);
112
+ }
113
+ return null;
114
+ } finally{
115
+ await client.disconnect();
116
+ }
117
+ }
118
+ /**
119
+ * Agent command processor - polls for and processes commands
120
+ */ export class AgentCommandProcessor {
121
+ config;
122
+ client = null;
123
+ running = false;
124
+ handlers = new Map();
125
+ constructor(config){
126
+ this.config = config;
127
+ // Default handlers
128
+ this.handlers.set('status', this.handleStatus.bind(this));
129
+ this.handlers.set('abort', this.handleAbort.bind(this));
130
+ this.handlers.set('pause', this.handlePause.bind(this));
131
+ }
132
+ /**
133
+ * Register a custom command handler
134
+ */ onCommand(type, handler) {
135
+ this.handlers.set(type, handler);
136
+ }
137
+ /**
138
+ * Start processing commands (non-blocking poll loop)
139
+ */ async start(pollIntervalSeconds = 5) {
140
+ const { taskId, agentId, redisHost = process.env.CFN_REDIS_HOST || 'localhost', redisPort = parseInt(process.env.CFN_REDIS_PORT || '6379', 10), redisPassword = process.env.CFN_REDIS_PASSWORD || undefined } = this.config;
141
+ this.client = createClient({
142
+ socket: {
143
+ host: redisHost,
144
+ port: redisPort
145
+ },
146
+ password: redisPassword || undefined
147
+ });
148
+ await this.client.connect();
149
+ this.running = true;
150
+ const commandsKey = getCommandsKey(taskId, agentId);
151
+ console.log(`[agent-processor] Started listening on ${commandsKey}`);
152
+ // Non-blocking poll loop
153
+ while(this.running){
154
+ try {
155
+ // Short BLPOP to check for commands without blocking too long
156
+ const result = await this.client.blPop(commandsKey, pollIntervalSeconds);
157
+ if (result) {
158
+ const command = JSON.parse(result.element);
159
+ console.log(`[agent-processor] Received command: ${command.type} (${command.id})`);
160
+ // Process command
161
+ const handler = this.handlers.get(command.type);
162
+ if (handler) {
163
+ const response = await handler(command);
164
+ // Send response if handler returned status
165
+ if (response && command.replyTo) {
166
+ await this.client.lPush(command.replyTo, JSON.stringify(response));
167
+ console.log(`[agent-processor] Sent response to ${command.replyTo}`);
168
+ }
169
+ } else {
170
+ console.warn(`[agent-processor] No handler for command type: ${command.type}`);
171
+ }
172
+ }
173
+ } catch (err) {
174
+ if (this.running) {
175
+ console.error(`[agent-processor] Error processing command:`, err);
176
+ }
177
+ }
178
+ }
179
+ }
180
+ /**
181
+ * Stop processing commands
182
+ */ async stop() {
183
+ this.running = false;
184
+ if (this.client) {
185
+ await this.client.disconnect();
186
+ this.client = null;
187
+ }
188
+ console.log(`[agent-processor] Stopped`);
189
+ }
190
+ /**
191
+ * Update agent status in Redis
192
+ */ async updateStatus(status) {
193
+ if (!this.client) return;
194
+ const { taskId, agentId } = this.config;
195
+ const fullStatus = {
196
+ agentId,
197
+ taskId,
198
+ status: 'running',
199
+ timestamp: new Date().toISOString(),
200
+ ...status
201
+ };
202
+ const statusKey = getStatusKey(taskId, agentId);
203
+ await this.client.set(statusKey, JSON.stringify(fullStatus));
204
+ // Expire after 1 hour
205
+ await this.client.expire(statusKey, 3600);
206
+ }
207
+ // Default handlers
208
+ async handleStatus(command) {
209
+ const { taskId, agentId } = this.config;
210
+ return {
211
+ agentId,
212
+ taskId,
213
+ status: 'running',
214
+ timestamp: new Date().toISOString(),
215
+ metadata: {
216
+ respondingTo: command.id
217
+ }
218
+ };
219
+ }
220
+ async handleAbort(_command) {
221
+ const { taskId, agentId } = this.config;
222
+ console.log(`[agent-processor] Abort requested, shutting down...`);
223
+ // Schedule stop after response
224
+ setTimeout(()=>this.stop(), 100);
225
+ return {
226
+ agentId,
227
+ taskId,
228
+ status: 'aborting',
229
+ timestamp: new Date().toISOString()
230
+ };
231
+ }
232
+ async handlePause(command) {
233
+ const { taskId, agentId } = this.config;
234
+ const pauseSeconds = command.payload?.seconds || 10;
235
+ console.log(`[agent-processor] Pausing for ${pauseSeconds}s...`);
236
+ await new Promise((resolve)=>setTimeout(resolve, pauseSeconds * 1000));
237
+ console.log(`[agent-processor] Resumed after pause`);
238
+ return {
239
+ agentId,
240
+ taskId,
241
+ status: 'running',
242
+ timestamp: new Date().toISOString(),
243
+ metadata: {
244
+ pausedFor: pauseSeconds
245
+ }
246
+ };
247
+ }
248
+ }
249
+ // ============================================================================
250
+ // CLI Entry Point
251
+ // ============================================================================
252
+ function printHelp() {
253
+ console.log(`
254
+ Agent Messaging - Bidirectional Redis Communication
255
+
256
+ USAGE:
257
+ # Send command to agent (Main Chat side)
258
+ npx tsx src/cli/coordination/agent-messaging.ts send \\
259
+ --task-id <id> --agent-id <aid> --command <type> [--payload <json>]
260
+
261
+ # Listen for commands (Agent side)
262
+ npx tsx src/cli/coordination/agent-messaging.ts listen \\
263
+ --task-id <id> --agent-id <aid> [--poll-interval <seconds>]
264
+
265
+ # Get agent status (Main Chat side)
266
+ npx tsx src/cli/coordination/agent-messaging.ts status \\
267
+ --task-id <id> --agent-id <aid>
268
+
269
+ COMMANDS:
270
+ status Request agent status update
271
+ redirect Redirect agent to new task (include --payload)
272
+ abort Request clean agent abort
273
+ pause Pause agent (include --payload '{"seconds": 10}')
274
+ custom Custom command (include --payload)
275
+
276
+ EXAMPLES:
277
+ # Request status from agent
278
+ npx tsx src/cli/coordination/agent-messaging.ts send \\
279
+ --task-id cfn-cli-123 --agent-id backend-dev-456 --command status
280
+
281
+ # Abort agent
282
+ npx tsx src/cli/coordination/agent-messaging.ts send \\
283
+ --task-id cfn-cli-123 --agent-id backend-dev-456 --command abort
284
+
285
+ # Pause agent for 30 seconds
286
+ npx tsx src/cli/coordination/agent-messaging.ts send \\
287
+ --task-id cfn-cli-123 --agent-id backend-dev-456 \\
288
+ --command pause --payload '{"seconds": 30}'
289
+
290
+ # Redirect agent to new context
291
+ npx tsx src/cli/coordination/agent-messaging.ts send \\
292
+ --task-id cfn-cli-123 --agent-id backend-dev-456 \\
293
+ --command redirect --payload '{"newTask": "Focus on security tests"}'
294
+
295
+ # Start listening for commands (agent side)
296
+ npx tsx src/cli/coordination/agent-messaging.ts listen \\
297
+ --task-id cfn-cli-123 --agent-id backend-dev-456 --poll-interval 5
298
+ `);
299
+ }
300
+ async function main() {
301
+ const args = process.argv.slice(2);
302
+ const action = args[0];
303
+ if (!action || action === '--help' || action === '-h') {
304
+ printHelp();
305
+ process.exit(0);
306
+ }
307
+ // Parse common args
308
+ let taskId;
309
+ let agentId;
310
+ let command;
311
+ let payload;
312
+ let pollInterval = 5;
313
+ for(let i = 1; i < args.length; i++){
314
+ const arg = args[i];
315
+ const value = args[i + 1];
316
+ switch(arg){
317
+ case '--task-id':
318
+ case '-t':
319
+ taskId = value;
320
+ i++;
321
+ break;
322
+ case '--agent-id':
323
+ case '-a':
324
+ agentId = value;
325
+ i++;
326
+ break;
327
+ case '--command':
328
+ case '-c':
329
+ command = value;
330
+ i++;
331
+ break;
332
+ case '--payload':
333
+ case '-p':
334
+ try {
335
+ payload = JSON.parse(value);
336
+ } catch {
337
+ console.error('Invalid JSON payload');
338
+ process.exit(1);
339
+ }
340
+ i++;
341
+ break;
342
+ case '--poll-interval':
343
+ pollInterval = parseInt(value, 10);
344
+ i++;
345
+ break;
346
+ }
347
+ }
348
+ if (!taskId || !agentId) {
349
+ console.error('Error: --task-id and --agent-id are required');
350
+ process.exit(1);
351
+ }
352
+ const config = {
353
+ taskId,
354
+ agentId
355
+ };
356
+ switch(action){
357
+ case 'send':
358
+ {
359
+ if (!command) {
360
+ console.error('Error: --command is required for send action');
361
+ process.exit(1);
362
+ }
363
+ const result = await sendCommand(config, {
364
+ type: command,
365
+ payload
366
+ });
367
+ console.log(JSON.stringify(result, null, 2));
368
+ // Wait for response
369
+ console.log('\n[agent-msg] Waiting for response (30s timeout)...');
370
+ const response = await waitForResponse(config, result.commandId, 30);
371
+ if (response) {
372
+ console.log('[agent-msg] Response received:');
373
+ console.log(JSON.stringify(response, null, 2));
374
+ } else {
375
+ console.log('[agent-msg] No response received (timeout)');
376
+ }
377
+ break;
378
+ }
379
+ case 'listen':
380
+ {
381
+ const processor = new AgentCommandProcessor(config);
382
+ // Handle shutdown signals
383
+ process.on('SIGINT', async ()=>{
384
+ console.log('\n[agent-msg] Received SIGINT, stopping...');
385
+ await processor.stop();
386
+ process.exit(0);
387
+ });
388
+ await processor.start(pollInterval);
389
+ break;
390
+ }
391
+ case 'status':
392
+ {
393
+ const status = await getAgentStatus(config);
394
+ if (status) {
395
+ console.log(JSON.stringify(status, null, 2));
396
+ } else {
397
+ console.log('No status available for agent');
398
+ }
399
+ break;
400
+ }
401
+ default:
402
+ console.error(`Unknown action: ${action}`);
403
+ printHelp();
404
+ process.exit(1);
405
+ }
406
+ }
407
+ // Run if called directly
408
+ if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, '/') || '')) {
409
+ main().catch((err)=>{
410
+ console.error('[agent-msg] Fatal error:', err);
411
+ process.exit(2);
412
+ });
413
+ }
414
+
415
+ //# sourceMappingURL=agent-messaging.js.map