@intrect/openswarm 0.9.3 → 0.10.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 (374) hide show
  1. package/README.md +76 -1
  2. package/config.example.yaml +6 -0
  3. package/dist/adapters/agenticLoop.d.ts +24 -0
  4. package/dist/adapters/agenticLoop.d.ts.map +1 -1
  5. package/dist/adapters/agenticLoop.js +130 -11
  6. package/dist/adapters/agenticLoop.js.map +1 -1
  7. package/dist/adapters/applyPatch.d.ts +21 -0
  8. package/dist/adapters/applyPatch.d.ts.map +1 -0
  9. package/dist/adapters/applyPatch.js +175 -0
  10. package/dist/adapters/applyPatch.js.map +1 -0
  11. package/dist/adapters/base.d.ts.map +1 -1
  12. package/dist/adapters/base.js +7 -0
  13. package/dist/adapters/base.js.map +1 -1
  14. package/dist/adapters/codex.js +10 -0
  15. package/dist/adapters/codex.js.map +1 -1
  16. package/dist/adapters/codexResponses.d.ts +8 -0
  17. package/dist/adapters/codexResponses.d.ts.map +1 -1
  18. package/dist/adapters/codexResponses.js +86 -8
  19. package/dist/adapters/codexResponses.js.map +1 -1
  20. package/dist/adapters/errorClassification.d.ts +8 -0
  21. package/dist/adapters/errorClassification.d.ts.map +1 -0
  22. package/dist/adapters/errorClassification.js +54 -0
  23. package/dist/adapters/errorClassification.js.map +1 -0
  24. package/dist/adapters/gpt.d.ts.map +1 -1
  25. package/dist/adapters/gpt.js +12 -1
  26. package/dist/adapters/gpt.js.map +1 -1
  27. package/dist/adapters/local.d.ts.map +1 -1
  28. package/dist/adapters/local.js +9 -1
  29. package/dist/adapters/local.js.map +1 -1
  30. package/dist/adapters/openrouter.d.ts.map +1 -1
  31. package/dist/adapters/openrouter.js +9 -1
  32. package/dist/adapters/openrouter.js.map +1 -1
  33. package/dist/adapters/rateLimitError.d.ts +29 -0
  34. package/dist/adapters/rateLimitError.d.ts.map +1 -0
  35. package/dist/adapters/rateLimitError.js +64 -0
  36. package/dist/adapters/rateLimitError.js.map +1 -0
  37. package/dist/adapters/resultParsing.d.ts.map +1 -1
  38. package/dist/adapters/resultParsing.js +18 -0
  39. package/dist/adapters/resultParsing.js.map +1 -1
  40. package/dist/adapters/tools.d.ts +3 -0
  41. package/dist/adapters/tools.d.ts.map +1 -1
  42. package/dist/adapters/tools.js +148 -11
  43. package/dist/adapters/tools.js.map +1 -1
  44. package/dist/adapters/types.d.ts +15 -0
  45. package/dist/adapters/types.d.ts.map +1 -1
  46. package/dist/adapters/webTools.d.ts.map +1 -1
  47. package/dist/adapters/webTools.js +44 -21
  48. package/dist/adapters/webTools.js.map +1 -1
  49. package/dist/agents/agentPair.d.ts +9 -0
  50. package/dist/agents/agentPair.d.ts.map +1 -1
  51. package/dist/agents/agentPair.js.map +1 -1
  52. package/dist/agents/auditor.d.ts.map +1 -1
  53. package/dist/agents/auditor.js +3 -0
  54. package/dist/agents/auditor.js.map +1 -1
  55. package/dist/agents/documenter.d.ts.map +1 -1
  56. package/dist/agents/documenter.js +3 -0
  57. package/dist/agents/documenter.js.map +1 -1
  58. package/dist/agents/draftAnalyzer.d.ts +30 -7
  59. package/dist/agents/draftAnalyzer.d.ts.map +1 -1
  60. package/dist/agents/draftAnalyzer.js +181 -30
  61. package/dist/agents/draftAnalyzer.js.map +1 -1
  62. package/dist/agents/multiLensReview.d.ts +49 -0
  63. package/dist/agents/multiLensReview.d.ts.map +1 -0
  64. package/dist/agents/multiLensReview.js +148 -0
  65. package/dist/agents/multiLensReview.js.map +1 -0
  66. package/dist/agents/pairPipeline.d.ts +7 -1
  67. package/dist/agents/pairPipeline.d.ts.map +1 -1
  68. package/dist/agents/pairPipeline.js +110 -66
  69. package/dist/agents/pairPipeline.js.map +1 -1
  70. package/dist/agents/pipelineFormat.d.ts.map +1 -1
  71. package/dist/agents/pipelineFormat.js +4 -0
  72. package/dist/agents/pipelineFormat.js.map +1 -1
  73. package/dist/agents/reviewer.d.ts +16 -0
  74. package/dist/agents/reviewer.d.ts.map +1 -1
  75. package/dist/agents/reviewer.js +30 -0
  76. package/dist/agents/reviewer.js.map +1 -1
  77. package/dist/agents/skillDocumenter.d.ts.map +1 -1
  78. package/dist/agents/skillDocumenter.js +3 -0
  79. package/dist/agents/skillDocumenter.js.map +1 -1
  80. package/dist/agents/tester.d.ts.map +1 -1
  81. package/dist/agents/tester.js +3 -0
  82. package/dist/agents/tester.js.map +1 -1
  83. package/dist/agents/worker.d.ts +14 -0
  84. package/dist/agents/worker.d.ts.map +1 -1
  85. package/dist/agents/worker.js +123 -22
  86. package/dist/agents/worker.js.map +1 -1
  87. package/dist/automation/autonomousRunner.d.ts +12 -0
  88. package/dist/automation/autonomousRunner.d.ts.map +1 -1
  89. package/dist/automation/autonomousRunner.js +218 -29
  90. package/dist/automation/autonomousRunner.js.map +1 -1
  91. package/dist/automation/runnerExecution.d.ts +15 -0
  92. package/dist/automation/runnerExecution.d.ts.map +1 -1
  93. package/dist/automation/runnerExecution.js +77 -1
  94. package/dist/automation/runnerExecution.js.map +1 -1
  95. package/dist/automation/runnerState.d.ts +7 -0
  96. package/dist/automation/runnerState.d.ts.map +1 -1
  97. package/dist/automation/runnerState.js +23 -1
  98. package/dist/automation/runnerState.js.map +1 -1
  99. package/dist/automation/runnerTypes.d.ts +2 -0
  100. package/dist/automation/runnerTypes.d.ts.map +1 -1
  101. package/dist/automation/scheduler.d.ts +12 -0
  102. package/dist/automation/scheduler.d.ts.map +1 -1
  103. package/dist/automation/scheduler.js +29 -2
  104. package/dist/automation/scheduler.js.map +1 -1
  105. package/dist/automation/taskSource.d.ts +9 -1
  106. package/dist/automation/taskSource.d.ts.map +1 -1
  107. package/dist/automation/taskSource.js +13 -2
  108. package/dist/automation/taskSource.js.map +1 -1
  109. package/dist/cli/auditPM.d.ts +40 -0
  110. package/dist/cli/auditPM.d.ts.map +1 -0
  111. package/dist/cli/auditPM.js +152 -0
  112. package/dist/cli/auditPM.js.map +1 -0
  113. package/dist/cli/designPipeline.d.ts +30 -0
  114. package/dist/cli/designPipeline.d.ts.map +1 -0
  115. package/dist/cli/designPipeline.js +113 -0
  116. package/dist/cli/designPipeline.js.map +1 -0
  117. package/dist/cli/mcpCommand.d.ts +22 -0
  118. package/dist/cli/mcpCommand.d.ts.map +1 -0
  119. package/dist/cli/mcpCommand.js +93 -0
  120. package/dist/cli/mcpCommand.js.map +1 -0
  121. package/dist/cli/projectHandler.d.ts.map +1 -1
  122. package/dist/cli/projectHandler.js +4 -2
  123. package/dist/cli/projectHandler.js.map +1 -1
  124. package/dist/cli/reviewAudit.d.ts +130 -0
  125. package/dist/cli/reviewAudit.d.ts.map +1 -0
  126. package/dist/cli/reviewAudit.js +283 -0
  127. package/dist/cli/reviewAudit.js.map +1 -0
  128. package/dist/cli/reviewCommand.d.ts +53 -0
  129. package/dist/cli/reviewCommand.d.ts.map +1 -0
  130. package/dist/cli/reviewCommand.js +207 -0
  131. package/dist/cli/reviewCommand.js.map +1 -0
  132. package/dist/cli/reviewMaxCommand.d.ts +35 -0
  133. package/dist/cli/reviewMaxCommand.d.ts.map +1 -0
  134. package/dist/cli/reviewMaxCommand.js +278 -0
  135. package/dist/cli/reviewMaxCommand.js.map +1 -0
  136. package/dist/cli/reviewProgress.d.ts +34 -0
  137. package/dist/cli/reviewProgress.d.ts.map +1 -0
  138. package/dist/cli/reviewProgress.js +109 -0
  139. package/dist/cli/reviewProgress.js.map +1 -0
  140. package/dist/cli/scheduleCommand.d.ts +15 -0
  141. package/dist/cli/scheduleCommand.d.ts.map +1 -0
  142. package/dist/cli/scheduleCommand.js +68 -0
  143. package/dist/cli/scheduleCommand.js.map +1 -0
  144. package/dist/cli.js +182 -26
  145. package/dist/cli.js.map +1 -1
  146. package/dist/core/config.d.ts +24 -0
  147. package/dist/core/config.d.ts.map +1 -1
  148. package/dist/core/config.js +42 -0
  149. package/dist/core/config.js.map +1 -1
  150. package/dist/core/providerOverride.d.ts.map +1 -1
  151. package/dist/core/providerOverride.js +16 -13
  152. package/dist/core/providerOverride.js.map +1 -1
  153. package/dist/core/service.d.ts.map +1 -1
  154. package/dist/core/service.js +11 -0
  155. package/dist/core/service.js.map +1 -1
  156. package/dist/core/types.d.ts +38 -0
  157. package/dist/core/types.d.ts.map +1 -1
  158. package/dist/discord/discordHandlers.d.ts.map +1 -1
  159. package/dist/discord/discordHandlers.js +1 -0
  160. package/dist/discord/discordHandlers.js.map +1 -1
  161. package/dist/index.js +4 -0
  162. package/dist/index.js.map +1 -1
  163. package/dist/linear/linear.d.ts +21 -0
  164. package/dist/linear/linear.d.ts.map +1 -1
  165. package/dist/linear/linear.js +109 -4
  166. package/dist/linear/linear.js.map +1 -1
  167. package/dist/linear/projectUpdater.js +3 -1
  168. package/dist/linear/projectUpdater.js.map +1 -1
  169. package/dist/locale/prompts/en.d.ts.map +1 -1
  170. package/dist/locale/prompts/en.js +104 -11
  171. package/dist/locale/prompts/en.js.map +1 -1
  172. package/dist/locale/prompts/ko.d.ts.map +1 -1
  173. package/dist/locale/prompts/ko.js +103 -11
  174. package/dist/locale/prompts/ko.js.map +1 -1
  175. package/dist/locale/types.d.ts +12 -0
  176. package/dist/locale/types.d.ts.map +1 -1
  177. package/dist/mcp/mcpClient.d.ts +28 -1
  178. package/dist/mcp/mcpClient.d.ts.map +1 -1
  179. package/dist/mcp/mcpClient.js +74 -3
  180. package/dist/mcp/mcpClient.js.map +1 -1
  181. package/dist/orchestration/decisionEngine.d.ts +20 -0
  182. package/dist/orchestration/decisionEngine.d.ts.map +1 -1
  183. package/dist/orchestration/decisionEngine.js +23 -0
  184. package/dist/orchestration/decisionEngine.js.map +1 -1
  185. package/dist/orchestration/taskScheduler.d.ts.map +1 -1
  186. package/dist/orchestration/taskScheduler.js +12 -1
  187. package/dist/orchestration/taskScheduler.js.map +1 -1
  188. package/dist/support/chatBackend.d.ts +18 -0
  189. package/dist/support/chatBackend.d.ts.map +1 -1
  190. package/dist/support/chatBackend.js +92 -8
  191. package/dist/support/chatBackend.js.map +1 -1
  192. package/dist/support/chatSession.d.ts +51 -0
  193. package/dist/support/chatSession.d.ts.map +1 -0
  194. package/dist/support/chatSession.js +134 -0
  195. package/dist/support/chatSession.js.map +1 -0
  196. package/dist/support/chatTui.d.ts.map +1 -1
  197. package/dist/support/chatTui.js +6 -75
  198. package/dist/support/chatTui.js.map +1 -1
  199. package/dist/support/concurrencyPool.d.ts +18 -0
  200. package/dist/support/concurrencyPool.d.ts.map +1 -0
  201. package/dist/support/concurrencyPool.js +46 -0
  202. package/dist/support/concurrencyPool.js.map +1 -0
  203. package/dist/support/dashboardHtml.d.ts +1 -1
  204. package/dist/support/dashboardHtml.d.ts.map +1 -1
  205. package/dist/support/dashboardHtml.js +0 -28
  206. package/dist/support/dashboardHtml.js.map +1 -1
  207. package/dist/support/editParser.d.ts +26 -0
  208. package/dist/support/editParser.d.ts.map +1 -1
  209. package/dist/support/editParser.js +43 -0
  210. package/dist/support/editParser.js.map +1 -1
  211. package/dist/support/goalCommand.d.ts +35 -0
  212. package/dist/support/goalCommand.d.ts.map +1 -0
  213. package/dist/support/goalCommand.js +112 -0
  214. package/dist/support/goalCommand.js.map +1 -0
  215. package/dist/support/index.d.ts +0 -1
  216. package/dist/support/index.d.ts.map +1 -1
  217. package/dist/support/index.js +0 -1
  218. package/dist/support/index.js.map +1 -1
  219. package/dist/support/web.d.ts.map +1 -1
  220. package/dist/support/web.js +0 -7
  221. package/dist/support/web.js.map +1 -1
  222. package/dist/taskState/store.d.ts +5 -0
  223. package/dist/taskState/store.d.ts.map +1 -1
  224. package/dist/taskState/store.js +16 -0
  225. package/dist/taskState/store.js.map +1 -1
  226. package/dist/telemetry/telemetry.d.ts +42 -0
  227. package/dist/telemetry/telemetry.d.ts.map +1 -0
  228. package/dist/telemetry/telemetry.js +138 -0
  229. package/dist/telemetry/telemetry.js.map +1 -0
  230. package/dist/tui/App.d.ts +20 -0
  231. package/dist/tui/App.d.ts.map +1 -0
  232. package/dist/tui/App.js +52 -0
  233. package/dist/tui/App.js.map +1 -0
  234. package/dist/tui/chatModel.d.ts +81 -0
  235. package/dist/tui/chatModel.d.ts.map +1 -0
  236. package/dist/tui/chatModel.js +129 -0
  237. package/dist/tui/chatModel.js.map +1 -0
  238. package/dist/tui/components/AuditBoard.d.ts +10 -0
  239. package/dist/tui/components/AuditBoard.d.ts.map +1 -0
  240. package/dist/tui/components/AuditBoard.js +50 -0
  241. package/dist/tui/components/AuditBoard.js.map +1 -0
  242. package/dist/tui/components/ChatInput.d.ts +14 -0
  243. package/dist/tui/components/ChatInput.d.ts.map +1 -0
  244. package/dist/tui/components/ChatInput.js +46 -0
  245. package/dist/tui/components/ChatInput.js.map +1 -0
  246. package/dist/tui/components/ChatLog.d.ts +12 -0
  247. package/dist/tui/components/ChatLog.d.ts.map +1 -0
  248. package/dist/tui/components/ChatLog.js +47 -0
  249. package/dist/tui/components/ChatLog.js.map +1 -0
  250. package/dist/tui/components/CommandPalette.d.ts +6 -0
  251. package/dist/tui/components/CommandPalette.d.ts.map +1 -0
  252. package/dist/tui/components/CommandPalette.js +14 -0
  253. package/dist/tui/components/CommandPalette.js.map +1 -0
  254. package/dist/tui/components/ContextBar.d.ts +9 -0
  255. package/dist/tui/components/ContextBar.d.ts.map +1 -0
  256. package/dist/tui/components/ContextBar.js +18 -0
  257. package/dist/tui/components/ContextBar.js.map +1 -0
  258. package/dist/tui/components/DataTable.d.ts +6 -0
  259. package/dist/tui/components/DataTable.d.ts.map +1 -0
  260. package/dist/tui/components/DataTable.js +13 -0
  261. package/dist/tui/components/DataTable.js.map +1 -0
  262. package/dist/tui/components/HelpBar.d.ts +2 -0
  263. package/dist/tui/components/HelpBar.d.ts.map +1 -0
  264. package/dist/tui/components/HelpBar.js +8 -0
  265. package/dist/tui/components/HelpBar.js.map +1 -0
  266. package/dist/tui/components/LiveLog.d.ts +6 -0
  267. package/dist/tui/components/LiveLog.d.ts.map +1 -0
  268. package/dist/tui/components/LiveLog.js +10 -0
  269. package/dist/tui/components/LiveLog.js.map +1 -0
  270. package/dist/tui/components/LogLine.d.ts +4 -0
  271. package/dist/tui/components/LogLine.d.ts.map +1 -0
  272. package/dist/tui/components/LogLine.js +8 -0
  273. package/dist/tui/components/LogLine.js.map +1 -0
  274. package/dist/tui/components/SelectList.d.ts +6 -0
  275. package/dist/tui/components/SelectList.d.ts.map +1 -0
  276. package/dist/tui/components/SelectList.js +15 -0
  277. package/dist/tui/components/SelectList.js.map +1 -0
  278. package/dist/tui/components/StageTimeline.d.ts +7 -0
  279. package/dist/tui/components/StageTimeline.d.ts.map +1 -0
  280. package/dist/tui/components/StageTimeline.js +18 -0
  281. package/dist/tui/components/StageTimeline.js.map +1 -0
  282. package/dist/tui/components/StatusBar.d.ts +7 -0
  283. package/dist/tui/components/StatusBar.d.ts.map +1 -0
  284. package/dist/tui/components/StatusBar.js +8 -0
  285. package/dist/tui/components/StatusBar.js.map +1 -0
  286. package/dist/tui/components/SubagentTree.d.ts +10 -0
  287. package/dist/tui/components/SubagentTree.d.ts.map +1 -0
  288. package/dist/tui/components/SubagentTree.js +12 -0
  289. package/dist/tui/components/SubagentTree.js.map +1 -0
  290. package/dist/tui/components/TabBar.d.ts +5 -0
  291. package/dist/tui/components/TabBar.d.ts.map +1 -0
  292. package/dist/tui/components/TabBar.js +9 -0
  293. package/dist/tui/components/TabBar.js.map +1 -0
  294. package/dist/tui/components/WorkingIndicator.d.ts +6 -0
  295. package/dist/tui/components/WorkingIndicator.d.ts.map +1 -0
  296. package/dist/tui/components/WorkingIndicator.js +20 -0
  297. package/dist/tui/components/WorkingIndicator.js.map +1 -0
  298. package/dist/tui/hooks/useMonitor.d.ts +8 -0
  299. package/dist/tui/hooks/useMonitor.d.ts.map +1 -0
  300. package/dist/tui/hooks/useMonitor.js +42 -0
  301. package/dist/tui/hooks/useMonitor.js.map +1 -0
  302. package/dist/tui/hooks/usePipelineEvents.d.ts +6 -0
  303. package/dist/tui/hooks/usePipelineEvents.d.ts.map +1 -0
  304. package/dist/tui/hooks/usePipelineEvents.js +21 -0
  305. package/dist/tui/hooks/usePipelineEvents.js.map +1 -0
  306. package/dist/tui/hooks/useTerminalSize.d.ts +6 -0
  307. package/dist/tui/hooks/useTerminalSize.d.ts.map +1 -0
  308. package/dist/tui/hooks/useTerminalSize.js +26 -0
  309. package/dist/tui/hooks/useTerminalSize.js.map +1 -0
  310. package/dist/tui/index.d.ts +22 -0
  311. package/dist/tui/index.d.ts.map +1 -0
  312. package/dist/tui/index.js +18 -0
  313. package/dist/tui/index.js.map +1 -0
  314. package/dist/tui/inputDebug.d.ts +20 -0
  315. package/dist/tui/inputDebug.d.ts.map +1 -0
  316. package/dist/tui/inputDebug.js +42 -0
  317. package/dist/tui/inputDebug.js.map +1 -0
  318. package/dist/tui/loadingMessages.d.ts +11 -0
  319. package/dist/tui/loadingMessages.d.ts.map +1 -0
  320. package/dist/tui/loadingMessages.js +39 -0
  321. package/dist/tui/loadingMessages.js.map +1 -0
  322. package/dist/tui/logFormat.d.ts +10 -0
  323. package/dist/tui/logFormat.d.ts.map +1 -0
  324. package/dist/tui/logFormat.js +86 -0
  325. package/dist/tui/logFormat.js.map +1 -0
  326. package/dist/tui/markdown.d.ts +3 -0
  327. package/dist/tui/markdown.d.ts.map +1 -0
  328. package/dist/tui/markdown.js +33 -0
  329. package/dist/tui/markdown.js.map +1 -0
  330. package/dist/tui/monitorApi.d.ts +6 -0
  331. package/dist/tui/monitorApi.d.ts.map +1 -0
  332. package/dist/tui/monitorApi.js +35 -0
  333. package/dist/tui/monitorApi.js.map +1 -0
  334. package/dist/tui/monitorRows.d.ts +43 -0
  335. package/dist/tui/monitorRows.d.ts.map +1 -0
  336. package/dist/tui/monitorRows.js +65 -0
  337. package/dist/tui/monitorRows.js.map +1 -0
  338. package/dist/tui/panels/ChatPanel.d.ts +16 -0
  339. package/dist/tui/panels/ChatPanel.d.ts.map +1 -0
  340. package/dist/tui/panels/ChatPanel.js +305 -0
  341. package/dist/tui/panels/ChatPanel.js.map +1 -0
  342. package/dist/tui/panels/LogsPanel.d.ts +8 -0
  343. package/dist/tui/panels/LogsPanel.d.ts.map +1 -0
  344. package/dist/tui/panels/LogsPanel.js +19 -0
  345. package/dist/tui/panels/LogsPanel.js.map +1 -0
  346. package/dist/tui/panels/MonitorPanel.d.ts +8 -0
  347. package/dist/tui/panels/MonitorPanel.d.ts.map +1 -0
  348. package/dist/tui/panels/MonitorPanel.js +19 -0
  349. package/dist/tui/panels/MonitorPanel.js.map +1 -0
  350. package/dist/tui/panels/PipelinePanel.d.ts +6 -0
  351. package/dist/tui/panels/PipelinePanel.d.ts.map +1 -0
  352. package/dist/tui/panels/PipelinePanel.js +22 -0
  353. package/dist/tui/panels/PipelinePanel.js.map +1 -0
  354. package/dist/tui/pipelineEvents.d.ts +21 -0
  355. package/dist/tui/pipelineEvents.d.ts.map +1 -0
  356. package/dist/tui/pipelineEvents.js +30 -0
  357. package/dist/tui/pipelineEvents.js.map +1 -0
  358. package/dist/tui/sse.d.ts +24 -0
  359. package/dist/tui/sse.d.ts.map +1 -0
  360. package/dist/tui/sse.js +78 -0
  361. package/dist/tui/sse.js.map +1 -0
  362. package/dist/tui/subagentTree.d.ts +10 -0
  363. package/dist/tui/subagentTree.d.ts.map +1 -0
  364. package/dist/tui/subagentTree.js +30 -0
  365. package/dist/tui/subagentTree.js.map +1 -0
  366. package/dist/tui/tabs.d.ts +10 -0
  367. package/dist/tui/tabs.d.ts.map +1 -0
  368. package/dist/tui/tabs.js +26 -0
  369. package/dist/tui/tabs.js.map +1 -0
  370. package/dist/tui/theme.d.ts +29 -0
  371. package/dist/tui/theme.d.ts.map +1 -0
  372. package/dist/tui/theme.js +34 -0
  373. package/dist/tui/theme.js.map +1 -0
  374. package/package.json +13 -3
