@intrect/openswarm 0.17.2 → 0.17.4

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 (263) hide show
  1. package/README.md +21 -5
  2. package/config.example.yaml +18 -0
  3. package/dist/adapters/agenticLoop.d.ts +2 -0
  4. package/dist/adapters/agenticLoop.d.ts.map +1 -1
  5. package/dist/adapters/agenticLoop.js +6 -3
  6. package/dist/adapters/agenticLoop.js.map +1 -1
  7. package/dist/adapters/base.d.ts.map +1 -1
  8. package/dist/adapters/base.js +9 -0
  9. package/dist/adapters/base.js.map +1 -1
  10. package/dist/adapters/claude.d.ts.map +1 -1
  11. package/dist/adapters/claude.js +6 -3
  12. package/dist/adapters/claude.js.map +1 -1
  13. package/dist/adapters/codex.d.ts.map +1 -1
  14. package/dist/adapters/codex.js +3 -2
  15. package/dist/adapters/codex.js.map +1 -1
  16. package/dist/adapters/codexResponses.d.ts +2 -1
  17. package/dist/adapters/codexResponses.d.ts.map +1 -1
  18. package/dist/adapters/codexResponses.js +34 -30
  19. package/dist/adapters/codexResponses.js.map +1 -1
  20. package/dist/adapters/errorClassification.d.ts +11 -0
  21. package/dist/adapters/errorClassification.d.ts.map +1 -1
  22. package/dist/adapters/errorClassification.js +21 -0
  23. package/dist/adapters/errorClassification.js.map +1 -1
  24. package/dist/adapters/gpt.d.ts.map +1 -1
  25. package/dist/adapters/gpt.js +1 -0
  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 +1 -0
  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 +1 -0
  32. package/dist/adapters/openrouter.js.map +1 -1
  33. package/dist/adapters/processRegistry.d.ts.map +1 -1
  34. package/dist/adapters/processRegistry.js +8 -0
  35. package/dist/adapters/processRegistry.js.map +1 -1
  36. package/dist/adapters/types.d.ts +2 -0
  37. package/dist/adapters/types.d.ts.map +1 -1
  38. package/dist/agents/pairPipeline.d.ts +10 -139
  39. package/dist/agents/pairPipeline.d.ts.map +1 -1
  40. package/dist/agents/pairPipeline.js +161 -37
  41. package/dist/agents/pairPipeline.js.map +1 -1
  42. package/dist/agents/pairPipelineTypes.d.ts +111 -0
  43. package/dist/agents/pairPipelineTypes.d.ts.map +1 -0
  44. package/dist/agents/pairPipelineTypes.js +2 -0
  45. package/dist/agents/pairPipelineTypes.js.map +1 -0
  46. package/dist/agents/pipelineGuards.d.ts.map +1 -1
  47. package/dist/agents/pipelineGuards.js +346 -0
  48. package/dist/agents/pipelineGuards.js.map +1 -1
  49. package/dist/agents/pipelineTaskPrefix.d.ts +3 -0
  50. package/dist/agents/pipelineTaskPrefix.d.ts.map +1 -0
  51. package/dist/agents/pipelineTaskPrefix.js +16 -0
  52. package/dist/agents/pipelineTaskPrefix.js.map +1 -0
  53. package/dist/agents/reflection.d.ts +9 -1
  54. package/dist/agents/reflection.d.ts.map +1 -1
  55. package/dist/agents/reflection.js +27 -1
  56. package/dist/agents/reflection.js.map +1 -1
  57. package/dist/agents/reviewer.d.ts +6 -0
  58. package/dist/agents/reviewer.d.ts.map +1 -1
  59. package/dist/agents/reviewer.js +4 -1
  60. package/dist/agents/reviewer.js.map +1 -1
  61. package/dist/agents/stageErrorClassification.d.ts +16 -0
  62. package/dist/agents/stageErrorClassification.d.ts.map +1 -0
  63. package/dist/agents/stageErrorClassification.js +38 -0
  64. package/dist/agents/stageErrorClassification.js.map +1 -0
  65. package/dist/agents/stageModelResolver.d.ts +8 -0
  66. package/dist/agents/stageModelResolver.d.ts.map +1 -0
  67. package/dist/agents/stageModelResolver.js +22 -0
  68. package/dist/agents/stageModelResolver.js.map +1 -0
  69. package/dist/agents/worker.d.ts +33 -0
  70. package/dist/agents/worker.d.ts.map +1 -1
  71. package/dist/agents/worker.js +59 -4
  72. package/dist/agents/worker.js.map +1 -1
  73. package/dist/agents/workerFanout.d.ts +38 -0
  74. package/dist/agents/workerFanout.d.ts.map +1 -0
  75. package/dist/agents/workerFanout.js +292 -0
  76. package/dist/agents/workerFanout.js.map +1 -0
  77. package/dist/agents/workerFanoutGate.d.ts +48 -0
  78. package/dist/agents/workerFanoutGate.d.ts.map +1 -0
  79. package/dist/agents/workerFanoutGate.js +146 -0
  80. package/dist/agents/workerFanoutGate.js.map +1 -0
  81. package/dist/agents/workerValidationEvidence.d.ts +5 -0
  82. package/dist/agents/workerValidationEvidence.d.ts.map +1 -0
  83. package/dist/agents/workerValidationEvidence.js +75 -0
  84. package/dist/agents/workerValidationEvidence.js.map +1 -0
  85. package/dist/automation/autonomousRunner.d.ts +26 -6
  86. package/dist/automation/autonomousRunner.d.ts.map +1 -1
  87. package/dist/automation/autonomousRunner.js +243 -45
  88. package/dist/automation/autonomousRunner.js.map +1 -1
  89. package/dist/automation/backlogGrooming.d.ts +52 -0
  90. package/dist/automation/backlogGrooming.d.ts.map +1 -0
  91. package/dist/automation/backlogGrooming.js +231 -0
  92. package/dist/automation/backlogGrooming.js.map +1 -0
  93. package/dist/automation/runnerExecution.d.ts +1 -1
  94. package/dist/automation/runnerExecution.d.ts.map +1 -1
  95. package/dist/automation/runnerExecution.js +31 -9
  96. package/dist/automation/runnerExecution.js.map +1 -1
  97. package/dist/automation/runnerState.d.ts +9 -0
  98. package/dist/automation/runnerState.d.ts.map +1 -1
  99. package/dist/automation/runnerState.js +17 -0
  100. package/dist/automation/runnerState.js.map +1 -1
  101. package/dist/automation/runnerTypes.d.ts +3 -1
  102. package/dist/automation/runnerTypes.d.ts.map +1 -1
  103. package/dist/automation/taskSource.d.ts +6 -3
  104. package/dist/automation/taskSource.d.ts.map +1 -1
  105. package/dist/automation/taskSource.js +5 -0
  106. package/dist/automation/taskSource.js.map +1 -1
  107. package/dist/cli/daemon.d.ts +20 -3
  108. package/dist/cli/daemon.d.ts.map +1 -1
  109. package/dist/cli/daemon.js +42 -2
  110. package/dist/cli/daemon.js.map +1 -1
  111. package/dist/cli/fixBoard.d.ts +14 -0
  112. package/dist/cli/fixBoard.d.ts.map +1 -0
  113. package/dist/cli/fixBoard.js +32 -0
  114. package/dist/cli/fixBoard.js.map +1 -0
  115. package/dist/cli/fixCommand.d.ts +8 -0
  116. package/dist/cli/fixCommand.d.ts.map +1 -1
  117. package/dist/cli/fixCommand.js +47 -8
  118. package/dist/cli/fixCommand.js.map +1 -1
  119. package/dist/cli/memoryCommand.d.ts +37 -0
  120. package/dist/cli/memoryCommand.d.ts.map +1 -0
  121. package/dist/cli/memoryCommand.js +156 -0
  122. package/dist/cli/memoryCommand.js.map +1 -0
  123. package/dist/cli/reviewAudit.d.ts +68 -0
  124. package/dist/cli/reviewAudit.d.ts.map +1 -1
  125. package/dist/cli/reviewAudit.js +147 -4
  126. package/dist/cli/reviewAudit.js.map +1 -1
  127. package/dist/cli/reviewMaxCommand.d.ts +2 -0
  128. package/dist/cli/reviewMaxCommand.d.ts.map +1 -1
  129. package/dist/cli/reviewMaxCommand.js +61 -36
  130. package/dist/cli/reviewMaxCommand.js.map +1 -1
  131. package/dist/cli.js +68 -13
  132. package/dist/cli.js.map +1 -1
  133. package/dist/core/config.d.ts +413 -0
  134. package/dist/core/config.d.ts.map +1 -1
  135. package/dist/core/config.js +51 -5
  136. package/dist/core/config.js.map +1 -1
  137. package/dist/core/eventHub.d.ts +22 -0
  138. package/dist/core/eventHub.d.ts.map +1 -1
  139. package/dist/core/eventHub.js +1 -0
  140. package/dist/core/eventHub.js.map +1 -1
  141. package/dist/core/providerOverride.d.ts +9 -0
  142. package/dist/core/providerOverride.d.ts.map +1 -1
  143. package/dist/core/providerOverride.js +12 -0
  144. package/dist/core/providerOverride.js.map +1 -1
  145. package/dist/core/service.d.ts.map +1 -1
  146. package/dist/core/service.js +8 -2
  147. package/dist/core/service.js.map +1 -1
  148. package/dist/core/types.d.ts +61 -1
  149. package/dist/core/types.d.ts.map +1 -1
  150. package/dist/linear/linear.d.ts +2 -1
  151. package/dist/linear/linear.d.ts.map +1 -1
  152. package/dist/linear/linear.js +16 -6
  153. package/dist/linear/linear.js.map +1 -1
  154. package/dist/locale/prompts/en.d.ts.map +1 -1
  155. package/dist/locale/prompts/en.js +11 -1
  156. package/dist/locale/prompts/en.js.map +1 -1
  157. package/dist/locale/prompts/ko.d.ts.map +1 -1
  158. package/dist/locale/prompts/ko.js +11 -1
  159. package/dist/locale/prompts/ko.js.map +1 -1
  160. package/dist/memory/compaction.d.ts +2 -2
  161. package/dist/memory/compaction.d.ts.map +1 -1
  162. package/dist/memory/compaction.js +22 -17
  163. package/dist/memory/compaction.js.map +1 -1
  164. package/dist/memory/index.d.ts +3 -3
  165. package/dist/memory/index.js +3 -3
  166. package/dist/memory/memoryCore.d.ts +15 -27
  167. package/dist/memory/memoryCore.d.ts.map +1 -1
  168. package/dist/memory/memoryCore.js +112 -97
  169. package/dist/memory/memoryCore.js.map +1 -1
  170. package/dist/memory/memoryFilters.d.ts +8 -0
  171. package/dist/memory/memoryFilters.d.ts.map +1 -0
  172. package/dist/memory/memoryFilters.js +26 -0
  173. package/dist/memory/memoryFilters.js.map +1 -0
  174. package/dist/memory/memoryOps.d.ts +7 -23
  175. package/dist/memory/memoryOps.d.ts.map +1 -1
  176. package/dist/memory/memoryOps.js +49 -109
  177. package/dist/memory/memoryOps.js.map +1 -1
  178. package/dist/memory/repoKnowledge.d.ts.map +1 -1
  179. package/dist/memory/repoKnowledge.js +48 -3
  180. package/dist/memory/repoKnowledge.js.map +1 -1
  181. package/dist/orchestration/conflictDetector.d.ts.map +1 -1
  182. package/dist/orchestration/conflictDetector.js +26 -7
  183. package/dist/orchestration/conflictDetector.js.map +1 -1
  184. package/dist/orchestration/decisionEngine.d.ts +1 -0
  185. package/dist/orchestration/decisionEngine.d.ts.map +1 -1
  186. package/dist/orchestration/decisionEngine.js.map +1 -1
  187. package/dist/orchestration/taskScheduler.d.ts +5 -0
  188. package/dist/orchestration/taskScheduler.d.ts.map +1 -1
  189. package/dist/orchestration/taskScheduler.js +36 -12
  190. package/dist/orchestration/taskScheduler.js.map +1 -1
  191. package/dist/registry/memoryBridge.js +4 -4
  192. package/dist/registry/memoryBridge.js.map +1 -1
  193. package/dist/support/chatMemory.js +1 -1
  194. package/dist/support/chatMemory.js.map +1 -1
  195. package/dist/support/dashboardHtml.d.ts +1 -1
  196. package/dist/support/dashboardHtml.d.ts.map +1 -1
  197. package/dist/support/dashboardHtml.js +12 -0
  198. package/dist/support/dashboardHtml.js.map +1 -1
  199. package/dist/support/gitTracker.d.ts +30 -3
  200. package/dist/support/gitTracker.d.ts.map +1 -1
  201. package/dist/support/gitTracker.js +108 -29
  202. package/dist/support/gitTracker.js.map +1 -1
  203. package/dist/support/repoMetadata.d.ts +10 -0
  204. package/dist/support/repoMetadata.d.ts.map +1 -1
  205. package/dist/support/repoMetadata.js +31 -0
  206. package/dist/support/repoMetadata.js.map +1 -1
  207. package/dist/support/updateNotifier.d.ts +12 -0
  208. package/dist/support/updateNotifier.d.ts.map +1 -1
  209. package/dist/support/updateNotifier.js +71 -0
  210. package/dist/support/updateNotifier.js.map +1 -1
  211. package/dist/support/web.d.ts.map +1 -1
  212. package/dist/support/web.js +5 -3
  213. package/dist/support/web.js.map +1 -1
  214. package/dist/support/worktreeManager.d.ts +33 -0
  215. package/dist/support/worktreeManager.d.ts.map +1 -1
  216. package/dist/support/worktreeManager.js +172 -2
  217. package/dist/support/worktreeManager.js.map +1 -1
  218. package/dist/tui/App.js +1 -1
  219. package/dist/tui/App.js.map +1 -1
  220. package/dist/tui/components/AuditBoard.d.ts +3 -2
  221. package/dist/tui/components/AuditBoard.d.ts.map +1 -1
  222. package/dist/tui/components/AuditBoard.js +13 -4
  223. package/dist/tui/components/AuditBoard.js.map +1 -1
  224. package/dist/tui/components/DataTable.d.ts +3 -1
  225. package/dist/tui/components/DataTable.d.ts.map +1 -1
  226. package/dist/tui/components/DataTable.js +22 -5
  227. package/dist/tui/components/DataTable.js.map +1 -1
  228. package/dist/tui/components/StageTimeline.d.ts +1 -1
  229. package/dist/tui/components/StageTimeline.d.ts.map +1 -1
  230. package/dist/tui/components/StageTimeline.js +5 -2
  231. package/dist/tui/components/StageTimeline.js.map +1 -1
  232. package/dist/tui/components/SubagentTree.d.ts +6 -6
  233. package/dist/tui/components/SubagentTree.d.ts.map +1 -1
  234. package/dist/tui/components/SubagentTree.js +33 -5
  235. package/dist/tui/components/SubagentTree.js.map +1 -1
  236. package/dist/tui/eventCoalescer.d.ts +29 -0
  237. package/dist/tui/eventCoalescer.d.ts.map +1 -0
  238. package/dist/tui/eventCoalescer.js +56 -0
  239. package/dist/tui/eventCoalescer.js.map +1 -0
  240. package/dist/tui/hooks/usePipelineEvents.d.ts +1 -1
  241. package/dist/tui/hooks/usePipelineEvents.d.ts.map +1 -1
  242. package/dist/tui/hooks/usePipelineEvents.js +18 -4
  243. package/dist/tui/hooks/usePipelineEvents.js.map +1 -1
  244. package/dist/tui/monitorRows.d.ts +41 -2
  245. package/dist/tui/monitorRows.d.ts.map +1 -1
  246. package/dist/tui/monitorRows.js +86 -7
  247. package/dist/tui/monitorRows.js.map +1 -1
  248. package/dist/tui/panels/MonitorPanel.d.ts +2 -1
  249. package/dist/tui/panels/MonitorPanel.d.ts.map +1 -1
  250. package/dist/tui/panels/MonitorPanel.js +2 -2
  251. package/dist/tui/panels/MonitorPanel.js.map +1 -1
  252. package/dist/tui/panels/PipelinePanel.d.ts.map +1 -1
  253. package/dist/tui/panels/PipelinePanel.js +6 -1
  254. package/dist/tui/panels/PipelinePanel.js.map +1 -1
  255. package/dist/tui/pipelineEvents.d.ts +14 -0
  256. package/dist/tui/pipelineEvents.d.ts.map +1 -1
  257. package/dist/tui/pipelineEvents.js +89 -1
  258. package/dist/tui/pipelineEvents.js.map +1 -1
  259. package/dist/tui/subagentTree.d.ts +26 -3
  260. package/dist/tui/subagentTree.d.ts.map +1 -1
  261. package/dist/tui/subagentTree.js +89 -14
  262. package/dist/tui/subagentTree.js.map +1 -1
  263. package/package.json +1 -1
