@intrect/openswarm 0.2.0
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/LICENSE +21 -0
- package/README.md +544 -0
- package/config.example.yaml +107 -0
- package/dist/adapters/base.d.ts +8 -0
- package/dist/adapters/base.d.ts.map +1 -0
- package/dist/adapters/base.js +104 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/claude.d.ts +13 -0
- package/dist/adapters/claude.d.ts.map +1 -0
- package/dist/adapters/claude.js +318 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/codex.d.ts +14 -0
- package/dist/adapters/codex.d.ts.map +1 -0
- package/dist/adapters/codex.js +366 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/cryptoQuantAdapter.d.ts +85 -0
- package/dist/adapters/cryptoQuantAdapter.d.ts.map +1 -0
- package/dist/adapters/cryptoQuantAdapter.js +238 -0
- package/dist/adapters/cryptoQuantAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +17 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +47 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/processRegistry.d.ts +38 -0
- package/dist/adapters/processRegistry.d.ts.map +1 -0
- package/dist/adapters/processRegistry.js +147 -0
- package/dist/adapters/processRegistry.js.map +1 -0
- package/dist/adapters/streamBuffer.d.ts +59 -0
- package/dist/adapters/streamBuffer.d.ts.map +1 -0
- package/dist/adapters/streamBuffer.js +123 -0
- package/dist/adapters/streamBuffer.js.map +1 -0
- package/dist/adapters/types.d.ts +65 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +6 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/agents/agentBus.d.ts +160 -0
- package/dist/agents/agentBus.d.ts.map +1 -0
- package/dist/agents/agentBus.js +350 -0
- package/dist/agents/agentBus.js.map +1 -0
- package/dist/agents/agentPair.d.ts +210 -0
- package/dist/agents/agentPair.d.ts.map +1 -0
- package/dist/agents/agentPair.js +420 -0
- package/dist/agents/agentPair.js.map +1 -0
- package/dist/agents/auditor.d.ts +27 -0
- package/dist/agents/auditor.d.ts.map +1 -0
- package/dist/agents/auditor.js +237 -0
- package/dist/agents/auditor.js.map +1 -0
- package/dist/agents/cliStreamParser.d.ts +18 -0
- package/dist/agents/cliStreamParser.d.ts.map +1 -0
- package/dist/agents/cliStreamParser.js +156 -0
- package/dist/agents/cliStreamParser.js.map +1 -0
- package/dist/agents/documenter.d.ts +31 -0
- package/dist/agents/documenter.d.ts.map +1 -0
- package/dist/agents/documenter.js +285 -0
- package/dist/agents/documenter.js.map +1 -0
- package/dist/agents/index.d.ts +10 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +10 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/pairMetrics.d.ts +63 -0
- package/dist/agents/pairMetrics.d.ts.map +1 -0
- package/dist/agents/pairMetrics.js +231 -0
- package/dist/agents/pairMetrics.js.map +1 -0
- package/dist/agents/pairPipeline.d.ts +155 -0
- package/dist/agents/pairPipeline.d.ts.map +1 -0
- package/dist/agents/pairPipeline.js +793 -0
- package/dist/agents/pairPipeline.js.map +1 -0
- package/dist/agents/pairWebhook.d.ts +59 -0
- package/dist/agents/pairWebhook.d.ts.map +1 -0
- package/dist/agents/pairWebhook.js +242 -0
- package/dist/agents/pairWebhook.js.map +1 -0
- package/dist/agents/pipelineFormat.d.ts +11 -0
- package/dist/agents/pipelineFormat.d.ts.map +1 -0
- package/dist/agents/pipelineFormat.js +164 -0
- package/dist/agents/pipelineFormat.js.map +1 -0
- package/dist/agents/pipelineGuards.d.ts +23 -0
- package/dist/agents/pipelineGuards.d.ts.map +1 -0
- package/dist/agents/pipelineGuards.js +175 -0
- package/dist/agents/pipelineGuards.js.map +1 -0
- package/dist/agents/reviewer.d.ts +37 -0
- package/dist/agents/reviewer.d.ts.map +1 -0
- package/dist/agents/reviewer.js +213 -0
- package/dist/agents/reviewer.js.map +1 -0
- package/dist/agents/skillDocumenter.d.ts +23 -0
- package/dist/agents/skillDocumenter.d.ts.map +1 -0
- package/dist/agents/skillDocumenter.js +218 -0
- package/dist/agents/skillDocumenter.js.map +1 -0
- package/dist/agents/tester.d.ts +37 -0
- package/dist/agents/tester.d.ts.map +1 -0
- package/dist/agents/tester.js +308 -0
- package/dist/agents/tester.js.map +1 -0
- package/dist/agents/worker.d.ts +30 -0
- package/dist/agents/worker.d.ts.map +1 -0
- package/dist/agents/worker.js +128 -0
- package/dist/agents/worker.js.map +1 -0
- package/dist/automation/autonomousRunner.d.ts +123 -0
- package/dist/automation/autonomousRunner.d.ts.map +1 -0
- package/dist/automation/autonomousRunner.js +1082 -0
- package/dist/automation/autonomousRunner.js.map +1 -0
- package/dist/automation/ciWorker.d.ts +51 -0
- package/dist/automation/ciWorker.d.ts.map +1 -0
- package/dist/automation/ciWorker.js +282 -0
- package/dist/automation/ciWorker.js.map +1 -0
- package/dist/automation/conflictResolver.d.ts +29 -0
- package/dist/automation/conflictResolver.d.ts.map +1 -0
- package/dist/automation/conflictResolver.js +261 -0
- package/dist/automation/conflictResolver.js.map +1 -0
- package/dist/automation/dailyReporter.d.ts +26 -0
- package/dist/automation/dailyReporter.d.ts.map +1 -0
- package/dist/automation/dailyReporter.js +132 -0
- package/dist/automation/dailyReporter.js.map +1 -0
- package/dist/automation/index.d.ts +7 -0
- package/dist/automation/index.d.ts.map +1 -0
- package/dist/automation/index.js +7 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/longRunningMonitor.d.ts +26 -0
- package/dist/automation/longRunningMonitor.d.ts.map +1 -0
- package/dist/automation/longRunningMonitor.js +231 -0
- package/dist/automation/longRunningMonitor.js.map +1 -0
- package/dist/automation/prOwnership.d.ts +18 -0
- package/dist/automation/prOwnership.d.ts.map +1 -0
- package/dist/automation/prOwnership.js +61 -0
- package/dist/automation/prOwnership.js.map +1 -0
- package/dist/automation/prProcessor.d.ts +62 -0
- package/dist/automation/prProcessor.d.ts.map +1 -0
- package/dist/automation/prProcessor.js +700 -0
- package/dist/automation/prProcessor.js.map +1 -0
- package/dist/automation/runnerExecution.d.ts +49 -0
- package/dist/automation/runnerExecution.d.ts.map +1 -0
- package/dist/automation/runnerExecution.js +663 -0
- package/dist/automation/runnerExecution.js.map +1 -0
- package/dist/automation/runnerState.d.ts +170 -0
- package/dist/automation/runnerState.d.ts.map +1 -0
- package/dist/automation/runnerState.js +495 -0
- package/dist/automation/runnerState.js.map +1 -0
- package/dist/automation/runnerTypes.d.ts +46 -0
- package/dist/automation/runnerTypes.d.ts.map +1 -0
- package/dist/automation/runnerTypes.js +5 -0
- package/dist/automation/runnerTypes.js.map +1 -0
- package/dist/automation/scheduler.d.ts +75 -0
- package/dist/automation/scheduler.d.ts.map +1 -0
- package/dist/automation/scheduler.js +394 -0
- package/dist/automation/scheduler.js.map +1 -0
- package/dist/cli/promptHandler.d.ts +13 -0
- package/dist/cli/promptHandler.d.ts.map +1 -0
- package/dist/cli/promptHandler.js +189 -0
- package/dist/cli/promptHandler.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/config.d.ts +308 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +529 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/eventHub.d.ts +194 -0
- package/dist/core/eventHub.d.ts.map +1 -0
- package/dist/core/eventHub.js +136 -0
- package/dist/core/eventHub.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/service.d.ts +27 -0
- package/dist/core/service.d.ts.map +1 -0
- package/dist/core/service.js +438 -0
- package/dist/core/service.js.map +1 -0
- package/dist/core/traceCollector.d.ts +105 -0
- package/dist/core/traceCollector.d.ts.map +1 -0
- package/dist/core/traceCollector.js +141 -0
- package/dist/core/traceCollector.js.map +1 -0
- package/dist/core/types.d.ts +413 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/discord/discordCore.d.ts +104 -0
- package/dist/discord/discordCore.d.ts.map +1 -0
- package/dist/discord/discordCore.js +698 -0
- package/dist/discord/discordCore.js.map +1 -0
- package/dist/discord/discordHandlers.d.ts +86 -0
- package/dist/discord/discordHandlers.d.ts.map +1 -0
- package/dist/discord/discordHandlers.js +849 -0
- package/dist/discord/discordHandlers.js.map +1 -0
- package/dist/discord/discordPair.d.ts +6 -0
- package/dist/discord/discordPair.d.ts.map +1 -0
- package/dist/discord/discordPair.js +567 -0
- package/dist/discord/discordPair.js.map +1 -0
- package/dist/discord/index.d.ts +4 -0
- package/dist/discord/index.d.ts.map +1 -0
- package/dist/discord/index.js +11 -0
- package/dist/discord/index.js.map +1 -0
- package/dist/github/github.d.ts +236 -0
- package/dist/github/github.d.ts.map +1 -0
- package/dist/github/github.js +535 -0
- package/dist/github/github.js.map +1 -0
- package/dist/github/index.d.ts +2 -0
- package/dist/github/index.d.ts.map +1 -0
- package/dist/github/index.js +2 -0
- package/dist/github/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge/analyzer.d.ts +36 -0
- package/dist/knowledge/analyzer.d.ts.map +1 -0
- package/dist/knowledge/analyzer.js +170 -0
- package/dist/knowledge/analyzer.js.map +1 -0
- package/dist/knowledge/gitInfo.d.ts +10 -0
- package/dist/knowledge/gitInfo.d.ts.map +1 -0
- package/dist/knowledge/gitInfo.js +134 -0
- package/dist/knowledge/gitInfo.js.map +1 -0
- package/dist/knowledge/graph.d.ts +45 -0
- package/dist/knowledge/graph.d.ts.map +1 -0
- package/dist/knowledge/graph.js +262 -0
- package/dist/knowledge/graph.js.map +1 -0
- package/dist/knowledge/graphqlExporter.d.ts +64 -0
- package/dist/knowledge/graphqlExporter.d.ts.map +1 -0
- package/dist/knowledge/graphqlExporter.js +333 -0
- package/dist/knowledge/graphqlExporter.js.map +1 -0
- package/dist/knowledge/index.d.ts +58 -0
- package/dist/knowledge/index.d.ts.map +1 -0
- package/dist/knowledge/index.js +212 -0
- package/dist/knowledge/index.js.map +1 -0
- package/dist/knowledge/repository.d.ts +64 -0
- package/dist/knowledge/repository.d.ts.map +1 -0
- package/dist/knowledge/repository.js +250 -0
- package/dist/knowledge/repository.js.map +1 -0
- package/dist/knowledge/riskOnAnalyzer.d.ts +79 -0
- package/dist/knowledge/riskOnAnalyzer.d.ts.map +1 -0
- package/dist/knowledge/riskOnAnalyzer.js +243 -0
- package/dist/knowledge/riskOnAnalyzer.js.map +1 -0
- package/dist/knowledge/scanner.d.ts +14 -0
- package/dist/knowledge/scanner.d.ts.map +1 -0
- package/dist/knowledge/scanner.js +350 -0
- package/dist/knowledge/scanner.js.map +1 -0
- package/dist/knowledge/store.d.ts +23 -0
- package/dist/knowledge/store.d.ts.map +1 -0
- package/dist/knowledge/store.js +86 -0
- package/dist/knowledge/store.js.map +1 -0
- package/dist/knowledge/types.d.ts +288 -0
- package/dist/knowledge/types.d.ts.map +1 -0
- package/dist/knowledge/types.js +111 -0
- package/dist/knowledge/types.js.map +1 -0
- package/dist/linear/index.d.ts +3 -0
- package/dist/linear/index.d.ts.map +1 -0
- package/dist/linear/index.js +3 -0
- package/dist/linear/index.js.map +1 -0
- package/dist/linear/linear.d.ts +160 -0
- package/dist/linear/linear.d.ts.map +1 -0
- package/dist/linear/linear.js +983 -0
- package/dist/linear/linear.js.map +1 -0
- package/dist/linear/projectUpdater.d.ts +23 -0
- package/dist/linear/projectUpdater.d.ts.map +1 -0
- package/dist/linear/projectUpdater.js +347 -0
- package/dist/linear/projectUpdater.js.map +1 -0
- package/dist/locale/en.d.ts +3 -0
- package/dist/locale/en.d.ts.map +1 -0
- package/dist/locale/en.js +436 -0
- package/dist/locale/en.js.map +1 -0
- package/dist/locale/index.d.ts +28 -0
- package/dist/locale/index.d.ts.map +1 -0
- package/dist/locale/index.js +89 -0
- package/dist/locale/index.js.map +1 -0
- package/dist/locale/ko.d.ts +3 -0
- package/dist/locale/ko.d.ts.map +1 -0
- package/dist/locale/ko.js +436 -0
- package/dist/locale/ko.js.map +1 -0
- package/dist/locale/prompts/en.d.ts +3 -0
- package/dist/locale/prompts/en.d.ts.map +1 -0
- package/dist/locale/prompts/en.js +278 -0
- package/dist/locale/prompts/en.js.map +1 -0
- package/dist/locale/prompts/ko.d.ts +3 -0
- package/dist/locale/prompts/ko.d.ts.map +1 -0
- package/dist/locale/prompts/ko.js +279 -0
- package/dist/locale/prompts/ko.js.map +1 -0
- package/dist/locale/types.d.ts +407 -0
- package/dist/locale/types.d.ts.map +1 -0
- package/dist/locale/types.js +5 -0
- package/dist/locale/types.js.map +1 -0
- package/dist/memory/codex.d.ts +93 -0
- package/dist/memory/codex.d.ts.map +1 -0
- package/dist/memory/codex.js +366 -0
- package/dist/memory/codex.js.map +1 -0
- package/dist/memory/compaction.d.ts +21 -0
- package/dist/memory/compaction.d.ts.map +1 -0
- package/dist/memory/compaction.js +205 -0
- package/dist/memory/compaction.js.map +1 -0
- package/dist/memory/index.d.ts +13 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +13 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/memoryCore.d.ts +178 -0
- package/dist/memory/memoryCore.d.ts.map +1 -0
- package/dist/memory/memoryCore.js +623 -0
- package/dist/memory/memoryCore.js.map +1 -0
- package/dist/memory/memoryOps.d.ts +90 -0
- package/dist/memory/memoryOps.d.ts.map +1 -0
- package/dist/memory/memoryOps.js +606 -0
- package/dist/memory/memoryOps.js.map +1 -0
- package/dist/orchestration/conflictDetector.d.ts +15 -0
- package/dist/orchestration/conflictDetector.d.ts.map +1 -0
- package/dist/orchestration/conflictDetector.js +130 -0
- package/dist/orchestration/conflictDetector.js.map +1 -0
- package/dist/orchestration/decisionEngine.d.ts +177 -0
- package/dist/orchestration/decisionEngine.d.ts.map +1 -0
- package/dist/orchestration/decisionEngine.js +496 -0
- package/dist/orchestration/decisionEngine.js.map +1 -0
- package/dist/orchestration/index.d.ts +5 -0
- package/dist/orchestration/index.d.ts.map +1 -0
- package/dist/orchestration/index.js +5 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/orchestration/taskParser.d.ts +67 -0
- package/dist/orchestration/taskParser.d.ts.map +1 -0
- package/dist/orchestration/taskParser.js +542 -0
- package/dist/orchestration/taskParser.js.map +1 -0
- package/dist/orchestration/taskScheduler.d.ts +141 -0
- package/dist/orchestration/taskScheduler.d.ts.map +1 -0
- package/dist/orchestration/taskScheduler.js +317 -0
- package/dist/orchestration/taskScheduler.js.map +1 -0
- package/dist/orchestration/workflow.d.ts +145 -0
- package/dist/orchestration/workflow.d.ts.map +1 -0
- package/dist/orchestration/workflow.js +301 -0
- package/dist/orchestration/workflow.js.map +1 -0
- package/dist/runners/cliRunner.d.ts +11 -0
- package/dist/runners/cliRunner.d.ts.map +1 -0
- package/dist/runners/cliRunner.js +194 -0
- package/dist/runners/cliRunner.js.map +1 -0
- package/dist/support/apiCache.d.ts +85 -0
- package/dist/support/apiCache.d.ts.map +1 -0
- package/dist/support/apiCache.js +163 -0
- package/dist/support/apiCache.js.map +1 -0
- package/dist/support/chat.d.ts +3 -0
- package/dist/support/chat.d.ts.map +1 -0
- package/dist/support/chat.js +304 -0
- package/dist/support/chat.js.map +1 -0
- package/dist/support/chatBackend.d.ts +25 -0
- package/dist/support/chatBackend.d.ts.map +1 -0
- package/dist/support/chatBackend.js +235 -0
- package/dist/support/chatBackend.js.map +1 -0
- package/dist/support/chatMemory.d.ts +71 -0
- package/dist/support/chatMemory.d.ts.map +1 -0
- package/dist/support/chatMemory.js +119 -0
- package/dist/support/chatMemory.js.map +1 -0
- package/dist/support/chatTui.d.ts +3 -0
- package/dist/support/chatTui.d.ts.map +1 -0
- package/dist/support/chatTui.js +998 -0
- package/dist/support/chatTui.js.map +1 -0
- package/dist/support/costTracker.d.ts +29 -0
- package/dist/support/costTracker.d.ts.map +1 -0
- package/dist/support/costTracker.js +113 -0
- package/dist/support/costTracker.js.map +1 -0
- package/dist/support/dashboardHtml.d.ts +3 -0
- package/dist/support/dashboardHtml.d.ts.map +1 -0
- package/dist/support/dashboardHtml.js +2070 -0
- package/dist/support/dashboardHtml.js.map +1 -0
- package/dist/support/delete-beliefs.d.ts +2 -0
- package/dist/support/delete-beliefs.d.ts.map +1 -0
- package/dist/support/delete-beliefs.js +34 -0
- package/dist/support/delete-beliefs.js.map +1 -0
- package/dist/support/dev.d.ts +55 -0
- package/dist/support/dev.d.ts.map +1 -0
- package/dist/support/dev.js +298 -0
- package/dist/support/dev.js.map +1 -0
- package/dist/support/editParser.d.ts +37 -0
- package/dist/support/editParser.d.ts.map +1 -0
- package/dist/support/editParser.js +365 -0
- package/dist/support/editParser.js.map +1 -0
- package/dist/support/gitStatus.d.ts +21 -0
- package/dist/support/gitStatus.d.ts.map +1 -0
- package/dist/support/gitStatus.js +108 -0
- package/dist/support/gitStatus.js.map +1 -0
- package/dist/support/gitTracker.d.ts +30 -0
- package/dist/support/gitTracker.d.ts.map +1 -0
- package/dist/support/gitTracker.js +143 -0
- package/dist/support/gitTracker.js.map +1 -0
- package/dist/support/index.d.ts +13 -0
- package/dist/support/index.d.ts.map +1 -0
- package/dist/support/index.js +13 -0
- package/dist/support/index.js.map +1 -0
- package/dist/support/planner.d.ts +58 -0
- package/dist/support/planner.d.ts.map +1 -0
- package/dist/support/planner.js +395 -0
- package/dist/support/planner.js.map +1 -0
- package/dist/support/projectMapper.d.ts +46 -0
- package/dist/support/projectMapper.d.ts.map +1 -0
- package/dist/support/projectMapper.js +259 -0
- package/dist/support/projectMapper.js.map +1 -0
- package/dist/support/quotaTracker.d.ts +29 -0
- package/dist/support/quotaTracker.d.ts.map +1 -0
- package/dist/support/quotaTracker.js +89 -0
- package/dist/support/quotaTracker.js.map +1 -0
- package/dist/support/rateLimiter.d.ts +101 -0
- package/dist/support/rateLimiter.d.ts.map +1 -0
- package/dist/support/rateLimiter.js +219 -0
- package/dist/support/rateLimiter.js.map +1 -0
- package/dist/support/rollback.d.ts +61 -0
- package/dist/support/rollback.d.ts.map +1 -0
- package/dist/support/rollback.js +328 -0
- package/dist/support/rollback.js.map +1 -0
- package/dist/support/stuckDetector.d.ts +68 -0
- package/dist/support/stuckDetector.d.ts.map +1 -0
- package/dist/support/stuckDetector.js +174 -0
- package/dist/support/stuckDetector.js.map +1 -0
- package/dist/support/timeWindow.d.ts +60 -0
- package/dist/support/timeWindow.d.ts.map +1 -0
- package/dist/support/timeWindow.js +236 -0
- package/dist/support/timeWindow.js.map +1 -0
- package/dist/support/web.d.ts +11 -0
- package/dist/support/web.d.ts.map +1 -0
- package/dist/support/web.js +938 -0
- package/dist/support/web.js.map +1 -0
- package/dist/support/workflowLinear.d.ts +99 -0
- package/dist/support/workflowLinear.d.ts.map +1 -0
- package/dist/support/workflowLinear.js +257 -0
- package/dist/support/workflowLinear.js.map +1 -0
- package/dist/support/worktreeManager.d.ts +20 -0
- package/dist/support/worktreeManager.d.ts.map +1 -0
- package/dist/support/worktreeManager.js +144 -0
- package/dist/support/worktreeManager.js.map +1 -0
- package/dist/taskState/store.d.ts +101 -0
- package/dist/taskState/store.d.ts.map +1 -0
- package/dist/taskState/store.js +346 -0
- package/dist/taskState/store.js.map +1 -0
- package/package.json +70 -0
- package/templates/AGENTS.md +432 -0
- package/templates/BOOT.md +25 -0
- package/templates/BOOTSTRAP.md +50 -0
- package/templates/CHANGELOG_AUDIT.md +74 -0
- package/templates/HEARTBEAT.md +86 -0
- package/templates/IDENTITY.md +27 -0
- package/templates/ISSUE_ANALYSIS.md +31 -0
- package/templates/PR_LAND.md +75 -0
- package/templates/PR_REVIEW.md +97 -0
- package/templates/SOUL.dev.md +52 -0
- package/templates/SOUL.md +81 -0
- package/templates/TOOLS.md +52 -0
- package/templates/USER.md +22 -0
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
// OpenSwarm - Autonomous Runner
|
|
2
|
+
// Heartbeat → Decision → Execution → Report
|
|
3
|
+
import { Cron } from 'croner';
|
|
4
|
+
import { loadTaskState, saveTaskState, buildProjectsInfo, appendPipelineHistory, getPipelineHistory, incrementRejection, clearRejection, isRejectionLimitReached, canRetryNow, setRetryTime, clearRetryTime, formatRetryTime, getDailyCompletedCount, getDailyPaceInfo, recordProjectCompletion, canProjectAcceptTask, getProjectWindowCount, } from './runnerState.js';
|
|
5
|
+
import { getDecisionEngine, } from '../orchestration/decisionEngine.js';
|
|
6
|
+
// ExecutorResult used via execution.reportExecutionResult
|
|
7
|
+
import { checkWorkAllowed } from '../support/timeWindow.js';
|
|
8
|
+
import { saveCognitiveMemory } from '../memory/index.js';
|
|
9
|
+
import * as linear from '../linear/index.js';
|
|
10
|
+
import { updateProjectAfterTask } from '../linear/projectUpdater.js';
|
|
11
|
+
import { initScheduler } from '../orchestration/taskScheduler.js';
|
|
12
|
+
import { formatPipelineResultEmbed, } from '../agents/pairPipeline.js';
|
|
13
|
+
import * as planner from '../support/planner.js';
|
|
14
|
+
import * as execution from './runnerExecution.js';
|
|
15
|
+
import { reportToDiscord, fetchLinearTasks } from './runnerExecution.js';
|
|
16
|
+
import { t } from '../locale/index.js';
|
|
17
|
+
import { broadcastEvent } from '../core/eventHub.js';
|
|
18
|
+
import { pruneWorktrees } from '../support/worktreeManager.js';
|
|
19
|
+
import { refreshGraph, toProjectSlug } from '../knowledge/index.js';
|
|
20
|
+
import { checkAllMonitors, getActiveMonitors } from './longRunningMonitor.js';
|
|
21
|
+
import { detectFileConflicts } from '../orchestration/conflictDetector.js';
|
|
22
|
+
import { checkQuotaAllowance } from '../support/quotaTracker.js';
|
|
23
|
+
// Re-export types and integration setters (used by service.ts)
|
|
24
|
+
export { setDiscordReporter, setLinearFetcher } from './runnerExecution.js';
|
|
25
|
+
let runnerInstance = null;
|
|
26
|
+
export class AutonomousRunner {
|
|
27
|
+
config;
|
|
28
|
+
engine;
|
|
29
|
+
scheduler;
|
|
30
|
+
cronJob = null;
|
|
31
|
+
state = {
|
|
32
|
+
isRunning: false,
|
|
33
|
+
lastHeartbeat: 0,
|
|
34
|
+
consecutiveErrors: 0,
|
|
35
|
+
};
|
|
36
|
+
// Heartbeat concurrency guard
|
|
37
|
+
_heartbeatRunning = false;
|
|
38
|
+
// Explicitly enabled project paths (allow-list; empty = nothing runs)
|
|
39
|
+
enabledProjects = new Set();
|
|
40
|
+
/** Check if a resolved path is under any enabled project */
|
|
41
|
+
isProjectEnabled(resolvedPath) {
|
|
42
|
+
if (this.enabledProjects.size === 0)
|
|
43
|
+
return false;
|
|
44
|
+
if (this.enabledProjects.has(resolvedPath))
|
|
45
|
+
return true;
|
|
46
|
+
// Check if resolvedPath is a subdirectory of any enabled project
|
|
47
|
+
for (const enabled of this.enabledProjects) {
|
|
48
|
+
if (resolvedPath.startsWith(enabled + '/'))
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// Last fetched Linear tasks (for dashboard display)
|
|
54
|
+
lastFetchedTasks = [];
|
|
55
|
+
// Cache: linearProjectName → resolvedLocalPath (populated during task execution)
|
|
56
|
+
projectPathCache = new Map();
|
|
57
|
+
// Turbo mode: faster heartbeat, higher daily cap, no stage skipping
|
|
58
|
+
turboMode = false;
|
|
59
|
+
turboExpiresAt = null;
|
|
60
|
+
static TURBO_DURATION_MS = 4 * 60 * 60 * 1000; // 4 hours auto-expire
|
|
61
|
+
// Track completed/failed task IDs to prevent re-selection (persisted to disk)
|
|
62
|
+
completedTaskIds = new Set();
|
|
63
|
+
failedTaskCounts = new Map();
|
|
64
|
+
failedTaskRetryTimes = new Map(); // issueId → next retry timestamp (ms)
|
|
65
|
+
static MAX_RETRY_COUNT = 4; // Increased from 2 to allow more retries with backoff
|
|
66
|
+
get taskStateRef() {
|
|
67
|
+
return {
|
|
68
|
+
completedTaskIds: this.completedTaskIds,
|
|
69
|
+
failedTaskCounts: this.failedTaskCounts,
|
|
70
|
+
failedTaskRetryTimes: this.failedTaskRetryTimes,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
loadTaskState() {
|
|
74
|
+
loadTaskState(this.taskStateRef);
|
|
75
|
+
}
|
|
76
|
+
saveTaskState() {
|
|
77
|
+
saveTaskState(this.taskStateRef);
|
|
78
|
+
}
|
|
79
|
+
formatTaskContext(task) {
|
|
80
|
+
const parts = [];
|
|
81
|
+
if (task.linearProject?.name)
|
|
82
|
+
parts.push(`[${task.linearProject.name}]`);
|
|
83
|
+
if (task.issueIdentifier)
|
|
84
|
+
parts.push(task.issueIdentifier);
|
|
85
|
+
else if (task.issueId)
|
|
86
|
+
parts.push(task.issueId.slice(0, 8));
|
|
87
|
+
return parts.length > 0 ? parts.join(' ') : '';
|
|
88
|
+
}
|
|
89
|
+
constructor(config) {
|
|
90
|
+
this.config = config;
|
|
91
|
+
this.loadTaskState(); // Restore completed/failed task IDs from disk
|
|
92
|
+
this.engine = getDecisionEngine({
|
|
93
|
+
allowedProjects: config.allowedProjects,
|
|
94
|
+
linearTeamId: config.linearTeamId,
|
|
95
|
+
autoExecute: config.autoExecute,
|
|
96
|
+
maxConsecutiveTasks: config.maxConsecutiveTasks,
|
|
97
|
+
cooldownSeconds: config.cooldownSeconds,
|
|
98
|
+
dryRun: config.dryRun,
|
|
99
|
+
});
|
|
100
|
+
// Initialize TaskScheduler
|
|
101
|
+
this.scheduler = initScheduler({
|
|
102
|
+
maxConcurrent: config.maxConcurrentTasks ?? 1,
|
|
103
|
+
allowSameProjectConcurrent: false,
|
|
104
|
+
worktreeMode: config.worktreeMode ?? false,
|
|
105
|
+
});
|
|
106
|
+
// Set up scheduler event handling
|
|
107
|
+
this.setupSchedulerEvents();
|
|
108
|
+
}
|
|
109
|
+
setupSchedulerEvents() {
|
|
110
|
+
this.scheduler.on('started', async (running) => {
|
|
111
|
+
const taskCtx = this.formatTaskContext(running.task);
|
|
112
|
+
console.log(`[Scheduler] Task started: ${taskCtx} ${running.task.title}`);
|
|
113
|
+
broadcastEvent({ type: 'task:started', data: { taskId: running.task.id, title: running.task.title, issueIdentifier: running.task.issueIdentifier } });
|
|
114
|
+
});
|
|
115
|
+
this.scheduler.on('completed', async ({ task, result }) => {
|
|
116
|
+
const taskCtx = this.formatTaskContext(task);
|
|
117
|
+
console.log(`[Scheduler] Task completed: ${taskCtx} ${task.title}`);
|
|
118
|
+
broadcastEvent({ type: 'task:completed', data: { taskId: task.id, success: result.success, duration: result.totalDuration } });
|
|
119
|
+
this.recordPipelineHistory(task, result);
|
|
120
|
+
await reportToDiscord(formatPipelineResultEmbed(result));
|
|
121
|
+
// Track as completed ONLY on success to prevent re-selection (persist to disk)
|
|
122
|
+
if (task.issueId && result.success) {
|
|
123
|
+
this.completedTaskIds.add(task.issueId);
|
|
124
|
+
clearRejection(task.issueId); // Clear rejection count on success
|
|
125
|
+
clearRetryTime(task.issueId, this.failedTaskRetryTimes); // Clear retry backoff time
|
|
126
|
+
this.saveTaskState();
|
|
127
|
+
// Track project-level pace (5h rolling window)
|
|
128
|
+
const projectName = task.linearProject?.name ?? 'unknown';
|
|
129
|
+
recordProjectCompletion(projectName, result.totalCost?.costUsd);
|
|
130
|
+
}
|
|
131
|
+
// Skip completion handling for decomposed tasks. Child issues represent the runnable work.
|
|
132
|
+
if (result.finalStatus === 'decomposed') {
|
|
133
|
+
console.log(`[Scheduler] Task decomposed into sub-issues, skipping Done state`);
|
|
134
|
+
this.scheduleNextHeartbeat();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// On success, update Linear issue to Done
|
|
138
|
+
if (result.success && task.issueId) {
|
|
139
|
+
try {
|
|
140
|
+
await execution.syncSuccessState(task);
|
|
141
|
+
await linear.logPairComplete(task.issueId, result.sessionId, {
|
|
142
|
+
attempts: result.iterations,
|
|
143
|
+
duration: Math.floor(result.totalDuration / 1000),
|
|
144
|
+
filesChanged: result.workerResult?.filesChanged || [],
|
|
145
|
+
workerSummary: result.workerResult?.summary,
|
|
146
|
+
workerCommands: result.workerResult?.commands,
|
|
147
|
+
reviewerFeedback: result.reviewResult?.feedback,
|
|
148
|
+
reviewerDecision: result.reviewResult?.decision,
|
|
149
|
+
testResults: result.testerResult ? {
|
|
150
|
+
passed: result.testerResult.testsPassed,
|
|
151
|
+
failed: result.testerResult.testsFailed,
|
|
152
|
+
coverage: result.testerResult.coverage,
|
|
153
|
+
failedTests: result.testerResult.failedTests,
|
|
154
|
+
} : undefined,
|
|
155
|
+
});
|
|
156
|
+
await execution.reconcileCompletionState(task);
|
|
157
|
+
console.log(`[Scheduler] Issue ${task.issueId} marked as Done`);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
console.error(`[Scheduler] Failed to update issue state:`, err);
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
await saveCognitiveMemory('strategy', `Pipeline execution succeeded: "${task.title}"`, { confidence: 0.9, derivedFrom: task.issueId });
|
|
164
|
+
}
|
|
165
|
+
catch (memErr) {
|
|
166
|
+
console.warn(`[Scheduler] Memory save failed (non-critical):`, memErr);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Linear project Status Update + Overview refresh (non-blocking)
|
|
170
|
+
if (task.linearProject) {
|
|
171
|
+
updateProjectAfterTask(task.linearProject.id, task.linearProject.name, {
|
|
172
|
+
title: task.title,
|
|
173
|
+
success: result.success,
|
|
174
|
+
duration: result.totalDuration,
|
|
175
|
+
issueIdentifier: task.issueIdentifier,
|
|
176
|
+
cost: result.totalCost?.costUsd,
|
|
177
|
+
projectPath: result.taskContext?.projectPath,
|
|
178
|
+
}).catch(e => console.warn('[Scheduler] Project update failed:', e));
|
|
179
|
+
}
|
|
180
|
+
this.scheduleNextHeartbeat();
|
|
181
|
+
});
|
|
182
|
+
this.scheduler.on('failed', async ({ task, result }) => {
|
|
183
|
+
const taskCtx = this.formatTaskContext(task);
|
|
184
|
+
console.log(`[Scheduler] Task failed: ${taskCtx} ${task.title}`);
|
|
185
|
+
broadcastEvent({ type: 'task:completed', data: { taskId: task.id, success: false, duration: result.totalDuration } });
|
|
186
|
+
this.recordPipelineHistory(task, result);
|
|
187
|
+
await reportToDiscord(formatPipelineResultEmbed(result));
|
|
188
|
+
// If rejected, track rejection count and block after max attempts
|
|
189
|
+
if (task.issueId && result.finalStatus === 'rejected') {
|
|
190
|
+
const feedback = result.reviewResult?.feedback || 'No feedback provided';
|
|
191
|
+
const rejectionCount = incrementRejection(task.issueId, feedback);
|
|
192
|
+
console.log(`[Scheduler] Task rejected (${rejectionCount}/3): ${taskCtx} ${task.title}`);
|
|
193
|
+
console.log(`[Scheduler] Rejection reason: ${feedback}`);
|
|
194
|
+
if (isRejectionLimitReached(task.issueId)) {
|
|
195
|
+
// Max rejections reached - permanently block
|
|
196
|
+
this.completedTaskIds.add(task.issueId); // Prevent re-selection
|
|
197
|
+
clearRetryTime(task.issueId, this.failedTaskRetryTimes); // Clear retry time
|
|
198
|
+
this.saveTaskState();
|
|
199
|
+
try {
|
|
200
|
+
await execution.syncFailureState(task, `Max rejection limit reached (${rejectionCount} attempts): ${feedback}`);
|
|
201
|
+
await linear.logBlocked(task.issueId, 'autonomous-runner', `⚠️ **Max rejection limit reached (${rejectionCount} attempts)**\n\n` +
|
|
202
|
+
`This task has been rejected ${rejectionCount} times by the reviewer and requires manual intervention.\n\n` +
|
|
203
|
+
`**Latest rejection reason:**\n${feedback}\n\n` +
|
|
204
|
+
`**Action required:** Please review the task requirements and code manually, or adjust the task scope.`);
|
|
205
|
+
console.log(`[Scheduler] Issue ${task.issueId} permanently blocked (max rejections reached)`);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
console.error(`[Scheduler] Failed to update issue state:`, err);
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// Not max yet - schedule retry with exponential backoff
|
|
214
|
+
const nextRetryTime = setRetryTime(task.issueId, rejectionCount, this.failedTaskRetryTimes);
|
|
215
|
+
const retryIn = formatRetryTime(nextRetryTime);
|
|
216
|
+
this.saveTaskState();
|
|
217
|
+
try {
|
|
218
|
+
await execution.syncFailureState(task, `Review rejected (${rejectionCount}/3): ${feedback}`);
|
|
219
|
+
await linear.logBlocked(task.issueId, 'autonomous-runner', t('runner.reviewRejected', { feedback }) +
|
|
220
|
+
`\n\n**Rejection count:** ${rejectionCount}/3 - Will retry automatically ${retryIn}.`);
|
|
221
|
+
console.log(`[Scheduler] Issue ${task.issueId} marked as Todo (blocked) (rejected ${rejectionCount}/3) — retry ${retryIn}`);
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
console.error(`[Scheduler] Failed to update issue state:`, err);
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Track failure count — block after MAX_RETRY_COUNT failures
|
|
230
|
+
if (task.issueId) {
|
|
231
|
+
const count = (this.failedTaskCounts.get(task.issueId) ?? 0) + 1;
|
|
232
|
+
this.failedTaskCounts.set(task.issueId, count);
|
|
233
|
+
if (count >= AutonomousRunner.MAX_RETRY_COUNT) {
|
|
234
|
+
// Max retries exceeded - permanently block
|
|
235
|
+
this.completedTaskIds.add(task.issueId); // Prevent re-selection
|
|
236
|
+
clearRetryTime(task.issueId, this.failedTaskRetryTimes); // Clear retry time
|
|
237
|
+
this.saveTaskState();
|
|
238
|
+
console.log(`[Scheduler] Task failure count: ${count}/${AutonomousRunner.MAX_RETRY_COUNT} for ${taskCtx} — BLOCKED`);
|
|
239
|
+
try {
|
|
240
|
+
await execution.syncFailureState(task, `Autonomous execution failed ${count} times`);
|
|
241
|
+
await linear.logBlocked(task.issueId, 'autonomous-runner', `Autonomous execution failed ${count} times. Moving to Blocked for manual review.`);
|
|
242
|
+
console.log(`[Scheduler] Issue ${task.issueId} marked as Todo (blocked) (max retries exceeded)`);
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
console.error(`[Scheduler] Failed to update issue state:`, err);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// Schedule retry with exponential backoff
|
|
250
|
+
const nextRetryTime = setRetryTime(task.issueId, count, this.failedTaskRetryTimes);
|
|
251
|
+
const retryIn = formatRetryTime(nextRetryTime);
|
|
252
|
+
console.log(`[Scheduler] Task failure count: ${count}/${AutonomousRunner.MAX_RETRY_COUNT} for ${taskCtx} — retry ${retryIn}`);
|
|
253
|
+
this.saveTaskState();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Linear project Status Update + Overview refresh (non-blocking)
|
|
257
|
+
if (task.linearProject) {
|
|
258
|
+
updateProjectAfterTask(task.linearProject.id, task.linearProject.name, {
|
|
259
|
+
title: task.title,
|
|
260
|
+
success: result.success,
|
|
261
|
+
duration: result.totalDuration,
|
|
262
|
+
issueIdentifier: task.issueIdentifier,
|
|
263
|
+
cost: result.totalCost?.costUsd,
|
|
264
|
+
projectPath: result.taskContext?.projectPath,
|
|
265
|
+
}).catch(e => console.warn('[Scheduler] Project update failed:', e));
|
|
266
|
+
}
|
|
267
|
+
this.scheduleNextHeartbeat();
|
|
268
|
+
});
|
|
269
|
+
this.scheduler.on('error', async ({ task, error }) => {
|
|
270
|
+
const taskCtx = this.formatTaskContext(task);
|
|
271
|
+
console.error(`[Scheduler] Task error: ${taskCtx} ${task.title}`, error);
|
|
272
|
+
await reportToDiscord(t('runner.pipelineError', { title: `${taskCtx} ${task.title}`, error: error.message }));
|
|
273
|
+
});
|
|
274
|
+
this.scheduler.on('slotFreed', () => {
|
|
275
|
+
// Auto-execute next task when slot becomes available
|
|
276
|
+
void this.runAvailableTasks();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
filterAlreadyProcessed(tasks) {
|
|
280
|
+
let recovered = 0;
|
|
281
|
+
let backoffSkipped = 0;
|
|
282
|
+
const recoverableStates = new Set(['Todo', 'In Progress', 'In Review']);
|
|
283
|
+
const filtered = tasks.filter(task => {
|
|
284
|
+
const id = task.issueId || task.id;
|
|
285
|
+
// Check rejection limit first
|
|
286
|
+
if (isRejectionLimitReached(id)) {
|
|
287
|
+
return false; // Skip tasks that hit max rejection limit
|
|
288
|
+
}
|
|
289
|
+
// Recover issues in active states from completed/failed list
|
|
290
|
+
// (user or system intentionally moved back to active, so retry)
|
|
291
|
+
if (recoverableStates.has(task.linearState || '') && (this.completedTaskIds.has(id) || (this.failedTaskCounts.get(id) ?? 0) >= AutonomousRunner.MAX_RETRY_COUNT)) {
|
|
292
|
+
this.completedTaskIds.delete(id);
|
|
293
|
+
this.failedTaskCounts.delete(id);
|
|
294
|
+
clearRejection(id); // Clear rejection count on recovery
|
|
295
|
+
clearRetryTime(id, this.failedTaskRetryTimes); // Clear retry backoff time
|
|
296
|
+
recovered++;
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (this.completedTaskIds.has(id))
|
|
300
|
+
return false;
|
|
301
|
+
if ((this.failedTaskCounts.get(id) ?? 0) >= AutonomousRunner.MAX_RETRY_COUNT)
|
|
302
|
+
return false;
|
|
303
|
+
// Check if task is in exponential backoff period
|
|
304
|
+
if (!canRetryNow(id, this.failedTaskRetryTimes)) {
|
|
305
|
+
backoffSkipped++;
|
|
306
|
+
return false; // Skip tasks still in backoff period
|
|
307
|
+
}
|
|
308
|
+
return true;
|
|
309
|
+
});
|
|
310
|
+
if (recovered > 0) {
|
|
311
|
+
this.saveTaskState();
|
|
312
|
+
this.syslog(`♻ Recovered ${recovered} Todo issues from completed/failed/rejected list`);
|
|
313
|
+
}
|
|
314
|
+
if (backoffSkipped > 0) {
|
|
315
|
+
this.syslog(`⏰ Skipped ${backoffSkipped} tasks in exponential backoff period`);
|
|
316
|
+
}
|
|
317
|
+
return filtered;
|
|
318
|
+
}
|
|
319
|
+
/** Schedule next heartbeat with pace-aware cooldown */
|
|
320
|
+
_nextHeartbeatTimer = null;
|
|
321
|
+
scheduleNextHeartbeat() {
|
|
322
|
+
if (this._nextHeartbeatTimer)
|
|
323
|
+
return; // already scheduled
|
|
324
|
+
const isTurbo = this.getTurboMode();
|
|
325
|
+
// Turbo: 5min flat, no progressive slowdown
|
|
326
|
+
if (isTurbo) {
|
|
327
|
+
const turboCooldown = 5 * 60_000; // 5min
|
|
328
|
+
console.log(`[AutonomousRunner] TURBO: next heartbeat in 5min`);
|
|
329
|
+
this._nextHeartbeatTimer = setTimeout(() => {
|
|
330
|
+
this._nextHeartbeatTimer = null;
|
|
331
|
+
void this.heartbeat();
|
|
332
|
+
}, turboCooldown);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
// Normal: progressive slowdown based on 5h window usage
|
|
336
|
+
const perProjectCap = this.config.dailyTaskCap ?? 6;
|
|
337
|
+
const globalCap = Math.max(this.enabledProjects.size, 3) * perProjectCap;
|
|
338
|
+
const baseCooldown = this.config.interTaskCooldownMs ?? 1_800_000; // 30min default
|
|
339
|
+
const totalInWindow = getDailyCompletedCount();
|
|
340
|
+
// Progressive slowdown: ratio² × 3 multiplier
|
|
341
|
+
const ratio = totalInWindow / globalCap;
|
|
342
|
+
const multiplier = 1 + (ratio * ratio * 3);
|
|
343
|
+
const adjustedCooldown = Math.round(baseCooldown * multiplier);
|
|
344
|
+
const cooldownMin = Math.round(adjustedCooldown / 60_000);
|
|
345
|
+
console.log(`[AutonomousRunner] Scheduling next heartbeat in ${cooldownMin}min (5h window: ${totalInWindow}/${globalCap}, multiplier: ${multiplier.toFixed(2)}x)`);
|
|
346
|
+
this._nextHeartbeatTimer = setTimeout(() => {
|
|
347
|
+
this._nextHeartbeatTimer = null;
|
|
348
|
+
void this.heartbeat();
|
|
349
|
+
}, adjustedCooldown);
|
|
350
|
+
}
|
|
351
|
+
async runAvailableTasks() {
|
|
352
|
+
if (!this.config.pairMode || !this.config.maxConcurrentTasks) {
|
|
353
|
+
return; // Parallel processing disabled
|
|
354
|
+
}
|
|
355
|
+
await this.scheduler.runAvailable(async (task, projectPath) => {
|
|
356
|
+
return this.executePipeline(task, projectPath);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
getRolesForProject(projectPath) {
|
|
360
|
+
// Find per-project configuration
|
|
361
|
+
const projectConfig = this.config.projectAgents?.find(pa => projectPath.includes(pa.projectPath.replace('~', '')));
|
|
362
|
+
if (!projectConfig?.roles && !this.config.defaultRoles) {
|
|
363
|
+
// Convert from legacy configuration
|
|
364
|
+
return {
|
|
365
|
+
worker: {
|
|
366
|
+
enabled: true,
|
|
367
|
+
model: this.config.workerModel || 'claude-sonnet-4-5-20250929',
|
|
368
|
+
timeoutMs: this.config.workerTimeoutMs ?? 0,
|
|
369
|
+
},
|
|
370
|
+
reviewer: {
|
|
371
|
+
enabled: true,
|
|
372
|
+
model: this.config.reviewerModel || 'claude-haiku-4-5-20251001',
|
|
373
|
+
timeoutMs: this.config.reviewerTimeoutMs ?? 0,
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
// Apply per-project overrides
|
|
378
|
+
const base = this.config.defaultRoles || {
|
|
379
|
+
worker: { enabled: true, model: 'claude-sonnet-4-5-20250929', timeoutMs: 0 },
|
|
380
|
+
reviewer: { enabled: true, model: 'claude-haiku-4-5-20251001', timeoutMs: 0 },
|
|
381
|
+
};
|
|
382
|
+
if (!projectConfig?.roles) {
|
|
383
|
+
return base;
|
|
384
|
+
}
|
|
385
|
+
// Merge overrides
|
|
386
|
+
return {
|
|
387
|
+
worker: { ...base.worker, ...projectConfig.roles.worker },
|
|
388
|
+
reviewer: { ...base.reviewer, ...projectConfig.roles.reviewer },
|
|
389
|
+
tester: projectConfig.roles.tester
|
|
390
|
+
? { ...base.tester, ...projectConfig.roles.tester }
|
|
391
|
+
: base.tester,
|
|
392
|
+
documenter: projectConfig.roles.documenter
|
|
393
|
+
? { ...base.documenter, ...projectConfig.roles.documenter }
|
|
394
|
+
: base.documenter,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
async start() {
|
|
398
|
+
if (this.state.isRunning) {
|
|
399
|
+
console.log('[AutonomousRunner] Already running');
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
await this.engine.init();
|
|
403
|
+
// worktree mode: clean up dangling worktrees at startup
|
|
404
|
+
if (this.config.worktreeMode) {
|
|
405
|
+
for (const projectPath of this.config.allowedProjects) {
|
|
406
|
+
const resolvedPath = projectPath.replace('~', process.env.HOME || '');
|
|
407
|
+
pruneWorktrees(resolvedPath).catch((e) => console.error(`[AutonomousRunner] Worktree prune failed for ${resolvedPath}:`, e));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Set up cron job
|
|
411
|
+
this.cronJob = new Cron(this.config.heartbeatSchedule, async () => {
|
|
412
|
+
await this.heartbeat();
|
|
413
|
+
});
|
|
414
|
+
this.state.isRunning = true;
|
|
415
|
+
this.state.startedAt = Date.now();
|
|
416
|
+
console.log(`[AutonomousRunner] Started with schedule: ${this.config.heartbeatSchedule}`);
|
|
417
|
+
await reportToDiscord(`🤖 ${t('runner.modeStarted')}\n` +
|
|
418
|
+
`Schedule: \`${this.config.heartbeatSchedule}\`\n` +
|
|
419
|
+
`Auto-execute: ${this.config.autoExecute ? '✅' : '❌'}\n` +
|
|
420
|
+
`Projects: ${this.config.allowedProjects.join(', ')}`);
|
|
421
|
+
// Immediate execution option
|
|
422
|
+
if (this.config.triggerNow) {
|
|
423
|
+
console.log('[AutonomousRunner] Triggering immediate heartbeat in 10s...');
|
|
424
|
+
setTimeout(() => void this.heartbeat(), 10000); // Run after 10s (wait for Discord/Linear connection)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
stop() {
|
|
428
|
+
if (this.cronJob) {
|
|
429
|
+
this.cronJob.stop();
|
|
430
|
+
this.cronJob = null;
|
|
431
|
+
}
|
|
432
|
+
this.state.isRunning = false;
|
|
433
|
+
console.log('[AutonomousRunner] Stopped');
|
|
434
|
+
}
|
|
435
|
+
buildStats() {
|
|
436
|
+
const stats = this.scheduler.getStats();
|
|
437
|
+
return {
|
|
438
|
+
runningTasks: stats.running,
|
|
439
|
+
queuedTasks: stats.queued,
|
|
440
|
+
completedToday: stats.completed,
|
|
441
|
+
uptime: this.state.startedAt ? Date.now() - this.state.startedAt : 0,
|
|
442
|
+
schedulerPaused: this.scheduler.isPaused(),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
refreshKnowledgeGraphs() {
|
|
446
|
+
for (const projectPath of this.config.allowedProjects) {
|
|
447
|
+
const resolvedPath = projectPath.replace('~', process.env.HOME || '');
|
|
448
|
+
refreshGraph(resolvedPath).then(graph => {
|
|
449
|
+
if (graph) {
|
|
450
|
+
const slug = toProjectSlug(resolvedPath);
|
|
451
|
+
broadcastEvent({
|
|
452
|
+
type: 'knowledge:updated',
|
|
453
|
+
data: { projectSlug: slug, nodeCount: graph.nodeCount, edgeCount: graph.edgeCount },
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}).catch((e) => {
|
|
457
|
+
console.error(`[AutonomousRunner] Knowledge graph refresh failed for ${resolvedPath}:`, e);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/** Send system message to dashboard LIVE LOG */
|
|
462
|
+
syslog(line) {
|
|
463
|
+
console.log(`[HB] ${line}`);
|
|
464
|
+
broadcastEvent({ type: 'log', data: { taskId: 'system', stage: 'heartbeat', line } });
|
|
465
|
+
}
|
|
466
|
+
async heartbeat() {
|
|
467
|
+
if (this._heartbeatRunning) {
|
|
468
|
+
console.log('[AutonomousRunner] Heartbeat already running, skipping');
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
this._heartbeatRunning = true;
|
|
472
|
+
console.log('[AutonomousRunner] Heartbeat triggered');
|
|
473
|
+
this.state.lastHeartbeat = Date.now();
|
|
474
|
+
broadcastEvent({ type: 'stats', data: this.buildStats() });
|
|
475
|
+
broadcastEvent({ type: 'heartbeat' });
|
|
476
|
+
this.syslog('▶ Heartbeat started');
|
|
477
|
+
try {
|
|
478
|
+
// 0. Knowledge graph refresh (async, service continues even on failure)
|
|
479
|
+
this.refreshKnowledgeGraphs();
|
|
480
|
+
// 0.5 Long-running monitor passive check (before time window)
|
|
481
|
+
const active = getActiveMonitors().filter(m => m.state === 'pending' || m.state === 'running');
|
|
482
|
+
if (active.length > 0) {
|
|
483
|
+
const checked = await checkAllMonitors().catch(() => 0);
|
|
484
|
+
this.syslog(`✓ Monitors: ${checked} checked / ${active.length} active`);
|
|
485
|
+
}
|
|
486
|
+
// 1. Check time window
|
|
487
|
+
const timeCheck = checkWorkAllowed();
|
|
488
|
+
if (!timeCheck.allowed) {
|
|
489
|
+
console.log(`[AutonomousRunner] Blocked: ${timeCheck.reason}`);
|
|
490
|
+
this.syslog(`⛔ Time window blocked: ${timeCheck.reason}`);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
this.syslog('✓ Time window: allowed');
|
|
494
|
+
// 1.5 Quota gate — skip heartbeat if Claude Max quota is too high
|
|
495
|
+
const quotaCheck = await checkQuotaAllowance(80);
|
|
496
|
+
if (!quotaCheck.allowed) {
|
|
497
|
+
console.log(`[AutonomousRunner] Quota gate: SKIP — ${quotaCheck.reason}`);
|
|
498
|
+
broadcastEvent({ type: 'log', data: { taskId: 'system', stage: 'quota', line: `⏸ ${quotaCheck.reason}` } });
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (quotaCheck.utilization !== undefined && quotaCheck.utilization > 60) {
|
|
502
|
+
console.log(`[AutonomousRunner] Quota warning: ${quotaCheck.utilization.toFixed(0)}% utilization`);
|
|
503
|
+
}
|
|
504
|
+
// 1.6 Pace gate — per-project 5h rolling window
|
|
505
|
+
const isTurbo = this.getTurboMode();
|
|
506
|
+
const perProjectCap = isTurbo ? 20 : (this.config.dailyTaskCap ?? 6);
|
|
507
|
+
const totalInWindow = getDailyCompletedCount();
|
|
508
|
+
// 전역 상한: 프로젝트 수 × per-project cap (안전장치)
|
|
509
|
+
const globalCap = Math.max(this.enabledProjects.size, 3) * perProjectCap;
|
|
510
|
+
if (totalInWindow >= globalCap) {
|
|
511
|
+
console.log(`[AutonomousRunner] Global pace limit: ${totalInWindow}/${globalCap} tasks in 5h window — skipping`);
|
|
512
|
+
this.syslog(`⏸ Global pace: ${totalInWindow}/${globalCap} (5h window)`);
|
|
513
|
+
broadcastEvent({ type: 'log', data: { taskId: 'system', stage: 'pace', line: `⏸ Global pace: ${totalInWindow}/${globalCap}` } });
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const modeLabel = isTurbo ? 'TURBO' : 'Normal';
|
|
517
|
+
this.syslog(`✓ Pace: ${totalInWindow}/${globalCap} global, ${perProjectCap}/project [${modeLabel}]`);
|
|
518
|
+
// 2. Fetch tasks from Linear
|
|
519
|
+
this.syslog('⟳ Fetching tasks from Linear...');
|
|
520
|
+
const fetchResult = await fetchLinearTasks();
|
|
521
|
+
if (fetchResult.error) {
|
|
522
|
+
this.syslog(`✗ Linear fetch error: ${fetchResult.error}`);
|
|
523
|
+
await reportToDiscord(`⚠️ Linear fetch failed: ${fetchResult.error}`);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const tasks = fetchResult.tasks;
|
|
527
|
+
if (tasks.length === 0) {
|
|
528
|
+
this.syslog('— No tasks in backlog');
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
this.lastFetchedTasks = tasks;
|
|
532
|
+
this.syslog(`✓ Found ${tasks.length} tasks from Linear`);
|
|
533
|
+
// Filter out completed and over-retried tasks
|
|
534
|
+
const filteredTasks = this.filterAlreadyProcessed(tasks);
|
|
535
|
+
if (filteredTasks.length === 0) {
|
|
536
|
+
this.syslog('— All tasks already completed or max retries exceeded');
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (filteredTasks.length !== tasks.length) {
|
|
540
|
+
this.syslog(` Filtered: ${tasks.length} → ${filteredTasks.length} (skipped ${tasks.length - filteredTasks.length} completed/failed)`);
|
|
541
|
+
}
|
|
542
|
+
// Parallel processing mode
|
|
543
|
+
if (this.config.maxConcurrentTasks && this.config.maxConcurrentTasks > 1 && this.config.pairMode) {
|
|
544
|
+
await this.heartbeatParallel(filteredTasks);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
// 3. Run Decision Engine (single task)
|
|
548
|
+
this.syslog('⟳ Running Decision Engine...');
|
|
549
|
+
const decision = await this.engine.heartbeat(filteredTasks);
|
|
550
|
+
this.syslog(`→ Decision: ${decision.action} — ${decision.reason}`);
|
|
551
|
+
this.state.lastDecision = decision;
|
|
552
|
+
// 4. Handle decision
|
|
553
|
+
if (decision.action === 'execute' && decision.task) {
|
|
554
|
+
await this.executeTaskPairMode(decision.task);
|
|
555
|
+
}
|
|
556
|
+
else if (decision.action === 'defer' && decision.task) {
|
|
557
|
+
this.state.pendingApproval = decision.task;
|
|
558
|
+
await this.requestApproval(decision);
|
|
559
|
+
}
|
|
560
|
+
this.state.consecutiveErrors = 0;
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
this.state.consecutiveErrors++;
|
|
564
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
565
|
+
console.error('[AutonomousRunner] Heartbeat error:', msg);
|
|
566
|
+
this.syslog(`✗ Heartbeat error: ${msg}`);
|
|
567
|
+
if (this.state.consecutiveErrors >= 3) {
|
|
568
|
+
await reportToDiscord(t('runner.consecutiveErrors', { count: this.state.consecutiveErrors, error: msg }));
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
finally {
|
|
572
|
+
this._heartbeatRunning = false;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async heartbeatParallel(tasks) {
|
|
576
|
+
const availableSlots = this.scheduler.getAvailableSlots();
|
|
577
|
+
const runningCount = this.scheduler.getStats().running;
|
|
578
|
+
this.syslog(` Parallel mode | slots: ${availableSlots} free / ${this.config.maxConcurrentTasks} max | running: ${runningCount}`);
|
|
579
|
+
if (availableSlots === 0) {
|
|
580
|
+
this.syslog(`⏳ All slots busy (${runningCount} tasks running), waiting...`);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
// Fill all available slots (worktree mode isolates each task)
|
|
584
|
+
const maxSlots = availableSlots;
|
|
585
|
+
// Pre-filter tasks to enabled projects only (before DecisionEngine selection)
|
|
586
|
+
// This prevents DecisionEngine from wasting its max-slot budget on non-enabled projects.
|
|
587
|
+
// Only execute Todo tasks; Backlog is fetched for dashboard display only
|
|
588
|
+
const executableTasks = tasks.filter(t => t.linearState !== 'Backlog');
|
|
589
|
+
let tasksForEngine = executableTasks;
|
|
590
|
+
if (this.enabledProjects.size > 0) {
|
|
591
|
+
tasksForEngine = executableTasks.filter(task => {
|
|
592
|
+
const projName = task.linearProject?.name;
|
|
593
|
+
if (!projName)
|
|
594
|
+
return false;
|
|
595
|
+
const cachedPath = this.projectPathCache.get(projName)
|
|
596
|
+
?? this.projectPathCache.get(projName.toLowerCase())
|
|
597
|
+
?? this.projectPathCache.get(projName.replace(/-/g, ' '));
|
|
598
|
+
if (!cachedPath) {
|
|
599
|
+
this.syslog(` ⚠ No path cache for project "${projName}" — skipping ${task.issueIdentifier}`);
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
const enabled = this.isProjectEnabled(cachedPath);
|
|
603
|
+
if (!enabled) {
|
|
604
|
+
this.syslog(` ⚠ Project "${projName}" (${cachedPath}) not enabled — skipping ${task.issueIdentifier}`);
|
|
605
|
+
}
|
|
606
|
+
return enabled;
|
|
607
|
+
});
|
|
608
|
+
if (tasksForEngine.length === 0) {
|
|
609
|
+
this.syslog(`⚠ No enabled tasks (${executableTasks.length} executable, ${tasks.length - executableTasks.length} backlog)`);
|
|
610
|
+
this.syslog(` Path cache: [${[...this.projectPathCache.entries()].map(([k, v]) => `${k}→${v}`).join(', ')}]`);
|
|
611
|
+
this.syslog(` Enabled: [${[...this.enabledProjects].join(', ')}]`);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
this.syslog(` Tasks: ${tasksForEngine.length} enabled-or-uncached / ${executableTasks.length} executable / ${tasks.length} total`);
|
|
615
|
+
}
|
|
616
|
+
// Get validated task list from DecisionEngine
|
|
617
|
+
this.syslog('⟳ Decision Engine evaluating tasks...');
|
|
618
|
+
const decision = await this.engine.heartbeatMultiple(tasksForEngine, maxSlots, [] // No project exclusion — worktree mode isolates each task
|
|
619
|
+
);
|
|
620
|
+
console.log(`[AutonomousRunner] Decision: ${decision.action} — ${decision.reason} (${decision.tasks?.length ?? 0} tasks)`);
|
|
621
|
+
if (decision.action === 'skip' || decision.action === 'defer') {
|
|
622
|
+
this.syslog(`→ Decision: ${decision.action} — ${decision.reason}`);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
// Add validated tasks to queue (with conflict detection)
|
|
626
|
+
let enqueuedCount = 0;
|
|
627
|
+
// Pre-filter: resolve project paths and skip invalid tasks
|
|
628
|
+
const candidates = [];
|
|
629
|
+
for (const { task } of decision.tasks) {
|
|
630
|
+
if (this.scheduler.isTaskQueued(task.id) || this.scheduler.isTaskRunning(task.id)) {
|
|
631
|
+
this.syslog(` Skip (already queued/running): ${task.issueIdentifier || task.id.slice(0, 8)} ${task.title}`);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const projectPath = await this.resolveProjectPath(task);
|
|
635
|
+
if (!projectPath) {
|
|
636
|
+
this.syslog(`✗ Cannot resolve project path for "${task.linearProject?.name || task.title}" — skipping`);
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
if (task.linearProject?.name) {
|
|
640
|
+
this.projectPathCache.set(task.linearProject.name, projectPath);
|
|
641
|
+
}
|
|
642
|
+
if (this.scheduler.isProjectBusy(projectPath)) {
|
|
643
|
+
this.syslog(` Project busy: ${projectPath}`);
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (this.enabledProjects.size > 0 && !this.isProjectEnabled(projectPath)) {
|
|
647
|
+
this.syslog(` Project not enabled: ${projectPath}`);
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
// 프로젝트별 5시간 윈도우 cap 체크
|
|
651
|
+
const projName = task.linearProject?.name ?? 'unknown';
|
|
652
|
+
const perProjectCap = this.config.dailyTaskCap ?? 6;
|
|
653
|
+
if (!canProjectAcceptTask(projName, perProjectCap)) {
|
|
654
|
+
const count = getProjectWindowCount(projName);
|
|
655
|
+
this.syslog(` ⏸ ${projName}: ${count}/${perProjectCap} tasks in 5h window — throttled`);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
candidates.push({ task, projectPath });
|
|
659
|
+
}
|
|
660
|
+
// Group candidates by projectPath for conflict detection
|
|
661
|
+
const byProject = new Map();
|
|
662
|
+
for (const c of candidates) {
|
|
663
|
+
const group = byProject.get(c.projectPath) || [];
|
|
664
|
+
group.push(c);
|
|
665
|
+
byProject.set(c.projectPath, group);
|
|
666
|
+
}
|
|
667
|
+
// Detect file conflicts per project using Knowledge Graph
|
|
668
|
+
const safeTasks = new Set(); // task IDs safe to enqueue
|
|
669
|
+
for (const [projPath, group] of byProject) {
|
|
670
|
+
if (group.length <= 1) {
|
|
671
|
+
// 단일 태스크 → 충돌 없음
|
|
672
|
+
for (const c of group)
|
|
673
|
+
safeTasks.add(c.task.id);
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
try {
|
|
677
|
+
const result = await detectFileConflicts(group.map(c => c.task), projPath);
|
|
678
|
+
for (const t of result.safe) {
|
|
679
|
+
safeTasks.add(t.id);
|
|
680
|
+
}
|
|
681
|
+
for (const cg of result.conflictGroups) {
|
|
682
|
+
const ids = cg.tasks.map(t => t.issueIdentifier || t.id.slice(0, 8)).join(', ');
|
|
683
|
+
this.syslog(`Conflict group: [${ids}] shared: ${cg.sharedModules.join(', ')}`);
|
|
684
|
+
// 충돌 그룹의 연기된 태스크 로그
|
|
685
|
+
for (const t of cg.tasks) {
|
|
686
|
+
if (!safeTasks.has(t.id)) {
|
|
687
|
+
this.syslog(`Conflict detected — deferring: ${t.issueIdentifier || t.id.slice(0, 8)} ${t.title}`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
catch (err) {
|
|
693
|
+
// KG 분석 실패 시 모든 태스크를 safe로 처리 (graceful degradation)
|
|
694
|
+
console.warn(`[AutonomousRunner] Conflict detection failed for ${projPath}:`, err);
|
|
695
|
+
for (const c of group)
|
|
696
|
+
safeTasks.add(c.task.id);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// Enqueue safe tasks only
|
|
700
|
+
for (const { task, projectPath } of candidates) {
|
|
701
|
+
if (!safeTasks.has(task.id))
|
|
702
|
+
continue;
|
|
703
|
+
this.scheduler.enqueue(task, projectPath);
|
|
704
|
+
broadcastEvent({ type: 'task:queued', data: { taskId: task.id, title: task.title, projectPath, issueIdentifier: task.issueIdentifier } });
|
|
705
|
+
this.syslog(`✓ Queued: ${task.issueIdentifier || ''} ${task.title} → ${projectPath.split('/').slice(-2).join('/')}`);
|
|
706
|
+
enqueuedCount++;
|
|
707
|
+
// Claim the task immediately: set Linear to 'In Progress' so restarts don't re-queue it
|
|
708
|
+
if (task.issueId) {
|
|
709
|
+
linear.updateIssueState(task.issueId, 'In Progress').catch((err) => console.warn(`[AutonomousRunner] Failed to claim issue ${task.issueIdentifier}:`, err));
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (enqueuedCount === 0 && decision.skippedCount > 0) {
|
|
713
|
+
this.syslog(`— No new tasks queued (skipped: ${decision.skippedCount})`);
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
this.syslog(`✓ Enqueued ${enqueuedCount} task(s) | skipped: ${decision.skippedCount}`);
|
|
717
|
+
}
|
|
718
|
+
// Execute tasks
|
|
719
|
+
await this.runAvailableTasks();
|
|
720
|
+
}
|
|
721
|
+
/** Execute task in pair mode */
|
|
722
|
+
async executeTaskPairMode(task) {
|
|
723
|
+
// Auto-resolve project path
|
|
724
|
+
const projectPath = await this.resolveProjectPath(task);
|
|
725
|
+
// Error if project path mapping failed
|
|
726
|
+
if (!projectPath) {
|
|
727
|
+
const errorMsg = `Failed to resolve project path for "${task.linearProject?.name || task.title}"`;
|
|
728
|
+
console.error(`[AutonomousRunner] ${errorMsg}`);
|
|
729
|
+
await reportToDiscord(t('runner.projectMappingFailed', { title: task.title, project: task.linearProject?.name || 'unknown' }));
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
// Skip if project is not in enabled list (allow-list; empty = nothing runs)
|
|
733
|
+
if (this.enabledProjects.size > 0 && !this.isProjectEnabled(projectPath)) {
|
|
734
|
+
console.log(`[AutonomousRunner] Project not enabled, skipping: ${projectPath}`);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
// Cache linearProjectName → resolvedPath for dashboard
|
|
738
|
+
if (task.linearProject?.name) {
|
|
739
|
+
this.projectPathCache.set(task.linearProject.name, projectPath);
|
|
740
|
+
}
|
|
741
|
+
console.log(`[AutonomousRunner] projectPath: ${projectPath}`);
|
|
742
|
+
// Check task decomposition (when enableDecomposition is set)
|
|
743
|
+
if (this.config.enableDecomposition) {
|
|
744
|
+
const threshold = this.config.decompositionThresholdMinutes ?? 30;
|
|
745
|
+
const needsDecomp = planner.needsDecomposition(task, threshold);
|
|
746
|
+
if (needsDecomp) {
|
|
747
|
+
console.log(`[AutonomousRunner] Task "${task.title}" needs decomposition (>${threshold}min estimated)`);
|
|
748
|
+
const decomposed = await this.decomposeTask(task, projectPath, threshold);
|
|
749
|
+
if (decomposed) {
|
|
750
|
+
// Decomposition succeeded - sub-tasks added to queue, skip original task
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
// Decomposition failed - execute original task as-is
|
|
754
|
+
console.log('[AutonomousRunner] Decomposition failed, executing original task');
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
// Use scheduler for parallel processing mode
|
|
758
|
+
if (this.config.maxConcurrentTasks && this.config.maxConcurrentTasks > 1) {
|
|
759
|
+
this.scheduler.enqueue(task, projectPath);
|
|
760
|
+
broadcastEvent({ type: 'task:queued', data: { taskId: task.id, title: task.title, projectPath, issueIdentifier: task.issueIdentifier } });
|
|
761
|
+
await this.runAvailableTasks();
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
// Single execution (legacy)
|
|
765
|
+
const result = await this.executePipeline(task, projectPath);
|
|
766
|
+
await reportToDiscord(formatPipelineResultEmbed(result));
|
|
767
|
+
// Update Linear issue state
|
|
768
|
+
if (task.issueId) {
|
|
769
|
+
try {
|
|
770
|
+
if (result.success) {
|
|
771
|
+
// On success, move to Done
|
|
772
|
+
await execution.syncSuccessState(task);
|
|
773
|
+
await linear.logPairComplete(task.issueId, result.sessionId, {
|
|
774
|
+
attempts: result.iterations,
|
|
775
|
+
duration: Math.floor(result.totalDuration / 1000),
|
|
776
|
+
filesChanged: result.workerResult?.filesChanged || [],
|
|
777
|
+
workerSummary: result.workerResult?.summary,
|
|
778
|
+
workerCommands: result.workerResult?.commands,
|
|
779
|
+
reviewerFeedback: result.reviewResult?.feedback,
|
|
780
|
+
reviewerDecision: result.reviewResult?.decision,
|
|
781
|
+
testResults: result.testerResult ? {
|
|
782
|
+
passed: result.testerResult.testsPassed,
|
|
783
|
+
failed: result.testerResult.testsFailed,
|
|
784
|
+
coverage: result.testerResult.coverage,
|
|
785
|
+
failedTests: result.testerResult.failedTests,
|
|
786
|
+
} : undefined,
|
|
787
|
+
});
|
|
788
|
+
await execution.reconcileCompletionState(task);
|
|
789
|
+
console.log(`[AutonomousRunner] Issue ${task.issueId} marked as Done`);
|
|
790
|
+
try {
|
|
791
|
+
await saveCognitiveMemory('strategy', `Pair execution succeeded: "${task.title}"`, { confidence: 0.9, derivedFrom: task.issueId });
|
|
792
|
+
}
|
|
793
|
+
catch (memErr) {
|
|
794
|
+
console.warn(`[AutonomousRunner] Memory save failed (non-critical):`, memErr);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
else if (result.finalStatus === 'rejected') {
|
|
798
|
+
// Change to Blocked on review rejection
|
|
799
|
+
await execution.syncFailureState(task, `Review rejected: ${result.reviewResult?.feedback || t('common.fallback.noDescription')}`);
|
|
800
|
+
await linear.logBlocked(task.issueId, 'autonomous-runner', t('runner.reviewRejected', { feedback: result.reviewResult?.feedback || t('common.fallback.noDescription') }));
|
|
801
|
+
console.log(`[AutonomousRunner] Issue ${task.issueId} marked as Todo (blocked) (rejected)`);
|
|
802
|
+
}
|
|
803
|
+
// If failed, keep In Progress (retry on next heartbeat)
|
|
804
|
+
}
|
|
805
|
+
catch (err) {
|
|
806
|
+
console.error(`[AutonomousRunner] Failed to update issue state:`, err);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
getExecCtx() {
|
|
811
|
+
return {
|
|
812
|
+
allowedProjects: this.config.allowedProjects,
|
|
813
|
+
plannerModel: this.config.plannerModel,
|
|
814
|
+
plannerTimeoutMs: this.config.plannerTimeoutMs,
|
|
815
|
+
pairMaxAttempts: this.config.pairMaxAttempts,
|
|
816
|
+
enableDecomposition: this.config.enableDecomposition,
|
|
817
|
+
decompositionThresholdMinutes: this.config.decompositionThresholdMinutes,
|
|
818
|
+
decompositionMaxDepth: this.config.decomposition?.maxDepth ?? 2,
|
|
819
|
+
decompositionMaxChildren: this.config.decomposition?.maxChildrenPerTask ?? 5,
|
|
820
|
+
decompositionDailyLimit: this.config.decomposition?.dailyLimit ?? 20,
|
|
821
|
+
decompositionAutoBacklog: this.config.decomposition?.autoBacklog ?? true,
|
|
822
|
+
jobProfiles: this.config.jobProfiles,
|
|
823
|
+
getRolesForProject: (p) => this.getRolesForProject(p),
|
|
824
|
+
reportToDiscord,
|
|
825
|
+
worktreeMode: this.config.worktreeMode ?? false,
|
|
826
|
+
scheduleNextHeartbeat: () => this.scheduleNextHeartbeat(),
|
|
827
|
+
guards: this.config.guards,
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
async resolveProjectPath(task) {
|
|
831
|
+
return execution.resolveProjectPath(this.getExecCtx(), task);
|
|
832
|
+
}
|
|
833
|
+
async decomposeTask(task, projectPath, targetMinutes) {
|
|
834
|
+
return execution.decomposeTask(this.getExecCtx(), task, projectPath, targetMinutes);
|
|
835
|
+
}
|
|
836
|
+
async executePipeline(task, projectPath) {
|
|
837
|
+
return execution.executePipeline(this.getExecCtx(), task, projectPath);
|
|
838
|
+
}
|
|
839
|
+
async requestApproval(decision) {
|
|
840
|
+
return execution.requestApproval(decision, reportToDiscord);
|
|
841
|
+
}
|
|
842
|
+
async approve() {
|
|
843
|
+
if (!this.state.pendingApproval) {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
const task = this.state.pendingApproval;
|
|
847
|
+
this.state.pendingApproval = undefined;
|
|
848
|
+
// Get workflow from Decision Engine
|
|
849
|
+
const decision = await this.engine.heartbeat([task]);
|
|
850
|
+
if (decision.workflow && decision.task) {
|
|
851
|
+
await this.executeTaskPairMode(decision.task);
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
856
|
+
reject() {
|
|
857
|
+
if (!this.state.pendingApproval) {
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
this.state.pendingApproval = undefined;
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
863
|
+
async runNow() {
|
|
864
|
+
await this.heartbeat();
|
|
865
|
+
}
|
|
866
|
+
getState() {
|
|
867
|
+
return { ...this.state };
|
|
868
|
+
}
|
|
869
|
+
getAllowedProjects() {
|
|
870
|
+
return this.config.allowedProjects ?? [];
|
|
871
|
+
}
|
|
872
|
+
updateAllowedProjects(paths) {
|
|
873
|
+
this.config.allowedProjects = paths;
|
|
874
|
+
this.engine.updateAllowedProjects(paths);
|
|
875
|
+
}
|
|
876
|
+
getStats() {
|
|
877
|
+
return { isRunning: this.state.isRunning, lastHeartbeat: this.state.lastHeartbeat,
|
|
878
|
+
engineStats: this.engine.getStats(), pendingApproval: !!this.state.pendingApproval,
|
|
879
|
+
schedulerStats: this.scheduler.getStats(),
|
|
880
|
+
turboMode: this.turboMode,
|
|
881
|
+
turboExpiresAt: this.turboExpiresAt,
|
|
882
|
+
dailyPace: getDailyPaceInfo(),
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
// ============================================
|
|
886
|
+
// Turbo Mode
|
|
887
|
+
// ============================================
|
|
888
|
+
getTurboMode() {
|
|
889
|
+
// Auto-expire turbo
|
|
890
|
+
if (this.turboMode && this.turboExpiresAt && Date.now() >= this.turboExpiresAt) {
|
|
891
|
+
this.setTurboMode(false);
|
|
892
|
+
}
|
|
893
|
+
return this.turboMode;
|
|
894
|
+
}
|
|
895
|
+
setTurboMode(enabled) {
|
|
896
|
+
this.turboMode = enabled;
|
|
897
|
+
if (enabled) {
|
|
898
|
+
this.turboExpiresAt = Date.now() + AutonomousRunner.TURBO_DURATION_MS;
|
|
899
|
+
const expiresIn = Math.round(AutonomousRunner.TURBO_DURATION_MS / 3_600_000);
|
|
900
|
+
console.log(`[AutonomousRunner] TURBO MODE ON (auto-expires in ${expiresIn}h)`);
|
|
901
|
+
broadcastEvent({ type: 'log', data: { taskId: 'system', stage: 'turbo', line: `TURBO ON — expires in ${expiresIn}h` } });
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
this.turboExpiresAt = null;
|
|
905
|
+
console.log('[AutonomousRunner] TURBO MODE OFF');
|
|
906
|
+
broadcastEvent({ type: 'log', data: { taskId: 'system', stage: 'turbo', line: 'TURBO OFF — normal pace resumed' } });
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
getAdapterSummary() {
|
|
910
|
+
const defaultAdapter = this.config.defaultAdapter ?? 'claude';
|
|
911
|
+
const defaultRoles = this.config.defaultRoles;
|
|
912
|
+
return {
|
|
913
|
+
defaultAdapter,
|
|
914
|
+
worker: {
|
|
915
|
+
adapter: defaultRoles?.worker?.adapter ?? defaultAdapter,
|
|
916
|
+
model: defaultRoles?.worker?.model ?? this.config.workerModel ?? 'claude-sonnet-4-5-20250929',
|
|
917
|
+
enabled: defaultRoles?.worker?.enabled !== false,
|
|
918
|
+
},
|
|
919
|
+
reviewer: {
|
|
920
|
+
adapter: defaultRoles?.reviewer?.adapter ?? defaultAdapter,
|
|
921
|
+
model: defaultRoles?.reviewer?.model ?? this.config.reviewerModel ?? 'claude-haiku-4-5-20251001',
|
|
922
|
+
enabled: defaultRoles?.reviewer?.enabled !== false,
|
|
923
|
+
},
|
|
924
|
+
tester: defaultRoles?.tester ? {
|
|
925
|
+
adapter: defaultRoles.tester.adapter ?? defaultAdapter,
|
|
926
|
+
model: defaultRoles.tester.model,
|
|
927
|
+
enabled: defaultRoles.tester.enabled !== false,
|
|
928
|
+
} : undefined,
|
|
929
|
+
documenter: defaultRoles?.documenter ? {
|
|
930
|
+
adapter: defaultRoles.documenter.adapter ?? defaultAdapter,
|
|
931
|
+
model: defaultRoles.documenter.model,
|
|
932
|
+
enabled: defaultRoles.documenter.enabled !== false,
|
|
933
|
+
} : undefined,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
switchProvider(adapter) {
|
|
937
|
+
const mapModelForProvider = (model, role) => {
|
|
938
|
+
const current = model || '';
|
|
939
|
+
const isClaudeModel = current.startsWith('claude-');
|
|
940
|
+
// ChatGPT 계정 Codex에서는 gpt-5.x / gpt-*-codex 계열만 지원
|
|
941
|
+
// o-series (o3, o4-mini 등)는 사용 불가
|
|
942
|
+
const isCodexCompatible = current.startsWith('gpt-');
|
|
943
|
+
if (adapter === 'codex') {
|
|
944
|
+
if (isCodexCompatible)
|
|
945
|
+
return current;
|
|
946
|
+
// 비호환 모델(o-series 포함) → 모델 플래그 생략 → Codex 기본값 사용
|
|
947
|
+
return '';
|
|
948
|
+
}
|
|
949
|
+
if (isClaudeModel)
|
|
950
|
+
return current;
|
|
951
|
+
if (role === 'reviewer')
|
|
952
|
+
return 'claude-sonnet-4-20250514';
|
|
953
|
+
return 'claude-haiku-4-5-20251001';
|
|
954
|
+
};
|
|
955
|
+
this.config.defaultAdapter = adapter;
|
|
956
|
+
if (this.config.defaultRoles) {
|
|
957
|
+
this.config.defaultRoles.worker = {
|
|
958
|
+
...this.config.defaultRoles.worker,
|
|
959
|
+
adapter,
|
|
960
|
+
model: mapModelForProvider(this.config.defaultRoles.worker.model, 'worker'),
|
|
961
|
+
};
|
|
962
|
+
this.config.defaultRoles.reviewer = {
|
|
963
|
+
...this.config.defaultRoles.reviewer,
|
|
964
|
+
adapter,
|
|
965
|
+
model: mapModelForProvider(this.config.defaultRoles.reviewer.model, 'reviewer'),
|
|
966
|
+
};
|
|
967
|
+
if (this.config.defaultRoles.tester) {
|
|
968
|
+
this.config.defaultRoles.tester = {
|
|
969
|
+
...this.config.defaultRoles.tester,
|
|
970
|
+
adapter,
|
|
971
|
+
model: mapModelForProvider(this.config.defaultRoles.tester.model, 'tester'),
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
if (this.config.defaultRoles.documenter) {
|
|
975
|
+
this.config.defaultRoles.documenter = {
|
|
976
|
+
...this.config.defaultRoles.documenter,
|
|
977
|
+
adapter,
|
|
978
|
+
model: mapModelForProvider(this.config.defaultRoles.documenter.model, 'documenter'),
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
if (this.config.defaultRoles.auditor) {
|
|
982
|
+
this.config.defaultRoles.auditor = {
|
|
983
|
+
...this.config.defaultRoles.auditor,
|
|
984
|
+
adapter,
|
|
985
|
+
model: mapModelForProvider(this.config.defaultRoles.auditor.model, 'auditor'),
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
if (this.config.defaultRoles['skill-documenter']) {
|
|
989
|
+
this.config.defaultRoles['skill-documenter'] = {
|
|
990
|
+
...this.config.defaultRoles['skill-documenter'],
|
|
991
|
+
adapter,
|
|
992
|
+
model: mapModelForProvider(this.config.defaultRoles['skill-documenter'].model, 'skill-documenter'),
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (this.config.workerModel) {
|
|
997
|
+
this.config.workerModel = mapModelForProvider(this.config.workerModel, 'worker');
|
|
998
|
+
}
|
|
999
|
+
if (this.config.reviewerModel) {
|
|
1000
|
+
this.config.reviewerModel = mapModelForProvider(this.config.reviewerModel, 'reviewer');
|
|
1001
|
+
}
|
|
1002
|
+
console.log(`[AutonomousRunner] Provider switched: ${adapter}`);
|
|
1003
|
+
}
|
|
1004
|
+
pauseScheduler() { this.scheduler.pause(); }
|
|
1005
|
+
resumeScheduler() { this.scheduler.resume(); }
|
|
1006
|
+
getQueuedTasks() { return this.scheduler.getQueuedTasks(); }
|
|
1007
|
+
getRunningTasks() { return this.scheduler.getRunningTasks(); }
|
|
1008
|
+
getPipelineHistory(limit = 50) { return getPipelineHistory(limit); }
|
|
1009
|
+
recordPipelineHistory(task, result) {
|
|
1010
|
+
appendPipelineHistory({
|
|
1011
|
+
sessionId: result.sessionId, issueIdentifier: task.issueIdentifier || task.issueId,
|
|
1012
|
+
issueId: task.issueId, taskTitle: task.title, projectName: task.linearProject?.name,
|
|
1013
|
+
projectPath: result.taskContext?.projectPath, success: result.success,
|
|
1014
|
+
finalStatus: result.finalStatus, iterations: result.iterations,
|
|
1015
|
+
totalDuration: result.totalDuration,
|
|
1016
|
+
stages: result.stages.map(s => ({ stage: s.stage, success: s.success, duration: s.duration })),
|
|
1017
|
+
cost: result.totalCost ? { costUsd: result.totalCost.costUsd,
|
|
1018
|
+
inputTokens: result.totalCost.inputTokens, outputTokens: result.totalCost.outputTokens } : undefined,
|
|
1019
|
+
prUrl: result.prUrl, reviewerFeedback: result.reviewResult?.feedback,
|
|
1020
|
+
completedAt: new Date().toISOString(),
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
disableProject(projectPath) {
|
|
1024
|
+
this.enabledProjects.delete(projectPath);
|
|
1025
|
+
console.log(`[AutonomousRunner] Project disabled: ${projectPath}`);
|
|
1026
|
+
}
|
|
1027
|
+
enableProject(projectPath) {
|
|
1028
|
+
this.enabledProjects.add(projectPath);
|
|
1029
|
+
console.log(`[AutonomousRunner] Project enabled: ${projectPath}`);
|
|
1030
|
+
}
|
|
1031
|
+
/** Get all currently enabled project paths */
|
|
1032
|
+
getEnabledProjects() {
|
|
1033
|
+
return Array.from(this.enabledProjects);
|
|
1034
|
+
}
|
|
1035
|
+
/** Pre-register project path in cache (name → path) */
|
|
1036
|
+
registerProjectPath(name, projectPath) {
|
|
1037
|
+
if (!this.projectPathCache.has(name)) {
|
|
1038
|
+
this.projectPathCache.set(name, projectPath);
|
|
1039
|
+
}
|
|
1040
|
+
// Also register capitalized variant to handle "openswarm" ↔ "Openswarm" mismatch
|
|
1041
|
+
const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
|
|
1042
|
+
if (capitalized !== name && !this.projectPathCache.has(capitalized)) {
|
|
1043
|
+
this.projectPathCache.set(capitalized, projectPath);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
getProjectsInfo() {
|
|
1047
|
+
const running = this.scheduler.getRunningTasks();
|
|
1048
|
+
const queued = this.scheduler.getQueuedTasks();
|
|
1049
|
+
// Update path cache from currently running tasks
|
|
1050
|
+
for (const r of running) {
|
|
1051
|
+
if (r.task.linearProject?.name)
|
|
1052
|
+
this.projectPathCache.set(r.task.linearProject.name, r.projectPath);
|
|
1053
|
+
}
|
|
1054
|
+
return buildProjectsInfo(this.lastFetchedTasks, running, queued, this.projectPathCache, this.enabledProjects);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
export function getRunner(config) {
|
|
1058
|
+
if (!runnerInstance && config) {
|
|
1059
|
+
runnerInstance = new AutonomousRunner(config);
|
|
1060
|
+
}
|
|
1061
|
+
if (!runnerInstance) {
|
|
1062
|
+
throw new Error('Runner not initialized. Call getRunner with config first.');
|
|
1063
|
+
}
|
|
1064
|
+
return runnerInstance;
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Start runner (convenience function)
|
|
1068
|
+
*/
|
|
1069
|
+
export async function startAutonomous(config) {
|
|
1070
|
+
const runner = getRunner(config);
|
|
1071
|
+
await runner.start();
|
|
1072
|
+
return runner;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Stop runner (convenience function)
|
|
1076
|
+
*/
|
|
1077
|
+
export function stopAutonomous() {
|
|
1078
|
+
if (runnerInstance) {
|
|
1079
|
+
runnerInstance.stop();
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
//# sourceMappingURL=autonomousRunner.js.map
|