@@ -1,8 +1,8 @@
1
1
  // OpenSwarm - Autonomous Runner
2
2
  // Heartbeat → Decision → Execution → Report
3
3
  import { Cron } from 'croner';
4
- import { loadTaskState, saveTaskState, buildProjectsInfo, appendPipelineHistory, getPipelineHistory, incrementRejection, clearRejection, isRejectionLimitReached, canRetryNow, setRetryTime, clearRetryTime, formatRetryTime, getDailyPaceInfo, recordProjectCompletion, canProjectAcceptTask, getProjectWindowCount, } from './runnerState.js';
5
- import { getDecisionEngine, } from '../orchestration/decisionEngine.js';
4
+ import { loadTaskState, saveTaskState, buildProjectsInfo, appendPipelineHistory, getPipelineHistory, incrementRejection, clearRejection, isRejectionLimitReached, canRetryNow, setRetryTime, clearRetryTime, formatRetryTime, getDailyPaceInfo, recordProjectCompletion, canProjectAcceptTask, getProjectWindowCount, loadProjectSelection, saveProjectSelection, } from './runnerState.js';
5
+ import { getDecisionEngine, classifyStuck, } from '../orchestration/decisionEngine.js';
6
6
  // ExecutorResult used via execution.reportExecutionResult
7
7
  import { checkWorkAllowed } from '../support/timeWindow.js';