@@ -4,10 +4,13 @@ import type { AdapterName } from '../adapters/types.js';
4
4
  export { setNotifier, setTaskSource } from './runnerExecution.js';
5
5
  export type { AutonomousConfig, RunnerState } from './runnerTypes.js';
6
6
  export type { ProjectInfo } from './runnerState.js';
7
+ export declare function decisionSelectionBudget(availableSlots: number, candidateCount: number): number;
7
8
  export declare class AutonomousRunner {
8
9
  private config;
9
10
  private engine;
10
11
  private scheduler;
12
+ /** Adapter default-model cache for the dashboard PAIR bar (INT-2393). */
13
+ private defaultModelCache;
11
14
  private cronJob;
12
15
  private state;
13
16
  private _heartbeatRunning;
@@ -30,6 +33,9 @@ export declare class AutonomousRunner {
30
33
  * untouched stays the legacy "run all allowed projects" fallback. (INT-2207)
31
34
  */
32
35
  private shouldFilterByEnabled;
36
+ private sameProjectCandidateCap;
37
+ private currentProjectLoad;
38
+ private canQueueProjectCandidate;
33
39
  /** Persist the project selection so it survives a restart. No-op under dryRun
34
40
  * (tests) to avoid touching the real ~/.openswarm. (INT-2208) */
35
41
  private persistSelection;
@@ -41,9 +47,11 @@ export declare class AutonomousRunner {
41
47
  private completedTaskIds;
42
48
  private failedTaskCounts;
43
49
  private failedTaskRetryTimes;
50
+ private lastFailureDetails;
44
51
  private static readonly MAX_RETRY_COUNT;
45
52
  private rateLimitUntil;
46
53
  private unresolvableIssueIds;
54
+ private lastBacklogGroomingAt;
47
55
  private get taskStateRef();
48
56
  private loadTaskState;
49
57
  private saveTaskState;
@@ -78,8 +86,20 @@ export declare class AutonomousRunner {
78
86
  * and stay silent while the summary is identical to the previous heartbeat.
79
87
  */
80
88
  private syslogSkipSummary;
89
+ private groupTasksForGrooming;
90
+ private maybeRunBacklogGrooming;
81
91
  heartbeat(): Promise<void>;
82
92
  private heartbeatParallel;
93
+ private resolveRunnableCandidates;
94
+ private detectSafeCandidateIds;
95
+ /**
96
+ * Re-attempt of a previously failed/rejected issue: carry the last failure
97
+ * feedback into the run so the worker's first iteration addresses it instead
98
+ * of repeating the same mistake blind (INT-2474). Called on BOTH execution
99
+ * paths — parallel enqueue and the serial (maxConcurrentTasks=1) heartbeat.
100
+ */
101
+ private attachPriorFeedback;
102
+ private enqueueCandidate;
83
103
  /** Execute task in pair mode */
84
104
  private executeTaskPairMode;
85
105
  private getExecCtx;
@@ -110,29 +130,29 @@ export declare class AutonomousRunner {
110
130
  };
111
131
  getTurboMode(): boolean;
112
132
  setTurboMode(enabled: boolean): void;
113
- getAdapterSummary(): {
133
+ getAdapterSummary(): Promise<{
114
134
  defaultAdapter: "codex" | "local" | "codex-responses" | "gpt" | "lmstudio" | "openrouter" | "claude";
115
135
  worker: {
116
- adapter: "codex" | "local" | "codex-responses" | "gpt" | "lmstudio" | "openrouter" | "claude";
136
+ adapter: import("../core/types.js").AgentAdapterName;
117
137
  model: string | undefined;
118
138
  enabled: boolean;
119
139
  };
120
140
  reviewer: {
121
- adapter: "codex" | "local" | "codex-responses" | "gpt" | "lmstudio" | "openrouter" | "claude";
141
+ adapter: import("../core/types.js").AgentAdapterName;
122
142
  model: string | undefined;
123
143
  enabled: boolean;
124
144
  };
125
145
  tester: {
126
- adapter: "codex" | "local" | "codex-responses" | "gpt" | "lmstudio" | "openrouter" | "claude";
146
+ adapter: import("../core/types.js").AgentAdapterName;
127
147
  model: string | undefined;
128
148
  enabled: boolean;
129
149
  } | undefined;
130
150
  documenter: {
131
- adapter: "codex" | "local" | "codex-responses" | "gpt" | "lmstudio" | "openrouter" | "claude";
151
+ adapter: import("../core/types.js").AgentAdapterName;
132
152
  model: string | undefined;
133
153
  enabled: boolean;
134
154
  } | undefined;
135
- };
155
+ }>;
136
156
  switchProvider(adapter: AdapterName): void;
137
157
  pauseScheduler(): void;
138
158
  resumeScheduler(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"autonomousRunner.d.ts","sourceRoot":"","sources":["../../src/automation/autonomousRunner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"autonomousRunner.d.ts","sourceRoot":"","sources":["../../src/automation/autonomousRunner.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,13 @@
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, loadProjectSelection, saveProjectSelection, } from './runnerState.js';
4
+ import { loadTaskState, saveTaskState, buildProjectsInfo, appendPipelineHistory, getPipelineHistory, incrementRejection, clearRejection, isRejectionLimitReached, canRetryNow, setRetryTime, clearRetryTime, formatRetryTime, getDailyPaceInfo, recordProjectCompletion, loadProjectSelection, saveProjectSelection, recordLastFailureDetail, } from './runnerState.js';
5
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';
9
9
  import { updateProjectAfterTask } from '../linear/projectUpdater.js';
10
- import { initScheduler } from '../orchestration/taskScheduler.js';
10
+ import { initScheduler, normalizeProjectPath } from '../orchestration/taskScheduler.js';
11
11
  import { formatPipelineResultEmbed, } from '../agents/pairPipeline.js';
12
12
  import * as planner from '../support/planner.js';
13
13
  import * as execution from './runnerExecution.js';
@@ -22,13 +22,25 @@ import { STUCK_LABEL } from '../linear/index.js';
22
22
  import { refreshGraph, toProjectSlug } from '../knowledge/index.js';
23
23
  import { checkAllMonitors, getActiveMonitors } from './longRunningMonitor.js';
24
24
  import { detectFileConflicts } from '../orchestration/conflictDetector.js';
25
+ import { resolveAdapterDefaultModel } from '../agents/stageModelResolver.js';
26
+ import { applyBacklogGrooming, filterGroomableTasks, runBacklogGroomingPlanner, summarizeGroomingDecision, } from './backlogGrooming.js';
25
27
  // Re-export types and integration setters (used by service.ts)
26
28
  export { setNotifier, setTaskSource } from './runnerExecution.js';
27
29
  let runnerInstance = null;
30
+ const DECISION_SELECTION_OVERSAMPLE = 3;
31
+ export function decisionSelectionBudget(availableSlots, candidateCount) {
32
+ const slots = Math.max(0, Math.floor(availableSlots));
33
+ const candidates = Math.max(0, Math.floor(candidateCount));
34
+ if (slots === 0 || candidates === 0)
35
+ return 0;
36
+ return Math.min(candidates, Math.max(slots, slots * DECISION_SELECTION_OVERSAMPLE));
37
+ }
28
38
  export class AutonomousRunner {
29
39
  config;
30
40
  engine;
31
41
  scheduler;
42
+ /** Adapter default-model cache for the dashboard PAIR bar (INT-2393). */
43
+ defaultModelCache = new Map();
32
44
  cronJob = null;
33
45
  state = {
34
46
  isRunning: false,
@@ -80,6 +92,30 @@ export class AutonomousRunner {
80
92
  shouldFilterByEnabled() {
81
93
  return this.projectSelectionTouched || this.enabledProjects.size > 0;
82
94
  }
95
+ sameProjectCandidateCap() {
96
+ const sameProjectParallel = (this.config.allowSameProjectConcurrent ?? true) && (this.config.worktreeMode ?? false);
97
+ if (!sameProjectParallel || this.config.maxConcurrentPerProject == null)
98
+ return null;
99
+ const cap = Math.floor(this.config.maxConcurrentPerProject);
100
+ const maxConcurrent = this.config.maxConcurrentTasks ?? 1;
101
+ return Math.max(1, Math.min(cap, maxConcurrent));
102
+ }
103
+ currentProjectLoad(projectPath) {
104
+ const target = normalizeProjectPath(projectPath);
105
+ const queued = this.scheduler.getQueuedTasks()
106
+ .filter(task => normalizeProjectPath(task.projectPath) === target)
107
+ .length;
108
+ const running = this.scheduler.getRunningTasks()
109
+ .filter(task => normalizeProjectPath(task.projectPath) === target)
110
+ .length;
111
+ return queued + running;
112
+ }
113
+ canQueueProjectCandidate(projectPath) {
114
+ const cap = this.sameProjectCandidateCap();
115
+ if (cap == null)
116
+ return true;
117
+ return this.currentProjectLoad(projectPath) < cap;
118
+ }
83
119
  /** Persist the project selection so it survives a restart. No-op under dryRun
84
120
  * (tests) to avoid touching the real ~/.openswarm. (INT-2208) */
85
121
  persistSelection() {
@@ -99,6 +135,10 @@ export class AutonomousRunner {
99
135
  completedTaskIds = new Set();
100
136
  failedTaskCounts = new Map();
101
137
  failedTaskRetryTimes = new Map(); // issueId → next retry timestamp (ms)
138
+ // Last failure feedback per issue — re-injected into the next attempt's worker
139
+ // prompt so re-picked tasks don't restart blind and repeat the same mistake
140
+ // the reviewer already called out (INT-2474). Persisted; cleared on success.
141
+ lastFailureDetails = new Map();
102
142
  static MAX_RETRY_COUNT = 4; // Increased from 2 to allow more retries with backoff
103
143
  // Rate-limit hold: epoch ms until which all task execution is paused.
104
144
  // Set when any adapter returns a 429 / usage_limit_reached response (INT-1906).
@@ -107,11 +147,13 @@ export class AutonomousRunner {
107
147
  // the first resolve failure so they aren't re-picked every heartbeat (which
108
148
  // starved other actionable tasks — they were top-priority but never runnable). (INT-1875)
109
149
  unresolvableIssueIds = new Set();
150
+ lastBacklogGroomingAt = 0;
110
151
  get taskStateRef() {
111
152
  return {
112
153
  completedTaskIds: this.completedTaskIds,
113
154
  failedTaskCounts: this.failedTaskCounts,
114
155
  failedTaskRetryTimes: this.failedTaskRetryTimes,
156
+ lastFailureDetails: this.lastFailureDetails,
115
157
  };
116
158
  }
117
159
  loadTaskState() {
@@ -158,6 +200,7 @@ export class AutonomousRunner {
158
200
  this.scheduler = initScheduler({
159
201
  maxConcurrent: config.maxConcurrentTasks ?? 1,
160
202
  allowSameProjectConcurrent: config.allowSameProjectConcurrent ?? true,
203
+ maxConcurrentPerProject: config.maxConcurrentPerProject,
161
204
  worktreeMode: config.worktreeMode ?? false,
162
205
  });
163
206
  // Set up scheduler event handling
@@ -180,6 +223,7 @@ export class AutonomousRunner {
180
223
  this.completedTaskIds.add(task.issueId);
181
224
  clearRejection(task.issueId); // Clear rejection count on success
182
225
  clearRetryTime(task.issueId, this.failedTaskRetryTimes); // Clear retry backoff time
226
+ this.lastFailureDetails.delete(task.issueId); // Stale feedback must not haunt future work
183
227
  this.saveTaskState();
184
228
  // Track project-level pace (5h rolling window)
185
229
  const projectName = task.linearProject?.name ?? 'unknown';
@@ -297,6 +341,8 @@ export class AutonomousRunner {
297
341
  if (task.issueId && result.finalStatus === 'rejected') {
298
342
  const feedback = result.reviewResult?.feedback || 'No feedback provided';
299
343
  const rejectionCount = incrementRejection(task.issueId, feedback);
344
+ // Persist for prompt injection on the retry (same mechanism as failures).
345
+ recordLastFailureDetail(this.taskStateRef, task.issueId, feedback);
300
346
  // Store the rejection reason as a repo pitfall (constraint) — blocks repeating the same mistake
301
347
  if (result.taskContext?.projectPath) {
302
348
  await recordTaskOutcome(result.taskContext.projectPath, {
@@ -344,17 +390,20 @@ export class AutonomousRunner {
344
390
  if (task.issueId) {
345
391
  const count = (this.failedTaskCounts.get(task.issueId) ?? 0) + 1;
346
392
  this.failedTaskCounts.set(task.issueId, count);
393
+ // Surface the underlying failure (worker CLI error / review feedback) so the
394
+ // stuck comment is actionable instead of an opaque "failed N times", AND
395
+ // persist it so the NEXT attempt starts with this feedback instead of
396
+ // blind — re-picked tasks used to repeat the exact same mistake (INT-2474).
397
+ const failureDetail = result.workerResult?.error
398
+ || result.reviewResult?.feedback
399
+ || 'No error detail captured (worker produced no output).';
400
+ recordLastFailureDetail(this.taskStateRef, task.issueId, failureDetail);
347
401
  if (count >= AutonomousRunner.MAX_RETRY_COUNT) {
348
402
  // Max retries exceeded - permanently block
349
403
  this.completedTaskIds.add(task.issueId); // Prevent re-selection
350
404
  clearRetryTime(task.issueId, this.failedTaskRetryTimes); // Clear retry time
351
405
  this.saveTaskState();
352
406
  console.log(`[Scheduler] Task failure count: ${count}/${AutonomousRunner.MAX_RETRY_COUNT} for ${taskCtx} — STUCK`);
353
- // Surface the underlying failure (worker CLI error / review feedback) so the
354
- // stuck comment is actionable instead of an opaque "failed N times".
355
- const failureDetail = result.workerResult?.error
356
- || result.reviewResult?.feedback
357
- || 'No error detail captured (worker produced no output).';
358
407
  try {
359
408
  await execution.syncFailureState(task, `Autonomous execution failed ${count} times: ${failureDetail}`);
360
409
  await getTaskSource()?.logStuck(task.issueId, 'autonomous-runner', `Autonomous execution failed ${count} times in a row — automatic retries exhausted.\n\n` +
@@ -655,6 +704,87 @@ export class AutonomousRunner {
655
704
  for (const line of lines)
656
705
  this.syslog(line);
657
706
  }
707
+ async groupTasksForGrooming(tasks) {
708
+ const byProjectId = new Map();
709
+ for (const repoPath of this.config.allowedProjects) {
710
+ try {
711
+ const resolvedPath = repoPath.replace('~', process.env.HOME || '');
712
+ const meta = await loadRepoMetadata(resolvedPath);
713
+ if (meta?.linear?.projectId)
714
+ byProjectId.set(meta.linear.projectId, resolvedPath);
715
+ }
716
+ catch {
717
+ // Grooming is advisory; unreadable metadata should not block normal work.
718
+ }
719
+ }
720
+ const groups = new Map();
721
+ for (const task of tasks) {
722
+ const projectPath = task.projectPath
723
+ ?? (task.linearProject?.id ? byProjectId.get(task.linearProject.id) : undefined)
724
+ ?? undefined;
725
+ if (!projectPath)
726
+ continue;
727
+ const list = groups.get(projectPath) ?? [];
728
+ list.push(task);
729
+ groups.set(projectPath, list);
730
+ }
731
+ return groups;
732
+ }
733
+ async maybeRunBacklogGrooming(tasks) {
734
+ const cfg = this.config.backlogGrooming;
735
+ if (!cfg?.enabled)
736
+ return tasks;
737
+ const cadenceMs = Math.max(1, cfg.cadenceHours ?? 24) * 60 * 60 * 1000;
738
+ const now = Date.now();
739
+ if (this.lastBacklogGroomingAt && now - this.lastBacklogGroomingAt < cadenceMs)
740
+ return tasks;
741
+ const source = getTaskSource();
742
+ if (!source) {
743
+ this.syslog('⚠ Backlog grooming skipped: no task source');
744
+ return tasks;
745
+ }
746
+ const groomable = filterGroomableTasks(tasks);
747
+ if (groomable.length === 0)
748
+ return tasks;
749
+ const mode = cfg.mode ?? 'comment';
750
+ const moved = new Set();
751
+ const groups = await this.groupTasksForGrooming(groomable);
752
+ if (groups.size === 0) {
753
+ this.syslog('⚠ Backlog grooming skipped: no mapped project paths');
754
+ return tasks;
755
+ }
756
+ let successfulPlannerRuns = 0;
757
+ for (const [projectPath, groupTasks] of groups) {
758
+ this.syslog(`⟳ Backlog grooming: ${groupTasks.length} issue(s) in ${projectPath.split('/').pop()}`);
759
+ const result = await runBacklogGroomingPlanner({
760
+ tasks: groupTasks,
761
+ projectPath,
762
+ projectName: groupTasks[0]?.linearProject?.name,
763
+ model: cfg.plannerModel ?? this.config.plannerModel,
764
+ timeoutMs: cfg.plannerTimeoutMs ?? this.config.plannerTimeoutMs,
765
+ maxIssues: cfg.maxIssues,
766
+ onLog: (line) => broadcastEvent({ type: 'log', data: { taskId: 'system', stage: 'groom', line } }),
767
+ });
768
+ if (!result.success) {
769
+ this.syslog(`⚠ Backlog grooming failed: ${result.error ?? 'unknown error'}`);
770
+ continue;
771
+ }
772
+ successfulPlannerRuns++;
773
+ const validIssueIds = new Set(groupTasks.map(task => task.issueId || task.id));
774
+ const applied = await applyBacklogGrooming(source, result, mode, validIssueIds);
775
+ for (const issueId of applied.movedIssueIds)
776
+ moved.add(issueId);
777
+ this.syslog(`✓ Backlog grooming: ${result.decisions.length} decision(s), ${applied.commented} comment(s), ${applied.failedComments} comment failure(s), ${applied.updatedDescriptions} description update(s), ${applied.moved} moved, ${applied.skippedUnknown} unknown skipped`);
778
+ for (const decision of result.decisions.slice(0, 5)) {
779
+ this.syslog(` ${summarizeGroomingDecision(decision)}`);
780
+ }
781
+ }
782
+ if (successfulPlannerRuns > 0)
783
+ this.lastBacklogGroomingAt = now;
784
+ if (moved.size === 0)
785
+ return tasks;
786
+ return tasks.filter(task => !moved.has(task.issueId || task.id));
787
+ }
658
788
  async heartbeat() {
659
789
  if (this._heartbeatRunning) {
660
790
  console.log('[AutonomousRunner] Heartbeat already running, skipping');
@@ -721,13 +851,19 @@ export class AutonomousRunner {
721
851
  await reportToDiscord(`⚠️ Linear fetch failed: ${fetchResult.error}`);
722
852
  return;
723
853
  }
724
- const tasks = fetchResult.tasks;
854
+ let tasks = fetchResult.tasks;
725
855
  if (tasks.length === 0) {
726
856
  this.syslog('— No tasks in backlog');
727
857
  return;
728
858
  }
729
859
  this.lastFetchedTasks = tasks;
730
860
  this.syslog(`✓ Found ${tasks.length} tasks from Linear`);
861
+ tasks = await this.maybeRunBacklogGrooming(tasks);
862
+ this.lastFetchedTasks = tasks;
863
+ if (tasks.length === 0) {
864
+ this.syslog('— No executable tasks after backlog grooming');
865
+ return;
866
+ }
731
867
  // Filter out completed and over-retried tasks
732
868
  const filteredTasks = this.filterAlreadyProcessed(tasks);
733
869
  if (filteredTasks.length === 0) {
@@ -836,20 +972,67 @@ export class AutonomousRunner {
836
972
  }
837
973
  this.syslog(` Tasks: ${tasksForEngine.length} enabled-or-uncached / ${executableTasks.length} executable / ${tasks.length} total`);
838
974
  }
839
- // Get validated task list from DecisionEngine
840
- this.syslog('⟳ Decision Engine evaluating tasks...');
841
- const decision = await this.engine.heartbeatMultiple(tasksForEngine, maxSlots, [] // No project exclusion — worktree mode isolates each task
842
- );
843
- console.log(`[AutonomousRunner] Decision: ${decision.action} — ${decision.reason} (${decision.tasks?.length ?? 0} tasks)`);
844
- if (decision.action === 'skip' || decision.action === 'defer') {
845
- this.syslog(`→ Decision: ${decision.action} — ${decision.reason}`);
846
- return;
847
- }
848
- // Add validated tasks to queue (with conflict detection)
849
975
  let enqueuedCount = 0;
850
- // Pre-filter: resolve project paths and skip invalid tasks
976
+ let skippedCount = 0;
977
+ const consideredTaskIds = new Set();
978
+ let pass = 0;
979
+ while (enqueuedCount < maxSlots) {
980
+ const remainingSlots = maxSlots - enqueuedCount;
981
+ const selectableTasks = tasksForEngine.filter(task => !consideredTaskIds.has(task.id));
982
+ const selectionBudget = decisionSelectionBudget(remainingSlots, selectableTasks.length);
983
+ if (selectionBudget === 0)
984
+ break;
985
+ this.syslog(pass === 0
986
+ ? '⟳ Decision Engine evaluating tasks...'
987
+ : `⟳ Backfill pass (${remainingSlots} slot(s) open)...`);
988
+ const decision = await this.engine.heartbeatMultiple(selectableTasks, selectionBudget, [] // No project exclusion — worktree mode isolates each task
989
+ );
990
+ console.log(`[AutonomousRunner] Decision: ${decision.action} — ${decision.reason} (${decision.tasks?.length ?? 0} tasks)`);
991
+ skippedCount += decision.skippedCount ?? 0;
992
+ if (decision.action === 'skip' || decision.action === 'defer') {
993
+ this.syslog(`→ Decision: ${decision.action} — ${decision.reason}`);
994
+ break;
995
+ }
996
+ for (const { task } of decision.tasks) {
997
+ consideredTaskIds.add(task.id);
998
+ }
999
+ const candidates = await this.resolveRunnableCandidates(decision.tasks);
1000
+ const safeTasks = await this.detectSafeCandidateIds(candidates);
1001
+ const before = enqueuedCount;
1002
+ for (const { task, projectPath } of candidates) {
1003
+ if (enqueuedCount >= maxSlots)
1004
+ break;
1005
+ if (!safeTasks.has(task.id))
1006
+ continue;
1007
+ if (!this.canQueueProjectCandidate(projectPath)) {
1008
+ this.syslog(` Project cap reached: ${projectPath}`);
1009
+ continue;
1010
+ }
1011
+ this.enqueueCandidate(task, projectPath);
1012
+ enqueuedCount++;
1013
+ }
1014
+ pass++;
1015
+ if (enqueuedCount >= maxSlots)
1016
+ break;
1017
+ if (decision.tasks.length === 0)
1018
+ break;
1019
+ if (selectionBudget >= selectableTasks.length)
1020
+ break;
1021
+ if (enqueuedCount === before)
1022
+ continue;
1023
+ }
1024
+ if (enqueuedCount === 0 && skippedCount > 0) {
1025
+ this.syslog(`— No new tasks queued (skipped: ${skippedCount})`);
1026
+ }
1027
+ else {
1028
+ this.syslog(`✓ Enqueued ${enqueuedCount} task(s) | skipped: ${skippedCount}`);
1029
+ }
1030
+ // Execute tasks
1031
+ await this.runAvailableTasks();
1032
+ }
1033
+ async resolveRunnableCandidates(decisionTasks) {
851
1034
  const candidates = [];
852
- for (const { task } of decision.tasks) {
1035
+ for (const { task } of decisionTasks) {
853
1036
  if (this.scheduler.isTaskQueued(task.id) || this.scheduler.isTaskRunning(task.id)) {
854
1037
  this.syslog(` Skip (already queued/running): ${task.issueIdentifier || task.id.slice(0, 8)} ${task.title}`);
855
1038
  continue;
@@ -877,6 +1060,9 @@ export class AutonomousRunner {
877
1060
  // rate limiter only. Completions are still recorded for cost telemetry.
878
1061
  candidates.push({ task, projectPath });
879
1062
  }
1063
+ return candidates;
1064
+ }
1065
+ async detectSafeCandidateIds(candidates) {
880
1066
  // Group candidates by projectPath for conflict detection
881
1067
  const byProject = new Map();
882
1068
  for (const c of candidates) {
@@ -916,30 +1102,36 @@ export class AutonomousRunner {
916
1102
  safeTasks.add(c.task.id);
917
1103
  }
918
1104
  }
919
- // Enqueue safe tasks only
920
- for (const { task, projectPath } of candidates) {
921
- if (!safeTasks.has(task.id))
922
- continue;
923
- this.scheduler.enqueue(task, projectPath);
924
- broadcastEvent({ type: 'task:queued', data: { taskId: task.id, title: task.title, projectPath, issueIdentifier: task.issueIdentifier } });
925
- this.syslog(`✓ Queued: ${task.issueIdentifier || ''} ${task.title} ${projectPath.split('/').slice(-2).join('/')}`);
926
- enqueuedCount++;
927
- // Claim the task immediately: set Linear to 'In Progress' so restarts don't re-queue it
928
- if (task.issueId) {
929
- getTaskSource()?.updateState(task.issueId, 'In Progress').catch((err) => console.warn(`[AutonomousRunner] Failed to claim issue ${task.issueIdentifier}:`, err));
930
- }
931
- }
932
- if (enqueuedCount === 0 && decision.skippedCount > 0) {
933
- this.syslog(`— No new tasks queued (skipped: ${decision.skippedCount})`);
934
- }
935
- else {
936
- this.syslog(`✓ Enqueued ${enqueuedCount} task(s) | skipped: ${decision.skippedCount}`);
1105
+ return safeTasks;
1106
+ }
1107
+ /**
1108
+ * Re-attempt of a previously failed/rejected issue: carry the last failure
1109
+ * feedback into the run so the worker's first iteration addresses it instead
1110
+ * of repeating the same mistake blind (INT-2474). Called on BOTH execution
1111
+ * paths parallel enqueue and the serial (maxConcurrentTasks=1) heartbeat.
1112
+ */
1113
+ attachPriorFeedback(task) {
1114
+ if (!task.issueId)
1115
+ return;
1116
+ const prior = this.lastFailureDetails.get(task.issueId);
1117
+ if (prior)
1118
+ task.priorAttemptFeedback = prior.detail;
1119
+ }
1120
+ enqueueCandidate(task, projectPath) {
1121
+ this.attachPriorFeedback(task);
1122
+ this.scheduler.enqueue(task, projectPath);
1123
+ broadcastEvent({ type: 'task:queued', data: { taskId: task.id, title: task.title, projectPath, issueIdentifier: task.issueIdentifier } });
1124
+ this.syslog(`✓ Queued: ${task.issueIdentifier || ''} ${task.title} → ${projectPath.split('/').slice(-2).join('/')}`);
1125
+ // Claim the task immediately: set Linear to 'In Progress' so restarts don't re-queue it
1126
+ if (task.issueId) {
1127
+ getTaskSource()?.updateState(task.issueId, 'In Progress').catch((err) => console.warn(`[AutonomousRunner] Failed to claim issue ${task.issueIdentifier}:`, err));
937
1128
  }
938
- // Execute tasks
939
- await this.runAvailableTasks();
940
1129
  }
941
1130
  /** Execute task in pair mode */
942
1131
  async executeTaskPairMode(task) {
1132
+ // Serial path (maxConcurrentTasks=1) bypasses enqueueCandidate — attach the
1133
+ // prior-session feedback here too so both paths inject it (INT-2474).
1134
+ this.attachPriorFeedback(task);
943
1135
  // Auto-resolve project path
944
1136
  const projectPath = await this.resolveProjectPath(task);
945
1137
  // Error if project path mapping failed
@@ -1146,19 +1338,25 @@ export class AutonomousRunner {
1146
1338
  broadcastEvent({ type: 'log', data: { taskId: 'system', stage: 'turbo', line: 'TURBO OFF — normal pace resumed' } });
1147
1339
  }
1148
1340
  }
1149
- getAdapterSummary() {
1341
+ async getAdapterSummary() {
1150
1342
  const defaultAdapter = this.config.defaultAdapter ?? 'codex';
1151
1343
  const defaultRoles = this.config.defaultRoles;
1344
+ const workerAdapter = defaultRoles?.worker?.adapter ?? defaultAdapter;
1345
+ const reviewerAdapter = defaultRoles?.reviewer?.adapter ?? defaultAdapter;
1152
1346
  return {
1153
1347
  defaultAdapter,
1154
1348
  worker: {
1155
- adapter: defaultRoles?.worker?.adapter ?? defaultAdapter,
1156
- model: defaultRoles?.worker?.model ?? this.config.workerModel,
1349
+ adapter: workerAdapter,
1350
+ // Resolve the adapter's real default when config omits the model, so the
1351
+ // dashboard's PAIR bar shows what's running instead of "-". (INT-2393)
1352
+ model: defaultRoles?.worker?.model ?? this.config.workerModel
1353
+ ?? await resolveAdapterDefaultModel(workerAdapter, this.defaultModelCache),
1157
1354
  enabled: defaultRoles?.worker?.enabled !== false,
1158
1355
  },
1159
1356
  reviewer: {
1160
- adapter: defaultRoles?.reviewer?.adapter ?? defaultAdapter,
1161
- model: defaultRoles?.reviewer?.model ?? this.config.reviewerModel,
1357
+ adapter: reviewerAdapter,
1358
+ model: defaultRoles?.reviewer?.model ?? this.config.reviewerModel
1359
+ ?? await resolveAdapterDefaultModel(reviewerAdapter, this.defaultModelCache),
1162
1360
  enabled: defaultRoles?.reviewer?.enabled !== false,
1163
1361
  },
1164
1362
  tester: defaultRoles?.tester ? {