@amsterdamdatalabs/enact-factory 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +566 -0
- package/dist/adapters/agenticLoop.d.ts +90 -0
- package/dist/adapters/agenticLoop.d.ts.map +1 -0
- package/dist/adapters/agenticLoop.js +219 -0
- package/dist/adapters/agenticLoop.js.map +1 -0
- package/dist/adapters/base.d.ts +16 -0
- package/dist/adapters/base.d.ts.map +1 -0
- package/dist/adapters/base.js +135 -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/cursor.d.ts +13 -0
- package/dist/adapters/cursor.d.ts.map +1 -0
- package/dist/adapters/cursor.js +300 -0
- package/dist/adapters/cursor.js.map +1 -0
- package/dist/adapters/envPath.d.ts +20 -0
- package/dist/adapters/envPath.d.ts.map +1 -0
- package/dist/adapters/envPath.js +49 -0
- package/dist/adapters/envPath.js.map +1 -0
- package/dist/adapters/hermes.d.ts +13 -0
- package/dist/adapters/hermes.d.ts.map +1 -0
- package/dist/adapters/hermes.js +283 -0
- package/dist/adapters/hermes.js.map +1 -0
- package/dist/adapters/index.d.ts +18 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +56 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.d.ts +13 -0
- package/dist/adapters/opencode.d.ts.map +1 -0
- package/dist/adapters/opencode.js +282 -0
- package/dist/adapters/opencode.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/responses.d.ts +16 -0
- package/dist/adapters/responses.d.ts.map +1 -0
- package/dist/adapters/responses.js +244 -0
- package/dist/adapters/responses.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/tools.d.ts +30 -0
- package/dist/adapters/tools.d.ts.map +1 -0
- package/dist/adapters/tools.js +219 -0
- package/dist/adapters/tools.js.map +1 -0
- package/dist/adapters/types.d.ts +82 -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 +215 -0
- package/dist/agents/agentPair.d.ts.map +1 -0
- package/dist/agents/agentPair.js +456 -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 +238 -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 +286 -0
- package/dist/agents/documenter.js.map +1 -0
- package/dist/agents/draftAnalyzer.d.ts +50 -0
- package/dist/agents/draftAnalyzer.d.ts.map +1 -0
- package/dist/agents/draftAnalyzer.js +289 -0
- package/dist/agents/draftAnalyzer.js.map +1 -0
- package/dist/agents/evaluator.d.ts +61 -0
- package/dist/agents/evaluator.d.ts.map +1 -0
- package/dist/agents/evaluator.js +338 -0
- package/dist/agents/evaluator.js.map +1 -0
- package/dist/agents/executor.d.ts +33 -0
- package/dist/agents/executor.d.ts.map +1 -0
- package/dist/agents/executor.js +130 -0
- package/dist/agents/executor.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 +232 -0
- package/dist/agents/pairMetrics.js.map +1 -0
- package/dist/agents/pairPipeline.d.ts +184 -0
- package/dist/agents/pairPipeline.d.ts.map +1 -0
- package/dist/agents/pairPipeline.js +934 -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 +8 -0
- package/dist/agents/pipelineFormat.d.ts.map +1 -0
- package/dist/agents/pipelineFormat.js +65 -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 +257 -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 +214 -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 +219 -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 +309 -0
- package/dist/agents/tester.js.map +1 -0
- package/dist/automation/autonomousRunner.d.ts +145 -0
- package/dist/automation/autonomousRunner.d.ts.map +1 -0
- package/dist/automation/autonomousRunner.js +1272 -0
- package/dist/automation/autonomousRunner.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 +130 -0
- package/dist/automation/dailyReporter.js.map +1 -0
- package/dist/automation/index.d.ts +5 -0
- package/dist/automation/index.d.ts.map +1 -0
- package/dist/automation/index.js +5 -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 +356 -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/runnerExecution.d.ts +57 -0
- package/dist/automation/runnerExecution.d.ts.map +1 -0
- package/dist/automation/runnerExecution.js +701 -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 +496 -0
- package/dist/automation/runnerState.js.map +1 -0
- package/dist/automation/runnerTypes.d.ts +57 -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 +402 -0
- package/dist/automation/scheduler.js.map +1 -0
- package/dist/azdo/azdo.d.ts +70 -0
- package/dist/azdo/azdo.d.ts.map +1 -0
- package/dist/azdo/azdo.js +328 -0
- package/dist/azdo/azdo.js.map +1 -0
- package/dist/azdo/index.d.ts +3 -0
- package/dist/azdo/index.d.ts.map +1 -0
- package/dist/azdo/index.js +3 -0
- package/dist/azdo/index.js.map +1 -0
- package/dist/azdo/projectUpdater.d.ts +13 -0
- package/dist/azdo/projectUpdater.d.ts.map +1 -0
- package/dist/azdo/projectUpdater.js +155 -0
- package/dist/azdo/projectUpdater.js.map +1 -0
- package/dist/azureDevOps/client.d.ts +75 -0
- package/dist/azureDevOps/client.d.ts.map +1 -0
- package/dist/azureDevOps/client.js +150 -0
- package/dist/azureDevOps/client.js.map +1 -0
- package/dist/azureDevOps/hierarchy.d.ts +119 -0
- package/dist/azureDevOps/hierarchy.d.ts.map +1 -0
- package/dist/azureDevOps/hierarchy.js +470 -0
- package/dist/azureDevOps/hierarchy.js.map +1 -0
- package/dist/azureDevOps/mapper.d.ts +101 -0
- package/dist/azureDevOps/mapper.d.ts.map +1 -0
- package/dist/azureDevOps/mapper.js +438 -0
- package/dist/azureDevOps/mapper.js.map +1 -0
- package/dist/azureDevOps/stateMapping.d.ts +15 -0
- package/dist/azureDevOps/stateMapping.d.ts.map +1 -0
- package/dist/azureDevOps/stateMapping.js +141 -0
- package/dist/azureDevOps/stateMapping.js.map +1 -0
- package/dist/cli/authHandler.d.ts +13 -0
- package/dist/cli/authHandler.d.ts.map +1 -0
- package/dist/cli/authHandler.js +70 -0
- package/dist/cli/authHandler.js.map +1 -0
- package/dist/cli/checkHandler.d.ts +27 -0
- package/dist/cli/checkHandler.d.ts.map +1 -0
- package/dist/cli/checkHandler.js +560 -0
- package/dist/cli/checkHandler.js.map +1 -0
- package/dist/cli/daemon.d.ts +30 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +141 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/factoryCommands.d.ts +3 -0
- package/dist/cli/factoryCommands.d.ts.map +1 -0
- package/dist/cli/factoryCommands.js +165 -0
- package/dist/cli/factoryCommands.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 +193 -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 +320 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/agentLifecycle.d.ts +322 -0
- package/dist/core/agentLifecycle.d.ts.map +1 -0
- package/dist/core/agentLifecycle.js +230 -0
- package/dist/core/agentLifecycle.js.map +1 -0
- package/dist/core/areaMapping.d.ts +9 -0
- package/dist/core/areaMapping.d.ts.map +1 -0
- package/dist/core/areaMapping.js +37 -0
- package/dist/core/areaMapping.js.map +1 -0
- package/dist/core/config.d.ts +469 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +780 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/dashboardContract.d.ts +204 -0
- package/dist/core/dashboardContract.d.ts.map +1 -0
- package/dist/core/dashboardContract.js +205 -0
- package/dist/core/dashboardContract.js.map +1 -0
- package/dist/core/devopsModel.d.ts +138 -0
- package/dist/core/devopsModel.d.ts.map +1 -0
- package/dist/core/devopsModel.js +137 -0
- package/dist/core/devopsModel.js.map +1 -0
- package/dist/core/envFile.d.ts +11 -0
- package/dist/core/envFile.d.ts.map +1 -0
- package/dist/core/envFile.js +104 -0
- package/dist/core/envFile.js.map +1 -0
- package/dist/core/eventHub.d.ts +220 -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 +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/laneExecutionState.d.ts +29 -0
- package/dist/core/laneExecutionState.d.ts.map +1 -0
- package/dist/core/laneExecutionState.js +18 -0
- package/dist/core/laneExecutionState.js.map +1 -0
- package/dist/core/laneStatus.d.ts +49 -0
- package/dist/core/laneStatus.d.ts.map +1 -0
- package/dist/core/laneStatus.js +153 -0
- package/dist/core/laneStatus.js.map +1 -0
- package/dist/core/prSidecar.d.ts +96 -0
- package/dist/core/prSidecar.d.ts.map +1 -0
- package/dist/core/prSidecar.js +33 -0
- package/dist/core/prSidecar.js.map +1 -0
- package/dist/core/runtimeConfig.d.ts +6 -0
- package/dist/core/runtimeConfig.d.ts.map +1 -0
- package/dist/core/runtimeConfig.js +24 -0
- package/dist/core/runtimeConfig.js.map +1 -0
- package/dist/core/scmProvider.d.ts +19 -0
- package/dist/core/scmProvider.d.ts.map +1 -0
- package/dist/core/scmProvider.js +38 -0
- package/dist/core/scmProvider.js.map +1 -0
- package/dist/core/service.d.ts +10 -0
- package/dist/core/service.d.ts.map +1 -0
- package/dist/core/service.js +297 -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 +432 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/workItemMapper.d.ts +39 -0
- package/dist/core/workItemMapper.d.ts.map +1 -0
- package/dist/core/workItemMapper.js +427 -0
- package/dist/core/workItemMapper.js.map +1 -0
- package/dist/core/workItemModel.d.ts +120 -0
- package/dist/core/workItemModel.d.ts.map +1 -0
- package/dist/core/workItemModel.js +104 -0
- package/dist/core/workItemModel.js.map +1 -0
- package/dist/core/workItemPayload.d.ts +195 -0
- package/dist/core/workItemPayload.d.ts.map +1 -0
- package/dist/core/workItemPayload.js +24 -0
- package/dist/core/workItemPayload.js.map +1 -0
- package/dist/core/workspaceConfig.d.ts +57 -0
- package/dist/core/workspaceConfig.d.ts.map +1 -0
- package/dist/core/workspaceConfig.js +184 -0
- package/dist/core/workspaceConfig.js.map +1 -0
- package/dist/doctor.d.ts +18 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +34 -0
- package/dist/doctor.js.map +1 -0
- package/dist/factory/activeSkill.d.ts +11 -0
- package/dist/factory/activeSkill.d.ts.map +1 -0
- package/dist/factory/activeSkill.js +44 -0
- package/dist/factory/activeSkill.js.map +1 -0
- package/dist/factory/assignment.d.ts +54 -0
- package/dist/factory/assignment.d.ts.map +1 -0
- package/dist/factory/assignment.js +94 -0
- package/dist/factory/assignment.js.map +1 -0
- package/dist/factory/auditLog.d.ts +10 -0
- package/dist/factory/auditLog.d.ts.map +1 -0
- package/dist/factory/auditLog.js +38 -0
- package/dist/factory/auditLog.js.map +1 -0
- package/dist/factory/closureRequirements.d.ts +12 -0
- package/dist/factory/closureRequirements.d.ts.map +1 -0
- package/dist/factory/closureRequirements.js +30 -0
- package/dist/factory/closureRequirements.js.map +1 -0
- package/dist/factory/delegationPrompt.d.ts +3 -0
- package/dist/factory/delegationPrompt.d.ts.map +1 -0
- package/dist/factory/delegationPrompt.js +16 -0
- package/dist/factory/delegationPrompt.js.map +1 -0
- package/dist/factory/http.d.ts +3 -0
- package/dist/factory/http.d.ts.map +1 -0
- package/dist/factory/http.js +555 -0
- package/dist/factory/http.js.map +1 -0
- package/dist/factory/lifecyclePushMap.d.ts +4 -0
- package/dist/factory/lifecyclePushMap.d.ts.map +1 -0
- package/dist/factory/lifecyclePushMap.js +7 -0
- package/dist/factory/lifecyclePushMap.js.map +1 -0
- package/dist/factory/missions.d.ts +125 -0
- package/dist/factory/missions.d.ts.map +1 -0
- package/dist/factory/missions.js +304 -0
- package/dist/factory/missions.js.map +1 -0
- package/dist/factory/mode.d.ts +9 -0
- package/dist/factory/mode.d.ts.map +1 -0
- package/dist/factory/mode.js +30 -0
- package/dist/factory/mode.js.map +1 -0
- package/dist/factory/operatorActiveSkill.d.ts +15 -0
- package/dist/factory/operatorActiveSkill.d.ts.map +1 -0
- package/dist/factory/operatorActiveSkill.js +95 -0
- package/dist/factory/operatorActiveSkill.js.map +1 -0
- package/dist/factory/paseoDispatcher.d.ts +52 -0
- package/dist/factory/paseoDispatcher.d.ts.map +1 -0
- package/dist/factory/paseoDispatcher.js +122 -0
- package/dist/factory/paseoDispatcher.js.map +1 -0
- package/dist/factory/paseoLifecycle.d.ts +32 -0
- package/dist/factory/paseoLifecycle.d.ts.map +1 -0
- package/dist/factory/paseoLifecycle.js +260 -0
- package/dist/factory/paseoLifecycle.js.map +1 -0
- package/dist/factory/paths.d.ts +31 -0
- package/dist/factory/paths.d.ts.map +1 -0
- package/dist/factory/paths.js +139 -0
- package/dist/factory/paths.js.map +1 -0
- package/dist/factory/progressWatchdog.d.ts +58 -0
- package/dist/factory/progressWatchdog.d.ts.map +1 -0
- package/dist/factory/progressWatchdog.js +160 -0
- package/dist/factory/progressWatchdog.js.map +1 -0
- package/dist/factory/roster.d.ts +59 -0
- package/dist/factory/roster.d.ts.map +1 -0
- package/dist/factory/roster.js +116 -0
- package/dist/factory/roster.js.map +1 -0
- package/dist/factory/runtime.d.ts +44 -0
- package/dist/factory/runtime.d.ts.map +1 -0
- package/dist/factory/runtime.js +238 -0
- package/dist/factory/runtime.js.map +1 -0
- package/dist/factory/sync.d.ts +29 -0
- package/dist/factory/sync.d.ts.map +1 -0
- package/dist/factory/sync.js +77 -0
- package/dist/factory/sync.js.map +1 -0
- package/dist/factory/workitemQueues.d.ts +37 -0
- package/dist/factory/workitemQueues.d.ts.map +1 -0
- package/dist/factory/workitemQueues.js +99 -0
- package/dist/factory/workitemQueues.js.map +1 -0
- package/dist/factory/workitemTriage.d.ts +9 -0
- package/dist/factory/workitemTriage.d.ts.map +1 -0
- package/dist/factory/workitemTriage.js +81 -0
- package/dist/factory/workitemTriage.js.map +1 -0
- package/dist/hooks.d.ts +18 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +96 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +90 -0
- package/dist/index.js.map +1 -0
- package/dist/install/agentCatalog.d.ts +7 -0
- package/dist/install/agentCatalog.d.ts.map +1 -0
- package/dist/install/agentCatalog.js +28 -0
- package/dist/install/agentCatalog.js.map +1 -0
- package/dist/install/bundlePaths.d.ts +10 -0
- package/dist/install/bundlePaths.d.ts.map +1 -0
- package/dist/install/bundlePaths.js +30 -0
- package/dist/install/bundlePaths.js.map +1 -0
- package/dist/install/codex.d.ts +43 -0
- package/dist/install/codex.d.ts.map +1 -0
- package/dist/install/codex.js +207 -0
- package/dist/install/codex.js.map +1 -0
- package/dist/install/enactHome.d.ts +37 -0
- package/dist/install/enactHome.d.ts.map +1 -0
- package/dist/install/enactHome.js +152 -0
- package/dist/install/enactHome.js.map +1 -0
- package/dist/install/plugins.d.ts +115 -0
- package/dist/install/plugins.d.ts.map +1 -0
- package/dist/install/plugins.js +259 -0
- package/dist/install/plugins.js.map +1 -0
- package/dist/install/setup.d.ts +33 -0
- package/dist/install/setup.d.ts.map +1 -0
- package/dist/install/setup.js +167 -0
- package/dist/install/setup.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 +435 -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 +84 -0
- package/dist/locale/index.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 +254 -0
- package/dist/locale/prompts/en.js.map +1 -0
- package/dist/locale/types.d.ts +433 -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/mcp/server.d.ts +489 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +597 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/orchestration/decisionEngine.d.ts +175 -0
- package/dist/orchestration/decisionEngine.d.ts.map +1 -0
- package/dist/orchestration/decisionEngine.js +471 -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/workItemParser.d.ts +67 -0
- package/dist/orchestration/workItemParser.d.ts.map +1 -0
- package/dist/orchestration/workItemParser.js +560 -0
- package/dist/orchestration/workItemParser.js.map +1 -0
- package/dist/orchestration/workItemScheduler.d.ts +141 -0
- package/dist/orchestration/workItemScheduler.d.ts.map +1 -0
- package/dist/orchestration/workItemScheduler.js +317 -0
- package/dist/orchestration/workItemScheduler.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/providers/codexSessions.d.ts +93 -0
- package/dist/providers/codexSessions.d.ts.map +1 -0
- package/dist/providers/codexSessions.js +366 -0
- package/dist/providers/codexSessions.js.map +1 -0
- package/dist/registry/bsDetector.d.ts +24 -0
- package/dist/registry/bsDetector.d.ts.map +1 -0
- package/dist/registry/bsDetector.js +276 -0
- package/dist/registry/bsDetector.js.map +1 -0
- package/dist/registry/entityScanner.d.ts +36 -0
- package/dist/registry/entityScanner.d.ts.map +1 -0
- package/dist/registry/entityScanner.js +693 -0
- package/dist/registry/entityScanner.js.map +1 -0
- package/dist/registry/index.d.ts +9 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +13 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/schema.d.ts +307 -0
- package/dist/registry/schema.d.ts.map +1 -0
- package/dist/registry/schema.js +139 -0
- package/dist/registry/schema.js.map +1 -0
- package/dist/registry/sqliteStore.d.ts +101 -0
- package/dist/registry/sqliteStore.d.ts.map +1 -0
- package/dist/registry/sqliteStore.js +688 -0
- package/dist/registry/sqliteStore.js.map +1 -0
- package/dist/registry/workItemBridge.d.ts +8 -0
- package/dist/registry/workItemBridge.d.ts.map +1 -0
- package/dist/registry/workItemBridge.js +30 -0
- package/dist/registry/workItemBridge.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 +193 -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 +305 -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 +289 -0
- package/dist/support/chatBackend.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 +1082 -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 +5 -0
- package/dist/support/dashboardHtml.d.ts.map +1 -0
- package/dist/support/dashboardHtml.js +2629 -0
- package/dist/support/dashboardHtml.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/ghosttyThemeCatalog.generated.d.ts +2 -0
- package/dist/support/ghosttyThemeCatalog.generated.d.ts.map +1 -0
- package/dist/support/ghosttyThemeCatalog.generated.js +11116 -0
- package/dist/support/ghosttyThemeCatalog.generated.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 +12 -0
- package/dist/support/index.d.ts.map +1 -0
- package/dist/support/index.js +12 -0
- package/dist/support/index.js.map +1 -0
- package/dist/support/planner.d.ts +64 -0
- package/dist/support/planner.d.ts.map +1 -0
- package/dist/support/planner.js +396 -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 +273 -0
- package/dist/support/projectMapper.js.map +1 -0
- package/dist/support/pty-helper.py +117 -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 +329 -0
- package/dist/support/rollback.js.map +1 -0
- package/dist/support/sharedShell.d.ts +17 -0
- package/dist/support/sharedShell.d.ts.map +1 -0
- package/dist/support/sharedShell.js +439 -0
- package/dist/support/sharedShell.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/terminalBridge.d.ts +18 -0
- package/dist/support/terminalBridge.d.ts.map +1 -0
- package/dist/support/terminalBridge.js +553 -0
- package/dist/support/terminalBridge.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/uiThemes.d.ts +44 -0
- package/dist/support/uiThemes.d.ts.map +1 -0
- package/dist/support/uiThemes.js +290 -0
- package/dist/support/uiThemes.js.map +1 -0
- package/dist/support/web.d.ts +29 -0
- package/dist/support/web.d.ts.map +1 -0
- package/dist/support/web.js +1097 -0
- package/dist/support/web.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 +140 -0
- package/dist/support/worktreeManager.js.map +1 -0
- package/dist/task_state_model.py +55 -0
- package/dist/workItemState/store.d.ts +122 -0
- package/dist/workItemState/store.d.ts.map +1 -0
- package/dist/workItemState/store.js +438 -0
- package/dist/workItemState/store.js.map +1 -0
- package/dist/workItems/azdoBridge.d.ts +42 -0
- package/dist/workItems/azdoBridge.d.ts.map +1 -0
- package/dist/workItems/azdoBridge.js +143 -0
- package/dist/workItems/azdoBridge.js.map +1 -0
- package/dist/workItems/azdoSyncRuntime.d.ts +28 -0
- package/dist/workItems/azdoSyncRuntime.d.ts.map +1 -0
- package/dist/workItems/azdoSyncRuntime.js +158 -0
- package/dist/workItems/azdoSyncRuntime.js.map +1 -0
- package/dist/workItems/azureDevOpsSync.d.ts +128 -0
- package/dist/workItems/azureDevOpsSync.d.ts.map +1 -0
- package/dist/workItems/azureDevOpsSync.js +748 -0
- package/dist/workItems/azureDevOpsSync.js.map +1 -0
- package/dist/workItems/helpers.d.ts +11 -0
- package/dist/workItems/helpers.d.ts.map +1 -0
- package/dist/workItems/helpers.js +17 -0
- package/dist/workItems/helpers.js.map +1 -0
- package/dist/workItems/index.d.ts +21 -0
- package/dist/workItems/index.d.ts.map +1 -0
- package/dist/workItems/index.js +89 -0
- package/dist/workItems/index.js.map +1 -0
- package/dist/workItems/localWorkItemFetcher.d.ts +55 -0
- package/dist/workItems/localWorkItemFetcher.d.ts.map +1 -0
- package/dist/workItems/localWorkItemFetcher.js +209 -0
- package/dist/workItems/localWorkItemFetcher.js.map +1 -0
- package/dist/workItems/migrations/001_rename_workItem_to_work_item.sql +10 -0
- package/dist/workItems/postgresStore.d.ts +78 -0
- package/dist/workItems/postgresStore.d.ts.map +1 -0
- package/dist/workItems/postgresStore.js +937 -0
- package/dist/workItems/postgresStore.js.map +1 -0
- package/dist/workItems/schema.d.ts +257 -0
- package/dist/workItems/schema.d.ts.map +1 -0
- package/dist/workItems/schema.js +176 -0
- package/dist/workItems/schema.js.map +1 -0
- package/dist/workItems/sqliteStore.d.ts +124 -0
- package/dist/workItems/sqliteStore.d.ts.map +1 -0
- package/dist/workItems/sqliteStore.js +713 -0
- package/dist/workItems/sqliteStore.js.map +1 -0
- package/dist/workItems/workItemBoardHtml.d.ts +5 -0
- package/dist/workItems/workItemBoardHtml.d.ts.map +1 -0
- package/dist/workItems/workItemBoardHtml.js +2192 -0
- package/dist/workItems/workItemBoardHtml.js.map +1 -0
- package/package.json +99 -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/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
- package/templates/WORKITEM_ANALYSIS.md +31 -0
- package/templates/agents/executor.md +26 -0
- package/templates/agents/plan.md +22 -0
- package/templates/agents/ralph.md +37 -0
- package/templates/agents/review.md +22 -0
- package/templates/agents/team.md +39 -0
|
@@ -0,0 +1,1272 @@
|
|
|
1
|
+
// EnactFactory - Autonomous Runner
|
|
2
|
+
// Heartbeat → Decision → Execution → Report
|
|
3
|
+
import { Cron } from 'croner';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { loadWorkItemState, saveWorkItemState, buildProjectsInfo, appendPipelineHistory, getPipelineHistory, incrementRejection, clearRejection, isRejectionLimitReached, canRetryNow, setRetryTime, clearRetryTime, formatRetryTime, getDailyCompletedCount, getDailyPaceInfo, recordProjectCompletion, canProjectAcceptWorkItem, getProjectWindowCount, } from '../automation/runnerState.js';
|
|
6
|
+
import { getDecisionEngine, } from '../orchestration/decisionEngine.js';
|
|
7
|
+
// ExecutorResult used via execution.reportExecutionResult
|
|
8
|
+
import { checkWorkAllowed } from '../support/timeWindow.js';
|
|
9
|
+
import * as azdo from '../azdo/index.js';
|
|
10
|
+
import { updateProjectAfterWorkItem } from '../azdo/projectUpdater.js';
|
|
11
|
+
import { initScheduler } from '../orchestration/workItemScheduler.js';
|
|
12
|
+
import { formatPipelineResultEmbed, } from '../agents/pairPipeline.js';
|
|
13
|
+
import * as planner from '../support/planner.js';
|
|
14
|
+
import { inferProviderFromModel } from '../support/chatBackend.js';
|
|
15
|
+
import * as execution from '../automation/runnerExecution.js';
|
|
16
|
+
import { getWorkItemSource, reportToDiscord, fetchAzdoWorkItems } from '../automation/runnerExecution.js';
|
|
17
|
+
import { t } from '../locale/index.js';
|
|
18
|
+
import { broadcastEvent } from '../core/eventHub.js';
|
|
19
|
+
import { pruneWorktrees } from '../support/worktreeManager.js';
|
|
20
|
+
import { checkAllMonitors, getActiveMonitors } from '../automation/longRunningMonitor.js';
|
|
21
|
+
import { checkQuotaAllowance } from '../support/quotaTracker.js';
|
|
22
|
+
import { branchNameFor } from '../core/devopsModel.js';
|
|
23
|
+
import { createAssignment, listAssignments } from '../factory/assignment.js';
|
|
24
|
+
import { resolveFactoryRoot } from '../factory/paths.js';
|
|
25
|
+
import { dispatchWorkItemToPaseo } from '../factory/paseoDispatcher.js';
|
|
26
|
+
import { reconcilePaseoAssignments } from '../factory/paseoLifecycle.js';
|
|
27
|
+
import { buildWorkItemStateSyncComment, markWorkItemDispatched } from '../workItemState/store.js';
|
|
28
|
+
// Re-export types and integration setters (used by service.ts)
|
|
29
|
+
export { setDiscordReporter, setAzdoFetcher, setLocalFetcher } from '../automation/runnerExecution.js';
|
|
30
|
+
let runnerInstance = null;
|
|
31
|
+
export class AutonomousRunner {
|
|
32
|
+
config;
|
|
33
|
+
engine;
|
|
34
|
+
scheduler;
|
|
35
|
+
cronJob = null;
|
|
36
|
+
state = {
|
|
37
|
+
isRunning: false,
|
|
38
|
+
lastHeartbeat: 0,
|
|
39
|
+
consecutiveErrors: 0,
|
|
40
|
+
};
|
|
41
|
+
// Heartbeat concurrency guard
|
|
42
|
+
_heartbeatRunning = false;
|
|
43
|
+
// Explicitly enabled project paths (allow-list; empty = nothing runs)
|
|
44
|
+
enabledProjects = new Set();
|
|
45
|
+
/**
|
|
46
|
+
* macOS (APFS default) and Windows have case-insensitive filesystems by
|
|
47
|
+
* default, so `/Users/x/dev/AnalogModeling` and `/Users/x/dev/analogModeling`
|
|
48
|
+
* refer to the same directory. Do the enabled-set comparison in a case-
|
|
49
|
+
* insensitive way on those platforms so UI-captured casing doesn't
|
|
50
|
+
* mismatch Azdo's project-name casing.
|
|
51
|
+
*/
|
|
52
|
+
get pathsCaseInsensitive() {
|
|
53
|
+
return process.platform === 'darwin' || process.platform === 'win32';
|
|
54
|
+
}
|
|
55
|
+
normalizePath(p) {
|
|
56
|
+
return this.pathsCaseInsensitive ? p.toLowerCase() : p;
|
|
57
|
+
}
|
|
58
|
+
/** Check if a resolved path is under any enabled project */
|
|
59
|
+
isProjectEnabled(resolvedPath) {
|
|
60
|
+
if (this.enabledProjects.size === 0)
|
|
61
|
+
return false;
|
|
62
|
+
const needle = this.normalizePath(resolvedPath);
|
|
63
|
+
for (const enabled of this.enabledProjects) {
|
|
64
|
+
const hay = this.normalizePath(enabled);
|
|
65
|
+
if (hay === needle)
|
|
66
|
+
return true;
|
|
67
|
+
if (needle.startsWith(hay + '/'))
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
// Last fetched Azdo workItems (for dashboard display)
|
|
73
|
+
lastFetchedWorkItems = [];
|
|
74
|
+
// Cache: azdoProjectName → resolvedLocalPath (populated during workItem execution)
|
|
75
|
+
projectPathCache = new Map();
|
|
76
|
+
// Execution pause: boots paused — user must explicitly resumeExecution() before claims start
|
|
77
|
+
executionPaused = true;
|
|
78
|
+
// Turbo mode: faster heartbeat, higher daily cap, no stage skipping
|
|
79
|
+
turboMode = false;
|
|
80
|
+
turboExpiresAt = null;
|
|
81
|
+
static TURBO_DURATION_MS = 4 * 60 * 60 * 1000; // 4 hours auto-expire
|
|
82
|
+
// Track completed/failed workItem IDs to prevent re-selection (persisted to disk)
|
|
83
|
+
completedWorkItemIds = new Set();
|
|
84
|
+
failedWorkItemCounts = new Map();
|
|
85
|
+
failedWorkItemRetryTimes = new Map(); // workItemId → next retry timestamp (ms)
|
|
86
|
+
static MAX_RETRY_COUNT = 4; // Increased from 2 to allow more retries with backoff
|
|
87
|
+
get workItemStateRef() {
|
|
88
|
+
return {
|
|
89
|
+
completedWorkItemIds: this.completedWorkItemIds,
|
|
90
|
+
failedWorkItemCounts: this.failedWorkItemCounts,
|
|
91
|
+
failedWorkItemRetryTimes: this.failedWorkItemRetryTimes,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
loadWorkItemState() {
|
|
95
|
+
loadWorkItemState(this.workItemStateRef);
|
|
96
|
+
}
|
|
97
|
+
saveWorkItemState() {
|
|
98
|
+
saveWorkItemState(this.workItemStateRef);
|
|
99
|
+
}
|
|
100
|
+
formatWorkItemContext(workItem) {
|
|
101
|
+
const parts = [];
|
|
102
|
+
if (workItem.azdoProject?.name)
|
|
103
|
+
parts.push(`[${workItem.azdoProject.name}]`);
|
|
104
|
+
if (workItem.workItemIdentifier)
|
|
105
|
+
parts.push(workItem.workItemIdentifier);
|
|
106
|
+
else if (workItem.workItemId)
|
|
107
|
+
parts.push(workItem.workItemId.slice(0, 8));
|
|
108
|
+
return parts.length > 0 ? parts.join(' ') : '';
|
|
109
|
+
}
|
|
110
|
+
constructor(config) {
|
|
111
|
+
this.config = config;
|
|
112
|
+
this.loadWorkItemState(); // Restore completed/failed workItem IDs from disk
|
|
113
|
+
this.engine = getDecisionEngine({
|
|
114
|
+
allowedProjects: config.allowedProjects,
|
|
115
|
+
azdoTeamId: config.azdoTeamId,
|
|
116
|
+
autoExecute: config.autoExecute,
|
|
117
|
+
maxConsecutiveWorkItems: config.maxConsecutiveWorkItems,
|
|
118
|
+
cooldownSeconds: config.cooldownSeconds,
|
|
119
|
+
dryRun: config.dryRun,
|
|
120
|
+
});
|
|
121
|
+
// Initialize WorkItemScheduler
|
|
122
|
+
this.scheduler = initScheduler({
|
|
123
|
+
maxConcurrent: config.maxConcurrentWorkItems ?? 1,
|
|
124
|
+
allowSameProjectConcurrent: false,
|
|
125
|
+
worktreeMode: config.worktreeMode ?? false,
|
|
126
|
+
});
|
|
127
|
+
// Set up scheduler event handling
|
|
128
|
+
this.setupSchedulerEvents();
|
|
129
|
+
}
|
|
130
|
+
setupSchedulerEvents() {
|
|
131
|
+
this.scheduler.on('started', async (running) => {
|
|
132
|
+
const workItemCtx = this.formatWorkItemContext(running.workItem);
|
|
133
|
+
console.log(`[Scheduler] WorkItem started: ${workItemCtx} ${running.workItem.title}`);
|
|
134
|
+
broadcastEvent({ type: 'workItem:started', data: { workItemId: running.workItem.id, title: running.workItem.title, workItemIdentifier: running.workItem.workItemIdentifier } });
|
|
135
|
+
});
|
|
136
|
+
this.scheduler.on('completed', async ({ workItem, result }) => {
|
|
137
|
+
const workItemCtx = this.formatWorkItemContext(workItem);
|
|
138
|
+
console.log(`[Scheduler] WorkItem completed: ${workItemCtx} ${workItem.title}`);
|
|
139
|
+
broadcastEvent({ type: 'workItem:completed', data: { workItemId: workItem.id, success: result.success, duration: result.totalDuration } });
|
|
140
|
+
this.recordPipelineHistory(workItem, result);
|
|
141
|
+
await reportToDiscord(formatPipelineResultEmbed(result));
|
|
142
|
+
if (result.finalStatus === 'dispatched') {
|
|
143
|
+
console.log(`[Scheduler] WorkItem dispatched to Paseo, waiting for external completion: ${workItemCtx} ${workItem.title}`);
|
|
144
|
+
this.scheduleNextHeartbeat();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Track as completed ONLY on success to prevent re-selection (persist to disk)
|
|
148
|
+
if (workItem.workItemId && result.success) {
|
|
149
|
+
this.completedWorkItemIds.add(workItem.workItemId);
|
|
150
|
+
clearRejection(workItem.workItemId); // Clear rejection count on success
|
|
151
|
+
clearRetryTime(workItem.workItemId, this.failedWorkItemRetryTimes); // Clear retry backoff time
|
|
152
|
+
this.saveWorkItemState();
|
|
153
|
+
// Track project-level pace (5h rolling window)
|
|
154
|
+
const projectName = workItem.azdoProject?.name ?? 'unknown';
|
|
155
|
+
recordProjectCompletion(projectName, result.totalCost?.costUsd);
|
|
156
|
+
}
|
|
157
|
+
// Skip completion handling for decomposed workItems. Child workItems represent the runnable work.
|
|
158
|
+
if (result.finalStatus === 'decomposed') {
|
|
159
|
+
console.log(`[Scheduler] WorkItem decomposed into sub-workItems, skipping Done state`);
|
|
160
|
+
this.scheduleNextHeartbeat();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// On success, update Azdo workItem to Done
|
|
164
|
+
if (result.success && workItem.workItemId) {
|
|
165
|
+
try {
|
|
166
|
+
await execution.syncSuccessState(workItem);
|
|
167
|
+
await azdo.logPairComplete(workItem.workItemId, result.sessionId, {
|
|
168
|
+
attempts: result.iterations,
|
|
169
|
+
duration: Math.floor(result.totalDuration / 1000),
|
|
170
|
+
filesChanged: result.executorResult?.filesChanged || [],
|
|
171
|
+
executorSummary: result.executorResult?.summary,
|
|
172
|
+
executorCommands: result.executorResult?.commands,
|
|
173
|
+
reviewerFeedback: result.reviewResult?.feedback,
|
|
174
|
+
reviewerDecision: result.reviewResult?.decision,
|
|
175
|
+
testResults: result.testerResult ? {
|
|
176
|
+
passed: result.testerResult.testsPassed,
|
|
177
|
+
failed: result.testerResult.testsFailed,
|
|
178
|
+
coverage: result.testerResult.coverage,
|
|
179
|
+
failedTests: result.testerResult.failedTests,
|
|
180
|
+
} : undefined,
|
|
181
|
+
});
|
|
182
|
+
await execution.reconcileCompletionState(workItem);
|
|
183
|
+
console.log(`[Scheduler] WorkItem ${workItem.workItemId} marked as Done`);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
console.error(`[Scheduler] Failed to update workItem state:`, err);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Azdo project Status Update + Overview refresh (non-blocking)
|
|
190
|
+
if (workItem.azdoProject) {
|
|
191
|
+
updateProjectAfterWorkItem(workItem.azdoProject.id, workItem.azdoProject.name, {
|
|
192
|
+
title: workItem.title,
|
|
193
|
+
success: result.success,
|
|
194
|
+
duration: result.totalDuration,
|
|
195
|
+
workItemId: workItem.workItemId,
|
|
196
|
+
workItemIdentifier: workItem.workItemIdentifier,
|
|
197
|
+
cost: result.totalCost?.costUsd,
|
|
198
|
+
projectPath: result.workItemContext?.projectPath,
|
|
199
|
+
}).catch(e => console.warn('[Scheduler] Project update failed:', e));
|
|
200
|
+
}
|
|
201
|
+
this.scheduleNextHeartbeat();
|
|
202
|
+
});
|
|
203
|
+
this.scheduler.on('failed', async ({ workItem, result }) => {
|
|
204
|
+
const workItemCtx = this.formatWorkItemContext(workItem);
|
|
205
|
+
console.log(`[Scheduler] WorkItem failed: ${workItemCtx} ${workItem.title}`);
|
|
206
|
+
broadcastEvent({ type: 'workItem:completed', data: { workItemId: workItem.id, success: false, duration: result.totalDuration } });
|
|
207
|
+
this.recordPipelineHistory(workItem, result);
|
|
208
|
+
await reportToDiscord(formatPipelineResultEmbed(result));
|
|
209
|
+
// If rejected, track rejection count and block after max attempts
|
|
210
|
+
if (workItem.workItemId && result.finalStatus === 'rejected') {
|
|
211
|
+
const feedback = result.reviewResult?.feedback || 'No feedback provided';
|
|
212
|
+
const rejectionCount = incrementRejection(workItem.workItemId, feedback);
|
|
213
|
+
console.log(`[Scheduler] WorkItem rejected (${rejectionCount}/3): ${workItemCtx} ${workItem.title}`);
|
|
214
|
+
console.log(`[Scheduler] Rejection reason: ${feedback}`);
|
|
215
|
+
if (isRejectionLimitReached(workItem.workItemId)) {
|
|
216
|
+
// Max rejections reached - permanently block
|
|
217
|
+
this.completedWorkItemIds.add(workItem.workItemId); // Prevent re-selection
|
|
218
|
+
clearRetryTime(workItem.workItemId, this.failedWorkItemRetryTimes); // Clear retry time
|
|
219
|
+
this.saveWorkItemState();
|
|
220
|
+
try {
|
|
221
|
+
await execution.syncFailureState(workItem, `Max rejection limit reached (${rejectionCount} attempts): ${feedback}`);
|
|
222
|
+
await azdo.logBlocked(workItem.workItemId, 'autonomous-runner', `⚠️ **Max rejection limit reached (${rejectionCount} attempts)**\n\n` +
|
|
223
|
+
`This workItem has been rejected ${rejectionCount} times by the reviewer and requires manual intervention.\n\n` +
|
|
224
|
+
`**Latest rejection reason:**\n${feedback}\n\n` +
|
|
225
|
+
`**Action required:** Please review the workItem requirements and code manually, or adjust the workItem scope.`);
|
|
226
|
+
console.log(`[Scheduler] WorkItem ${workItem.workItemId} permanently blocked (max rejections reached)`);
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
console.error(`[Scheduler] Failed to update workItem state:`, err);
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Not max yet - schedule retry with exponential backoff
|
|
235
|
+
const nextRetryTime = setRetryTime(workItem.workItemId, rejectionCount, this.failedWorkItemRetryTimes);
|
|
236
|
+
const retryIn = formatRetryTime(nextRetryTime);
|
|
237
|
+
broadcastEvent({
|
|
238
|
+
type: 'log',
|
|
239
|
+
data: {
|
|
240
|
+
workItemId: workItem.id,
|
|
241
|
+
stage: 'retry',
|
|
242
|
+
line: `Retry backoff (rejected): ${rejectionCount}/3 — next attempt ${retryIn}`,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
this.saveWorkItemState();
|
|
246
|
+
try {
|
|
247
|
+
await execution.syncFailureState(workItem, `Review rejected (${rejectionCount}/3): ${feedback}`);
|
|
248
|
+
await azdo.logBlocked(workItem.workItemId, 'autonomous-runner', t('runner.reviewRejected', { feedback }) +
|
|
249
|
+
`\n\n**Rejection count:** ${rejectionCount}/3 - Will retry automatically ${retryIn}.`);
|
|
250
|
+
console.log(`[Scheduler] WorkItem ${workItem.workItemId} marked as Todo (blocked) (rejected ${rejectionCount}/3) — retry ${retryIn}`);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
console.error(`[Scheduler] Failed to update workItem state:`, err);
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Track failure count — block after MAX_RETRY_COUNT failures
|
|
259
|
+
if (workItem.workItemId) {
|
|
260
|
+
const count = (this.failedWorkItemCounts.get(workItem.workItemId) ?? 0) + 1;
|
|
261
|
+
this.failedWorkItemCounts.set(workItem.workItemId, count);
|
|
262
|
+
if (count >= AutonomousRunner.MAX_RETRY_COUNT) {
|
|
263
|
+
// Max retries exceeded - permanently block
|
|
264
|
+
this.completedWorkItemIds.add(workItem.workItemId); // Prevent re-selection
|
|
265
|
+
clearRetryTime(workItem.workItemId, this.failedWorkItemRetryTimes); // Clear retry time
|
|
266
|
+
this.saveWorkItemState();
|
|
267
|
+
console.log(`[Scheduler] WorkItem failure count: ${count}/${AutonomousRunner.MAX_RETRY_COUNT} for ${workItemCtx} — BLOCKED`);
|
|
268
|
+
try {
|
|
269
|
+
await execution.syncFailureState(workItem, `Autonomous execution failed ${count} times`);
|
|
270
|
+
await azdo.logBlocked(workItem.workItemId, 'autonomous-runner', `Autonomous execution failed ${count} times. Moving to Blocked for manual review.`);
|
|
271
|
+
console.log(`[Scheduler] WorkItem ${workItem.workItemId} marked as Todo (blocked) (max retries exceeded)`);
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
console.error(`[Scheduler] Failed to update workItem state:`, err);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
// Schedule retry with exponential backoff
|
|
279
|
+
const nextRetryTime = setRetryTime(workItem.workItemId, count, this.failedWorkItemRetryTimes);
|
|
280
|
+
const retryIn = formatRetryTime(nextRetryTime);
|
|
281
|
+
console.log(`[Scheduler] WorkItem failure count: ${count}/${AutonomousRunner.MAX_RETRY_COUNT} for ${workItemCtx} — retry ${retryIn}`);
|
|
282
|
+
broadcastEvent({
|
|
283
|
+
type: 'log',
|
|
284
|
+
data: {
|
|
285
|
+
workItemId: workItem.id,
|
|
286
|
+
stage: 'retry',
|
|
287
|
+
line: `Retry backoff: ${count}/${AutonomousRunner.MAX_RETRY_COUNT} — next attempt ${retryIn}`,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
this.saveWorkItemState();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Azdo project Status Update + Overview refresh (non-blocking)
|
|
294
|
+
if (workItem.azdoProject) {
|
|
295
|
+
updateProjectAfterWorkItem(workItem.azdoProject.id, workItem.azdoProject.name, {
|
|
296
|
+
title: workItem.title,
|
|
297
|
+
success: result.success,
|
|
298
|
+
duration: result.totalDuration,
|
|
299
|
+
workItemIdentifier: workItem.workItemIdentifier,
|
|
300
|
+
cost: result.totalCost?.costUsd,
|
|
301
|
+
projectPath: result.workItemContext?.projectPath,
|
|
302
|
+
}).catch(e => console.warn('[Scheduler] Project update failed:', e));
|
|
303
|
+
}
|
|
304
|
+
this.scheduleNextHeartbeat();
|
|
305
|
+
});
|
|
306
|
+
this.scheduler.on('error', async ({ workItem, error }) => {
|
|
307
|
+
const workItemCtx = this.formatWorkItemContext(workItem);
|
|
308
|
+
console.error(`[Scheduler] WorkItem error: ${workItemCtx} ${workItem.title}`, error);
|
|
309
|
+
await reportToDiscord(t('runner.pipelineError', { title: `${workItemCtx} ${workItem.title}`, error: error.message }));
|
|
310
|
+
});
|
|
311
|
+
this.scheduler.on('slotFreed', () => {
|
|
312
|
+
// Auto-execute next workItem when slot becomes available
|
|
313
|
+
void this.runAvailableWorkItems();
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
filterAlreadyProcessed(workItems) {
|
|
317
|
+
let recovered = 0;
|
|
318
|
+
let backoffSkipped = 0;
|
|
319
|
+
const recoverableStates = new Set(['Todo', 'In Progress', 'In Review']);
|
|
320
|
+
const filtered = workItems.filter(workItem => {
|
|
321
|
+
const id = workItem.workItemId || workItem.id;
|
|
322
|
+
// Check rejection limit first
|
|
323
|
+
if (isRejectionLimitReached(id)) {
|
|
324
|
+
return false; // Skip workItems that hit max rejection limit
|
|
325
|
+
}
|
|
326
|
+
// Recover workItems in active states from completed/failed list
|
|
327
|
+
// (user or system intentionally moved back to active, so retry)
|
|
328
|
+
if (recoverableStates.has(workItem.azdoState || '') && (this.completedWorkItemIds.has(id) || (this.failedWorkItemCounts.get(id) ?? 0) >= AutonomousRunner.MAX_RETRY_COUNT)) {
|
|
329
|
+
this.completedWorkItemIds.delete(id);
|
|
330
|
+
this.failedWorkItemCounts.delete(id);
|
|
331
|
+
clearRejection(id); // Clear rejection count on recovery
|
|
332
|
+
clearRetryTime(id, this.failedWorkItemRetryTimes); // Clear retry backoff time
|
|
333
|
+
recovered++;
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
if (this.completedWorkItemIds.has(id))
|
|
337
|
+
return false;
|
|
338
|
+
if ((this.failedWorkItemCounts.get(id) ?? 0) >= AutonomousRunner.MAX_RETRY_COUNT)
|
|
339
|
+
return false;
|
|
340
|
+
if (this.hasActivePaseoAssignment(id))
|
|
341
|
+
return false;
|
|
342
|
+
// Check if workItem is in exponential backoff period
|
|
343
|
+
if (!canRetryNow(id, this.failedWorkItemRetryTimes)) {
|
|
344
|
+
backoffSkipped++;
|
|
345
|
+
return false; // Skip workItems still in backoff period
|
|
346
|
+
}
|
|
347
|
+
return true;
|
|
348
|
+
});
|
|
349
|
+
if (recovered > 0) {
|
|
350
|
+
this.saveWorkItemState();
|
|
351
|
+
this.syslog(`♻ Recovered ${recovered} Todo workItems from completed/failed/rejected list`);
|
|
352
|
+
}
|
|
353
|
+
if (backoffSkipped > 0) {
|
|
354
|
+
this.syslog(`⏰ Skipped ${backoffSkipped} workItems in exponential backoff period`);
|
|
355
|
+
}
|
|
356
|
+
return filtered;
|
|
357
|
+
}
|
|
358
|
+
/** Schedule next heartbeat with pace-aware cooldown */
|
|
359
|
+
_nextHeartbeatTimer = null;
|
|
360
|
+
scheduleNextHeartbeat() {
|
|
361
|
+
if (this._nextHeartbeatTimer)
|
|
362
|
+
return; // already scheduled
|
|
363
|
+
const isTurbo = this.getTurboMode();
|
|
364
|
+
// Turbo: 5min flat, no progressive slowdown
|
|
365
|
+
if (isTurbo) {
|
|
366
|
+
const turboCooldown = 5 * 60_000; // 5min
|
|
367
|
+
console.log(`[AutonomousRunner] TURBO: next heartbeat in 5min`);
|
|
368
|
+
this._nextHeartbeatTimer = setTimeout(() => {
|
|
369
|
+
this._nextHeartbeatTimer = null;
|
|
370
|
+
void this.heartbeat();
|
|
371
|
+
}, turboCooldown);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
// Normal: progressive slowdown based on 5h window usage
|
|
375
|
+
const perProjectCap = this.config.dailyWorkItemCap ?? 6;
|
|
376
|
+
const globalCap = Math.max(this.enabledProjects.size, 3) * perProjectCap;
|
|
377
|
+
const baseCooldown = this.config.interWorkItemCooldownMs ?? 1_800_000; // 30min default
|
|
378
|
+
const totalInWindow = getDailyCompletedCount();
|
|
379
|
+
// Progressive slowdown: ratio² × 3 multiplier
|
|
380
|
+
const ratio = totalInWindow / globalCap;
|
|
381
|
+
const multiplier = 1 + (ratio * ratio * 3);
|
|
382
|
+
const adjustedCooldown = Math.round(baseCooldown * multiplier);
|
|
383
|
+
const cooldownMin = Math.round(adjustedCooldown / 60_000);
|
|
384
|
+
console.log(`[AutonomousRunner] Scheduling next heartbeat in ${cooldownMin}min (5h window: ${totalInWindow}/${globalCap}, multiplier: ${multiplier.toFixed(2)}x)`);
|
|
385
|
+
this._nextHeartbeatTimer = setTimeout(() => {
|
|
386
|
+
this._nextHeartbeatTimer = null;
|
|
387
|
+
void this.heartbeat();
|
|
388
|
+
}, adjustedCooldown);
|
|
389
|
+
}
|
|
390
|
+
async runAvailableWorkItems() {
|
|
391
|
+
if (!this.config.pairMode || !this.config.maxConcurrentWorkItems) {
|
|
392
|
+
return; // Parallel processing disabled
|
|
393
|
+
}
|
|
394
|
+
await this.scheduler.runAvailable(async (workItem, projectPath) => {
|
|
395
|
+
return this.shouldDispatchWithPaseo()
|
|
396
|
+
? this.dispatchWithPaseo(workItem, projectPath)
|
|
397
|
+
: this.executePipeline(workItem, projectPath);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
shouldDispatchWithPaseo() {
|
|
401
|
+
return (this.config.dispatchBackend ?? 'paseo') === 'paseo';
|
|
402
|
+
}
|
|
403
|
+
hasActivePaseoAssignment(workItemId) {
|
|
404
|
+
try {
|
|
405
|
+
return listAssignments(resolveFactoryRoot()).some((assignment) => {
|
|
406
|
+
return assignment.workItemId === workItemId
|
|
407
|
+
&& assignment.dispatcher === 'paseo'
|
|
408
|
+
&& (assignment.status === 'active' || assignment.status === 'stuck');
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async dispatchWithPaseo(workItem, projectPath) {
|
|
416
|
+
const startedAt = Date.now();
|
|
417
|
+
const workItemId = workItem.workItemId || workItem.id;
|
|
418
|
+
const workItemRef = workItem.workItemIdentifier || workItemId;
|
|
419
|
+
const worktreeName = branchNameFor({ workItemId: workItemRef, title: workItem.title }, 'Backlog item');
|
|
420
|
+
const factoryRoot = resolveFactoryRoot();
|
|
421
|
+
try {
|
|
422
|
+
if (this.hasActivePaseoAssignment(workItemId)) {
|
|
423
|
+
return {
|
|
424
|
+
success: true,
|
|
425
|
+
sessionId: `paseo-existing-${workItemId}`,
|
|
426
|
+
stages: [],
|
|
427
|
+
finalStatus: 'dispatched',
|
|
428
|
+
totalDuration: Date.now() - startedAt,
|
|
429
|
+
iterations: 0,
|
|
430
|
+
workItemContext: {
|
|
431
|
+
workItemIdentifier: workItem.workItemIdentifier || workItem.workItemId,
|
|
432
|
+
projectName: workItem.azdoProject?.name,
|
|
433
|
+
projectPath,
|
|
434
|
+
workItemTitle: workItem.title,
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
broadcastEvent({
|
|
439
|
+
type: 'log',
|
|
440
|
+
data: { workItemId, stage: 'dispatch', line: `Dispatching to Paseo (${this.config.paseo?.provider ?? 'codex/gpt-5.3-codex-spark'})` },
|
|
441
|
+
});
|
|
442
|
+
const dispatch = await dispatchWorkItemToPaseo({
|
|
443
|
+
workItemId,
|
|
444
|
+
workItemIdentifier: workItem.workItemIdentifier,
|
|
445
|
+
title: workItem.title,
|
|
446
|
+
description: workItem.description,
|
|
447
|
+
projectName: workItem.azdoProject?.name,
|
|
448
|
+
projectPath,
|
|
449
|
+
worktreeName,
|
|
450
|
+
}, this.config.paseo);
|
|
451
|
+
createAssignment({
|
|
452
|
+
workItemId,
|
|
453
|
+
operatorLane: 'ralph',
|
|
454
|
+
repoRoot: projectPath,
|
|
455
|
+
host: 'codex',
|
|
456
|
+
sessionId: dispatch.agentId,
|
|
457
|
+
dispatcher: 'paseo',
|
|
458
|
+
paseoAgentId: dispatch.agentId,
|
|
459
|
+
paseoProvider: dispatch.provider,
|
|
460
|
+
paseoMode: dispatch.mode,
|
|
461
|
+
paseoCwd: dispatch.cwd,
|
|
462
|
+
paseoWorktree: worktreeName,
|
|
463
|
+
paseoStatus: 'dispatched',
|
|
464
|
+
paseoLastCheckedAt: new Date().toISOString(),
|
|
465
|
+
}, factoryRoot);
|
|
466
|
+
if (workItem.workItemId) {
|
|
467
|
+
try {
|
|
468
|
+
const state = await markWorkItemDispatched(workItem.workItemId, {
|
|
469
|
+
workItemIdentifier: workItem.workItemIdentifier,
|
|
470
|
+
title: workItem.title,
|
|
471
|
+
projectId: workItem.azdoProject?.id,
|
|
472
|
+
projectName: workItem.azdoProject?.name,
|
|
473
|
+
azdoState: 'In Progress',
|
|
474
|
+
sessionId: dispatch.agentId,
|
|
475
|
+
branchName: worktreeName,
|
|
476
|
+
worktreePath: dispatch.cwd,
|
|
477
|
+
});
|
|
478
|
+
await azdo.logPairStart(workItem.workItemId, dispatch.agentId, dispatch.cwd);
|
|
479
|
+
await azdo.addComment(workItem.workItemId, buildWorkItemStateSyncComment(state, 'WorkItem dispatched to Paseo'));
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
console.warn(`[PaseoDispatch] Failed to persist in-progress state for ${workItemRef}:`, err);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
broadcastEvent({
|
|
486
|
+
type: 'log',
|
|
487
|
+
data: { workItemId, stage: 'dispatch', line: `Paseo agent: ${dispatch.agentId}` },
|
|
488
|
+
});
|
|
489
|
+
return {
|
|
490
|
+
success: true,
|
|
491
|
+
sessionId: dispatch.agentId,
|
|
492
|
+
stages: [],
|
|
493
|
+
finalStatus: 'dispatched',
|
|
494
|
+
totalDuration: Date.now() - startedAt,
|
|
495
|
+
iterations: 0,
|
|
496
|
+
workItemContext: {
|
|
497
|
+
workItemIdentifier: workItem.workItemIdentifier || workItem.workItemId,
|
|
498
|
+
projectName: workItem.azdoProject?.name,
|
|
499
|
+
projectPath: dispatch.cwd,
|
|
500
|
+
workItemTitle: workItem.title,
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
catch (err) {
|
|
505
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
506
|
+
broadcastEvent({
|
|
507
|
+
type: 'log',
|
|
508
|
+
data: { workItemId, stage: 'dispatch', line: `Paseo dispatch failed: ${message}` },
|
|
509
|
+
});
|
|
510
|
+
return {
|
|
511
|
+
success: false,
|
|
512
|
+
sessionId: `paseo-dispatch-failed-${Date.now()}`,
|
|
513
|
+
stages: [],
|
|
514
|
+
finalStatus: 'failed',
|
|
515
|
+
totalDuration: Date.now() - startedAt,
|
|
516
|
+
iterations: 0,
|
|
517
|
+
workItemContext: {
|
|
518
|
+
workItemIdentifier: workItem.workItemIdentifier || workItem.workItemId,
|
|
519
|
+
projectName: workItem.azdoProject?.name,
|
|
520
|
+
projectPath,
|
|
521
|
+
workItemTitle: workItem.title,
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
getRolesForProject(projectPath) {
|
|
527
|
+
// Find per-project configuration
|
|
528
|
+
const projectConfig = this.config.projectAgents?.find(pa => projectPath.includes(pa.path.replace('~', '')));
|
|
529
|
+
if (!projectConfig?.stages && !this.config.defaultStages) {
|
|
530
|
+
// Convert from legacy configuration
|
|
531
|
+
return {
|
|
532
|
+
coder: {
|
|
533
|
+
enabled: true,
|
|
534
|
+
model: this.config.executorModel || 'claude-sonnet-4-5-20250929',
|
|
535
|
+
timeoutMs: this.config.executorTimeoutMs ?? 0,
|
|
536
|
+
},
|
|
537
|
+
critic: {
|
|
538
|
+
enabled: true,
|
|
539
|
+
model: this.config.reviewerModel || 'claude-haiku-4-5-20251001',
|
|
540
|
+
timeoutMs: this.config.reviewerTimeoutMs ?? 0,
|
|
541
|
+
},
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
// Apply per-project overrides
|
|
545
|
+
const base = this.config.defaultStages || {
|
|
546
|
+
coder: { enabled: true, model: 'claude-sonnet-4-5-20250929', timeoutMs: 0 },
|
|
547
|
+
critic: { enabled: true, model: 'claude-haiku-4-5-20251001', timeoutMs: 0 },
|
|
548
|
+
};
|
|
549
|
+
if (!projectConfig?.stages) {
|
|
550
|
+
return base;
|
|
551
|
+
}
|
|
552
|
+
// Merge overrides
|
|
553
|
+
return {
|
|
554
|
+
coder: { ...base.coder, ...projectConfig.stages.coder },
|
|
555
|
+
critic: { ...base.critic, ...projectConfig.stages.critic },
|
|
556
|
+
tester: projectConfig.stages.tester
|
|
557
|
+
? { ...base.tester, ...projectConfig.stages.tester }
|
|
558
|
+
: base.tester,
|
|
559
|
+
documenter: projectConfig.stages.documenter
|
|
560
|
+
? { ...base.documenter, ...projectConfig.stages.documenter }
|
|
561
|
+
: base.documenter,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
async start() {
|
|
565
|
+
if (this.state.isRunning) {
|
|
566
|
+
console.log('[AutonomousRunner] Already running');
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
await this.engine.init();
|
|
570
|
+
// worktree mode: clean up dangling worktrees at startup
|
|
571
|
+
if (this.config.worktreeMode) {
|
|
572
|
+
for (const projectPath of this.config.allowedProjects) {
|
|
573
|
+
const resolvedPath = projectPath.replace(/^~(?=\/|$)/, homedir());
|
|
574
|
+
pruneWorktrees(resolvedPath).catch((e) => console.error(`[AutonomousRunner] Worktree prune failed for ${resolvedPath}:`, e));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
// Set up cron job
|
|
578
|
+
this.cronJob = new Cron(this.config.heartbeatSchedule, async () => {
|
|
579
|
+
await this.heartbeat();
|
|
580
|
+
});
|
|
581
|
+
this.state.isRunning = true;
|
|
582
|
+
this.state.startedAt = Date.now();
|
|
583
|
+
console.log(`[AutonomousRunner] Started with schedule: ${this.config.heartbeatSchedule}`);
|
|
584
|
+
await reportToDiscord(`🤖 ${t('runner.modeStarted')}\n` +
|
|
585
|
+
`Schedule: \`${this.config.heartbeatSchedule}\`\n` +
|
|
586
|
+
`Auto-execute: ${this.config.autoExecute ? '✅' : '❌'}\n` +
|
|
587
|
+
`Projects: ${this.config.allowedProjects.join(', ')}`);
|
|
588
|
+
// Immediate execution option
|
|
589
|
+
if (this.config.triggerNow) {
|
|
590
|
+
console.log('[AutonomousRunner] Triggering immediate heartbeat in 10s...');
|
|
591
|
+
setTimeout(() => void this.heartbeat(), 10000); // Run after 10s (wait for Discord/Azdo connection)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
stop() {
|
|
595
|
+
if (this.cronJob) {
|
|
596
|
+
this.cronJob.stop();
|
|
597
|
+
this.cronJob = null;
|
|
598
|
+
}
|
|
599
|
+
this.state.isRunning = false;
|
|
600
|
+
console.log('[AutonomousRunner] Stopped');
|
|
601
|
+
}
|
|
602
|
+
buildStats() {
|
|
603
|
+
const stats = this.scheduler.getStats();
|
|
604
|
+
return {
|
|
605
|
+
runningWorkItems: stats.running,
|
|
606
|
+
queuedWorkItems: stats.queued,
|
|
607
|
+
completedToday: stats.completed,
|
|
608
|
+
uptime: this.state.startedAt ? Date.now() - this.state.startedAt : 0,
|
|
609
|
+
schedulerPaused: this.scheduler.isPaused(),
|
|
610
|
+
executionPaused: this.executionPaused,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
/** Send system message to dashboard LIVE LOG */
|
|
614
|
+
syslog(line) {
|
|
615
|
+
console.log(`[HB] ${line}`);
|
|
616
|
+
broadcastEvent({ type: 'log', data: { workItemId: 'system', stage: 'heartbeat', line } });
|
|
617
|
+
}
|
|
618
|
+
async reconcilePaseoLifecycle() {
|
|
619
|
+
if ((this.config.dispatchBackend ?? 'paseo') !== 'paseo')
|
|
620
|
+
return;
|
|
621
|
+
const results = await reconcilePaseoAssignments(resolveFactoryRoot(), this.config.paseo, {
|
|
622
|
+
addComment: azdo.addComment,
|
|
623
|
+
updateAzdoState: azdo.updateWorkItemState,
|
|
624
|
+
});
|
|
625
|
+
const changed = results.filter((result) => result.action !== 'skipped');
|
|
626
|
+
if (changed.length > 0) {
|
|
627
|
+
const summary = changed.map((result) => `${result.workItemId}:${result.status}`).join(', ');
|
|
628
|
+
this.syslog(`✓ Paseo lifecycle reconciled: ${summary}`);
|
|
629
|
+
broadcastEvent({ type: 'stats', data: this.buildStats() });
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
async heartbeat() {
|
|
633
|
+
if (this._heartbeatRunning) {
|
|
634
|
+
console.log('[AutonomousRunner] Heartbeat already running, skipping');
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
this._heartbeatRunning = true;
|
|
638
|
+
console.log('[AutonomousRunner] Heartbeat triggered');
|
|
639
|
+
this.state.lastHeartbeat = Date.now();
|
|
640
|
+
broadcastEvent({ type: 'stats', data: this.buildStats() });
|
|
641
|
+
broadcastEvent({ type: 'heartbeat' });
|
|
642
|
+
this.syslog('▶ Heartbeat started');
|
|
643
|
+
try {
|
|
644
|
+
// 0.5 Long-running monitor passive check (before time window)
|
|
645
|
+
const active = getActiveMonitors().filter(m => m.state === 'pending' || m.state === 'running');
|
|
646
|
+
if (active.length > 0) {
|
|
647
|
+
const checked = await checkAllMonitors().catch(() => 0);
|
|
648
|
+
this.syslog(`✓ Monitors: ${checked} checked / ${active.length} active`);
|
|
649
|
+
}
|
|
650
|
+
await this.reconcilePaseoLifecycle().catch((error) => {
|
|
651
|
+
this.syslog(`⚠ Paseo lifecycle reconciliation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
652
|
+
});
|
|
653
|
+
// 1. Check time window
|
|
654
|
+
const timeCheck = checkWorkAllowed();
|
|
655
|
+
if (!timeCheck.allowed) {
|
|
656
|
+
console.log(`[AutonomousRunner] Blocked: ${timeCheck.reason}`);
|
|
657
|
+
this.syslog(`⛔ Time window blocked: ${timeCheck.reason}`);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
this.syslog('✓ Time window: allowed');
|
|
661
|
+
// 1.4 Execution pause gate — check before quota/pace/fetch
|
|
662
|
+
if (this.executionPaused) {
|
|
663
|
+
this.syslog('⏸ Execution paused — skipping claim cycle');
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
// 1.5 Quota gate — skip heartbeat if Claude Max quota is too high
|
|
667
|
+
const quotaCheck = await checkQuotaAllowance(80);
|
|
668
|
+
if (!quotaCheck.allowed) {
|
|
669
|
+
console.log(`[AutonomousRunner] Quota gate: SKIP — ${quotaCheck.reason}`);
|
|
670
|
+
broadcastEvent({ type: 'log', data: { workItemId: 'system', stage: 'quota', line: `⏸ ${quotaCheck.reason}` } });
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (quotaCheck.utilization !== undefined && quotaCheck.utilization > 60) {
|
|
674
|
+
console.log(`[AutonomousRunner] Quota warning: ${quotaCheck.utilization.toFixed(0)}% utilization`);
|
|
675
|
+
}
|
|
676
|
+
// 1.6 Pace gate — per-project 5h rolling window
|
|
677
|
+
const isTurbo = this.getTurboMode();
|
|
678
|
+
const perProjectCap = isTurbo ? 20 : (this.config.dailyWorkItemCap ?? 6);
|
|
679
|
+
const totalInWindow = getDailyCompletedCount();
|
|
680
|
+
// 전역 상한: 프로젝트 수 × per-project cap (안전장치)
|
|
681
|
+
const globalCap = Math.max(this.enabledProjects.size, 3) * perProjectCap;
|
|
682
|
+
if (totalInWindow >= globalCap) {
|
|
683
|
+
console.log(`[AutonomousRunner] Global pace limit: ${totalInWindow}/${globalCap} workItems in 5h window — skipping`);
|
|
684
|
+
this.syslog(`⏸ Global pace: ${totalInWindow}/${globalCap} (5h window)`);
|
|
685
|
+
broadcastEvent({ type: 'log', data: { workItemId: 'system', stage: 'pace', line: `⏸ Global pace: ${totalInWindow}/${globalCap}` } });
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const modeLabel = isTurbo ? 'TURBO' : 'Normal';
|
|
689
|
+
this.syslog(`✓ Pace: ${totalInWindow}/${globalCap} global, ${perProjectCap}/project [${modeLabel}]`);
|
|
690
|
+
// 2. Fetch workItems from configured source
|
|
691
|
+
const workItemSource = getWorkItemSource();
|
|
692
|
+
const sourceLabel = workItemSource === 'local' ? 'Local' : workItemSource === 'azdo' ? 'Azdo' : 'Unknown';
|
|
693
|
+
this.syslog(`⟳ Fetching workItems from ${sourceLabel}...`);
|
|
694
|
+
const fetchResult = await fetchAzdoWorkItems();
|
|
695
|
+
if (fetchResult.error) {
|
|
696
|
+
this.syslog(`✗ ${sourceLabel} fetch error: ${fetchResult.error}`);
|
|
697
|
+
await reportToDiscord(`⚠️ ${sourceLabel} fetch failed: ${fetchResult.error}`);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const workItems = fetchResult.workItems;
|
|
701
|
+
if (workItems.length === 0) {
|
|
702
|
+
this.syslog('— No workItems in backlog');
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
this.lastFetchedWorkItems = workItems;
|
|
706
|
+
this.syslog(`✓ Found ${workItems.length} workItems from ${sourceLabel}`);
|
|
707
|
+
// Filter out completed and over-retried workItems
|
|
708
|
+
const filteredWorkItems = this.filterAlreadyProcessed(workItems);
|
|
709
|
+
if (filteredWorkItems.length === 0) {
|
|
710
|
+
this.syslog('— All workItems already completed or max retries exceeded');
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (filteredWorkItems.length !== workItems.length) {
|
|
714
|
+
this.syslog(` Filtered: ${workItems.length} → ${filteredWorkItems.length} (skipped ${workItems.length - filteredWorkItems.length} completed/failed)`);
|
|
715
|
+
}
|
|
716
|
+
// Parallel processing mode
|
|
717
|
+
if (this.config.maxConcurrentWorkItems && this.config.maxConcurrentWorkItems > 1 && this.config.pairMode) {
|
|
718
|
+
await this.heartbeatParallel(filteredWorkItems);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
// 3. Run Decision Engine (single workItem)
|
|
722
|
+
this.syslog('⟳ Running Decision Engine...');
|
|
723
|
+
const decision = await this.engine.heartbeat(filteredWorkItems);
|
|
724
|
+
this.syslog(`→ Decision: ${decision.action} — ${decision.reason}`);
|
|
725
|
+
this.state.lastDecision = decision;
|
|
726
|
+
// 4. Handle decision
|
|
727
|
+
if (decision.action === 'execute' && decision.workItem) {
|
|
728
|
+
await this.executeWorkItemPairMode(decision.workItem);
|
|
729
|
+
}
|
|
730
|
+
else if (decision.action === 'defer' && decision.workItem) {
|
|
731
|
+
this.state.pendingApproval = decision.workItem;
|
|
732
|
+
await this.requestApproval(decision);
|
|
733
|
+
}
|
|
734
|
+
this.state.consecutiveErrors = 0;
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
this.state.consecutiveErrors++;
|
|
738
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
739
|
+
console.error('[AutonomousRunner] Heartbeat error:', msg);
|
|
740
|
+
this.syslog(`✗ Heartbeat error: ${msg}`);
|
|
741
|
+
if (this.state.consecutiveErrors >= 3) {
|
|
742
|
+
await reportToDiscord(t('runner.consecutiveErrors', { count: this.state.consecutiveErrors, error: msg }));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
finally {
|
|
746
|
+
this._heartbeatRunning = false;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
async heartbeatParallel(workItems) {
|
|
750
|
+
const availableSlots = this.scheduler.getAvailableSlots();
|
|
751
|
+
const runningCount = this.scheduler.getStats().running;
|
|
752
|
+
this.syslog(` Parallel mode | slots: ${availableSlots} free / ${this.config.maxConcurrentWorkItems} max | running: ${runningCount}`);
|
|
753
|
+
if (availableSlots === 0) {
|
|
754
|
+
this.syslog(`⏳ All slots busy (${runningCount} workItems running), waiting...`);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
// Fill all available slots (worktree mode isolates each workItem)
|
|
758
|
+
const maxSlots = availableSlots;
|
|
759
|
+
// Pre-filter workItems to enabled projects only (before DecisionEngine selection)
|
|
760
|
+
// This prevents DecisionEngine from wasting its max-slot budget on non-enabled projects.
|
|
761
|
+
// Only execute active states; Backlog/Blocked are fetched for dashboard visibility.
|
|
762
|
+
const executableStates = new Set(['Todo', 'In Progress', 'In Review']);
|
|
763
|
+
const executableWorkItems = workItems.filter(t => executableStates.has(t.azdoState || ''));
|
|
764
|
+
let workItemsForEngine = executableWorkItems;
|
|
765
|
+
if (this.enabledProjects.size > 0) {
|
|
766
|
+
workItemsForEngine = [];
|
|
767
|
+
for (const workItem of executableWorkItems) {
|
|
768
|
+
const projName = workItem.azdoProject?.name;
|
|
769
|
+
if (!projName) {
|
|
770
|
+
this.syslog(` ⚠ Skipping workItem without project mapping: ${workItem.workItemIdentifier}`);
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
let cachedPath = this.projectPathCache.get(projName)
|
|
774
|
+
?? this.projectPathCache.get(projName.toLowerCase())
|
|
775
|
+
?? this.projectPathCache.get(projName.replace(/-/g, ' '));
|
|
776
|
+
if (!cachedPath) {
|
|
777
|
+
const lowerProj = projName.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
778
|
+
for (const [key, path] of this.projectPathCache.entries()) {
|
|
779
|
+
const lowerKey = key.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
780
|
+
if (lowerKey.length > 3 && (lowerProj.includes(lowerKey) || lowerKey.includes(lowerProj))) {
|
|
781
|
+
cachedPath = path;
|
|
782
|
+
this.projectPathCache.set(projName, path);
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (!cachedPath) {
|
|
788
|
+
const resolvedPath = await this.resolveProjectPath(workItem);
|
|
789
|
+
if (resolvedPath) {
|
|
790
|
+
cachedPath = resolvedPath;
|
|
791
|
+
if (workItem.azdoProject?.name) {
|
|
792
|
+
this.projectPathCache.set(workItem.azdoProject.name, resolvedPath);
|
|
793
|
+
}
|
|
794
|
+
this.syslog(` ✓ Mapped project "${projName}" → ${resolvedPath}`);
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
this.syslog(` ⚠ No path cache for project "${projName}" — skipping ${workItem.workItemIdentifier}`);
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const enabled = this.isProjectEnabled(cachedPath);
|
|
802
|
+
if (!enabled) {
|
|
803
|
+
this.syslog(` ⚠ Project "${projName}" (${cachedPath}) not enabled — skipping ${workItem.workItemIdentifier}`);
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
workItemsForEngine.push(workItem);
|
|
807
|
+
}
|
|
808
|
+
if (workItemsForEngine.length === 0) {
|
|
809
|
+
this.syslog(`⚠ No enabled workItems (${executableWorkItems.length} executable, ${workItems.length - executableWorkItems.length} backlog)`);
|
|
810
|
+
this.syslog(` Path cache: [${[...this.projectPathCache.entries()].map(([k, v]) => `${k}→${v}`).join(', ')}]`);
|
|
811
|
+
this.syslog(` Enabled: [${[...this.enabledProjects].join(', ')}]`);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
this.syslog(` WorkItems: ${workItemsForEngine.length} enabled-or-uncached / ${executableWorkItems.length} executable / ${workItems.length} total`);
|
|
815
|
+
}
|
|
816
|
+
// Get validated workItem list from DecisionEngine
|
|
817
|
+
this.syslog('⟳ Decision Engine evaluating workItems...');
|
|
818
|
+
const decision = await this.engine.heartbeatMultiple(workItemsForEngine, maxSlots, [] // No project exclusion — worktree mode isolates each workItem
|
|
819
|
+
);
|
|
820
|
+
console.log(`[AutonomousRunner] Decision: ${decision.action} — ${decision.reason} (${decision.workItems?.length ?? 0} workItems)`);
|
|
821
|
+
if (decision.action === 'skip' || decision.action === 'defer') {
|
|
822
|
+
this.syslog(`→ Decision: ${decision.action} — ${decision.reason}`);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
// Add validated workItems to queue (with conflict detection)
|
|
826
|
+
let enqueuedCount = 0;
|
|
827
|
+
// Pre-filter: resolve project paths and skip invalid workItems
|
|
828
|
+
const candidates = [];
|
|
829
|
+
for (const { workItem } of decision.workItems) {
|
|
830
|
+
if (this.scheduler.isWorkItemQueued(workItem.id) || this.scheduler.isWorkItemRunning(workItem.id)) {
|
|
831
|
+
this.syslog(` Skip (already queued/running): ${workItem.workItemIdentifier || workItem.id.slice(0, 8)} ${workItem.title}`);
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
const projectPath = await this.resolveProjectPath(workItem);
|
|
835
|
+
if (!projectPath) {
|
|
836
|
+
this.syslog(`✗ Cannot resolve project path for "${workItem.azdoProject?.name || workItem.title}" — skipping`);
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
if (workItem.azdoProject?.name) {
|
|
840
|
+
this.projectPathCache.set(workItem.azdoProject.name, projectPath);
|
|
841
|
+
}
|
|
842
|
+
if (this.scheduler.isProjectBusy(projectPath)) {
|
|
843
|
+
this.syslog(` Project busy: ${projectPath}`);
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (this.enabledProjects.size > 0 && !this.isProjectEnabled(projectPath)) {
|
|
847
|
+
this.syslog(` Project not enabled: ${projectPath}`);
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
// 프로젝트별 5시간 윈도우 cap 체크
|
|
851
|
+
const projName = workItem.azdoProject?.name ?? 'unknown';
|
|
852
|
+
const perProjectCap = this.config.dailyWorkItemCap ?? 6;
|
|
853
|
+
if (!canProjectAcceptWorkItem(projName, perProjectCap)) {
|
|
854
|
+
const count = getProjectWindowCount(projName);
|
|
855
|
+
this.syslog(` ⏸ ${projName}: ${count}/${perProjectCap} workItems in 5h window — throttled`);
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
candidates.push({ workItem, projectPath });
|
|
859
|
+
}
|
|
860
|
+
// Enqueue all validated candidates
|
|
861
|
+
for (const { workItem, projectPath } of candidates) {
|
|
862
|
+
this.scheduler.enqueue(workItem, projectPath);
|
|
863
|
+
broadcastEvent({ type: 'workItem:queued', data: { workItemId: workItem.id, title: workItem.title, projectPath, workItemIdentifier: workItem.workItemIdentifier } });
|
|
864
|
+
this.syslog(`✓ Queued: ${workItem.workItemIdentifier || ''} ${workItem.title} → ${projectPath.split('/').slice(-2).join('/')}`);
|
|
865
|
+
enqueuedCount++;
|
|
866
|
+
// Claim the workItem immediately: set Azdo to 'In Progress' so restarts don't re-queue it
|
|
867
|
+
if (workItem.workItemId) {
|
|
868
|
+
azdo.updateWorkItemState(workItem.workItemId, 'In Progress').catch((err) => console.warn(`[AutonomousRunner] Failed to claim workItem ${workItem.workItemIdentifier}:`, err));
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (enqueuedCount === 0 && decision.skippedCount > 0) {
|
|
872
|
+
this.syslog(`— No new workItems queued (skipped: ${decision.skippedCount})`);
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
this.syslog(`✓ Enqueued ${enqueuedCount} workItem(s) | skipped: ${decision.skippedCount}`);
|
|
876
|
+
}
|
|
877
|
+
// Execute workItems
|
|
878
|
+
await this.runAvailableWorkItems();
|
|
879
|
+
}
|
|
880
|
+
/** Execute workItem in pair mode */
|
|
881
|
+
async executeWorkItemPairMode(workItem) {
|
|
882
|
+
// Auto-resolve project path
|
|
883
|
+
const projectPath = await this.resolveProjectPath(workItem);
|
|
884
|
+
// Error if project path mapping failed
|
|
885
|
+
if (!projectPath) {
|
|
886
|
+
const errorMsg = `Failed to resolve project path for "${workItem.azdoProject?.name || workItem.title}"`;
|
|
887
|
+
console.error(`[AutonomousRunner] ${errorMsg}`);
|
|
888
|
+
await reportToDiscord(t('runner.projectMappingFailed', { title: workItem.title, project: workItem.azdoProject?.name || 'unknown' }));
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
// Skip if project is not in enabled list (allow-list; empty = nothing runs)
|
|
892
|
+
if (this.enabledProjects.size > 0 && !this.isProjectEnabled(projectPath)) {
|
|
893
|
+
console.log(`[AutonomousRunner] Project not enabled, skipping: ${projectPath}`);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
// Cache azdoProjectName → resolvedPath for dashboard
|
|
897
|
+
if (workItem.azdoProject?.name) {
|
|
898
|
+
this.projectPathCache.set(workItem.azdoProject.name, projectPath);
|
|
899
|
+
}
|
|
900
|
+
console.log(`[AutonomousRunner] projectPath: ${projectPath}`);
|
|
901
|
+
// Check workItem decomposition (when enableDecomposition is set)
|
|
902
|
+
if (this.config.enableDecomposition) {
|
|
903
|
+
const threshold = this.config.decompositionThresholdMinutes ?? 30;
|
|
904
|
+
const needsDecomp = planner.needsDecomposition(workItem, threshold);
|
|
905
|
+
if (needsDecomp) {
|
|
906
|
+
console.log(`[AutonomousRunner] WorkItem "${workItem.title}" needs decomposition (>${threshold}min estimated)`);
|
|
907
|
+
const decomposed = await this.decomposeWorkItem(workItem, projectPath, threshold);
|
|
908
|
+
if (decomposed) {
|
|
909
|
+
// Decomposition succeeded - sub-workItems added to queue, skip original workItem
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
// Decomposition failed - execute original workItem as-is
|
|
913
|
+
console.log('[AutonomousRunner] Decomposition failed, executing original workItem');
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (this.shouldDispatchWithPaseo()) {
|
|
917
|
+
const result = await this.dispatchWithPaseo(workItem, projectPath);
|
|
918
|
+
await reportToDiscord(formatPipelineResultEmbed(result));
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
// Use scheduler for parallel processing mode
|
|
922
|
+
if (this.config.maxConcurrentWorkItems && this.config.maxConcurrentWorkItems > 1) {
|
|
923
|
+
this.scheduler.enqueue(workItem, projectPath);
|
|
924
|
+
broadcastEvent({ type: 'workItem:queued', data: { workItemId: workItem.id, title: workItem.title, projectPath, workItemIdentifier: workItem.workItemIdentifier } });
|
|
925
|
+
await this.runAvailableWorkItems();
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
// Single execution (legacy)
|
|
929
|
+
const result = await this.executePipeline(workItem, projectPath);
|
|
930
|
+
await reportToDiscord(formatPipelineResultEmbed(result));
|
|
931
|
+
// Update Azdo workItem state
|
|
932
|
+
if (workItem.workItemId) {
|
|
933
|
+
try {
|
|
934
|
+
if (result.success) {
|
|
935
|
+
// On success, move to Done
|
|
936
|
+
await execution.syncSuccessState(workItem);
|
|
937
|
+
await azdo.logPairComplete(workItem.workItemId, result.sessionId, {
|
|
938
|
+
attempts: result.iterations,
|
|
939
|
+
duration: Math.floor(result.totalDuration / 1000),
|
|
940
|
+
filesChanged: result.executorResult?.filesChanged || [],
|
|
941
|
+
executorSummary: result.executorResult?.summary,
|
|
942
|
+
executorCommands: result.executorResult?.commands,
|
|
943
|
+
reviewerFeedback: result.reviewResult?.feedback,
|
|
944
|
+
reviewerDecision: result.reviewResult?.decision,
|
|
945
|
+
testResults: result.testerResult ? {
|
|
946
|
+
passed: result.testerResult.testsPassed,
|
|
947
|
+
failed: result.testerResult.testsFailed,
|
|
948
|
+
coverage: result.testerResult.coverage,
|
|
949
|
+
failedTests: result.testerResult.failedTests,
|
|
950
|
+
} : undefined,
|
|
951
|
+
});
|
|
952
|
+
await execution.reconcileCompletionState(workItem);
|
|
953
|
+
console.log(`[AutonomousRunner] WorkItem ${workItem.workItemId} marked as Done`);
|
|
954
|
+
}
|
|
955
|
+
else if (result.finalStatus === 'rejected') {
|
|
956
|
+
// Change to Blocked on review rejection
|
|
957
|
+
await execution.syncFailureState(workItem, `Review rejected: ${result.reviewResult?.feedback || t('common.fallback.noDescription')}`);
|
|
958
|
+
await azdo.logBlocked(workItem.workItemId, 'autonomous-runner', t('runner.reviewRejected', { feedback: result.reviewResult?.feedback || t('common.fallback.noDescription') }));
|
|
959
|
+
console.log(`[AutonomousRunner] WorkItem ${workItem.workItemId} marked as Todo (blocked) (rejected)`);
|
|
960
|
+
}
|
|
961
|
+
// If failed, keep In Progress (retry on next heartbeat)
|
|
962
|
+
}
|
|
963
|
+
catch (err) {
|
|
964
|
+
console.error(`[AutonomousRunner] Failed to update workItem state:`, err);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
getExecCtx() {
|
|
969
|
+
return {
|
|
970
|
+
allowedProjects: this.config.allowedProjects,
|
|
971
|
+
plannerModel: this.config.plannerModel,
|
|
972
|
+
plannerTimeoutMs: this.config.plannerTimeoutMs,
|
|
973
|
+
pairMaxAttempts: this.config.pairMaxAttempts,
|
|
974
|
+
enableDecomposition: this.config.enableDecomposition,
|
|
975
|
+
decompositionThresholdMinutes: this.config.decompositionThresholdMinutes,
|
|
976
|
+
decompositionMaxDepth: this.config.decomposition?.maxDepth ?? 2,
|
|
977
|
+
decompositionMaxChildren: this.config.decomposition?.maxChildrenPerWorkItem ?? 5,
|
|
978
|
+
decompositionDailyLimit: this.config.decomposition?.dailyLimit ?? 20,
|
|
979
|
+
decompositionAutoBacklog: this.config.decomposition?.autoBacklog ?? true,
|
|
980
|
+
jobProfiles: this.config.jobProfiles,
|
|
981
|
+
getRolesForProject: (p) => this.getRolesForProject(p),
|
|
982
|
+
reportToDiscord,
|
|
983
|
+
worktreeMode: this.config.worktreeMode ?? false,
|
|
984
|
+
scheduleNextHeartbeat: () => this.scheduleNextHeartbeat(),
|
|
985
|
+
guards: this.config.guards,
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
async resolveProjectPath(workItem) {
|
|
989
|
+
return execution.resolveProjectPath(this.getExecCtx(), workItem);
|
|
990
|
+
}
|
|
991
|
+
async decomposeWorkItem(workItem, projectPath, targetMinutes) {
|
|
992
|
+
return execution.decomposeWorkItem(this.getExecCtx(), workItem, projectPath, targetMinutes);
|
|
993
|
+
}
|
|
994
|
+
async executePipeline(workItem, projectPath) {
|
|
995
|
+
return execution.executePipeline(this.getExecCtx(), workItem, projectPath);
|
|
996
|
+
}
|
|
997
|
+
async requestApproval(decision) {
|
|
998
|
+
return execution.requestApproval(decision, reportToDiscord);
|
|
999
|
+
}
|
|
1000
|
+
async approve() {
|
|
1001
|
+
if (this.executionPaused) {
|
|
1002
|
+
this.syslog('⏸ Execution paused — approval deferred');
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
if (!this.state.pendingApproval) {
|
|
1006
|
+
return false;
|
|
1007
|
+
}
|
|
1008
|
+
const workItem = this.state.pendingApproval;
|
|
1009
|
+
this.state.pendingApproval = undefined;
|
|
1010
|
+
// Get workflow from Decision Engine
|
|
1011
|
+
const decision = await this.engine.heartbeat([workItem]);
|
|
1012
|
+
if (decision.workflow && decision.workItem) {
|
|
1013
|
+
await this.executeWorkItemPairMode(decision.workItem);
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
reject() {
|
|
1019
|
+
if (!this.state.pendingApproval) {
|
|
1020
|
+
return false;
|
|
1021
|
+
}
|
|
1022
|
+
this.state.pendingApproval = undefined;
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
async runNow() {
|
|
1026
|
+
await this.heartbeat();
|
|
1027
|
+
}
|
|
1028
|
+
getState() {
|
|
1029
|
+
return { ...this.state };
|
|
1030
|
+
}
|
|
1031
|
+
getAllowedProjects() {
|
|
1032
|
+
return this.config.allowedProjects ?? [];
|
|
1033
|
+
}
|
|
1034
|
+
updateAllowedProjects(paths) {
|
|
1035
|
+
this.config.allowedProjects = paths;
|
|
1036
|
+
this.engine.updateAllowedProjects(paths);
|
|
1037
|
+
}
|
|
1038
|
+
getStats() {
|
|
1039
|
+
return { isRunning: this.state.isRunning, lastHeartbeat: this.state.lastHeartbeat,
|
|
1040
|
+
engineStats: this.engine.getStats(), pendingApproval: !!this.state.pendingApproval,
|
|
1041
|
+
schedulerStats: this.scheduler.getStats(),
|
|
1042
|
+
turboMode: this.turboMode,
|
|
1043
|
+
turboExpiresAt: this.turboExpiresAt,
|
|
1044
|
+
dailyPace: getDailyPaceInfo(),
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
// ============================================
|
|
1048
|
+
// Turbo Mode
|
|
1049
|
+
// ============================================
|
|
1050
|
+
getTurboMode() {
|
|
1051
|
+
// Auto-expire turbo
|
|
1052
|
+
if (this.turboMode && this.turboExpiresAt && Date.now() >= this.turboExpiresAt) {
|
|
1053
|
+
this.setTurboMode(false);
|
|
1054
|
+
}
|
|
1055
|
+
return this.turboMode;
|
|
1056
|
+
}
|
|
1057
|
+
setTurboMode(enabled) {
|
|
1058
|
+
this.turboMode = enabled;
|
|
1059
|
+
if (enabled) {
|
|
1060
|
+
this.turboExpiresAt = Date.now() + AutonomousRunner.TURBO_DURATION_MS;
|
|
1061
|
+
const expiresIn = Math.round(AutonomousRunner.TURBO_DURATION_MS / 3_600_000);
|
|
1062
|
+
console.log(`[AutonomousRunner] TURBO MODE ON (auto-expires in ${expiresIn}h)`);
|
|
1063
|
+
broadcastEvent({ type: 'log', data: { workItemId: 'system', stage: 'turbo', line: `TURBO ON — expires in ${expiresIn}h` } });
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
this.turboExpiresAt = null;
|
|
1067
|
+
console.log('[AutonomousRunner] TURBO MODE OFF');
|
|
1068
|
+
broadcastEvent({ type: 'log', data: { workItemId: 'system', stage: 'turbo', line: 'TURBO OFF — normal pace resumed' } });
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
pauseExecution(reason) {
|
|
1072
|
+
this.executionPaused = true;
|
|
1073
|
+
const msg = reason ? `⏸ Execution paused — ${reason} (in-flight will drain)` : '⏸ Execution paused — new claims blocked (in-flight will drain)';
|
|
1074
|
+
this.syslog(msg);
|
|
1075
|
+
broadcastEvent({ type: 'stats', data: this.buildStats() });
|
|
1076
|
+
}
|
|
1077
|
+
resumeExecution() {
|
|
1078
|
+
this.executionPaused = false;
|
|
1079
|
+
this.syslog('▶ Execution resumed — claiming new work');
|
|
1080
|
+
broadcastEvent({ type: 'stats', data: this.buildStats() });
|
|
1081
|
+
}
|
|
1082
|
+
isExecutionPaused() {
|
|
1083
|
+
return this.executionPaused;
|
|
1084
|
+
}
|
|
1085
|
+
getAdapterSummary() {
|
|
1086
|
+
const defaultAdapter = this.config.defaultAdapter ?? 'claude';
|
|
1087
|
+
const defaultStages = this.config.defaultStages;
|
|
1088
|
+
const getSummary = (stage, baseModel) => {
|
|
1089
|
+
const stageCfg = defaultStages?.[stage];
|
|
1090
|
+
const model = stageCfg?.model ?? baseModel;
|
|
1091
|
+
let adapter = inferProviderFromModel(model);
|
|
1092
|
+
if ((!model || model === 'auto') && stageCfg?.adapter) {
|
|
1093
|
+
adapter = stageCfg.adapter;
|
|
1094
|
+
}
|
|
1095
|
+
return {
|
|
1096
|
+
adapter,
|
|
1097
|
+
model: model ?? baseModel ?? 'auto',
|
|
1098
|
+
enabled: stageCfg?.enabled !== false,
|
|
1099
|
+
};
|
|
1100
|
+
};
|
|
1101
|
+
return {
|
|
1102
|
+
defaultAdapter,
|
|
1103
|
+
coder: getSummary('coder', this.config.executorModel),
|
|
1104
|
+
critic: getSummary('critic', this.config.reviewerModel),
|
|
1105
|
+
tester: defaultStages?.tester ? getSummary('tester') : undefined,
|
|
1106
|
+
documenter: defaultStages?.documenter ? getSummary('documenter') : undefined,
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
switchProvider(adapter) {
|
|
1110
|
+
const mapModelForProvider = (model, role) => {
|
|
1111
|
+
const current = model || '';
|
|
1112
|
+
const isClaudeModel = current.startsWith('claude-');
|
|
1113
|
+
// ChatGPT 계정 Codex에서는 gpt-5.x / gpt-*-codex 계열만 지원
|
|
1114
|
+
// o-series (o3, o4-mini 등)는 사용 불가
|
|
1115
|
+
const isCodexCompatible = current.startsWith('gpt-');
|
|
1116
|
+
if (adapter === 'codex') {
|
|
1117
|
+
if (isCodexCompatible)
|
|
1118
|
+
return current;
|
|
1119
|
+
// 비호환 모델(o-series 포함) → 모델 플래그 생략 → Codex 기본값 사용
|
|
1120
|
+
return '';
|
|
1121
|
+
}
|
|
1122
|
+
if (adapter === 'cursor' || adapter === 'hermes' || adapter === 'opencode' || adapter === 'responses') {
|
|
1123
|
+
return current || 'auto';
|
|
1124
|
+
}
|
|
1125
|
+
if (isClaudeModel)
|
|
1126
|
+
return current;
|
|
1127
|
+
if (role === 'critic')
|
|
1128
|
+
return 'claude-sonnet-4-20250514';
|
|
1129
|
+
return 'claude-haiku-4-5-20251001';
|
|
1130
|
+
};
|
|
1131
|
+
this.config.defaultAdapter = adapter;
|
|
1132
|
+
if (this.config.defaultStages) {
|
|
1133
|
+
this.config.defaultStages.coder = {
|
|
1134
|
+
...this.config.defaultStages.coder,
|
|
1135
|
+
adapter,
|
|
1136
|
+
model: mapModelForProvider(this.config.defaultStages.coder.model, 'coder'),
|
|
1137
|
+
};
|
|
1138
|
+
this.config.defaultStages.critic = {
|
|
1139
|
+
...this.config.defaultStages.critic,
|
|
1140
|
+
adapter,
|
|
1141
|
+
model: mapModelForProvider(this.config.defaultStages.critic.model, 'critic'),
|
|
1142
|
+
};
|
|
1143
|
+
if (this.config.defaultStages.tester) {
|
|
1144
|
+
this.config.defaultStages.tester = {
|
|
1145
|
+
...this.config.defaultStages.tester,
|
|
1146
|
+
adapter,
|
|
1147
|
+
model: mapModelForProvider(this.config.defaultStages.tester.model, 'tester'),
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
if (this.config.defaultStages.documenter) {
|
|
1151
|
+
this.config.defaultStages.documenter = {
|
|
1152
|
+
...this.config.defaultStages.documenter,
|
|
1153
|
+
adapter,
|
|
1154
|
+
model: mapModelForProvider(this.config.defaultStages.documenter.model, 'documenter'),
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
if (this.config.defaultStages.auditor) {
|
|
1158
|
+
this.config.defaultStages.auditor = {
|
|
1159
|
+
...this.config.defaultStages.auditor,
|
|
1160
|
+
adapter,
|
|
1161
|
+
model: mapModelForProvider(this.config.defaultStages.auditor.model, 'auditor'),
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
if (this.config.defaultStages['stage-documenter']) {
|
|
1165
|
+
this.config.defaultStages['stage-documenter'] = {
|
|
1166
|
+
...this.config.defaultStages['stage-documenter'],
|
|
1167
|
+
adapter,
|
|
1168
|
+
model: mapModelForProvider(this.config.defaultStages['stage-documenter'].model, 'stage-documenter'),
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (this.config.executorModel) {
|
|
1173
|
+
this.config.executorModel = mapModelForProvider(this.config.executorModel, 'coder');
|
|
1174
|
+
}
|
|
1175
|
+
if (this.config.reviewerModel) {
|
|
1176
|
+
this.config.reviewerModel = mapModelForProvider(this.config.reviewerModel, 'critic');
|
|
1177
|
+
}
|
|
1178
|
+
console.log(`[AutonomousRunner] Provider switched: ${adapter}`);
|
|
1179
|
+
}
|
|
1180
|
+
pauseScheduler() { this.scheduler.pause(); }
|
|
1181
|
+
resumeScheduler() { this.scheduler.resume(); }
|
|
1182
|
+
getQueuedWorkItems() { return this.scheduler.getQueuedWorkItems(); }
|
|
1183
|
+
getRunningWorkItems() { return this.scheduler.getRunningWorkItems(); }
|
|
1184
|
+
getPipelineHistory(limit = 50) { return getPipelineHistory(limit); }
|
|
1185
|
+
recordPipelineHistory(workItem, result) {
|
|
1186
|
+
appendPipelineHistory({
|
|
1187
|
+
sessionId: result.sessionId, workItemIdentifier: workItem.workItemIdentifier || workItem.workItemId,
|
|
1188
|
+
workItemId: workItem.workItemId, workItemTitle: workItem.title, projectName: workItem.azdoProject?.name,
|
|
1189
|
+
projectPath: result.workItemContext?.projectPath, success: result.success,
|
|
1190
|
+
finalStatus: result.finalStatus, iterations: result.iterations,
|
|
1191
|
+
totalDuration: result.totalDuration,
|
|
1192
|
+
stages: result.stages.map(s => ({ stage: s.stage, success: s.success, duration: s.duration })),
|
|
1193
|
+
cost: result.totalCost ? { costUsd: result.totalCost.costUsd,
|
|
1194
|
+
inputTokens: result.totalCost.inputTokens, outputTokens: result.totalCost.outputTokens } : undefined,
|
|
1195
|
+
prUrl: result.prUrl, reviewerFeedback: result.reviewResult?.feedback,
|
|
1196
|
+
completedAt: new Date().toISOString(),
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
disableProject(projectPath) {
|
|
1200
|
+
this.enabledProjects.delete(projectPath);
|
|
1201
|
+
console.log(`[AutonomousRunner] Project disabled: ${projectPath}`);
|
|
1202
|
+
}
|
|
1203
|
+
enableProject(projectPath) {
|
|
1204
|
+
this.enabledProjects.add(projectPath);
|
|
1205
|
+
console.log(`[AutonomousRunner] Project enabled: ${projectPath}`);
|
|
1206
|
+
}
|
|
1207
|
+
/** Get all currently enabled project paths */
|
|
1208
|
+
getEnabledProjects() {
|
|
1209
|
+
return Array.from(this.enabledProjects);
|
|
1210
|
+
}
|
|
1211
|
+
/** Get retry backoff info for a workItem/workItem id (if currently in backoff). */
|
|
1212
|
+
getRetryBackoff(workItemOrWorkItemId) {
|
|
1213
|
+
const retryAt = this.failedWorkItemRetryTimes.get(workItemOrWorkItemId);
|
|
1214
|
+
if (!retryAt)
|
|
1215
|
+
return null;
|
|
1216
|
+
const remainingMs = Math.max(0, retryAt - Date.now());
|
|
1217
|
+
if (remainingMs <= 0)
|
|
1218
|
+
return null;
|
|
1219
|
+
return {
|
|
1220
|
+
retryAt,
|
|
1221
|
+
remainingMs,
|
|
1222
|
+
failedCount: this.failedWorkItemCounts.get(workItemOrWorkItemId) ?? 0,
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
/** Pre-register project path in cache (name → path) */
|
|
1226
|
+
registerProjectPath(name, projectPath) {
|
|
1227
|
+
if (!this.projectPathCache.has(name)) {
|
|
1228
|
+
this.projectPathCache.set(name, projectPath);
|
|
1229
|
+
}
|
|
1230
|
+
// Also register capitalized variant to handle "enact-factory" ↔ "EnactFactory" mismatch
|
|
1231
|
+
const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
|
|
1232
|
+
if (capitalized !== name && !this.projectPathCache.has(capitalized)) {
|
|
1233
|
+
this.projectPathCache.set(capitalized, projectPath);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
getProjectsInfo() {
|
|
1237
|
+
const running = this.scheduler.getRunningWorkItems();
|
|
1238
|
+
const queued = this.scheduler.getQueuedWorkItems();
|
|
1239
|
+
// Update path cache from currently running workItems
|
|
1240
|
+
for (const r of running) {
|
|
1241
|
+
if (r.workItem.azdoProject?.name)
|
|
1242
|
+
this.projectPathCache.set(r.workItem.azdoProject.name, r.projectPath);
|
|
1243
|
+
}
|
|
1244
|
+
return buildProjectsInfo(this.lastFetchedWorkItems, running, queued, this.projectPathCache, this.enabledProjects);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
export function getRunner(config) {
|
|
1248
|
+
if (!runnerInstance && config) {
|
|
1249
|
+
runnerInstance = new AutonomousRunner(config);
|
|
1250
|
+
}
|
|
1251
|
+
if (!runnerInstance) {
|
|
1252
|
+
throw new Error('Runner not initialized. Call getRunner with config first.');
|
|
1253
|
+
}
|
|
1254
|
+
return runnerInstance;
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Start runner (convenience function)
|
|
1258
|
+
*/
|
|
1259
|
+
export async function startAutonomous(config) {
|
|
1260
|
+
const runner = getRunner(config);
|
|
1261
|
+
await runner.start();
|
|
1262
|
+
return runner;
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Stop runner (convenience function)
|
|
1266
|
+
*/
|
|
1267
|
+
export function stopAutonomous() {
|
|
1268
|
+
if (runnerInstance) {
|
|
1269
|
+
runnerInstance.stop();
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
//# sourceMappingURL=autonomousRunner.js.map
|