8
8
  import { recordTaskOutcome } from '../memory/repoKnowledge.js';
@@ -14,12 +14,14 @@ import * as execution from './runnerExecution.js';
14
14
  import { reportToDiscord, fetchLinearTasks, getTaskSource } from './runnerExecution.js';
15
15
  import { t } from '../locale/index.js';
16
16
  import { broadcastEvent } from '../core/eventHub.js';
17
+ import { writeProviderOverride } from '../core/providerOverride.js';
18
+ import { getTaskState } from '../taskState/store.js';
17
19
  import { pruneWorktrees } from '../support/worktreeManager.js';
18
20
  import { loadRepoMetadata } from '../support/repoMetadata.js';
21
+ import { STUCK_LABEL } from '../linear/index.js';
19
22
  import { refreshGraph, toProjectSlug } from '../knowledge/index.js';
20
23
  import { checkAllMonitors, getActiveMonitors } from './longRunningMonitor.js';
21
24
  import { detectFileConflicts } from '../orchestration/conflictDetector.js';
22
- import { checkQuotaAllowance } from '../support/quotaTracker.js';
23
25
  // Re-export types and integration setters (used by service.ts)
24
26
  export { setNotifier, setTaskSource } from './runnerExecution.js';
25
27
  let runnerInstance = null;
