@mclawnet/swarm 0.1.13 → 0.1.15

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 (320) hide show
  1. package/dist/__tests__/always-on-activity-reader.test.d.ts +2 -0
  2. package/dist/__tests__/always-on-activity-reader.test.d.ts.map +1 -0
  3. package/dist/__tests__/always-on-activity-reader.test.js +193 -0
  4. package/dist/__tests__/always-on-activity-reader.test.js.map +1 -0
  5. package/dist/__tests__/always-on-config.test.d.ts +2 -0
  6. package/dist/__tests__/always-on-config.test.d.ts.map +1 -0
  7. package/dist/__tests__/always-on-config.test.js +285 -0
  8. package/dist/__tests__/always-on-config.test.js.map +1 -0
  9. package/dist/__tests__/always-on-manager.test.d.ts +2 -0
  10. package/dist/__tests__/always-on-manager.test.d.ts.map +1 -0
  11. package/dist/__tests__/always-on-manager.test.js +797 -0
  12. package/dist/__tests__/always-on-manager.test.js.map +1 -0
  13. package/dist/__tests__/always-on-parity.test.d.ts +2 -0
  14. package/dist/__tests__/always-on-parity.test.d.ts.map +1 -0
  15. package/dist/__tests__/always-on-parity.test.js +20 -0
  16. package/dist/__tests__/always-on-parity.test.js.map +1 -0
  17. package/dist/__tests__/cascade-picker.test.d.ts +2 -0
  18. package/dist/__tests__/cascade-picker.test.d.ts.map +1 -0
  19. package/dist/__tests__/cascade-picker.test.js +122 -0
  20. package/dist/__tests__/cascade-picker.test.js.map +1 -0
  21. package/dist/__tests__/coordinator-shipment.test.d.ts +2 -0
  22. package/dist/__tests__/coordinator-shipment.test.d.ts.map +1 -0
  23. package/dist/__tests__/coordinator-shipment.test.js +280 -0
  24. package/dist/__tests__/coordinator-shipment.test.js.map +1 -0
  25. package/dist/__tests__/coordinator-workspace-recover.test.d.ts +2 -0
  26. package/dist/__tests__/coordinator-workspace-recover.test.d.ts.map +1 -0
  27. package/dist/__tests__/coordinator-workspace-recover.test.js +140 -0
  28. package/dist/__tests__/coordinator-workspace-recover.test.js.map +1 -0
  29. package/dist/__tests__/coordinator-workspace.test.d.ts +2 -0
  30. package/dist/__tests__/coordinator-workspace.test.d.ts.map +1 -0
  31. package/dist/__tests__/coordinator-workspace.test.js +135 -0
  32. package/dist/__tests__/coordinator-workspace.test.js.map +1 -0
  33. package/dist/__tests__/default-runner-epipe.test.d.ts +2 -0
  34. package/dist/__tests__/default-runner-epipe.test.d.ts.map +1 -0
  35. package/dist/__tests__/default-runner-epipe.test.js +43 -0
  36. package/dist/__tests__/default-runner-epipe.test.js.map +1 -0
  37. package/dist/__tests__/discovery-scheduler.test.d.ts +2 -0
  38. package/dist/__tests__/discovery-scheduler.test.d.ts.map +1 -0
  39. package/dist/__tests__/discovery-scheduler.test.js +367 -0
  40. package/dist/__tests__/discovery-scheduler.test.js.map +1 -0
  41. package/dist/__tests__/env-forward-e2e.test.d.ts +2 -0
  42. package/dist/__tests__/env-forward-e2e.test.d.ts.map +1 -0
  43. package/dist/__tests__/env-forward-e2e.test.js +57 -0
  44. package/dist/__tests__/env-forward-e2e.test.js.map +1 -0
  45. package/dist/__tests__/gh-pr-creator.test.d.ts +2 -0
  46. package/dist/__tests__/gh-pr-creator.test.d.ts.map +1 -0
  47. package/dist/__tests__/gh-pr-creator.test.js +107 -0
  48. package/dist/__tests__/gh-pr-creator.test.js.map +1 -0
  49. package/dist/__tests__/git-worktree-provider.test.d.ts +2 -0
  50. package/dist/__tests__/git-worktree-provider.test.d.ts.map +1 -0
  51. package/dist/__tests__/git-worktree-provider.test.js +98 -0
  52. package/dist/__tests__/git-worktree-provider.test.js.map +1 -0
  53. package/dist/__tests__/gitignore-check.test.d.ts +2 -0
  54. package/dist/__tests__/gitignore-check.test.d.ts.map +1 -0
  55. package/dist/__tests__/gitignore-check.test.js +39 -0
  56. package/dist/__tests__/gitignore-check.test.js.map +1 -0
  57. package/dist/__tests__/idea-research-source.test.d.ts +2 -0
  58. package/dist/__tests__/idea-research-source.test.d.ts.map +1 -0
  59. package/dist/__tests__/idea-research-source.test.js +425 -0
  60. package/dist/__tests__/idea-research-source.test.js.map +1 -0
  61. package/dist/__tests__/idea-todo-source.test.d.ts +2 -0
  62. package/dist/__tests__/idea-todo-source.test.d.ts.map +1 -0
  63. package/dist/__tests__/idea-todo-source.test.js +258 -0
  64. package/dist/__tests__/idea-todo-source.test.js.map +1 -0
  65. package/dist/__tests__/introspection-dedupe.test.d.ts +2 -0
  66. package/dist/__tests__/introspection-dedupe.test.d.ts.map +1 -0
  67. package/dist/__tests__/introspection-dedupe.test.js +484 -0
  68. package/dist/__tests__/introspection-dedupe.test.js.map +1 -0
  69. package/dist/__tests__/introspection-source.test.d.ts +2 -0
  70. package/dist/__tests__/introspection-source.test.d.ts.map +1 -0
  71. package/dist/__tests__/introspection-source.test.js +1051 -0
  72. package/dist/__tests__/introspection-source.test.js.map +1 -0
  73. package/dist/__tests__/migration-roles.test.js +1 -22
  74. package/dist/__tests__/migration-roles.test.js.map +1 -1
  75. package/dist/__tests__/reconcile-researching.test.d.ts +2 -0
  76. package/dist/__tests__/reconcile-researching.test.d.ts.map +1 -0
  77. package/dist/__tests__/reconcile-researching.test.js +224 -0
  78. package/dist/__tests__/reconcile-researching.test.js.map +1 -0
  79. package/dist/__tests__/role-loader-preamble-all.test.js +3 -1
  80. package/dist/__tests__/role-loader-preamble-all.test.js.map +1 -1
  81. package/dist/__tests__/role-loader.test.js +95 -0
  82. package/dist/__tests__/role-loader.test.js.map +1 -1
  83. package/dist/__tests__/role-prompt-no-legacy-protocol.test.js +3 -1
  84. package/dist/__tests__/role-prompt-no-legacy-protocol.test.js.map +1 -1
  85. package/dist/__tests__/secret-scrub.test.d.ts +2 -0
  86. package/dist/__tests__/secret-scrub.test.d.ts.map +1 -0
  87. package/dist/__tests__/secret-scrub.test.js +55 -0
  88. package/dist/__tests__/secret-scrub.test.js.map +1 -0
  89. package/dist/__tests__/shipment-actions.test.d.ts +2 -0
  90. package/dist/__tests__/shipment-actions.test.d.ts.map +1 -0
  91. package/dist/__tests__/shipment-actions.test.js +378 -0
  92. package/dist/__tests__/shipment-actions.test.js.map +1 -0
  93. package/dist/__tests__/shipment-persistence.test.d.ts +2 -0
  94. package/dist/__tests__/shipment-persistence.test.d.ts.map +1 -0
  95. package/dist/__tests__/shipment-persistence.test.js +120 -0
  96. package/dist/__tests__/shipment-persistence.test.js.map +1 -0
  97. package/dist/__tests__/shipment-pipeline.test.d.ts +2 -0
  98. package/dist/__tests__/shipment-pipeline.test.d.ts.map +1 -0
  99. package/dist/__tests__/shipment-pipeline.test.js +392 -0
  100. package/dist/__tests__/shipment-pipeline.test.js.map +1 -0
  101. package/dist/__tests__/shipment-report.test.d.ts +2 -0
  102. package/dist/__tests__/shipment-report.test.d.ts.map +1 -0
  103. package/dist/__tests__/shipment-report.test.js +78 -0
  104. package/dist/__tests__/shipment-report.test.js.map +1 -0
  105. package/dist/__tests__/shipment-stdin-integration.test.d.ts +2 -0
  106. package/dist/__tests__/shipment-stdin-integration.test.d.ts.map +1 -0
  107. package/dist/__tests__/shipment-stdin-integration.test.js +49 -0
  108. package/dist/__tests__/shipment-stdin-integration.test.js.map +1 -0
  109. package/dist/__tests__/shipment-type-parity.test.d.ts +2 -0
  110. package/dist/__tests__/shipment-type-parity.test.d.ts.map +1 -0
  111. package/dist/__tests__/shipment-type-parity.test.js +10 -0
  112. package/dist/__tests__/shipment-type-parity.test.js.map +1 -0
  113. package/dist/__tests__/snapshot-copy-provider.test.d.ts +2 -0
  114. package/dist/__tests__/snapshot-copy-provider.test.d.ts.map +1 -0
  115. package/dist/__tests__/snapshot-copy-provider.test.js +88 -0
  116. package/dist/__tests__/snapshot-copy-provider.test.js.map +1 -0
  117. package/dist/__tests__/swarm-coordinator-backend.test.js +153 -0
  118. package/dist/__tests__/swarm-coordinator-backend.test.js.map +1 -1
  119. package/dist/__tests__/swarm-coordinator-complete-intercept.test.d.ts +2 -0
  120. package/dist/__tests__/swarm-coordinator-complete-intercept.test.d.ts.map +1 -0
  121. package/dist/__tests__/swarm-coordinator-complete-intercept.test.js +111 -0
  122. package/dist/__tests__/swarm-coordinator-complete-intercept.test.js.map +1 -0
  123. package/dist/__tests__/task-store-source.test.d.ts +2 -0
  124. package/dist/__tests__/task-store-source.test.d.ts.map +1 -0
  125. package/dist/__tests__/task-store-source.test.js +56 -0
  126. package/dist/__tests__/task-store-source.test.js.map +1 -0
  127. package/dist/__tests__/transport-detect.test.d.ts +2 -0
  128. package/dist/__tests__/transport-detect.test.d.ts.map +1 -0
  129. package/dist/__tests__/transport-detect.test.js +92 -0
  130. package/dist/__tests__/transport-detect.test.js.map +1 -0
  131. package/dist/__tests__/workcycle-runner-cascade.test.d.ts +2 -0
  132. package/dist/__tests__/workcycle-runner-cascade.test.d.ts.map +1 -0
  133. package/dist/__tests__/workcycle-runner-cascade.test.js +203 -0
  134. package/dist/__tests__/workcycle-runner-cascade.test.js.map +1 -0
  135. package/dist/__tests__/workcycle-runner.test.d.ts +2 -0
  136. package/dist/__tests__/workcycle-runner.test.d.ts.map +1 -0
  137. package/dist/__tests__/workcycle-runner.test.js +369 -0
  138. package/dist/__tests__/workcycle-runner.test.js.map +1 -0
  139. package/dist/__tests__/workspace-diff.test.d.ts +2 -0
  140. package/dist/__tests__/workspace-diff.test.d.ts.map +1 -0
  141. package/dist/__tests__/workspace-diff.test.js +62 -0
  142. package/dist/__tests__/workspace-diff.test.js.map +1 -0
  143. package/dist/__tests__/workspace-manager.test.d.ts +2 -0
  144. package/dist/__tests__/workspace-manager.test.d.ts.map +1 -0
  145. package/dist/__tests__/workspace-manager.test.js +120 -0
  146. package/dist/__tests__/workspace-manager.test.js.map +1 -0
  147. package/dist/__tests__/workspace-types.test.d.ts +2 -0
  148. package/dist/__tests__/workspace-types.test.d.ts.map +1 -0
  149. package/dist/__tests__/workspace-types.test.js +37 -0
  150. package/dist/__tests__/workspace-types.test.js.map +1 -0
  151. package/dist/__tests__/worktree-gc.test.d.ts +2 -0
  152. package/dist/__tests__/worktree-gc.test.d.ts.map +1 -0
  153. package/dist/__tests__/worktree-gc.test.js +183 -0
  154. package/dist/__tests__/worktree-gc.test.js.map +1 -0
  155. package/dist/always-on/activity-reader.d.ts +27 -0
  156. package/dist/always-on/activity-reader.d.ts.map +1 -0
  157. package/dist/always-on/activity-reader.js +95 -0
  158. package/dist/always-on/activity-reader.js.map +1 -0
  159. package/dist/always-on/always-on-manager.d.ts +170 -0
  160. package/dist/always-on/always-on-manager.d.ts.map +1 -0
  161. package/dist/always-on/always-on-manager.js +538 -0
  162. package/dist/always-on/always-on-manager.js.map +1 -0
  163. package/dist/always-on/config.d.ts +141 -0
  164. package/dist/always-on/config.d.ts.map +1 -0
  165. package/dist/always-on/config.js +324 -0
  166. package/dist/always-on/config.js.map +1 -0
  167. package/dist/always-on/discovery-scheduler.d.ts +60 -0
  168. package/dist/always-on/discovery-scheduler.d.ts.map +1 -0
  169. package/dist/always-on/discovery-scheduler.js +287 -0
  170. package/dist/always-on/discovery-scheduler.js.map +1 -0
  171. package/dist/always-on/ideas-client.d.ts +23 -0
  172. package/dist/always-on/ideas-client.d.ts.map +1 -0
  173. package/dist/always-on/ideas-client.js +13 -0
  174. package/dist/always-on/ideas-client.js.map +1 -0
  175. package/dist/always-on/reconcile-researching.d.ts +42 -0
  176. package/dist/always-on/reconcile-researching.d.ts.map +1 -0
  177. package/dist/always-on/reconcile-researching.js +133 -0
  178. package/dist/always-on/reconcile-researching.js.map +1 -0
  179. package/dist/always-on/task-sources/cascade-picker.d.ts +42 -0
  180. package/dist/always-on/task-sources/cascade-picker.d.ts.map +1 -0
  181. package/dist/always-on/task-sources/cascade-picker.js +65 -0
  182. package/dist/always-on/task-sources/cascade-picker.js.map +1 -0
  183. package/dist/always-on/task-sources/idea-dedupe.d.ts +62 -0
  184. package/dist/always-on/task-sources/idea-dedupe.d.ts.map +1 -0
  185. package/dist/always-on/task-sources/idea-dedupe.js +130 -0
  186. package/dist/always-on/task-sources/idea-dedupe.js.map +1 -0
  187. package/dist/always-on/task-sources/idea-research-source.d.ts +46 -0
  188. package/dist/always-on/task-sources/idea-research-source.d.ts.map +1 -0
  189. package/dist/always-on/task-sources/idea-research-source.js +308 -0
  190. package/dist/always-on/task-sources/idea-research-source.js.map +1 -0
  191. package/dist/always-on/task-sources/idea-sort.d.ts +3 -0
  192. package/dist/always-on/task-sources/idea-sort.d.ts.map +1 -0
  193. package/dist/always-on/task-sources/idea-sort.js +25 -0
  194. package/dist/always-on/task-sources/idea-sort.js.map +1 -0
  195. package/dist/always-on/task-sources/idea-todo-source.d.ts +48 -0
  196. package/dist/always-on/task-sources/idea-todo-source.d.ts.map +1 -0
  197. package/dist/always-on/task-sources/idea-todo-source.js +226 -0
  198. package/dist/always-on/task-sources/idea-todo-source.js.map +1 -0
  199. package/dist/always-on/task-sources/introspection-source.d.ts +101 -0
  200. package/dist/always-on/task-sources/introspection-source.d.ts.map +1 -0
  201. package/dist/always-on/task-sources/introspection-source.js +695 -0
  202. package/dist/always-on/task-sources/introspection-source.js.map +1 -0
  203. package/dist/always-on/task-sources/task-store-source.d.ts +15 -0
  204. package/dist/always-on/task-sources/task-store-source.d.ts.map +1 -0
  205. package/dist/always-on/task-sources/task-store-source.js +59 -0
  206. package/dist/always-on/task-sources/task-store-source.js.map +1 -0
  207. package/dist/always-on/task-sources/types.d.ts +108 -0
  208. package/dist/always-on/task-sources/types.d.ts.map +1 -0
  209. package/dist/always-on/task-sources/types.js +13 -0
  210. package/dist/always-on/task-sources/types.js.map +1 -0
  211. package/dist/always-on/types.d.ts +76 -0
  212. package/dist/always-on/types.d.ts.map +1 -0
  213. package/dist/always-on/types.js +17 -0
  214. package/dist/always-on/types.js.map +1 -0
  215. package/dist/always-on/workcycle-runner.d.ts +115 -0
  216. package/dist/always-on/workcycle-runner.d.ts.map +1 -0
  217. package/dist/always-on/workcycle-runner.js +285 -0
  218. package/dist/always-on/workcycle-runner.js.map +1 -0
  219. package/dist/always-on/worktree-gc.d.ts +41 -0
  220. package/dist/always-on/worktree-gc.d.ts.map +1 -0
  221. package/dist/always-on/worktree-gc.js +167 -0
  222. package/dist/always-on/worktree-gc.js.map +1 -0
  223. package/dist/index.d.ts +26 -2
  224. package/dist/index.d.ts.map +1 -1
  225. package/dist/index.js +24 -1
  226. package/dist/index.js.map +1 -1
  227. package/dist/persistence.d.ts +37 -1
  228. package/dist/persistence.d.ts.map +1 -1
  229. package/dist/persistence.js +48 -0
  230. package/dist/persistence.js.map +1 -1
  231. package/dist/retrospective.d.ts.map +1 -1
  232. package/dist/retrospective.js +6 -0
  233. package/dist/retrospective.js.map +1 -1
  234. package/dist/roles/role-loader.d.ts +1 -1
  235. package/dist/roles/role-loader.d.ts.map +1 -1
  236. package/dist/roles/role-loader.js +18 -0
  237. package/dist/roles/role-loader.js.map +1 -1
  238. package/dist/roles/types.d.ts +12 -0
  239. package/dist/roles/types.d.ts.map +1 -1
  240. package/dist/shipment/gh-pr-creator.d.ts +28 -0
  241. package/dist/shipment/gh-pr-creator.d.ts.map +1 -0
  242. package/dist/shipment/gh-pr-creator.js +80 -0
  243. package/dist/shipment/gh-pr-creator.js.map +1 -0
  244. package/dist/shipment/report.d.ts +27 -0
  245. package/dist/shipment/report.d.ts.map +1 -0
  246. package/dist/shipment/report.js +41 -0
  247. package/dist/shipment/report.js.map +1 -0
  248. package/dist/shipment/secret-scrub.d.ts +12 -0
  249. package/dist/shipment/secret-scrub.d.ts.map +1 -0
  250. package/dist/shipment/secret-scrub.js +30 -0
  251. package/dist/shipment/secret-scrub.js.map +1 -0
  252. package/dist/shipment/shipment-actions.d.ts +85 -0
  253. package/dist/shipment/shipment-actions.d.ts.map +1 -0
  254. package/dist/shipment/shipment-actions.js +190 -0
  255. package/dist/shipment/shipment-actions.js.map +1 -0
  256. package/dist/shipment/shipment-pipeline.d.ts +48 -0
  257. package/dist/shipment/shipment-pipeline.d.ts.map +1 -0
  258. package/dist/shipment/shipment-pipeline.js +256 -0
  259. package/dist/shipment/shipment-pipeline.js.map +1 -0
  260. package/dist/shipment/transport-detect.d.ts +16 -0
  261. package/dist/shipment/transport-detect.d.ts.map +1 -0
  262. package/dist/shipment/transport-detect.js +54 -0
  263. package/dist/shipment/transport-detect.js.map +1 -0
  264. package/dist/shipment/workspace-diff.d.ts +39 -0
  265. package/dist/shipment/workspace-diff.d.ts.map +1 -0
  266. package/dist/shipment/workspace-diff.js +64 -0
  267. package/dist/shipment/workspace-diff.js.map +1 -0
  268. package/dist/swarm-coordinator.d.ts +20 -1
  269. package/dist/swarm-coordinator.d.ts.map +1 -1
  270. package/dist/swarm-coordinator.js +193 -10
  271. package/dist/swarm-coordinator.js.map +1 -1
  272. package/dist/types.d.ts +62 -0
  273. package/dist/types.d.ts.map +1 -1
  274. package/dist/workspace/git-worktree-provider.d.ts +11 -0
  275. package/dist/workspace/git-worktree-provider.d.ts.map +1 -0
  276. package/dist/workspace/git-worktree-provider.js +123 -0
  277. package/dist/workspace/git-worktree-provider.js.map +1 -0
  278. package/dist/workspace/gitignore-check.d.ts +10 -0
  279. package/dist/workspace/gitignore-check.d.ts.map +1 -0
  280. package/dist/workspace/gitignore-check.js +25 -0
  281. package/dist/workspace/gitignore-check.js.map +1 -0
  282. package/dist/workspace/index.d.ts +5 -0
  283. package/dist/workspace/index.d.ts.map +1 -0
  284. package/dist/workspace/index.js +5 -0
  285. package/dist/workspace/index.js.map +1 -0
  286. package/dist/workspace/snapshot-copy-provider.d.ts +11 -0
  287. package/dist/workspace/snapshot-copy-provider.d.ts.map +1 -0
  288. package/dist/workspace/snapshot-copy-provider.js +66 -0
  289. package/dist/workspace/snapshot-copy-provider.js.map +1 -0
  290. package/dist/workspace/types.d.ts +36 -0
  291. package/dist/workspace/types.d.ts.map +1 -0
  292. package/dist/workspace/types.js +2 -0
  293. package/dist/workspace/types.js.map +1 -0
  294. package/dist/workspace/workspace-manager.d.ts +30 -0
  295. package/dist/workspace/workspace-manager.d.ts.map +1 -0
  296. package/dist/workspace/workspace-manager.js +104 -0
  297. package/dist/workspace/workspace-manager.js.map +1 -0
  298. package/package.json +4 -4
  299. package/roles/queen.md +1 -0
  300. package/templates/introspection.md +64 -0
  301. package/templates/research-only.md +58 -0
  302. package/roles/preset-analyst-simons.md +0 -39
  303. package/roles/preset-architect-knuth.md +0 -39
  304. package/roles/preset-designer-norman.md +0 -39
  305. package/roles/preset-designer.md +0 -39
  306. package/roles/preset-dev-carmack.md +0 -39
  307. package/roles/preset-dev-gosling.md +0 -39
  308. package/roles/preset-developer.md +0 -52
  309. package/roles/preset-manager-grove.md +0 -39
  310. package/roles/preset-manager-musk.md +0 -39
  311. package/roles/preset-pm.md +0 -78
  312. package/roles/preset-researcher-feynman.md +0 -39
  313. package/roles/preset-reviewer.md +0 -46
  314. package/roles/preset-strategist-buffett.md +0 -39
  315. package/roles/preset-strategist-munger.md +0 -39
  316. package/roles/preset-strategist-sunzi.md +0 -39
  317. package/roles/preset-tester-beck.md +0 -40
  318. package/roles/preset-tester.md +0 -47
  319. package/roles/preset-writer-orwell.md +0 -39
  320. package/roles/preset-writer.md +0 -39
