@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.
Files changed (644) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +566 -0
  3. package/dist/adapters/agenticLoop.d.ts +90 -0
  4. package/dist/adapters/agenticLoop.d.ts.map +1 -0
  5. package/dist/adapters/agenticLoop.js +219 -0
  6. package/dist/adapters/agenticLoop.js.map +1 -0
  7. package/dist/adapters/base.d.ts +16 -0
  8. package/dist/adapters/base.d.ts.map +1 -0
  9. package/dist/adapters/base.js +135 -0
  10. package/dist/adapters/base.js.map +1 -0
  11. package/dist/adapters/claude.d.ts +13 -0
  12. package/dist/adapters/claude.d.ts.map +1 -0
  13. package/dist/adapters/claude.js +318 -0
  14. package/dist/adapters/claude.js.map +1 -0
  15. package/dist/adapters/codex.d.ts +14 -0
  16. package/dist/adapters/codex.d.ts.map +1 -0
  17. package/dist/adapters/codex.js +366 -0
  18. package/dist/adapters/codex.js.map +1 -0
  19. package/dist/adapters/cryptoQuantAdapter.d.ts +85 -0
  20. package/dist/adapters/cryptoQuantAdapter.d.ts.map +1 -0
  21. package/dist/adapters/cryptoQuantAdapter.js +238 -0
  22. package/dist/adapters/cryptoQuantAdapter.js.map +1 -0
  23. package/dist/adapters/cursor.d.ts +13 -0
  24. package/dist/adapters/cursor.d.ts.map +1 -0
  25. package/dist/adapters/cursor.js +300 -0
  26. package/dist/adapters/cursor.js.map +1 -0
  27. package/dist/adapters/envPath.d.ts +20 -0
  28. package/dist/adapters/envPath.d.ts.map +1 -0
  29. package/dist/adapters/envPath.js +49 -0
  30. package/dist/adapters/envPath.js.map +1 -0
  31. package/dist/adapters/hermes.d.ts +13 -0
  32. package/dist/adapters/hermes.d.ts.map +1 -0
  33. package/dist/adapters/hermes.js +283 -0
  34. package/dist/adapters/hermes.js.map +1 -0
  35. package/dist/adapters/index.d.ts +18 -0
  36. package/dist/adapters/index.d.ts.map +1 -0
  37. package/dist/adapters/index.js +56 -0
  38. package/dist/adapters/index.js.map +1 -0
  39. package/dist/adapters/opencode.d.ts +13 -0
  40. package/dist/adapters/opencode.d.ts.map +1 -0
  41. package/dist/adapters/opencode.js +282 -0
  42. package/dist/adapters/opencode.js.map +1 -0
  43. package/dist/adapters/processRegistry.d.ts +38 -0
  44. package/dist/adapters/processRegistry.d.ts.map +1 -0
  45. package/dist/adapters/processRegistry.js +147 -0
  46. package/dist/adapters/processRegistry.js.map +1 -0
  47. package/dist/adapters/responses.d.ts +16 -0
  48. package/dist/adapters/responses.d.ts.map +1 -0
  49. package/dist/adapters/responses.js +244 -0
  50. package/dist/adapters/responses.js.map +1 -0
  51. package/dist/adapters/streamBuffer.d.ts +59 -0
  52. package/dist/adapters/streamBuffer.d.ts.map +1 -0
  53. package/dist/adapters/streamBuffer.js +123 -0
  54. package/dist/adapters/streamBuffer.js.map +1 -0
  55. package/dist/adapters/tools.d.ts +30 -0
  56. package/dist/adapters/tools.d.ts.map +1 -0
  57. package/dist/adapters/tools.js +219 -0
  58. package/dist/adapters/tools.js.map +1 -0
  59. package/dist/adapters/types.d.ts +82 -0
  60. package/dist/adapters/types.d.ts.map +1 -0
  61. package/dist/adapters/types.js +6 -0
  62. package/dist/adapters/types.js.map +1 -0
  63. package/dist/agents/agentBus.d.ts +160 -0
  64. package/dist/agents/agentBus.d.ts.map +1 -0
  65. package/dist/agents/agentBus.js +350 -0
  66. package/dist/agents/agentBus.js.map +1 -0
  67. package/dist/agents/agentPair.d.ts +215 -0
  68. package/dist/agents/agentPair.d.ts.map +1 -0
  69. package/dist/agents/agentPair.js +456 -0
  70. package/dist/agents/agentPair.js.map +1 -0
  71. package/dist/agents/auditor.d.ts +27 -0
  72. package/dist/agents/auditor.d.ts.map +1 -0
  73. package/dist/agents/auditor.js +238 -0
  74. package/dist/agents/auditor.js.map +1 -0
  75. package/dist/agents/cliStreamParser.d.ts +18 -0
  76. package/dist/agents/cliStreamParser.d.ts.map +1 -0
  77. package/dist/agents/cliStreamParser.js +156 -0
  78. package/dist/agents/cliStreamParser.js.map +1 -0
  79. package/dist/agents/documenter.d.ts +31 -0
  80. package/dist/agents/documenter.d.ts.map +1 -0
  81. package/dist/agents/documenter.js +286 -0
  82. package/dist/agents/documenter.js.map +1 -0
  83. package/dist/agents/draftAnalyzer.d.ts +50 -0
  84. package/dist/agents/draftAnalyzer.d.ts.map +1 -0
  85. package/dist/agents/draftAnalyzer.js +289 -0
  86. package/dist/agents/draftAnalyzer.js.map +1 -0
  87. package/dist/agents/evaluator.d.ts +61 -0
  88. package/dist/agents/evaluator.d.ts.map +1 -0
  89. package/dist/agents/evaluator.js +338 -0
  90. package/dist/agents/evaluator.js.map +1 -0
  91. package/dist/agents/executor.d.ts +33 -0
  92. package/dist/agents/executor.d.ts.map +1 -0
  93. package/dist/agents/executor.js +130 -0
  94. package/dist/agents/executor.js.map +1 -0
  95. package/dist/agents/index.d.ts +10 -0
  96. package/dist/agents/index.d.ts.map +1 -0
  97. package/dist/agents/index.js +10 -0
  98. package/dist/agents/index.js.map +1 -0
  99. package/dist/agents/pairMetrics.d.ts +63 -0
  100. package/dist/agents/pairMetrics.d.ts.map +1 -0
  101. package/dist/agents/pairMetrics.js +232 -0
  102. package/dist/agents/pairMetrics.js.map +1 -0
  103. package/dist/agents/pairPipeline.d.ts +184 -0
  104. package/dist/agents/pairPipeline.d.ts.map +1 -0
  105. package/dist/agents/pairPipeline.js +934 -0
  106. package/dist/agents/pairPipeline.js.map +1 -0
  107. package/dist/agents/pairWebhook.d.ts +59 -0
  108. package/dist/agents/pairWebhook.d.ts.map +1 -0
  109. package/dist/agents/pairWebhook.js +242 -0
  110. package/dist/agents/pairWebhook.js.map +1 -0
  111. package/dist/agents/pipelineFormat.d.ts +8 -0
  112. package/dist/agents/pipelineFormat.d.ts.map +1 -0
  113. package/dist/agents/pipelineFormat.js +65 -0
  114. package/dist/agents/pipelineFormat.js.map +1 -0
  115. package/dist/agents/pipelineGuards.d.ts +23 -0
  116. package/dist/agents/pipelineGuards.d.ts.map +1 -0
  117. package/dist/agents/pipelineGuards.js +257 -0
  118. package/dist/agents/pipelineGuards.js.map +1 -0
  119. package/dist/agents/reviewer.d.ts +37 -0
  120. package/dist/agents/reviewer.d.ts.map +1 -0
  121. package/dist/agents/reviewer.js +214 -0
  122. package/dist/agents/reviewer.js.map +1 -0
  123. package/dist/agents/skillDocumenter.d.ts +23 -0
  124. package/dist/agents/skillDocumenter.d.ts.map +1 -0
  125. package/dist/agents/skillDocumenter.js +219 -0
  126. package/dist/agents/skillDocumenter.js.map +1 -0
  127. package/dist/agents/tester.d.ts +37 -0
  128. package/dist/agents/tester.d.ts.map +1 -0
  129. package/dist/agents/tester.js +309 -0
  130. package/dist/agents/tester.js.map +1 -0
  131. package/dist/automation/autonomousRunner.d.ts +145 -0
  132. package/dist/automation/autonomousRunner.d.ts.map +1 -0
  133. package/dist/automation/autonomousRunner.js +1272 -0
  134. package/dist/automation/autonomousRunner.js.map +1 -0
  135. package/dist/automation/dailyReporter.d.ts +26 -0
  136. package/dist/automation/dailyReporter.d.ts.map +1 -0
  137. package/dist/automation/dailyReporter.js +130 -0
  138. package/dist/automation/dailyReporter.js.map +1 -0
  139. package/dist/automation/index.d.ts +5 -0
  140. package/dist/automation/index.d.ts.map +1 -0
  141. package/dist/automation/index.js +5 -0
  142. package/dist/automation/index.js.map +1 -0
  143. package/dist/automation/longRunningMonitor.d.ts +26 -0
  144. package/dist/automation/longRunningMonitor.d.ts.map +1 -0
  145. package/dist/automation/longRunningMonitor.js +356 -0
  146. package/dist/automation/longRunningMonitor.js.map +1 -0
  147. package/dist/automation/prOwnership.d.ts +18 -0
  148. package/dist/automation/prOwnership.d.ts.map +1 -0
  149. package/dist/automation/prOwnership.js +61 -0
  150. package/dist/automation/prOwnership.js.map +1 -0
  151. package/dist/automation/runnerExecution.d.ts +57 -0
  152. package/dist/automation/runnerExecution.d.ts.map +1 -0
  153. package/dist/automation/runnerExecution.js +701 -0
  154. package/dist/automation/runnerExecution.js.map +1 -0
  155. package/dist/automation/runnerState.d.ts +170 -0
  156. package/dist/automation/runnerState.d.ts.map +1 -0
  157. package/dist/automation/runnerState.js +496 -0
  158. package/dist/automation/runnerState.js.map +1 -0
  159. package/dist/automation/runnerTypes.d.ts +57 -0
  160. package/dist/automation/runnerTypes.d.ts.map +1 -0
  161. package/dist/automation/runnerTypes.js +5 -0
  162. package/dist/automation/runnerTypes.js.map +1 -0
  163. package/dist/automation/scheduler.d.ts +75 -0
  164. package/dist/automation/scheduler.d.ts.map +1 -0
  165. package/dist/automation/scheduler.js +402 -0
  166. package/dist/automation/scheduler.js.map +1 -0
  167. package/dist/azdo/azdo.d.ts +70 -0
  168. package/dist/azdo/azdo.d.ts.map +1 -0
  169. package/dist/azdo/azdo.js +328 -0
  170. package/dist/azdo/azdo.js.map +1 -0
  171. package/dist/azdo/index.d.ts +3 -0
  172. package/dist/azdo/index.d.ts.map +1 -0
  173. package/dist/azdo/index.js +3 -0
  174. package/dist/azdo/index.js.map +1 -0
  175. package/dist/azdo/projectUpdater.d.ts +13 -0
  176. package/dist/azdo/projectUpdater.d.ts.map +1 -0
  177. package/dist/azdo/projectUpdater.js +155 -0
  178. package/dist/azdo/projectUpdater.js.map +1 -0
  179. package/dist/azureDevOps/client.d.ts +75 -0
  180. package/dist/azureDevOps/client.d.ts.map +1 -0
  181. package/dist/azureDevOps/client.js +150 -0
  182. package/dist/azureDevOps/client.js.map +1 -0
  183. package/dist/azureDevOps/hierarchy.d.ts +119 -0
  184. package/dist/azureDevOps/hierarchy.d.ts.map +1 -0
  185. package/dist/azureDevOps/hierarchy.js +470 -0
  186. package/dist/azureDevOps/hierarchy.js.map +1 -0
  187. package/dist/azureDevOps/mapper.d.ts +101 -0
  188. package/dist/azureDevOps/mapper.d.ts.map +1 -0
  189. package/dist/azureDevOps/mapper.js +438 -0
  190. package/dist/azureDevOps/mapper.js.map +1 -0
  191. package/dist/azureDevOps/stateMapping.d.ts +15 -0
  192. package/dist/azureDevOps/stateMapping.d.ts.map +1 -0
  193. package/dist/azureDevOps/stateMapping.js +141 -0
  194. package/dist/azureDevOps/stateMapping.js.map +1 -0
  195. package/dist/cli/authHandler.d.ts +13 -0
  196. package/dist/cli/authHandler.d.ts.map +1 -0
  197. package/dist/cli/authHandler.js +70 -0
  198. package/dist/cli/authHandler.js.map +1 -0
  199. package/dist/cli/checkHandler.d.ts +27 -0
  200. package/dist/cli/checkHandler.d.ts.map +1 -0
  201. package/dist/cli/checkHandler.js +560 -0
  202. package/dist/cli/checkHandler.js.map +1 -0
  203. package/dist/cli/daemon.d.ts +30 -0
  204. package/dist/cli/daemon.d.ts.map +1 -0
  205. package/dist/cli/daemon.js +141 -0
  206. package/dist/cli/daemon.js.map +1 -0
  207. package/dist/cli/factoryCommands.d.ts +3 -0
  208. package/dist/cli/factoryCommands.d.ts.map +1 -0
  209. package/dist/cli/factoryCommands.js +165 -0
  210. package/dist/cli/factoryCommands.js.map +1 -0
  211. package/dist/cli/promptHandler.d.ts +13 -0
  212. package/dist/cli/promptHandler.d.ts.map +1 -0
  213. package/dist/cli/promptHandler.js +193 -0
  214. package/dist/cli/promptHandler.js.map +1 -0
  215. package/dist/cli.d.ts +3 -0
  216. package/dist/cli.d.ts.map +1 -0
  217. package/dist/cli.js +320 -0
  218. package/dist/cli.js.map +1 -0
  219. package/dist/core/agentLifecycle.d.ts +322 -0
  220. package/dist/core/agentLifecycle.d.ts.map +1 -0
  221. package/dist/core/agentLifecycle.js +230 -0
  222. package/dist/core/agentLifecycle.js.map +1 -0
  223. package/dist/core/areaMapping.d.ts +9 -0
  224. package/dist/core/areaMapping.d.ts.map +1 -0
  225. package/dist/core/areaMapping.js +37 -0
  226. package/dist/core/areaMapping.js.map +1 -0
  227. package/dist/core/config.d.ts +469 -0
  228. package/dist/core/config.d.ts.map +1 -0
  229. package/dist/core/config.js +780 -0
  230. package/dist/core/config.js.map +1 -0
  231. package/dist/core/dashboardContract.d.ts +204 -0
  232. package/dist/core/dashboardContract.d.ts.map +1 -0
  233. package/dist/core/dashboardContract.js +205 -0
  234. package/dist/core/dashboardContract.js.map +1 -0
  235. package/dist/core/devopsModel.d.ts +138 -0
  236. package/dist/core/devopsModel.d.ts.map +1 -0
  237. package/dist/core/devopsModel.js +137 -0
  238. package/dist/core/devopsModel.js.map +1 -0
  239. package/dist/core/envFile.d.ts +11 -0
  240. package/dist/core/envFile.d.ts.map +1 -0
  241. package/dist/core/envFile.js +104 -0
  242. package/dist/core/envFile.js.map +1 -0
  243. package/dist/core/eventHub.d.ts +220 -0
  244. package/dist/core/eventHub.d.ts.map +1 -0
  245. package/dist/core/eventHub.js +136 -0
  246. package/dist/core/eventHub.js.map +1 -0
  247. package/dist/core/index.d.ts +8 -0
  248. package/dist/core/index.d.ts.map +1 -0
  249. package/dist/core/index.js +7 -0
  250. package/dist/core/index.js.map +1 -0
  251. package/dist/core/laneExecutionState.d.ts +29 -0
  252. package/dist/core/laneExecutionState.d.ts.map +1 -0
  253. package/dist/core/laneExecutionState.js +18 -0
  254. package/dist/core/laneExecutionState.js.map +1 -0
  255. package/dist/core/laneStatus.d.ts +49 -0
  256. package/dist/core/laneStatus.d.ts.map +1 -0
  257. package/dist/core/laneStatus.js +153 -0
  258. package/dist/core/laneStatus.js.map +1 -0
  259. package/dist/core/prSidecar.d.ts +96 -0
  260. package/dist/core/prSidecar.d.ts.map +1 -0
  261. package/dist/core/prSidecar.js +33 -0
  262. package/dist/core/prSidecar.js.map +1 -0
  263. package/dist/core/runtimeConfig.d.ts +6 -0
  264. package/dist/core/runtimeConfig.d.ts.map +1 -0
  265. package/dist/core/runtimeConfig.js +24 -0
  266. package/dist/core/runtimeConfig.js.map +1 -0
  267. package/dist/core/scmProvider.d.ts +19 -0
  268. package/dist/core/scmProvider.d.ts.map +1 -0
  269. package/dist/core/scmProvider.js +38 -0
  270. package/dist/core/scmProvider.js.map +1 -0
  271. package/dist/core/service.d.ts +10 -0
  272. package/dist/core/service.d.ts.map +1 -0
  273. package/dist/core/service.js +297 -0
  274. package/dist/core/service.js.map +1 -0
  275. package/dist/core/traceCollector.d.ts +105 -0
  276. package/dist/core/traceCollector.d.ts.map +1 -0
  277. package/dist/core/traceCollector.js +141 -0
  278. package/dist/core/traceCollector.js.map +1 -0
  279. package/dist/core/types.d.ts +432 -0
  280. package/dist/core/types.d.ts.map +1 -0
  281. package/dist/core/types.js +2 -0
  282. package/dist/core/types.js.map +1 -0
  283. package/dist/core/workItemMapper.d.ts +39 -0
  284. package/dist/core/workItemMapper.d.ts.map +1 -0
  285. package/dist/core/workItemMapper.js +427 -0
  286. package/dist/core/workItemMapper.js.map +1 -0
  287. package/dist/core/workItemModel.d.ts +120 -0
  288. package/dist/core/workItemModel.d.ts.map +1 -0
  289. package/dist/core/workItemModel.js +104 -0
  290. package/dist/core/workItemModel.js.map +1 -0
  291. package/dist/core/workItemPayload.d.ts +195 -0
  292. package/dist/core/workItemPayload.d.ts.map +1 -0
  293. package/dist/core/workItemPayload.js +24 -0
  294. package/dist/core/workItemPayload.js.map +1 -0
  295. package/dist/core/workspaceConfig.d.ts +57 -0
  296. package/dist/core/workspaceConfig.d.ts.map +1 -0
  297. package/dist/core/workspaceConfig.js +184 -0
  298. package/dist/core/workspaceConfig.js.map +1 -0
  299. package/dist/doctor.d.ts +18 -0
  300. package/dist/doctor.d.ts.map +1 -0
  301. package/dist/doctor.js +34 -0
  302. package/dist/doctor.js.map +1 -0
  303. package/dist/factory/activeSkill.d.ts +11 -0
  304. package/dist/factory/activeSkill.d.ts.map +1 -0
  305. package/dist/factory/activeSkill.js +44 -0
  306. package/dist/factory/activeSkill.js.map +1 -0
  307. package/dist/factory/assignment.d.ts +54 -0
  308. package/dist/factory/assignment.d.ts.map +1 -0
  309. package/dist/factory/assignment.js +94 -0
  310. package/dist/factory/assignment.js.map +1 -0
  311. package/dist/factory/auditLog.d.ts +10 -0
  312. package/dist/factory/auditLog.d.ts.map +1 -0
  313. package/dist/factory/auditLog.js +38 -0
  314. package/dist/factory/auditLog.js.map +1 -0
  315. package/dist/factory/closureRequirements.d.ts +12 -0
  316. package/dist/factory/closureRequirements.d.ts.map +1 -0
  317. package/dist/factory/closureRequirements.js +30 -0
  318. package/dist/factory/closureRequirements.js.map +1 -0
  319. package/dist/factory/delegationPrompt.d.ts +3 -0
  320. package/dist/factory/delegationPrompt.d.ts.map +1 -0
  321. package/dist/factory/delegationPrompt.js +16 -0
  322. package/dist/factory/delegationPrompt.js.map +1 -0
  323. package/dist/factory/http.d.ts +3 -0
  324. package/dist/factory/http.d.ts.map +1 -0
  325. package/dist/factory/http.js +555 -0
  326. package/dist/factory/http.js.map +1 -0
  327. package/dist/factory/lifecyclePushMap.d.ts +4 -0
  328. package/dist/factory/lifecyclePushMap.d.ts.map +1 -0
  329. package/dist/factory/lifecyclePushMap.js +7 -0
  330. package/dist/factory/lifecyclePushMap.js.map +1 -0
  331. package/dist/factory/missions.d.ts +125 -0
  332. package/dist/factory/missions.d.ts.map +1 -0
  333. package/dist/factory/missions.js +304 -0
  334. package/dist/factory/missions.js.map +1 -0
  335. package/dist/factory/mode.d.ts +9 -0
  336. package/dist/factory/mode.d.ts.map +1 -0
  337. package/dist/factory/mode.js +30 -0
  338. package/dist/factory/mode.js.map +1 -0
  339. package/dist/factory/operatorActiveSkill.d.ts +15 -0
  340. package/dist/factory/operatorActiveSkill.d.ts.map +1 -0
  341. package/dist/factory/operatorActiveSkill.js +95 -0
  342. package/dist/factory/operatorActiveSkill.js.map +1 -0
  343. package/dist/factory/paseoDispatcher.d.ts +52 -0
  344. package/dist/factory/paseoDispatcher.d.ts.map +1 -0
  345. package/dist/factory/paseoDispatcher.js +122 -0
  346. package/dist/factory/paseoDispatcher.js.map +1 -0
  347. package/dist/factory/paseoLifecycle.d.ts +32 -0
  348. package/dist/factory/paseoLifecycle.d.ts.map +1 -0
  349. package/dist/factory/paseoLifecycle.js +260 -0
  350. package/dist/factory/paseoLifecycle.js.map +1 -0
  351. package/dist/factory/paths.d.ts +31 -0
  352. package/dist/factory/paths.d.ts.map +1 -0
  353. package/dist/factory/paths.js +139 -0
  354. package/dist/factory/paths.js.map +1 -0
  355. package/dist/factory/progressWatchdog.d.ts +58 -0
  356. package/dist/factory/progressWatchdog.d.ts.map +1 -0
  357. package/dist/factory/progressWatchdog.js +160 -0
  358. package/dist/factory/progressWatchdog.js.map +1 -0
  359. package/dist/factory/roster.d.ts +59 -0
  360. package/dist/factory/roster.d.ts.map +1 -0
  361. package/dist/factory/roster.js +116 -0
  362. package/dist/factory/roster.js.map +1 -0
  363. package/dist/factory/runtime.d.ts +44 -0
  364. package/dist/factory/runtime.d.ts.map +1 -0
  365. package/dist/factory/runtime.js +238 -0
  366. package/dist/factory/runtime.js.map +1 -0
  367. package/dist/factory/sync.d.ts +29 -0
  368. package/dist/factory/sync.d.ts.map +1 -0
  369. package/dist/factory/sync.js +77 -0
  370. package/dist/factory/sync.js.map +1 -0
  371. package/dist/factory/workitemQueues.d.ts +37 -0
  372. package/dist/factory/workitemQueues.d.ts.map +1 -0
  373. package/dist/factory/workitemQueues.js +99 -0
  374. package/dist/factory/workitemQueues.js.map +1 -0
  375. package/dist/factory/workitemTriage.d.ts +9 -0
  376. package/dist/factory/workitemTriage.d.ts.map +1 -0
  377. package/dist/factory/workitemTriage.js +81 -0
  378. package/dist/factory/workitemTriage.js.map +1 -0
  379. package/dist/hooks.d.ts +18 -0
  380. package/dist/hooks.d.ts.map +1 -0
  381. package/dist/hooks.js +96 -0
  382. package/dist/hooks.js.map +1 -0
  383. package/dist/index.d.ts +3 -0
  384. package/dist/index.d.ts.map +1 -0
  385. package/dist/index.js +90 -0
  386. package/dist/index.js.map +1 -0
  387. package/dist/install/agentCatalog.d.ts +7 -0
  388. package/dist/install/agentCatalog.d.ts.map +1 -0
  389. package/dist/install/agentCatalog.js +28 -0
  390. package/dist/install/agentCatalog.js.map +1 -0
  391. package/dist/install/bundlePaths.d.ts +10 -0
  392. package/dist/install/bundlePaths.d.ts.map +1 -0
  393. package/dist/install/bundlePaths.js +30 -0
  394. package/dist/install/bundlePaths.js.map +1 -0
  395. package/dist/install/codex.d.ts +43 -0
  396. package/dist/install/codex.d.ts.map +1 -0
  397. package/dist/install/codex.js +207 -0
  398. package/dist/install/codex.js.map +1 -0
  399. package/dist/install/enactHome.d.ts +37 -0
  400. package/dist/install/enactHome.d.ts.map +1 -0
  401. package/dist/install/enactHome.js +152 -0
  402. package/dist/install/enactHome.js.map +1 -0
  403. package/dist/install/plugins.d.ts +115 -0
  404. package/dist/install/plugins.d.ts.map +1 -0
  405. package/dist/install/plugins.js +259 -0
  406. package/dist/install/plugins.js.map +1 -0
  407. package/dist/install/setup.d.ts +33 -0
  408. package/dist/install/setup.d.ts.map +1 -0
  409. package/dist/install/setup.js +167 -0
  410. package/dist/install/setup.js.map +1 -0
  411. package/dist/locale/en.d.ts +3 -0
  412. package/dist/locale/en.d.ts.map +1 -0
  413. package/dist/locale/en.js +435 -0
  414. package/dist/locale/en.js.map +1 -0
  415. package/dist/locale/index.d.ts +28 -0
  416. package/dist/locale/index.d.ts.map +1 -0
  417. package/dist/locale/index.js +84 -0
  418. package/dist/locale/index.js.map +1 -0
  419. package/dist/locale/prompts/en.d.ts +3 -0
  420. package/dist/locale/prompts/en.d.ts.map +1 -0
  421. package/dist/locale/prompts/en.js +254 -0
  422. package/dist/locale/prompts/en.js.map +1 -0
  423. package/dist/locale/types.d.ts +433 -0
  424. package/dist/locale/types.d.ts.map +1 -0
  425. package/dist/locale/types.js +5 -0
  426. package/dist/locale/types.js.map +1 -0
  427. package/dist/mcp/server.d.ts +489 -0
  428. package/dist/mcp/server.d.ts.map +1 -0
  429. package/dist/mcp/server.js +597 -0
  430. package/dist/mcp/server.js.map +1 -0
  431. package/dist/orchestration/decisionEngine.d.ts +175 -0
  432. package/dist/orchestration/decisionEngine.d.ts.map +1 -0
  433. package/dist/orchestration/decisionEngine.js +471 -0
  434. package/dist/orchestration/decisionEngine.js.map +1 -0
  435. package/dist/orchestration/index.d.ts +5 -0
  436. package/dist/orchestration/index.d.ts.map +1 -0
  437. package/dist/orchestration/index.js +5 -0
  438. package/dist/orchestration/index.js.map +1 -0
  439. package/dist/orchestration/workItemParser.d.ts +67 -0
  440. package/dist/orchestration/workItemParser.d.ts.map +1 -0
  441. package/dist/orchestration/workItemParser.js +560 -0
  442. package/dist/orchestration/workItemParser.js.map +1 -0
  443. package/dist/orchestration/workItemScheduler.d.ts +141 -0
  444. package/dist/orchestration/workItemScheduler.d.ts.map +1 -0
  445. package/dist/orchestration/workItemScheduler.js +317 -0
  446. package/dist/orchestration/workItemScheduler.js.map +1 -0
  447. package/dist/orchestration/workflow.d.ts +145 -0
  448. package/dist/orchestration/workflow.d.ts.map +1 -0
  449. package/dist/orchestration/workflow.js +301 -0
  450. package/dist/orchestration/workflow.js.map +1 -0
  451. package/dist/providers/codexSessions.d.ts +93 -0
  452. package/dist/providers/codexSessions.d.ts.map +1 -0
  453. package/dist/providers/codexSessions.js +366 -0
  454. package/dist/providers/codexSessions.js.map +1 -0
  455. package/dist/registry/bsDetector.d.ts +24 -0
  456. package/dist/registry/bsDetector.d.ts.map +1 -0
  457. package/dist/registry/bsDetector.js +276 -0
  458. package/dist/registry/bsDetector.js.map +1 -0
  459. package/dist/registry/entityScanner.d.ts +36 -0
  460. package/dist/registry/entityScanner.d.ts.map +1 -0
  461. package/dist/registry/entityScanner.js +693 -0
  462. package/dist/registry/entityScanner.js.map +1 -0
  463. package/dist/registry/index.d.ts +9 -0
  464. package/dist/registry/index.d.ts.map +1 -0
  465. package/dist/registry/index.js +13 -0
  466. package/dist/registry/index.js.map +1 -0
  467. package/dist/registry/schema.d.ts +307 -0
  468. package/dist/registry/schema.d.ts.map +1 -0
  469. package/dist/registry/schema.js +139 -0
  470. package/dist/registry/schema.js.map +1 -0
  471. package/dist/registry/sqliteStore.d.ts +101 -0
  472. package/dist/registry/sqliteStore.d.ts.map +1 -0
  473. package/dist/registry/sqliteStore.js +688 -0
  474. package/dist/registry/sqliteStore.js.map +1 -0
  475. package/dist/registry/workItemBridge.d.ts +8 -0
  476. package/dist/registry/workItemBridge.d.ts.map +1 -0
  477. package/dist/registry/workItemBridge.js +30 -0
  478. package/dist/registry/workItemBridge.js.map +1 -0
  479. package/dist/runners/cliRunner.d.ts +11 -0
  480. package/dist/runners/cliRunner.d.ts.map +1 -0
  481. package/dist/runners/cliRunner.js +193 -0
  482. package/dist/runners/cliRunner.js.map +1 -0
  483. package/dist/support/apiCache.d.ts +85 -0
  484. package/dist/support/apiCache.d.ts.map +1 -0
  485. package/dist/support/apiCache.js +163 -0
  486. package/dist/support/apiCache.js.map +1 -0
  487. package/dist/support/chat.d.ts +3 -0
  488. package/dist/support/chat.d.ts.map +1 -0
  489. package/dist/support/chat.js +305 -0
  490. package/dist/support/chat.js.map +1 -0
  491. package/dist/support/chatBackend.d.ts +25 -0
  492. package/dist/support/chatBackend.d.ts.map +1 -0
  493. package/dist/support/chatBackend.js +289 -0
  494. package/dist/support/chatBackend.js.map +1 -0
  495. package/dist/support/chatTui.d.ts +3 -0
  496. package/dist/support/chatTui.d.ts.map +1 -0
  497. package/dist/support/chatTui.js +1082 -0
  498. package/dist/support/chatTui.js.map +1 -0
  499. package/dist/support/costTracker.d.ts +29 -0
  500. package/dist/support/costTracker.d.ts.map +1 -0
  501. package/dist/support/costTracker.js +113 -0
  502. package/dist/support/costTracker.js.map +1 -0
  503. package/dist/support/dashboardHtml.d.ts +5 -0
  504. package/dist/support/dashboardHtml.d.ts.map +1 -0
  505. package/dist/support/dashboardHtml.js +2629 -0
  506. package/dist/support/dashboardHtml.js.map +1 -0
  507. package/dist/support/dev.d.ts +55 -0
  508. package/dist/support/dev.d.ts.map +1 -0
  509. package/dist/support/dev.js +298 -0
  510. package/dist/support/dev.js.map +1 -0
  511. package/dist/support/editParser.d.ts +37 -0
  512. package/dist/support/editParser.d.ts.map +1 -0
  513. package/dist/support/editParser.js +365 -0
  514. package/dist/support/editParser.js.map +1 -0
  515. package/dist/support/ghosttyThemeCatalog.generated.d.ts +2 -0
  516. package/dist/support/ghosttyThemeCatalog.generated.d.ts.map +1 -0
  517. package/dist/support/ghosttyThemeCatalog.generated.js +11116 -0
  518. package/dist/support/ghosttyThemeCatalog.generated.js.map +1 -0
  519. package/dist/support/gitStatus.d.ts +21 -0
  520. package/dist/support/gitStatus.d.ts.map +1 -0
  521. package/dist/support/gitStatus.js +108 -0
  522. package/dist/support/gitStatus.js.map +1 -0
  523. package/dist/support/gitTracker.d.ts +30 -0
  524. package/dist/support/gitTracker.d.ts.map +1 -0
  525. package/dist/support/gitTracker.js +143 -0
  526. package/dist/support/gitTracker.js.map +1 -0
  527. package/dist/support/index.d.ts +12 -0
  528. package/dist/support/index.d.ts.map +1 -0
  529. package/dist/support/index.js +12 -0
  530. package/dist/support/index.js.map +1 -0
  531. package/dist/support/planner.d.ts +64 -0
  532. package/dist/support/planner.d.ts.map +1 -0
  533. package/dist/support/planner.js +396 -0
  534. package/dist/support/planner.js.map +1 -0
  535. package/dist/support/projectMapper.d.ts +46 -0
  536. package/dist/support/projectMapper.d.ts.map +1 -0
  537. package/dist/support/projectMapper.js +273 -0
  538. package/dist/support/projectMapper.js.map +1 -0
  539. package/dist/support/pty-helper.py +117 -0
  540. package/dist/support/quotaTracker.d.ts +29 -0
  541. package/dist/support/quotaTracker.d.ts.map +1 -0
  542. package/dist/support/quotaTracker.js +89 -0
  543. package/dist/support/quotaTracker.js.map +1 -0
  544. package/dist/support/rateLimiter.d.ts +101 -0
  545. package/dist/support/rateLimiter.d.ts.map +1 -0
  546. package/dist/support/rateLimiter.js +219 -0
  547. package/dist/support/rateLimiter.js.map +1 -0
  548. package/dist/support/rollback.d.ts +61 -0
  549. package/dist/support/rollback.d.ts.map +1 -0
  550. package/dist/support/rollback.js +329 -0
  551. package/dist/support/rollback.js.map +1 -0
  552. package/dist/support/sharedShell.d.ts +17 -0
  553. package/dist/support/sharedShell.d.ts.map +1 -0
  554. package/dist/support/sharedShell.js +439 -0
  555. package/dist/support/sharedShell.js.map +1 -0
  556. package/dist/support/stuckDetector.d.ts +68 -0
  557. package/dist/support/stuckDetector.d.ts.map +1 -0
  558. package/dist/support/stuckDetector.js +174 -0
  559. package/dist/support/stuckDetector.js.map +1 -0
  560. package/dist/support/terminalBridge.d.ts +18 -0
  561. package/dist/support/terminalBridge.d.ts.map +1 -0
  562. package/dist/support/terminalBridge.js +553 -0
  563. package/dist/support/terminalBridge.js.map +1 -0
  564. package/dist/support/timeWindow.d.ts +60 -0
  565. package/dist/support/timeWindow.d.ts.map +1 -0
  566. package/dist/support/timeWindow.js +236 -0
  567. package/dist/support/timeWindow.js.map +1 -0
  568. package/dist/support/uiThemes.d.ts +44 -0
  569. package/dist/support/uiThemes.d.ts.map +1 -0
  570. package/dist/support/uiThemes.js +290 -0
  571. package/dist/support/uiThemes.js.map +1 -0
  572. package/dist/support/web.d.ts +29 -0
  573. package/dist/support/web.d.ts.map +1 -0
  574. package/dist/support/web.js +1097 -0
  575. package/dist/support/web.js.map +1 -0
  576. package/dist/support/worktreeManager.d.ts +20 -0
  577. package/dist/support/worktreeManager.d.ts.map +1 -0
  578. package/dist/support/worktreeManager.js +140 -0
  579. package/dist/support/worktreeManager.js.map +1 -0
  580. package/dist/task_state_model.py +55 -0
  581. package/dist/workItemState/store.d.ts +122 -0
  582. package/dist/workItemState/store.d.ts.map +1 -0
  583. package/dist/workItemState/store.js +438 -0
  584. package/dist/workItemState/store.js.map +1 -0
  585. package/dist/workItems/azdoBridge.d.ts +42 -0
  586. package/dist/workItems/azdoBridge.d.ts.map +1 -0
  587. package/dist/workItems/azdoBridge.js +143 -0
  588. package/dist/workItems/azdoBridge.js.map +1 -0
  589. package/dist/workItems/azdoSyncRuntime.d.ts +28 -0
  590. package/dist/workItems/azdoSyncRuntime.d.ts.map +1 -0
  591. package/dist/workItems/azdoSyncRuntime.js +158 -0
  592. package/dist/workItems/azdoSyncRuntime.js.map +1 -0
  593. package/dist/workItems/azureDevOpsSync.d.ts +128 -0
  594. package/dist/workItems/azureDevOpsSync.d.ts.map +1 -0
  595. package/dist/workItems/azureDevOpsSync.js +748 -0
  596. package/dist/workItems/azureDevOpsSync.js.map +1 -0
  597. package/dist/workItems/helpers.d.ts +11 -0
  598. package/dist/workItems/helpers.d.ts.map +1 -0
  599. package/dist/workItems/helpers.js +17 -0
  600. package/dist/workItems/helpers.js.map +1 -0
  601. package/dist/workItems/index.d.ts +21 -0
  602. package/dist/workItems/index.d.ts.map +1 -0
  603. package/dist/workItems/index.js +89 -0
  604. package/dist/workItems/index.js.map +1 -0
  605. package/dist/workItems/localWorkItemFetcher.d.ts +55 -0
  606. package/dist/workItems/localWorkItemFetcher.d.ts.map +1 -0
  607. package/dist/workItems/localWorkItemFetcher.js +209 -0
  608. package/dist/workItems/localWorkItemFetcher.js.map +1 -0
  609. package/dist/workItems/migrations/001_rename_workItem_to_work_item.sql +10 -0
  610. package/dist/workItems/postgresStore.d.ts +78 -0
  611. package/dist/workItems/postgresStore.d.ts.map +1 -0
  612. package/dist/workItems/postgresStore.js +937 -0
  613. package/dist/workItems/postgresStore.js.map +1 -0
  614. package/dist/workItems/schema.d.ts +257 -0
  615. package/dist/workItems/schema.d.ts.map +1 -0
  616. package/dist/workItems/schema.js +176 -0
  617. package/dist/workItems/schema.js.map +1 -0
  618. package/dist/workItems/sqliteStore.d.ts +124 -0
  619. package/dist/workItems/sqliteStore.d.ts.map +1 -0
  620. package/dist/workItems/sqliteStore.js +713 -0
  621. package/dist/workItems/sqliteStore.js.map +1 -0
  622. package/dist/workItems/workItemBoardHtml.d.ts +5 -0
  623. package/dist/workItems/workItemBoardHtml.d.ts.map +1 -0
  624. package/dist/workItems/workItemBoardHtml.js +2192 -0
  625. package/dist/workItems/workItemBoardHtml.js.map +1 -0
  626. package/package.json +99 -0
  627. package/templates/AGENTS.md +432 -0
  628. package/templates/BOOT.md +25 -0
  629. package/templates/BOOTSTRAP.md +50 -0
  630. package/templates/CHANGELOG_AUDIT.md +74 -0
  631. package/templates/HEARTBEAT.md +86 -0
  632. package/templates/IDENTITY.md +27 -0
  633. package/templates/PR_LAND.md +75 -0
  634. package/templates/PR_REVIEW.md +97 -0
  635. package/templates/SOUL.dev.md +52 -0
  636. package/templates/SOUL.md +81 -0
  637. package/templates/TOOLS.md +52 -0
  638. package/templates/USER.md +22 -0
  639. package/templates/WORKITEM_ANALYSIS.md +31 -0
  640. package/templates/agents/executor.md +26 -0
  641. package/templates/agents/plan.md +22 -0
  642. package/templates/agents/ralph.md +37 -0
  643. package/templates/agents/review.md +22 -0
  644. 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