@@ -35,8 +37,14 @@ export class AutonomousRunner {
35
37
  };
36
38
  // Heartbeat concurrency guard
37
39
  _heartbeatRunning = false;
38
- // Explicitly enabled project paths (allow-list; empty = nothing runs)
40
+ // Explicitly enabled project paths (allow-list; empty = nothing runs ONLY once
41
+ // the selection has been touched — see projectSelectionTouched).
39
42
  enabledProjects = new Set();
43
+ // Whether the user has explicitly enabled/disabled any project (dashboard/CLI).
44
+ // Before this, an empty enabledProjects means "no selection yet → all allowed
45
+ // projects run" (legacy fallback). After, an empty set means "nothing runs" —
46
+ // so disabling every project actually stops the daemon. (INT-2207)
47
+ projectSelectionTouched = false;
40
48
  /**
41
49
  * macOS (APFS default) and Windows have case-insensitive filesystems by
42
50
  * default, so `/Users/x/dev/AnalogModeling` and `/Users/x/dev/analogModeling`
@@ -64,6 +72,21 @@ export class AutonomousRunner {
64
72
  }
65
73
  return false;
66
74
  }
75
+ /**
76
+ * Whether to apply the enabledProjects allow-list. True once the user has made
77
+ * an explicit selection (touched), OR while any project is enabled. Empty +
78
+ * untouched stays the legacy "run all allowed projects" fallback. (INT-2207)
79
+ */
80
+ shouldFilterByEnabled() {
81
+ return this.projectSelectionTouched || this.enabledProjects.size > 0;
82
+ }
83
+ /** Persist the project selection so it survives a restart. No-op under dryRun
84
+ * (tests) to avoid touching the real ~/.openswarm. (INT-2208) */
85
+ persistSelection() {
86
+ if (this.config.dryRun)
87
+ return;
88
+ saveProjectSelection({ enabled: [...this.enabledProjects], touched: this.projectSelectionTouched });
89
+ }
67
90
  // Last fetched Linear tasks (for dashboard display)