@@ -0,0 +1,287 @@
1
+ // DiscoveryScheduler — M7.0 standalone primitive.
2
+ //
3
+ // Drives the always-on tick loop. Owns: tick timer, gate cascade, daily budget,
4
+ // ring buffer of recent decisions. Owns nothing about swarms / TaskStore /
5
+ // queens — that wiring lands in M7.1 via the `onFire` callback.
6
+ //
7
+ // Persistence: NONE in M7.0. Restart resets ticksToday and recentTicks.
8
+ // M7.3 will add a persisted view.
9
+ import { createLogger } from "@mclawnet/logger";
10
+ import { localYMD } from "./config.js";
11
+ import { DEFAULT_DAILY_BUDGET, DEFAULT_TICK_INTERVAL_MS, RECENT_TICKS_CAP, } from "./types.js";
12
+ const log = createLogger({ module: "swarm" });
13
+ export class DiscoveryScheduler {
14
+ mode;
15
+ tickIntervalOverride;
16
+ dailyBudget;
17
+ userGates;
18
+ onFire;
19
+ onTick;
20
+ now;
21
+ timer = null;
22
+ running = false;
23
+ halted = false;
24
+ inFlight = false; // a tick is currently executing
25
+ ticksToday = 0;
26
+ lastFireAt = null;
27
+ recentTicks = [];
28
+ nextTickAt = null;
29
+ constructor(opts) {
30
+ this.mode = opts.mode;
31
+ this.tickIntervalOverride = opts.tickInterval;
32
+ this.dailyBudget = opts.dailyBudget === undefined ? DEFAULT_DAILY_BUDGET : opts.dailyBudget;
33
+ this.userGates = opts.gates ?? [];
34
+ this.onFire = opts.onFire;
35
+ this.onTick = opts.onTick;
36
+ this.now = opts.now ?? (() => new Date());
37
+ }
38
+ /** Start the tick loop. No-op when mode === "off" or scheduler is halted. */
39
+ start() {
40
+ if (this.halted) {
41
+ log.warn("DiscoveryScheduler.start() called after halt — ignoring");
42
+ return;
43
+ }
44
+ if (this.running)
45
+ return;
46
+ this.running = true;
47
+ this.schedule();
48
+ }
49
+ /** Stop the tick loop. Idempotent. */
50
+ stop() {
51
+ this.running = false;
52
+ if (this.timer) {
53
+ clearTimeout(this.timer);
54
+ this.timer = null;
55
+ }
56
+ this.nextTickAt = null;
57
+ }
58
+ /**
59
+ * Manually trigger a tick. Bypasses the interval but still runs gates and
60
+ * still respects the in-flight guard: concurrent tickNow() calls reject the
61
+ * second caller with an Error so callers see deterministic semantics rather
62
+ * than silent overlap. Use `await` to serialise explicitly.
63
+ */
64
+ async tickNow() {
65
+ if (this.inFlight) {
66
+ throw new Error("DiscoveryScheduler.tickNow: a tick is already in progress");
67
+ }
68
+ return this.runTick();
69
+ }
70
+ /** Change mode at runtime. Cancels current timer and reschedules. */
71
+ setMode(mode) {
72
+ if (this.mode === mode)
73
+ return;
74
+ this.mode = mode;
75
+ // S2 hotfix: previously bailed when `!running`, which forced applyConfig
76
+ // to call start() explicitly after every setMode("active"). Let schedule()
77
+ // make the decision — it already no-ops on `!running` and on mode === "off".
78
+ this.schedule();
79
+ }
80
+ /**
81
+ * Change daily budget at runtime (M7.3). `null` removes the cap entirely.
82
+ * Does not retro-affect counters; if the new budget is lower than
83
+ * `ticksToday`, the next gate evaluation will simply skip.
84
+ */
85
+ setDailyBudget(budget) {
86
+ this.dailyBudget = budget;
87
+ }
88
+ /**
89
+ * Restore counters from persisted state (M7.3 AlwaysOnManager). Must be
90
+ * called BEFORE start() — restoring into a running scheduler is undefined
91
+ * behaviour. `lastFireAt` may be null when no prior fire exists.
92
+ */
93
+ restoreState(state) {
94
+ if (this.running) {
95
+ log.warn("DiscoveryScheduler.restoreState() called while running — ignoring");
96
+ return;
97
+ }
98
+ if (Number.isInteger(state.ticksToday) && state.ticksToday >= 0) {
99
+ this.ticksToday = state.ticksToday;
100
+ }
101
+ this.lastFireAt = state.lastFireAt;
102
+ }
103
+ snapshot() {
104
+ return {
105
+ mode: this.mode,
106
+ ticksToday: this.ticksToday,
107
+ lastFireAt: this.lastFireAt,
108
+ nextTickAt: this.nextTickAt,
109
+ recentTicks: [...this.recentTicks],
110
+ };
111
+ }
112
+ // ---- internal ---------------------------------------------------------
113
+ currentInterval() {
114
+ if (this.tickIntervalOverride !== undefined)
115
+ return this.tickIntervalOverride;
116
+ if (this.mode === "off")
117
+ return Number.POSITIVE_INFINITY;
118
+ return DEFAULT_TICK_INTERVAL_MS[this.mode];
119
+ }
120
+ /**
121
+ * Arm the next-tick timer. No-op when not running, halted, or mode=off.
122
+ * Always clears any existing timer first — both setMode() and a manual
123
+ * tickNow() can race with the previously armed interval.
124
+ */
125
+ schedule() {
126
+ if (this.timer) {
127
+ clearTimeout(this.timer);
128
+ this.timer = null;
129
+ }
130
+ if (!this.running || this.halted) {
131
+ this.nextTickAt = null;
132
+ return;
133
+ }
134
+ if (this.mode === "off") {
135
+ this.nextTickAt = null;
136
+ return;
137
+ }
138
+ const interval = this.currentInterval();
139
+ if (!Number.isFinite(interval)) {
140
+ this.nextTickAt = null;
141
+ return;
142
+ }
143
+ this.nextTickAt = new Date(this.now().getTime() + interval);
144
+ const t = setTimeout(() => {
145
+ this.timer = null;
146
+ // runTick is async; we deliberately do not await — schedule() is called
147
+ // by runTick itself once the tick (and onFire) completes.
148
+ this.runTick().catch((err) => {
149
+ log.warn({ err }, "DiscoveryScheduler tick threw unexpectedly");
150
+ // Defensive: still reschedule so a single bad tick doesn't kill the loop.
151
+ if (this.running && !this.halted)
152
+ this.schedule();
153
+ });
154
+ }, interval);
155
+ // Don't keep the event loop alive purely for ticks (matches WakeupScheduler).
156
+ if (typeof t.unref === "function") {
157
+ t.unref();
158
+ }
159
+ this.timer = t;
160
+ }
161
+ async runTick() {
162
+ this.inFlight = true;
163
+ const startedAt = this.now();
164
+ const startedMs = startedAt.getTime();
165
+ try {
166
+ // Daily reset: if calendar date has changed since lastFireAt, zero today.
167
+ // S3 hotfix: localYMD shared with AlwaysOnManager (single source of
168
+ // truth for "today" — was `Date.toDateString()` before, which uses a
169
+ // human-readable English string and didn't match the YMD persisted on
170
+ // disk).
171
+ if (this.lastFireAt && localYMD(this.lastFireAt) !== localYMD(startedAt)) {
172
+ this.ticksToday = 0;
173
+ }
174
+ const ctx = {
175
+ now: startedAt,
176
+ mode: this.mode,
177
+ ticksToday: this.ticksToday,
178
+ lastFireAt: this.lastFireAt,
179
+ };
180
+ const breakdown = [];
181
+ const gates = this.buildGateChain();
182
+ let finalDecision = { kind: "fire" };
183
+ for (const g of gates) {
184
+ const decision = await g.evaluate(ctx);
185
+ breakdown.push({ gate: g.name, decision });
186
+ if (decision.kind !== "fire") {
187
+ finalDecision = decision;
188
+ break;
189
+ }
190
+ }
191
+ let outcome;
192
+ if (finalDecision.kind === "halt") {
193
+ outcome = "halted";
194
+ this.halted = true;
195
+ this.stop();
196
+ }
197
+ else if (finalDecision.kind === "skip") {
198
+ outcome = "skipped";
199
+ }
200
+ else if (this.onFire) {
201
+ // I1 hotfix: increment budget AFTER onFire resolves. A rejected onFire
202
+ // (e.g. C1's "template not found", or any transient swarm spawn error)
203
+ // used to charge the daily quota AND lose the work — three quiet
204
+ // failures and the project was locked for the day. Now: a throw is
205
+ // recorded as a "skipped" decision so the next tick keeps trying.
206
+ try {
207
+ await this.onFire(ctx);
208
+ this.ticksToday += 1;
209
+ this.lastFireAt = startedAt;
210
+ outcome = "fired";
211
+ }
212
+ catch (err) {
213
+ log.warn({ err }, "DiscoveryScheduler onFire rejected; not burning budget");
214
+ outcome = "skipped";
215
+ breakdown.push({
216
+ gate: "onFire",
217
+ decision: {
218
+ kind: "skip",
219
+ reason: `onFire threw: ${err instanceof Error ? err.message : String(err)}`,
220
+ },
221
+ });
222
+ }
223
+ }
224
+ else {
225
+ // No onFire wired (M7.0 standalone primitive) — treat the gate-pass
226
+ // as a fire so existing counters / tests still observe ticksToday++.
227
+ this.ticksToday += 1;
228
+ this.lastFireAt = startedAt;
229
+ outcome = "fired";
230
+ }
231
+ const record = {
232
+ at: startedAt,
233
+ decision: outcome,
234
+ gateBreakdown: breakdown,
235
+ durationMs: this.now().getTime() - startedMs,
236
+ };
237
+ this.pushRecent(record);
238
+ if (this.onTick) {
239
+ try {
240
+ this.onTick(record);
241
+ }
242
+ catch (err) {
243
+ log.warn({ err }, "DiscoveryScheduler onTick observer threw; ignored");
244
+ }
245
+ }
246
+ return record;
247
+ }
248
+ finally {
249
+ this.inFlight = false;
250
+ // Schedule the next tick unless we halted or were stopped.
251
+ if (this.running && !this.halted) {
252
+ this.schedule();
253
+ }
254
+ }
255
+ }
256
+ /** Built-ins (in order) then user gates. */
257
+ buildGateChain() {
258
+ const builtins = [
259
+ {
260
+ name: "modeNotOff",
261
+ evaluate: (c) => c.mode === "off"
262
+ ? { kind: "skip", reason: "mode is off" }
263
+ : { kind: "fire" },
264
+ },
265
+ {
266
+ name: "dailyBudget",
267
+ evaluate: (c) => {
268
+ if (this.dailyBudget !== null && c.ticksToday >= this.dailyBudget) {
269
+ return {
270
+ kind: "skip",
271
+ reason: `daily budget exhausted (${c.ticksToday}/${this.dailyBudget})`,
272
+ };
273
+ }
274
+ return { kind: "fire" };
275
+ },
276
+ },
277
+ ];
278
+ return [...builtins, ...this.userGates];
279
+ }
280
+ pushRecent(record) {
281
+ this.recentTicks.push(record);
282
+ if (this.recentTicks.length > RECENT_TICKS_CAP) {
283
+ this.recentTicks.splice(0, this.recentTicks.length - RECENT_TICKS_CAP);
284
+ }
285
+ }
286
+ }
287
+ //# sourceMappingURL=discovery-scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery-scheduler.js","sourceRoot":"","sources":["../../src/always-on/discovery-scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Idea, IdeaStatus, IdeaPriority, UpdateIdeaInput, CreateIdeaInput, ListIdeasInput, ResearchRef, ResearchKind, ResearchConclusion } from "@mclawnet/shared";
2
+ export type { Idea, IdeaStatus, IdeaPriority, CreateIdeaInput, ResearchRef, ResearchKind, ResearchConclusion, };
3
+ /**
4
+ * Subset of UpdateIdeaInput the M8.0 IdeaTodoSource + M8.1 IdeaResearchSource
5
+ * need. M8.1 extends with researchReports (append-only array writeback) and
6
+ * researchKind (disambiguates the two researching flows; cleared back to null
7
+ * on failure-revert).
8
+ */
9
+ export type IdeasPatch = Partial<Pick<UpdateIdeaInput, "status" | "priority" | "linkedSwarmTaskId" | "linkedSessionId" | "tags" | "title" | "body" | "researchReports" | "researchKind" | "shipFailureReason">>;
10
+ /** Subset of ListIdeasInput supported by the cascade source today. */
11
+ export type IdeasListFilter = Partial<Pick<ListIdeasInput, "status" | "priority" | "projectId" | "tag" | "q" | "limit">>;
12
+ export interface IdeasClient {
13
+ list(filter: IdeasListFilter): Promise<Idea[]>;
14
+ get(id: string): Promise<Idea | null>;
15
+ patch(id: string, partial: IdeasPatch): Promise<Idea>;
16
+ /**
17
+ * M8.2 — POST /api/ideas. Used by IntrospectionSource to write back
18
+ * agent-discovered ideas (status defaults to "idea" hub-side when omitted).
19
+ * Returns the persisted row so callers can grab the assigned id.
20
+ */
21
+ create(input: CreateIdeaInput): Promise<Idea>;
22
+ }
23
+ //# sourceMappingURL=ideas-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ideas-client.d.ts","sourceRoot":"","sources":["../../src/always-on/ideas-client.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,IAAI,EACJ,UAAU,EACV,YAAY,EACZ,eAAe,EACf,eAAe,EACf,cAAc,EACd,WAAW,EACX,YAAY,EACZ,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,IAAI,EACJ,UAAU,EACV,YAAY,EACZ,eAAe,EACf,WAAW,EACX,YAAY,EACZ,kBAAkB,GACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,CAC9B,IAAI,CACF,eAAe,EACb,QAAQ,GACR,UAAU,GACV,mBAAmB,GACnB,iBAAiB,GACjB,MAAM,GACN,OAAO,GACP,MAAM,GACN,iBAAiB,GACjB,cAAc,GACd,mBAAmB,CACtB,CACF,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,eAAe,GAAG,OAAO,CACnC,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,KAAK,GAAG,GAAG,GAAG,OAAO,CAAC,CAClF,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C"}
@@ -0,0 +1,13 @@
1
+ // M8.0 — IdeasClient interface.
2
+ //
3
+ // Decouples IdeaTodoSource from the actual HTTP transport: tests inject a
4
+ // stub, the agent injects an HTTP-backed implementation
5
+ // (`createIdeasRestClient` in @mclawnet/agent) that talks to hub's
6
+ // /api/ideas/* routes. Keeping the interface in @mclawnet/swarm avoids
7
+ // circular package deps (swarm doesn't depend on agent).
8
+ //
9
+ // Re-exports the canonical Idea / IdeaStatus / IdeaPriority shapes from
10
+ // @mclawnet/shared so callers don't have to remember which package they live
11
+ // in.
12
+ export {};
13
+ //# sourceMappingURL=ideas-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ideas-client.js","sourceRoot":"","sources":["../../src/always-on/ideas-client.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,EAAE;AACF,0EAA0E;AAC1E,wDAAwD;AACxD,mEAAmE;AACnE,uEAAuE;AACvE,yDAAyD;AACzD,EAAE;AACF,wEAAwE;AACxE,6EAA6E;AAC7E,MAAM"}
@@ -0,0 +1,42 @@
1
+ import type { IdeasClient } from "./ideas-client.js";
2
+ export interface ReconcileLogger {
3
+ warn?: (obj: Record<string, unknown>, msg?: string) => void;
4
+ info?: (obj: Record<string, unknown>, msg?: string) => void;
5
+ }
6
+ export interface ReconcileOptions {
7
+ ideasClient: IdeasClient;
8
+ /** Returns the list of projectRoots the agent currently has wired. */
9
+ getProjectRoots: () => string[];
10
+ /**
11
+ * Returns the swarmIds currently recoverable on disk (recovery.json
12
+ * still present). The check is per-call — we don't cache so this runs
13
+ * after any inflight recovery on the swarm side.
14
+ */
15
+ listRecoverableSwarmIds: () => Array<{
16
+ swarmId: string;
17
+ workDir: string;
18
+ }>;
19
+ /** Optional logger. Defaults to silent. */
20
+ log?: ReconcileLogger;
21
+ /** Max ideas to list per project on the boot scan. Default 100. */
22
+ perProjectLimit?: number;
23
+ }
24
+ export interface ReconcileReport {
25
+ /** Projects scanned (post-encodeCwd failures excluded). */
26
+ projectsScanned: number;
27
+ /** Ideas observed at status=researching across all scanned projects. */
28
+ candidatesFound: number;
29
+ /** Ideas that were reverted (orphans whose linked swarm is gone). */
30
+ reverted: number;
31
+ /** Ideas left alone because their linked swarm still has recovery.json on disk. */
32
+ preserved: number;
33
+ /** Errors encountered during the sweep (best-effort; not re-thrown). */
34
+ errors: string[];
35
+ }
36
+ /**
37
+ * One-shot reconciliation. Designed to be awaited (or fire-and-forget) at
38
+ * the end of startAgent() after AlwaysOnManager has booted. Returns a
39
+ * report for logging — no exceptions cross the boundary.
40
+ */
41
+ export declare function reconcileOrphanedResearching(opts: ReconcileOptions): Promise<ReconcileReport>;
42
+ //# sourceMappingURL=reconcile-researching.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile-researching.d.ts","sourceRoot":"","sources":["../../src/always-on/reconcile-researching.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,mBAAmB,CAAC;AAIjE,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7D;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,WAAW,CAAC;IACzB,sEAAsE;IACtE,eAAe,EAAE,MAAM,MAAM,EAAE,CAAC;IAChC;;;;OAIG;IACH,uBAAuB,EAAE,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3E,2CAA2C;IAC3C,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB,mEAAmE;IACnE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,eAAe,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,eAAe,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AASD;;;;GAIG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,eAAe,CAAC,CA0G1B"}
@@ -0,0 +1,133 @@
1
+ // M8 hotfix I6 — orphaned-researching reconciliation.
2
+ //
3
+ // If the agent dies between IdeaTodoSource.onClaim / IdeaResearchSource.onClaim
4
+ // (which PATCH the idea to status=researching + linkedSwarmTaskId=<swarmId>)
5
+ // and the runner's terminal-state callback, the idea is left dangling at
6
+ // status=researching forever. No code path on the boot side noticed.
7
+ //
8
+ // This module exposes a one-shot reconciliation that the agent runs once,
9
+ // right after AlwaysOnManager.start() has booted all projects. It walks
10
+ // every still-researching idea, decides whether its linked swarm is still
11
+ // alive, and reverts the orphans:
12
+ // - researchKind="execute" → back to status="todo"
13
+ // - researchKind="investigate" → back to status="idea"
14
+ // - missing/unknown kind → defaults to "idea" (the safer revert)
15
+ // and clears `linkedSwarmTaskId` + `researchKind` on the way.
16
+ //
17
+ // We intentionally do NOT try to resume the cycle — the swarm process is
18
+ // gone and the most useful thing for the user is to see the idea back in
19
+ // the inbox where they can re-trigger it manually.
20
+ //
21
+ // Best-effort: hub failures, individual idea failures, and per-project
22
+ // scan failures all log a warn and continue. The agent must boot even when
23
+ // the hub is briefly unreachable.
24
+ import { encodeCwd } from "@mclawnet/shared";
25
+ /** Map a researching idea back to its pre-claim status. */
26
+ function revertStatus(idea) {
27
+ if (idea.researchKind === "execute")
28
+ return "todo";
29
+ // "investigate" or null/undefined → safest revert is "idea".
30
+ return "idea";
31
+ }
32
+ /**
33
+ * One-shot reconciliation. Designed to be awaited (or fire-and-forget) at
34
+ * the end of startAgent() after AlwaysOnManager has booted. Returns a
35
+ * report for logging — no exceptions cross the boundary.
36
+ */
37
+ export async function reconcileOrphanedResearching(opts) {
38
+ const { ideasClient, getProjectRoots, listRecoverableSwarmIds, log, perProjectLimit = 100 } = opts;
39
+ const report = {
40
+ projectsScanned: 0,
41
+ candidatesFound: 0,
42
+ reverted: 0,
43
+ preserved: 0,
44
+ errors: [],
45
+ };
46
+ let aliveSwarmIds;
47
+ try {
48
+ aliveSwarmIds = new Set(listRecoverableSwarmIds().map((s) => s.swarmId));
49
+ }
50
+ catch (err) {
51
+ const msg = err instanceof Error ? err.message : String(err);
52
+ log?.warn?.({ err: msg }, "reconcile: failed to list recoverable swarms — skipping sweep");
53
+ report.errors.push(`listRecoverableSwarmIds: ${msg}`);
54
+ return report;
55
+ }
56
+ let projectRoots;
57
+ try {
58
+ projectRoots = getProjectRoots();
59
+ }
60
+ catch (err) {
61
+ const msg = err instanceof Error ? err.message : String(err);
62
+ log?.warn?.({ err: msg }, "reconcile: getProjectRoots threw — skipping sweep");
63
+ report.errors.push(`getProjectRoots: ${msg}`);
64
+ return report;
65
+ }
66
+ for (const projectRoot of projectRoots) {
67
+ let projectId;
68
+ try {
69
+ projectId = encodeCwd(projectRoot);
70
+ }
71
+ catch (err) {
72
+ const msg = err instanceof Error ? err.message : String(err);
73
+ log?.warn?.({ err: msg, projectRoot }, "reconcile: encodeCwd failed — skipping project");
74
+ report.errors.push(`encodeCwd(${projectRoot}): ${msg}`);
75
+ continue;
76
+ }
77
+ report.projectsScanned += 1;
78
+ let researching;
79
+ try {
80
+ researching = await ideasClient.list({
81
+ status: "researching",
82
+ projectId,
83
+ limit: perProjectLimit,
84
+ });
85
+ }
86
+ catch (err) {
87
+ const msg = err instanceof Error ? err.message : String(err);
88
+ log?.warn?.({ err: msg, projectId }, "reconcile: list(status=researching) failed");
89
+ report.errors.push(`list(${projectId}): ${msg}`);
90
+ continue;
91
+ }
92
+ for (const idea of researching) {
93
+ report.candidatesFound += 1;
94
+ const swarmId = idea.linkedSwarmTaskId;
95
+ if (swarmId && aliveSwarmIds.has(swarmId)) {
96
+ // Linked swarm is still recoverable on disk — leave alone; the
97
+ // normal flow (or a manual recovery) will catch up.
98
+ report.preserved += 1;
99
+ continue;
100
+ }
101
+ const patch = {
102
+ status: revertStatus(idea),
103
+ researchKind: null,
104
+ linkedSwarmTaskId: null,
105
+ };
106
+ try {
107
+ await ideasClient.patch(idea.id, patch);
108
+ report.reverted += 1;
109
+ log?.info?.({
110
+ ideaId: idea.id,
111
+ projectId,
112
+ prevStatus: "researching",
113
+ nextStatus: patch.status,
114
+ prevSwarmId: swarmId ?? null,
115
+ }, "reconcile: reverted orphaned researching idea");
116
+ }
117
+ catch (err) {
118
+ const msg = err instanceof Error ? err.message : String(err);
119
+ log?.warn?.({ err: msg, ideaId: idea.id, projectId }, "reconcile: PATCH revert failed (will retry on next boot)");
120
+ report.errors.push(`patch(${idea.id}): ${msg}`);
121
+ }
122
+ }
123
+ }
124
+ log?.info?.({
125
+ projectsScanned: report.projectsScanned,
126
+ candidatesFound: report.candidatesFound,
127
+ reverted: report.reverted,
128
+ preserved: report.preserved,
129
+ errorCount: report.errors.length,
130
+ }, "reconcile: sweep complete");
131
+ return report;
132
+ }
133
+ //# sourceMappingURL=reconcile-researching.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile-researching.js","sourceRoot":"","sources":["../../src/always-on/reconcile-researching.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { TaskSource, TaskRef } from "./types.js";
2
+ export interface CascadePickerLogger {
3
+ warn?: (obj: Record<string, unknown>, msg?: string) => void;
4
+ info?: (obj: Record<string, unknown>, msg?: string) => void;
5
+ debug?: (obj: Record<string, unknown>, msg?: string) => void;
6
+ }
7
+ export interface CascadePickResult {
8
+ taskRef: TaskRef;
9
+ source: TaskSource;
10
+ }
11
+ export interface CascadePicker {
12
+ /** Try each source in order and return the first hit, or null. */
13
+ pick(projectRoot: string): Promise<CascadePickResult | null>;
14
+ /**
15
+ * Convenience adapter that drops the source reference — matches the
16
+ * legacy `pickNextTask: (root) => Promise<TaskRef | null>` shape used by
17
+ * M7 callers (e.g. AlwaysOnManager constructor option). The cascade
18
+ * picker remembers the last hit internally so the runner's terminal
19
+ * hooks can still resolve it.
20
+ */
21
+ pickNextTask(projectRoot: string): Promise<TaskRef | null>;
22
+ /**
23
+ * Returns the source that produced the given TaskRef in the most recent
24
+ * `pick()` / `pickNextTask()` call. Used by the runner to resolve hooks
25
+ * after a swarm reaches terminal state. Returns null when the ref is
26
+ * unknown (e.g. the runner is using the legacy single-source DI path).
27
+ */
28
+ resolveSource(taskRef: TaskRef): TaskSource | null;
29
+ }
30
+ export interface CascadePickerOptions {
31
+ /**
32
+ * Hotfix C1 — per-pick source filter. Called for every source before
33
+ * `source.pick()`; returning false skips the source for this tick (it is
34
+ * still resolvable via `resolveSource` for outstanding refs). The filter
35
+ * is consulted fresh on every pick, so toggling a source on/off in the
36
+ * always-on config takes effect without rebuilding the cascade. Default:
37
+ * every source enabled.
38
+ */
39
+ filter?: (source: TaskSource, projectRoot: string) => boolean;
40
+ }
41
+ export declare function createCascadePicker(sources: TaskSource[], log?: CascadePickerLogger, options?: CascadePickerOptions): CascadePicker;
42
+ //# sourceMappingURL=cascade-picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cascade-picker.d.ts","sourceRoot":"","sources":["../../../src/always-on/task-sources/cascade-picker.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEtD,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9D;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC7D;;;;;;OAMG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC3D;;;;;OAKG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;CAC/D;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,UAAU,EAAE,EACrB,GAAG,CAAC,EAAE,mBAAmB,EACzB,OAAO,GAAE,oBAAyB,GACjC,aAAa,CA+Df"}
@@ -0,0 +1,65 @@
1
+ // M8.0 — cascade-picker: tries TaskSource instances in order and returns the
2
+ // first hit along with the source that produced it. The runner needs the
3
+ // source reference so it can later call onClaim / onSuccess / onFailure
4
+ // hooks against the same instance that picked the task.
5
+ //
6
+ // Resilient by design: if a source's `pick()` throws we log a warn and try
7
+ // the next source. A single source crashing must not kill the whole cascade
8
+ // or the always-on cycle — silent fall-through is preferable to a stuck loop.
9
+ export function createCascadePicker(sources, log, options = {}) {
10
+ const sourceFilter = options.filter;
11
+ // Tracks which source produced each outstanding TaskRef (by reference id).
12
+ // Cleared lazily — entries live until overwritten or the process restarts;
13
+ // memory pressure is negligible because the cascade runs one pick per cycle.
14
+ const sourceByRefId = new Map();
15
+ async function pick(projectRoot) {
16
+ for (const source of sources) {
17
+ if (sourceFilter && !sourceFilter(source, projectRoot)) {
18
+ log?.debug?.({ source: source.name, projectRoot }, "cascade-picker: source disabled by filter — skipping");
19
+ continue;
20
+ }
21
+ let result = null;
22
+ try {
23
+ result = await source.pick(projectRoot);
24
+ }
25
+ catch (err) {
26
+ log?.warn?.({
27
+ err: err instanceof Error ? err.message : String(err),
28
+ source: source.name,
29
+ projectRoot,
30
+ }, "cascade-picker: source threw — trying next");
31
+ continue;
32
+ }
33
+ if (result) {
34
+ sourceByRefId.set(result.id, source);
35
+ log?.debug?.({ source: source.name, taskId: result.id, projectRoot }, "cascade-picker: hit");
36
+ return { taskRef: result, source };
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+ return {
42
+ pick,
43
+ async pickNextTask(projectRoot) {
44
+ const hit = await pick(projectRoot);
45
+ return hit ? hit.taskRef : null;
46
+ },
47
+ resolveSource(taskRef) {
48
+ // S6 hotfix — prefer matching by `kind` (the explicit TaskSourceKind
49
+ // union member), then fall back to `name` for sources that haven't
50
+ // adopted the `kind` field yet, and finally to the per-id map.
51
+ // resolveSource is called by the runner after a swarm reaches
52
+ // terminal state, so we need both signals to be authoritative.
53
+ if (taskRef.sourceKind) {
54
+ const byKind = sources.find((s) => s.kind === taskRef.sourceKind);
55
+ if (byKind)
56
+ return byKind;
57
+ const byName = sources.find((s) => s.name === taskRef.sourceKind);
58
+ if (byName)
59
+ return byName;
60
+ }
61
+ return sourceByRefId.get(taskRef.id) ?? null;
62
+ },
63
+ };
64
+ }
65
+ //# sourceMappingURL=cascade-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cascade-picker.js","sourceRoot":"","sources":["../../../src/always-on/task-sources/cascade-picker.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,yEAAyE;AACzE,wEAAwE;AACxE,wDAAwD;AACxD,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAC5E,8EAA8E;AA+C9E,MAAM,UAAU,mBAAmB,CACjC,OAAqB,EACrB,GAAyB,EACzB,UAAgC,EAAE;IAElC,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IACpC,2EAA2E;IAC3E,2EAA2E;IAC3E,6EAA6E;IAC7E,MAAM,aAAa,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEpD,KAAK,UAAU,IAAI,CAAC,WAAmB;QACrC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,YAAY,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,CAAC;gBACvD,GAAG,EAAE,KAAK,EAAE,CACV,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,EACpC,sDAAsD,CACvD,CAAC;gBACF,SAAS;YACX,CAAC;YACD,IAAI,MAAM,GAAmB,IAAI,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC1C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,EAAE,IAAI,EAAE,CACT;oBACE,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;oBACrD,MAAM,EAAE,MAAM,CAAC,IAAI;oBACnB,WAAW;iBACZ,EACD,4CAA4C,CAC7C,CAAC;gBACF,SAAS;YACX,CAAC;YACD,IAAI,MAAM,EAAE,CAAC;gBACX,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;gBACrC,GAAG,EAAE,KAAK,EAAE,CACV,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,EACvD,qBAAqB,CACtB,CAAC;gBACF,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YACrC,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,IAAI;QACJ,KAAK,CAAC,YAAY,CAAC,WAAmB;YACpC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,CAAC;YACpC,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QAClC,CAAC;QACD,aAAa,CAAC,OAAgB;YAC5B,qEAAqE;YACrE,mEAAmE;YACnE,+DAA+D;YAC/D,8DAA8D;YAC9D,+DAA+D;YAC/D,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;gBACvB,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;gBAClE,IAAI,MAAM;oBAAE,OAAO,MAAM,CAAC;gBAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;gBAClE,IAAI,MAAM;oBAAE,OAAO,MAAM,CAAC;YAC5B,CAAC;YACD,OAAO,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC;QAC/C,CAAC;KACF,CAAC;AACJ,CAAC"}