68
91
  lastFetchedTasks = [];
69
92
  // Cache: linearProjectName → resolvedLocalPath (populated during task execution)
@@ -77,6 +100,13 @@ export class AutonomousRunner {
77
100
  failedTaskCounts = new Map();
78
101
  failedTaskRetryTimes = new Map(); // issueId → next retry timestamp (ms)
79
102
  static MAX_RETRY_COUNT = 4; // Increased from 2 to allow more retries with backoff
103
+ // Rate-limit hold: epoch ms until which all task execution is paused.
104
+ // Set when any adapter returns a 429 / usage_limit_reached response (INT-1906).
105
+ rateLimitUntil = 0;
106
+ // Issues whose Linear project can't be mapped to a local repo path. Recorded on
107
+ // the first resolve failure so they aren't re-picked every heartbeat (which
108
+ // starved other actionable tasks — they were top-priority but never runnable). (INT-1875)
109
+ unresolvableIssueIds = new Set();
80
110
  get taskStateRef() {
81
111
  return {
82
112
  completedTaskIds: this.completedTaskIds,
@@ -103,6 +133,13 @@ export class AutonomousRunner {
103
133
  constructor(config) {
104
134
  this.config = config;
105
135
  this.loadTaskState(); // Restore completed/failed task IDs from disk
136
+ // Restore the persisted project selection so "disable all" survives a daemon
137
+ // restart. Skipped under dryRun (tests) so the real ~/.openswarm isn't touched. (INT-2208)
138
+ if (!config.dryRun) {
139
+ const sel = loadProjectSelection();
140
+ this.enabledProjects = new Set(sel.enabled);
141
+ this.projectSelectionTouched = sel.touched;
142
+ }
106
143
  this.engine = getDecisionEngine({
107
144
  allowedProjects: config.allowedProjects,
108
145
  linearTeamId: config.linearTeamId,
@@ -113,9 +150,11 @@ export class AutonomousRunner {
113
150
  includeBacklog: config.includeBacklog,
114
151
  });
115
152
  // Initialize TaskScheduler
153
+ // Same-repo parallelism is opt-in via config (default true) but the scheduler
154
+ // force-disables it unless worktreeMode is on — see TaskScheduler guard. (INT-1975)
116
155
  this.scheduler = initScheduler({
117
156
  maxConcurrent: config.maxConcurrentTasks ?? 1,
118
- allowSameProjectConcurrent: false,
157
+ allowSameProjectConcurrent: config.allowSameProjectConcurrent ?? true,
119
158
  worktreeMode: config.worktreeMode ?? false,
120
159
  });
121
160
  // Set up scheduler event handling
@@ -198,8 +237,55 @@ export class AutonomousRunner {
198
237
  }
199
238
  this.scheduleNextHeartbeat();
200
239
  });
240
+ this.scheduler.on('cancelled', async ({ task, result }) => {
241
+ const taskCtx = this.formatTaskContext(task);
242
+ console.log(`[Scheduler] Task cancelled: ${taskCtx} ${task.title}`);
243
+ broadcastEvent({ type: 'task:completed', data: { taskId: task.id, success: false, duration: result.totalDuration } });
244
+ this.recordPipelineHistory(task, result);
245
+ await execution.syncCancellationState(task);
246
+ // Keep parity with the completed/failed handlers: kick the next heartbeat
247
+ // so discovery resumes immediately instead of waiting for the next cron
248
+ // tick (matters in serial mode, where slotFreed/runAvailableTasks is a no-op).
249
+ this.scheduleNextHeartbeat();
250
+ });
201
251
  this.scheduler.on('failed', async ({ task, result }) => {
202
252
  const taskCtx = this.formatTaskContext(task);
253
+ // Rate-limited: pause execution until the quota resets. Do NOT count it as a
254
+ // task failure, run the rejection/block path, or post a Linear comment —
255
+ // that is exactly the retry-spam this issue fixes. (INT-1906)
256
+ if (result.finalStatus === 'rate_limited') {
257
+ const resetsAt = result.rateLimitResetsAt ?? Date.now() + 60_000;
258
+ this.rateLimitUntil = resetsAt;
259
+ const waitSec = Math.max(0, Math.ceil((resetsAt - Date.now()) / 1000));
260
+ const resetsLabel = new Date(resetsAt).toISOString();
261
+ console.warn(`[Scheduler] Rate limit hit for ${taskCtx} — pausing until ${resetsLabel} (~${waitSec}s)`);
262
+ broadcastEvent({
263
+ type: 'log',
264
+ data: { taskId: task.issueId || task.id, stage: 'rate_limit', line: `⏸ Rate limited — pausing ~${waitSec}s (until ${resetsLabel})` },
265
+ });
266
+ return;
267
+ }
268
+ // Infra/CLI failure: the worker/reviewer never actually ran (non-zero exit,
269
+ // auth expiry, spawn, timeout). This is NOT a task failure — do NOT increment
270
+ // the rejection/failure counters that mark an issue durably STUCK. Backoff-
271
+ // retry instead; the operator fixes the root cause (e.g. re-auth) and the task
272
+ // resumes on its own. This is what kept completable tasks (worker had already
273
+ // edited files) STUCK in production. (INT-2010)
274
+ if (result.finalStatus === 'infra_error') {
275
+ const detail = result.workerResult?.error || result.reviewResult?.feedback || 'infra/CLI execution error';
276
+ if (task.issueId) {
277
+ // Fixed mid-range backoff — we intentionally don't bump failure counts,
278
+ // so there's no attempt number to scale by.
279
+ const nextRetryTime = setRetryTime(task.issueId, 3, this.failedTaskRetryTimes);
280
+ this.saveTaskState();
281
+ console.warn(`[Scheduler] Infra error for ${taskCtx} (NOT counted toward STUCK) — backoff retry ${formatRetryTime(nextRetryTime)}: ${detail}`);
282
+ }
283
+ else {
284
+ console.warn(`[Scheduler] Infra error for ${taskCtx} (NOT counted toward STUCK): ${detail}`);
285
+ }
286
+ this.scheduleNextHeartbeat();
287
+ return;
288
+ }
203
289
  console.log(`[Scheduler] Task failed: ${taskCtx} ${task.title}`);
204
290
  broadcastEvent({ type: 'task:completed', data: { taskId: task.id, success: false, duration: result.totalDuration } });
205
291
  this.recordPipelineHistory(task, result);
@@ -225,11 +311,9 @@ export class AutonomousRunner {
225
311
  this.saveTaskState();
226
312
  try {
227
313
  await execution.syncFailureState(task, `Max rejection limit reached (${rejectionCount} attempts): ${feedback}`);
228
- await getTaskSource()?.logBlocked(task.issueId, 'autonomous-runner', `⚠️ **Max rejection limit reached (${rejectionCount} attempts)**\n\n` +
229
- `This task has been rejected ${rejectionCount} times by the reviewer and requires manual intervention.\n\n` +
230
- `**Latest rejection reason:**\n${feedback}\n\n` +
231
- `**Action required:** Please review the task requirements and code manually, or adjust the task scope.`);
232
- console.log(`[Scheduler] Issue ${task.issueId} permanently blocked (max rejections reached)`);
314
+ await getTaskSource()?.logStuck(task.issueId, 'autonomous-runner', `Rejected ${rejectionCount} times by the reviewer — automatic retries exhausted.\n\n` +
315
+ `**Latest rejection reason:**\n${feedback}`);
316
+ console.log(`[Scheduler] Issue ${task.issueId} marked STUCK (max rejections reached)`);
233
317
  }
234
318
  catch (err) {
235
319
  console.error(`[Scheduler] Failed to update issue state:`, err);
@@ -262,11 +346,17 @@ export class AutonomousRunner {
262
346
  this.completedTaskIds.add(task.issueId); // Prevent re-selection
263
347
  clearRetryTime(task.issueId, this.failedTaskRetryTimes); // Clear retry time
264
348
  this.saveTaskState();
265
- console.log(`[Scheduler] Task failure count: ${count}/${AutonomousRunner.MAX_RETRY_COUNT} for ${taskCtx} — BLOCKED`);
349
+ console.log(`[Scheduler] Task failure count: ${count}/${AutonomousRunner.MAX_RETRY_COUNT} for ${taskCtx} — STUCK`);
350
+ // Surface the underlying failure (worker CLI error / review feedback) so the
351
+ // stuck comment is actionable instead of an opaque "failed N times".
352
+ const failureDetail = result.workerResult?.error
353
+ || result.reviewResult?.feedback
354
+ || 'No error detail captured (worker produced no output).';
266
355
  try {
267
- await execution.syncFailureState(task, `Autonomous execution failed ${count} times`);
268
- await getTaskSource()?.logBlocked(task.issueId, 'autonomous-runner', `Autonomous execution failed ${count} times. Moving to Blocked for manual review.`);
269
- console.log(`[Scheduler] Issue ${task.issueId} marked as Todo (blocked) (max retries exceeded)`);
356
+ await execution.syncFailureState(task, `Autonomous execution failed ${count} times: ${failureDetail}`);
357
+ await getTaskSource()?.logStuck(task.issueId, 'autonomous-runner', `Autonomous execution failed ${count} times in a row automatic retries exhausted.\n\n` +
358
+ `**Last failure:**\n${failureDetail}`);
359
+ console.log(`[Scheduler] Issue ${task.issueId} marked STUCK (max retries exceeded)`);
270
360
  }
271
361
  catch (err) {
272
362
  console.error(`[Scheduler] Failed to update issue state:`, err);
@@ -305,11 +395,14 @@ export class AutonomousRunner {
305
395
  }
306
396
  filterAlreadyProcessed(tasks) {
307
397
  let recovered = 0;
398
+ let stuckSkipped = 0;
308
399
  let backoffSkipped = 0;
309
400
  let noProject = 0;
310
- const recoverableStates = new Set(['Todo', 'In Progress', 'In Review']);
401
+ let unresolvable = 0;
402
+ const toUnstick = [];
311
403
  const filtered = tasks.filter(task => {
312
404
  const id = task.issueId || task.id;
405
+ const isStuck = task.labels?.includes(STUCK_LABEL) ?? false;
313
406
  // No Linear project → can't be routed to a repo. Drop here (quietly, once per
314
407
  // heartbeat) instead of letting a whole batch of project-less Todos reach the
315
408
  // per-task selector and spam "No repo mapped to ... undefined" every cycle.
@@ -317,24 +410,54 @@ export class AutonomousRunner {
317
410
  noProject++;
318
411
  return false;
319
412
  }
413
+ // Project resolved to no local repo on a previous heartbeat → don't re-pick
414
+ // it (it would starve runnable tasks behind it). (INT-1875)
415
+ if (this.unresolvableIssueIds.has(id)) {
416
+ unresolvable++;
417
+ return false;
418
+ }
320
419
  // Check rejection limit first
321
420
  if (isRejectionLimitReached(id)) {
322
421
  return false; // Skip tasks that hit max rejection limit
323
422
  }
324
- // Recover issues in active states from completed/failed list
325
- // (user or system intentionally moved back to active, so retry)
326
- if (recoverableStates.has(task.linearState || '') && (this.completedTaskIds.has(id) || (this.failedTaskCounts.get(id) ?? 0) >= AutonomousRunner.MAX_RETRY_COUNT)) {
423
+ // Stuck handling (INT-1908): a permanently-blocked issue is parked in Backlog
424
+ // with the `swarm:stuck` label and must NOT be retried automatically. The
425
+ // recovery branch only fires when the user pulls the issue back to an active
426
+ // state — the previous code re-selected it every heartbeat because blocking
427
+ // left it in Todo (a recoverable state), which the recovery branch then
428
+ // mistook for deliberate user intervention.
429
+ const hasFailureHistory = this.completedTaskIds.has(id) || (this.failedTaskCounts.get(id) ?? 0) >= AutonomousRunner.MAX_RETRY_COUNT;
430
+ const stuckDecision = classifyStuck({ isStuck, linearState: task.linearState, hasFailureHistory });
431
+ if (stuckDecision === 'recover') {
327
432
  this.completedTaskIds.delete(id);
328
433
  this.failedTaskCounts.delete(id);
329
434
  clearRejection(id); // Clear rejection count on recovery
330
435
  clearRetryTime(id, this.failedTaskRetryTimes); // Clear retry backoff time
436
+ if (isStuck)
437
+ toUnstick.push(id); // strip the stuck label so it is not re-skipped
331
438
  recovered++;
332
439
  return true;
333
440
  }
441
+ if (stuckDecision === 'skip-stuck') {
442
+ // Durable across restarts: the label lives on the Linear issue, not in the
443
+ // in-memory counters that a restart would lose.
444
+ stuckSkipped++;
445
+ return false;
446
+ }
334
447
  if (this.completedTaskIds.has(id))
335
448
  return false;
336
449
  if ((this.failedTaskCounts.get(id) ?? 0) >= AutonomousRunner.MAX_RETRY_COUNT)
337
450
  return false;
451
+ // External-claim guard (INT-1979 dup): an issue set to 'In Progress' that THIS
452
+ // daemon never claimed is owned by a human or another agent — picking it up
453
+ // would re-decompose work someone is already doing (that spawned duplicate
454
+ // INT-1980 sub-issues + a redundant PR). markTaskInProgress writes
455
+ // execution.status='in_progress' when WE claim, so our own in-flight work
456
+ // (incl. resumption after a restart) still passes; a bare Linear 'In Progress'
457
+ // with no local claim record is skipped.
458
+ if (task.linearState === 'In Progress' && getTaskState(id)?.execution?.status !== 'in_progress') {
459
+ return false;
460
+ }
338
461
  // Check if task is in exponential backoff period
339
462
  if (!canRetryNow(id, this.failedTaskRetryTimes)) {
340
463
  backoffSkipped++;
@@ -342,6 +465,14 @@ export class AutonomousRunner {
342
465
  }
343
466
  return true;
344
467
  });
468
+ // Strip the stuck label from issues the user pulled back (fire-and-forget — a
469
+ // failed unstick just means the next heartbeat tries again).
470
+ for (const id of toUnstick) {
471
+ getTaskSource()?.unstick(id).catch(err => console.warn(`[AutonomousRunner] Failed to clear stuck label for ${id}:`, err));
472
+ }
473
+ if (stuckSkipped > 0) {
474
+ this.syslog(`🛑 Skipped ${stuckSkipped} stuck issue(s) (retries exhausted — remove the \`${STUCK_LABEL}\` label or move to Todo to retry)`);
475
+ }
345
476
  if (recovered > 0) {
346
477
  this.saveTaskState();
347
478
  this.syslog(`♻ Recovered ${recovered} Todo issues from completed/failed/rejected list`);
@@ -352,6 +483,9 @@ export class AutonomousRunner {
352
483
  if (noProject > 0) {
353
484
  this.syslog(`— Skipped ${noProject} issue(s) with no Linear project (assign a project in Linear to enable)`);
354
485
  }
486
+ if (unresolvable > 0) {
487
+ this.syslog(`— Skipped ${unresolvable} issue(s) whose Linear project maps to no local repo (fix the project or add the repo)`);
488
+ }
355
489
  return filtered;
356
490
  }
357
491
  /**
@@ -531,16 +665,20 @@ export class AutonomousRunner {
531
665
  return;
532
666
  }
533
667
  this.syslog('✓ Time window: allowed');
534
- // 1.5 Quota gate — skip heartbeat if Claude Max quota is too high
535
- const quotaCheck = await checkQuotaAllowance(80);
536
- if (!quotaCheck.allowed) {
537
- console.log(`[AutonomousRunner] Quota gate: SKIP ${quotaCheck.reason}`);
538
- broadcastEvent({ type: 'log', data: { taskId: 'system', stage: 'quota', line: `⏸ ${quotaCheck.reason}` } });
668
+ // 1.2 Rate-limit hold — skip the heartbeat while a 429 pause is still active.
669
+ // Cleared implicitly once the clock passes rateLimitUntil. (INT-1906)
670
+ if (Date.now() < this.rateLimitUntil) {
671
+ const remainSec = Math.max(0, Math.ceil((this.rateLimitUntil - Date.now()) / 1000));
672
+ const resetsLabel = new Date(this.rateLimitUntil).toISOString();
673
+ console.log(`[AutonomousRunner] Rate limit hold active — ${remainSec}s remaining (until ${resetsLabel})`);
674
+ this.syslog(`⏸ Rate limit hold: ~${remainSec}s remaining`);
675
+ broadcastEvent({ type: 'log', data: { taskId: 'system', stage: 'rate_limit', line: `⏸ Rate limit hold: ~${remainSec}s remaining` } });
539
676
  return;
540
677
  }
541
- if (quotaCheck.utilization !== undefined && quotaCheck.utilization > 60) {
542
- console.log(`[AutonomousRunner] Quota warning: ${quotaCheck.utilization.toFixed(0)}% utilization`);
543
- }
678
+ // 1.5 Quota gate (removed) was a Claude Max quota check (api.anthropic.com
679
+ // /oauth/usage). OpenSwarm runs codex-responses now, not claude -p, so a Claude
680
+ // quota gate is irrelevant; it only spammed 401s and could wrongly skip codex
681
+ // work. codex-responses self-protects via RateLimitError (scheduler pause).
544
682
  // 1.6 Pace gate (removed)
545
683
  // The 5h rolling window cap (globalCap = projects × dailyTaskCap) and
546
684
  // turbo-mode multiplier used to gate heartbeat here. Both were removed
@@ -619,7 +757,7 @@ export class AutonomousRunner {
619
757
  // Only execute Todo tasks; Backlog is fetched for dashboard display only
620
758
  const executableTasks = tasks.filter(t => t.linearState !== 'Backlog');
621
759
  let tasksForEngine = executableTasks;
622
- if (this.enabledProjects.size > 0) {
760
+ if (this.shouldFilterByEnabled()) {
623
761
  // Explicit repo↔Linear mapping — match fetched issues to repos by the Linear
624
762
  // projectId the user picked in `openswarm add` (written to <repo>/openswarm.json),
625
763
  // NOT by guessing from the repo directory name. Built fresh each cycle so a
@@ -685,6 +823,8 @@ export class AutonomousRunner {
685
823
  const projectPath = await this.resolveProjectPath(task);
686
824
  if (!projectPath) {
687
825
  this.syslog(`✗ Cannot resolve project path for "${task.linearProject?.name || task.title}" — skipping`);
826
+ // Record so it isn't re-picked every heartbeat (starvation). (INT-1875)
827
+ this.unresolvableIssueIds.add(task.issueId || task.id);
688
828
  continue;
689
829
  }
690
830
  if (task.linearProject?.name) {
@@ -694,7 +834,7 @@ export class AutonomousRunner {
694
834
  this.syslog(` Project busy: ${projectPath}`);
695
835
  continue;
696
836
  }
697
- if (this.enabledProjects.size > 0 && !this.isProjectEnabled(projectPath)) {
837
+ if (this.shouldFilterByEnabled() && !this.isProjectEnabled(projectPath)) {
698
838
  this.syslog(` Project not enabled: ${projectPath}`);
699
839
  continue;
700
840
  }
@@ -777,11 +917,16 @@ export class AutonomousRunner {
777
917
  if (!projectPath) {
778
918
  const errorMsg = `Failed to resolve project path for "${task.linearProject?.name || task.title}"`;
779
919
  console.error(`[AutonomousRunner] ${errorMsg}`);
920
+ // Record so this issue isn't re-picked every heartbeat (it would starve
921
+ // runnable tasks behind it). Cleared on restart. (INT-1875)
922
+ this.unresolvableIssueIds.add(task.issueId || task.id);
780
923
  await reportToDiscord(t('runner.projectMappingFailed', { title: task.title, project: task.linearProject?.name || 'unknown' }));
924
+ // Move on to the next actionable task instead of ending the heartbeat here.
925
+ this.scheduleNextHeartbeat();
781
926
  return;
782
927
  }
783
928
  // Skip if project is not in enabled list (allow-list; empty = nothing runs)
784
- if (this.enabledProjects.size > 0 && !this.isProjectEnabled(projectPath)) {
929
+ if (this.shouldFilterByEnabled() && !this.isProjectEnabled(projectPath)) {
785
930
  console.log(`[AutonomousRunner] Project not enabled, skipping: ${projectPath}`);
786
931
  return;
787
932
  }
@@ -814,6 +959,18 @@ export class AutonomousRunner {
814
959
  }
815
960
  // Single execution (legacy)
816
961
  const result = await this.executePipeline(task, projectPath);
962
+ // Rate-limited: pause until quota resets. Return before any Discord/Linear
963
+ // reporting or state change — no failure count, no card spam. Same as the
964
+ // scheduler 'failed' handler's rate_limited branch. (INT-1906)
965
+ if (result.finalStatus === 'rate_limited') {
966
+ const resetsAt = result.rateLimitResetsAt ?? Date.now() + 60_000;
967
+ this.rateLimitUntil = resetsAt;
968
+ const waitSec = Math.max(0, Math.ceil((resetsAt - Date.now()) / 1000));
969
+ const resetsLabel = new Date(resetsAt).toISOString();
970
+ console.warn(`[AutonomousRunner] Rate limit hit for ${this.formatTaskContext(task)} — pausing until ${resetsLabel} (~${waitSec}s)`);
971
+ broadcastEvent({ type: 'log', data: { taskId: task.issueId || task.id, stage: 'rate_limit', line: `⏸ Rate limited — pausing ~${waitSec}s (until ${resetsLabel})` } });
972
+ return;
973
+ }
817
974
  await reportToDiscord(formatPipelineResultEmbed(result));
818
975
  // Update Linear issue state
819
976
  if (task.issueId) {
@@ -1050,6 +1207,26 @@ export class AutonomousRunner {
1050
1207
  if (this.config.reviewerModel) {
1051
1208
  this.config.reviewerModel = mapModelForProvider(this.config.reviewerModel, 'reviewer');
1052
1209
  }
1210
+ // jobProfiles ALSO pin per-role models (config's light/heavy → e.g. qwen), and getModelForRole
1211
+ // gives the profile model precedence over defaultRoles. Remapping only defaultRoles left every
1212
+ // estimate-matched task on its old provider's model → "I switched to Codex but it still uses the
1213
+ // old provider". Remap the profiles too: an incompatible id becomes undefined so the adapter
1214
+ // falls back to its OWN default model.
1215
+ if (this.config.jobProfiles) {
1216
+ for (const profile of this.config.jobProfiles) {
1217
+ if (!profile.roles)
1218
+ continue;
1219
+ for (const role of Object.keys(profile.roles)) {
1220
+ const mapped = mapModelForProvider(profile.roles[role], role);
1221
+ if (mapped === undefined)
1222
+ delete profile.roles[role];
1223
+ else
1224
+ profile.roles[role] = mapped;
1225
+ }
1226
+ }
1227
+ }
1228
+ // Persist the choice so a daemon restart keeps it (in-memory switch was lost every restart).
1229
+ writeProviderOverride(adapter);
1053
1230
  console.log(`[AutonomousRunner] Provider switched: ${adapter}`);
1054
1231
  }
1055
1232
  pauseScheduler() { this.scheduler.pause(); }
@@ -1072,6 +1249,7 @@ export class AutonomousRunner {
1072
1249
  });
1073
1250
  }
1074
1251
  disableProject(projectPath) {
1252
+ this.projectSelectionTouched = true; // empty set now means "nothing runs" (INT-2207)
1075
1253
  this.enabledProjects.delete(projectPath);
1076
1254
  console.log(`[AutonomousRunner] Project disabled: ${projectPath}`);
1077
1255
  // Disabling gates new selection AND cancels any in-flight pipeline for this
@@ -1080,10 +1258,21 @@ export class AutonomousRunner {
1080
1258
  if (cancelled > 0) {
1081
1259
  this.syslog(`⏹ Cancelled ${cancelled} in-flight task(s) for disabled project ${projectPath.split('/').pop()}`);
1082
1260
  }
1261
+ this.persistSelection();
1083
1262
  }
1084
1263
  enableProject(projectPath) {
1264
+ this.projectSelectionTouched = true; // explicit selection from here on (INT-2207)
1085
1265
  this.enabledProjects.add(projectPath);
1266
+ // Enabling a repo (via `openswarm add` / the dashboard) must also ALLOW it:
1267
+ // resolveProjectPath only reads a repo's openswarm.json for paths in
1268
+ // allowedProjects, so an enabled-but-not-allowed repo never resolves
1269
+ // ("No repo mapped"). Keep config + DecisionEngine in sync. (INT-1970)
1270
+ const allowed = this.config.allowedProjects ?? [];
1271
+ if (!allowed.includes(projectPath)) {
1272
+ this.updateAllowedProjects([...allowed, projectPath]);
1273
+ }
1086
1274
  console.log(`[AutonomousRunner] Project enabled: ${projectPath}`);
1275
+ this.persistSelection();
1087
1276
  }
1088
1277
  /** Get all currently enabled project paths */
1089
1278
  getEnabledProjects() {