@overlordai/server 1.0.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 (345) hide show
  1. package/database/migrations/001-init-schema.sql +226 -0
  2. package/database/migrations/002-add-indexes.sql +17 -0
  3. package/database/migrations/003-add-settings-table.sql +4 -0
  4. package/database/migrations/004-add-developer-id-index.sql +5 -0
  5. package/dist/adapters/adapter.interface.d.ts +41 -0
  6. package/dist/adapters/adapter.interface.d.ts.map +1 -0
  7. package/dist/adapters/adapter.interface.js +6 -0
  8. package/dist/adapters/adapter.interface.js.map +1 -0
  9. package/dist/adapters/adapter.module.d.ts +3 -0
  10. package/dist/adapters/adapter.module.d.ts.map +1 -0
  11. package/dist/adapters/adapter.module.js +54 -0
  12. package/dist/adapters/adapter.module.js.map +1 -0
  13. package/dist/adapters/adapter.registry.d.ts +19 -0
  14. package/dist/adapters/adapter.registry.d.ts.map +1 -0
  15. package/dist/adapters/adapter.registry.js +51 -0
  16. package/dist/adapters/adapter.registry.js.map +1 -0
  17. package/dist/adapters/lark/lark-card.builder.d.ts +48 -0
  18. package/dist/adapters/lark/lark-card.builder.d.ts.map +1 -0
  19. package/dist/adapters/lark/lark-card.builder.js +259 -0
  20. package/dist/adapters/lark/lark-card.builder.js.map +1 -0
  21. package/dist/adapters/lark/lark-message.parser.d.ts +51 -0
  22. package/dist/adapters/lark/lark-message.parser.d.ts.map +1 -0
  23. package/dist/adapters/lark/lark-message.parser.js +189 -0
  24. package/dist/adapters/lark/lark-message.parser.js.map +1 -0
  25. package/dist/adapters/lark/lark-signature.d.ts +13 -0
  26. package/dist/adapters/lark/lark-signature.d.ts.map +1 -0
  27. package/dist/adapters/lark/lark-signature.js +58 -0
  28. package/dist/adapters/lark/lark-signature.js.map +1 -0
  29. package/dist/adapters/lark/lark.adapter.d.ts +65 -0
  30. package/dist/adapters/lark/lark.adapter.d.ts.map +1 -0
  31. package/dist/adapters/lark/lark.adapter.js +565 -0
  32. package/dist/adapters/lark/lark.adapter.js.map +1 -0
  33. package/dist/adapters/lark/lark.controller.d.ts +21 -0
  34. package/dist/adapters/lark/lark.controller.d.ts.map +1 -0
  35. package/dist/adapters/lark/lark.controller.js +120 -0
  36. package/dist/adapters/lark/lark.controller.js.map +1 -0
  37. package/dist/adapters/slack/slack.adapter.d.ts +19 -0
  38. package/dist/adapters/slack/slack.adapter.d.ts.map +1 -0
  39. package/dist/adapters/slack/slack.adapter.js +42 -0
  40. package/dist/adapters/slack/slack.adapter.js.map +1 -0
  41. package/dist/app.module.d.ts +5 -0
  42. package/dist/app.module.d.ts.map +1 -0
  43. package/dist/app.module.js +48 -0
  44. package/dist/app.module.js.map +1 -0
  45. package/dist/auth/auth.controller.d.ts +15 -0
  46. package/dist/auth/auth.controller.d.ts.map +1 -0
  47. package/dist/auth/auth.controller.js +67 -0
  48. package/dist/auth/auth.controller.js.map +1 -0
  49. package/dist/auth/auth.module.d.ts +3 -0
  50. package/dist/auth/auth.module.d.ts.map +1 -0
  51. package/dist/auth/auth.module.js +46 -0
  52. package/dist/auth/auth.module.js.map +1 -0
  53. package/dist/auth/auth.service.d.ts +62 -0
  54. package/dist/auth/auth.service.d.ts.map +1 -0
  55. package/dist/auth/auth.service.js +307 -0
  56. package/dist/auth/auth.service.js.map +1 -0
  57. package/dist/auth/decorators/allow-totp-setup.decorator.d.ts +3 -0
  58. package/dist/auth/decorators/allow-totp-setup.decorator.d.ts.map +1 -0
  59. package/dist/auth/decorators/allow-totp-setup.decorator.js +8 -0
  60. package/dist/auth/decorators/allow-totp-setup.decorator.js.map +1 -0
  61. package/dist/auth/decorators/project-roles.decorator.d.ts +4 -0
  62. package/dist/auth/decorators/project-roles.decorator.d.ts.map +1 -0
  63. package/dist/auth/decorators/project-roles.decorator.js +8 -0
  64. package/dist/auth/decorators/project-roles.decorator.js.map +1 -0
  65. package/dist/auth/decorators/roles.decorator.d.ts +4 -0
  66. package/dist/auth/decorators/roles.decorator.d.ts.map +1 -0
  67. package/dist/auth/decorators/roles.decorator.js +8 -0
  68. package/dist/auth/decorators/roles.decorator.js.map +1 -0
  69. package/dist/auth/extract-user.middleware.d.ts +21 -0
  70. package/dist/auth/extract-user.middleware.d.ts.map +1 -0
  71. package/dist/auth/extract-user.middleware.js +57 -0
  72. package/dist/auth/extract-user.middleware.js.map +1 -0
  73. package/dist/auth/guards/jwt-auth.guard.d.ts +14 -0
  74. package/dist/auth/guards/jwt-auth.guard.d.ts.map +1 -0
  75. package/dist/auth/guards/jwt-auth.guard.js +139 -0
  76. package/dist/auth/guards/jwt-auth.guard.js.map +1 -0
  77. package/dist/auth/guards/project-role.guard.d.ts +10 -0
  78. package/dist/auth/guards/project-role.guard.d.ts.map +1 -0
  79. package/dist/auth/guards/project-role.guard.js +72 -0
  80. package/dist/auth/guards/project-role.guard.js.map +1 -0
  81. package/dist/auth/guards/roles.guard.d.ts +8 -0
  82. package/dist/auth/guards/roles.guard.d.ts.map +1 -0
  83. package/dist/auth/guards/roles.guard.js +56 -0
  84. package/dist/auth/guards/roles.guard.js.map +1 -0
  85. package/dist/auth/jwt.strategy.d.ts +23 -0
  86. package/dist/auth/jwt.strategy.d.ts.map +1 -0
  87. package/dist/auth/jwt.strategy.js +49 -0
  88. package/dist/auth/jwt.strategy.js.map +1 -0
  89. package/dist/common/crypto.service.d.ts +31 -0
  90. package/dist/common/crypto.service.d.ts.map +1 -0
  91. package/dist/common/crypto.service.js +120 -0
  92. package/dist/common/crypto.service.js.map +1 -0
  93. package/dist/common/error-filter.d.ts +6 -0
  94. package/dist/common/error-filter.d.ts.map +1 -0
  95. package/dist/common/error-filter.js +78 -0
  96. package/dist/common/error-filter.js.map +1 -0
  97. package/dist/common/health.controller.d.ts +13 -0
  98. package/dist/common/health.controller.d.ts.map +1 -0
  99. package/dist/common/health.controller.js +75 -0
  100. package/dist/common/health.controller.js.map +1 -0
  101. package/dist/common/logger.service.d.ts +11 -0
  102. package/dist/common/logger.service.d.ts.map +1 -0
  103. package/dist/common/logger.service.js +48 -0
  104. package/dist/common/logger.service.js.map +1 -0
  105. package/dist/common/pagination.d.ts +18 -0
  106. package/dist/common/pagination.d.ts.map +1 -0
  107. package/dist/common/pagination.js +39 -0
  108. package/dist/common/pagination.js.map +1 -0
  109. package/dist/common/rate-limit.guard.d.ts +48 -0
  110. package/dist/common/rate-limit.guard.d.ts.map +1 -0
  111. package/dist/common/rate-limit.guard.js +129 -0
  112. package/dist/common/rate-limit.guard.js.map +1 -0
  113. package/dist/common/sensitive-filter.d.ts +7 -0
  114. package/dist/common/sensitive-filter.d.ts.map +1 -0
  115. package/dist/common/sensitive-filter.js +20 -0
  116. package/dist/common/sensitive-filter.js.map +1 -0
  117. package/dist/database/database.module.d.ts +3 -0
  118. package/dist/database/database.module.d.ts.map +1 -0
  119. package/dist/database/database.module.js +22 -0
  120. package/dist/database/database.module.js.map +1 -0
  121. package/dist/database/database.service.d.ts +13 -0
  122. package/dist/database/database.service.d.ts.map +1 -0
  123. package/dist/database/database.service.js +107 -0
  124. package/dist/database/database.service.js.map +1 -0
  125. package/dist/database/migration-runner.d.ts +5 -0
  126. package/dist/database/migration-runner.d.ts.map +1 -0
  127. package/dist/database/migration-runner.js +86 -0
  128. package/dist/database/migration-runner.js.map +1 -0
  129. package/dist/database/repositories/audit-log.repository.d.ts +29 -0
  130. package/dist/database/repositories/audit-log.repository.d.ts.map +1 -0
  131. package/dist/database/repositories/audit-log.repository.js +80 -0
  132. package/dist/database/repositories/audit-log.repository.js.map +1 -0
  133. package/dist/database/repositories/bot.repository.d.ts +67 -0
  134. package/dist/database/repositories/bot.repository.d.ts.map +1 -0
  135. package/dist/database/repositories/bot.repository.js +133 -0
  136. package/dist/database/repositories/bot.repository.js.map +1 -0
  137. package/dist/database/repositories/developer-token.repository.d.ts +40 -0
  138. package/dist/database/repositories/developer-token.repository.d.ts.map +1 -0
  139. package/dist/database/repositories/developer-token.repository.js +84 -0
  140. package/dist/database/repositories/developer-token.repository.js.map +1 -0
  141. package/dist/database/repositories/developer.repository.d.ts +25 -0
  142. package/dist/database/repositories/developer.repository.d.ts.map +1 -0
  143. package/dist/database/repositories/developer.repository.js +139 -0
  144. package/dist/database/repositories/developer.repository.js.map +1 -0
  145. package/dist/database/repositories/machine.repository.d.ts +39 -0
  146. package/dist/database/repositories/machine.repository.d.ts.map +1 -0
  147. package/dist/database/repositories/machine.repository.js +176 -0
  148. package/dist/database/repositories/machine.repository.js.map +1 -0
  149. package/dist/database/repositories/notification.repository.d.ts +19 -0
  150. package/dist/database/repositories/notification.repository.d.ts.map +1 -0
  151. package/dist/database/repositories/notification.repository.js +94 -0
  152. package/dist/database/repositories/notification.repository.js.map +1 -0
  153. package/dist/database/repositories/project-member.repository.d.ts +30 -0
  154. package/dist/database/repositories/project-member.repository.d.ts.map +1 -0
  155. package/dist/database/repositories/project-member.repository.js +75 -0
  156. package/dist/database/repositories/project-member.repository.js.map +1 -0
  157. package/dist/database/repositories/project.repository.d.ts +24 -0
  158. package/dist/database/repositories/project.repository.d.ts.map +1 -0
  159. package/dist/database/repositories/project.repository.js +154 -0
  160. package/dist/database/repositories/project.repository.js.map +1 -0
  161. package/dist/database/repositories/session.repository.d.ts +19 -0
  162. package/dist/database/repositories/session.repository.d.ts.map +1 -0
  163. package/dist/database/repositories/session.repository.js +117 -0
  164. package/dist/database/repositories/session.repository.js.map +1 -0
  165. package/dist/database/repositories/task.repository.d.ts +37 -0
  166. package/dist/database/repositories/task.repository.d.ts.map +1 -0
  167. package/dist/database/repositories/task.repository.js +229 -0
  168. package/dist/database/repositories/task.repository.js.map +1 -0
  169. package/dist/database/repositories/worker-token.repository.d.ts +20 -0
  170. package/dist/database/repositories/worker-token.repository.d.ts.map +1 -0
  171. package/dist/database/repositories/worker-token.repository.js +94 -0
  172. package/dist/database/repositories/worker-token.repository.js.map +1 -0
  173. package/dist/database/repositories/workspace.repository.d.ts +19 -0
  174. package/dist/database/repositories/workspace.repository.d.ts.map +1 -0
  175. package/dist/database/repositories/workspace.repository.js +82 -0
  176. package/dist/database/repositories/workspace.repository.js.map +1 -0
  177. package/dist/dispatcher/capability.service.d.ts +50 -0
  178. package/dist/dispatcher/capability.service.d.ts.map +1 -0
  179. package/dist/dispatcher/capability.service.js +159 -0
  180. package/dist/dispatcher/capability.service.js.map +1 -0
  181. package/dist/dispatcher/cleanup.service.d.ts +23 -0
  182. package/dist/dispatcher/cleanup.service.d.ts.map +1 -0
  183. package/dist/dispatcher/cleanup.service.js +107 -0
  184. package/dist/dispatcher/cleanup.service.js.map +1 -0
  185. package/dist/dispatcher/dedup.service.d.ts +48 -0
  186. package/dist/dispatcher/dedup.service.d.ts.map +1 -0
  187. package/dist/dispatcher/dedup.service.js +189 -0
  188. package/dist/dispatcher/dedup.service.js.map +1 -0
  189. package/dist/dispatcher/dispatcher.module.d.ts +3 -0
  190. package/dist/dispatcher/dispatcher.module.d.ts.map +1 -0
  191. package/dist/dispatcher/dispatcher.module.js +76 -0
  192. package/dist/dispatcher/dispatcher.module.js.map +1 -0
  193. package/dist/dispatcher/dispatcher.service.d.ts +134 -0
  194. package/dist/dispatcher/dispatcher.service.d.ts.map +1 -0
  195. package/dist/dispatcher/dispatcher.service.js +1034 -0
  196. package/dist/dispatcher/dispatcher.service.js.map +1 -0
  197. package/dist/dispatcher/heartbeat.service.d.ts +50 -0
  198. package/dist/dispatcher/heartbeat.service.d.ts.map +1 -0
  199. package/dist/dispatcher/heartbeat.service.js +154 -0
  200. package/dist/dispatcher/heartbeat.service.js.map +1 -0
  201. package/dist/dispatcher/machine-selector.d.ts +18 -0
  202. package/dist/dispatcher/machine-selector.d.ts.map +1 -0
  203. package/dist/dispatcher/machine-selector.js +144 -0
  204. package/dist/dispatcher/machine-selector.js.map +1 -0
  205. package/dist/dispatcher/pty-relay.service.d.ts +75 -0
  206. package/dist/dispatcher/pty-relay.service.d.ts.map +1 -0
  207. package/dist/dispatcher/pty-relay.service.js +404 -0
  208. package/dist/dispatcher/pty-relay.service.js.map +1 -0
  209. package/dist/dispatcher/reconciler.d.ts +39 -0
  210. package/dist/dispatcher/reconciler.d.ts.map +1 -0
  211. package/dist/dispatcher/reconciler.js +556 -0
  212. package/dist/dispatcher/reconciler.js.map +1 -0
  213. package/dist/dispatcher/scheduler.service.d.ts +50 -0
  214. package/dist/dispatcher/scheduler.service.d.ts.map +1 -0
  215. package/dist/dispatcher/scheduler.service.js +287 -0
  216. package/dist/dispatcher/scheduler.service.js.map +1 -0
  217. package/dist/dispatcher/state-machine.d.ts +16 -0
  218. package/dist/dispatcher/state-machine.d.ts.map +1 -0
  219. package/dist/dispatcher/state-machine.js +77 -0
  220. package/dist/dispatcher/state-machine.js.map +1 -0
  221. package/dist/dispatcher/task-log-batcher.d.ts +50 -0
  222. package/dist/dispatcher/task-log-batcher.d.ts.map +1 -0
  223. package/dist/dispatcher/task-log-batcher.js +184 -0
  224. package/dist/dispatcher/task-log-batcher.js.map +1 -0
  225. package/dist/dispatcher/worker-connection.manager.d.ts +49 -0
  226. package/dist/dispatcher/worker-connection.manager.d.ts.map +1 -0
  227. package/dist/dispatcher/worker-connection.manager.js +128 -0
  228. package/dist/dispatcher/worker-connection.manager.js.map +1 -0
  229. package/dist/main.d.ts +2 -0
  230. package/dist/main.d.ts.map +1 -0
  231. package/dist/main.js +85 -0
  232. package/dist/main.js.map +1 -0
  233. package/dist/notifier/debouncer.d.ts +39 -0
  234. package/dist/notifier/debouncer.d.ts.map +1 -0
  235. package/dist/notifier/debouncer.js +123 -0
  236. package/dist/notifier/debouncer.js.map +1 -0
  237. package/dist/notifier/notification-consumer.d.ts +88 -0
  238. package/dist/notifier/notification-consumer.d.ts.map +1 -0
  239. package/dist/notifier/notification-consumer.js +186 -0
  240. package/dist/notifier/notification-consumer.js.map +1 -0
  241. package/dist/notifier/notifier.module.d.ts +9 -0
  242. package/dist/notifier/notifier.module.d.ts.map +1 -0
  243. package/dist/notifier/notifier.module.js +58 -0
  244. package/dist/notifier/notifier.module.js.map +1 -0
  245. package/dist/notifier/notifier.service.d.ts +40 -0
  246. package/dist/notifier/notifier.service.d.ts.map +1 -0
  247. package/dist/notifier/notifier.service.js +191 -0
  248. package/dist/notifier/notifier.service.js.map +1 -0
  249. package/dist/notifier/template.service.d.ts +42 -0
  250. package/dist/notifier/template.service.d.ts.map +1 -0
  251. package/dist/notifier/template.service.js +201 -0
  252. package/dist/notifier/template.service.js.map +1 -0
  253. package/dist/redis/redis.module.d.ts +3 -0
  254. package/dist/redis/redis.module.d.ts.map +1 -0
  255. package/dist/redis/redis.module.js +22 -0
  256. package/dist/redis/redis.module.js.map +1 -0
  257. package/dist/redis/redis.service.d.ts +19 -0
  258. package/dist/redis/redis.service.d.ts.map +1 -0
  259. package/dist/redis/redis.service.js +69 -0
  260. package/dist/redis/redis.service.js.map +1 -0
  261. package/dist/web/admin/admin-audit.controller.d.ts +7 -0
  262. package/dist/web/admin/admin-audit.controller.d.ts.map +1 -0
  263. package/dist/web/admin/admin-audit.controller.js +53 -0
  264. package/dist/web/admin/admin-audit.controller.js.map +1 -0
  265. package/dist/web/admin/admin-bot.controller.d.ts +79 -0
  266. package/dist/web/admin/admin-bot.controller.d.ts.map +1 -0
  267. package/dist/web/admin/admin-bot.controller.js +193 -0
  268. package/dist/web/admin/admin-bot.controller.js.map +1 -0
  269. package/dist/web/admin/admin-developer.controller.d.ts +52 -0
  270. package/dist/web/admin/admin-developer.controller.d.ts.map +1 -0
  271. package/dist/web/admin/admin-developer.controller.js +160 -0
  272. package/dist/web/admin/admin-developer.controller.js.map +1 -0
  273. package/dist/web/admin/admin-machine.controller.d.ts +64 -0
  274. package/dist/web/admin/admin-machine.controller.d.ts.map +1 -0
  275. package/dist/web/admin/admin-machine.controller.js +111 -0
  276. package/dist/web/admin/admin-machine.controller.js.map +1 -0
  277. package/dist/web/admin/admin-project.controller.d.ts +45 -0
  278. package/dist/web/admin/admin-project.controller.d.ts.map +1 -0
  279. package/dist/web/admin/admin-project.controller.js +207 -0
  280. package/dist/web/admin/admin-project.controller.js.map +1 -0
  281. package/dist/web/admin/admin-settings.controller.d.ts +18 -0
  282. package/dist/web/admin/admin-settings.controller.d.ts.map +1 -0
  283. package/dist/web/admin/admin-settings.controller.js +93 -0
  284. package/dist/web/admin/admin-settings.controller.js.map +1 -0
  285. package/dist/web/admin/admin-token.controller.d.ts +45 -0
  286. package/dist/web/admin/admin-token.controller.d.ts.map +1 -0
  287. package/dist/web/admin/admin-token.controller.js +182 -0
  288. package/dist/web/admin/admin-token.controller.js.map +1 -0
  289. package/dist/web/dashboard.controller.d.ts +16 -0
  290. package/dist/web/dashboard.controller.d.ts.map +1 -0
  291. package/dist/web/dashboard.controller.js +78 -0
  292. package/dist/web/dashboard.controller.js.map +1 -0
  293. package/dist/web/dashboard.service.d.ts +39 -0
  294. package/dist/web/dashboard.service.d.ts.map +1 -0
  295. package/dist/web/dashboard.service.js +234 -0
  296. package/dist/web/dashboard.service.js.map +1 -0
  297. package/dist/web/interaction.service.d.ts +42 -0
  298. package/dist/web/interaction.service.d.ts.map +1 -0
  299. package/dist/web/interaction.service.js +102 -0
  300. package/dist/web/interaction.service.js.map +1 -0
  301. package/dist/web/machine.controller.d.ts +102 -0
  302. package/dist/web/machine.controller.d.ts.map +1 -0
  303. package/dist/web/machine.controller.js +121 -0
  304. package/dist/web/machine.controller.js.map +1 -0
  305. package/dist/web/notification.controller.d.ts +22 -0
  306. package/dist/web/notification.controller.d.ts.map +1 -0
  307. package/dist/web/notification.controller.js +70 -0
  308. package/dist/web/notification.controller.js.map +1 -0
  309. package/dist/web/profile.controller.d.ts +70 -0
  310. package/dist/web/profile.controller.d.ts.map +1 -0
  311. package/dist/web/profile.controller.js +262 -0
  312. package/dist/web/profile.controller.js.map +1 -0
  313. package/dist/web/project.controller.d.ts +8 -0
  314. package/dist/web/project.controller.d.ts.map +1 -0
  315. package/dist/web/project.controller.js +54 -0
  316. package/dist/web/project.controller.js.map +1 -0
  317. package/dist/web/pty.gateway.d.ts +32 -0
  318. package/dist/web/pty.gateway.d.ts.map +1 -0
  319. package/dist/web/pty.gateway.js +358 -0
  320. package/dist/web/pty.gateway.js.map +1 -0
  321. package/dist/web/search.service.d.ts +34 -0
  322. package/dist/web/search.service.d.ts.map +1 -0
  323. package/dist/web/search.service.js +106 -0
  324. package/dist/web/search.service.js.map +1 -0
  325. package/dist/web/task.controller.d.ts +54 -0
  326. package/dist/web/task.controller.d.ts.map +1 -0
  327. package/dist/web/task.controller.js +266 -0
  328. package/dist/web/task.controller.js.map +1 -0
  329. package/dist/web/web.module.d.ts +3 -0
  330. package/dist/web/web.module.d.ts.map +1 -0
  331. package/dist/web/web.module.js +97 -0
  332. package/dist/web/web.module.js.map +1 -0
  333. package/dist/web/worker-channel.gateway.d.ts +45 -0
  334. package/dist/web/worker-channel.gateway.d.ts.map +1 -0
  335. package/dist/web/worker-channel.gateway.js +283 -0
  336. package/dist/web/worker-channel.gateway.js.map +1 -0
  337. package/dist/web/worker.controller.d.ts +14 -0
  338. package/dist/web/worker.controller.d.ts.map +1 -0
  339. package/dist/web/worker.controller.js +73 -0
  340. package/dist/web/worker.controller.js.map +1 -0
  341. package/dist/web/workspace.controller.d.ts +109 -0
  342. package/dist/web/workspace.controller.d.ts.map +1 -0
  343. package/dist/web/workspace.controller.js +386 -0
  344. package/dist/web/workspace.controller.js.map +1 -0
  345. package/package.json +61 -0
@@ -0,0 +1,1034 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ var DispatcherService_1;
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.DispatcherService = void 0;
47
+ const common_1 = require("@nestjs/common");
48
+ const crypto = __importStar(require("node:crypto"));
49
+ const protocol_1 = require("@overlordai/protocol");
50
+ const task_repository_1 = require("../database/repositories/task.repository");
51
+ const machine_repository_1 = require("../database/repositories/machine.repository");
52
+ const project_repository_1 = require("../database/repositories/project.repository");
53
+ const project_member_repository_1 = require("../database/repositories/project-member.repository");
54
+ const worker_token_repository_1 = require("../database/repositories/worker-token.repository");
55
+ const audit_log_repository_1 = require("../database/repositories/audit-log.repository");
56
+ const developer_repository_1 = require("../database/repositories/developer.repository");
57
+ const database_service_1 = require("../database/database.service");
58
+ const auth_service_1 = require("../auth/auth.service");
59
+ const crypto_service_1 = require("../common/crypto.service");
60
+ const redis_service_1 = require("../redis/redis.service");
61
+ const state_machine_1 = require("./state-machine");
62
+ const dedup_service_1 = require("./dedup.service");
63
+ const scheduler_service_1 = require("./scheduler.service");
64
+ const worker_connection_manager_1 = require("./worker-connection.manager");
65
+ const cleanup_service_1 = require("./cleanup.service");
66
+ const workspace_repository_1 = require("../database/repositories/workspace.repository");
67
+ let DispatcherService = DispatcherService_1 = class DispatcherService {
68
+ taskRepo;
69
+ machineRepo;
70
+ projectRepo;
71
+ projectMemberRepo;
72
+ workerTokenRepo;
73
+ auditLogRepo;
74
+ developerRepo;
75
+ databaseService;
76
+ authService;
77
+ cryptoService;
78
+ redis;
79
+ dedupService;
80
+ schedulerService;
81
+ workerConnectionManager;
82
+ cleanupService;
83
+ workspaceRepo;
84
+ logger = new common_1.Logger(DispatcherService_1.name);
85
+ constructor(taskRepo, machineRepo, projectRepo, projectMemberRepo, workerTokenRepo, auditLogRepo, developerRepo, databaseService, authService, cryptoService, redis, dedupService, schedulerService, workerConnectionManager, cleanupService, workspaceRepo) {
86
+ this.taskRepo = taskRepo;
87
+ this.machineRepo = machineRepo;
88
+ this.projectRepo = projectRepo;
89
+ this.projectMemberRepo = projectMemberRepo;
90
+ this.workerTokenRepo = workerTokenRepo;
91
+ this.auditLogRepo = auditLogRepo;
92
+ this.developerRepo = developerRepo;
93
+ this.databaseService = databaseService;
94
+ this.authService = authService;
95
+ this.cryptoService = cryptoService;
96
+ this.redis = redis;
97
+ this.dedupService = dedupService;
98
+ this.schedulerService = schedulerService;
99
+ this.workerConnectionManager = workerConnectionManager;
100
+ this.cleanupService = cleanupService;
101
+ this.workspaceRepo = workspaceRepo;
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // handleCommand — unified entry point from adapters
105
+ // ---------------------------------------------------------------------------
106
+ async handleCommand(command) {
107
+ // Event-level dedup: checkEventDedup returns true if this is the first
108
+ // occurrence (Redis SET NX succeeded). False means the key already existed.
109
+ const isFirstOccurrence = await this.dedupService.checkEventDedup(command.eventId);
110
+ if (!isFirstOccurrence) {
111
+ this.logger.debug(`Duplicate event ${command.eventId}, discarding`);
112
+ return { success: false, message: 'Duplicate event' };
113
+ }
114
+ switch (command.type) {
115
+ case protocol_1.CommandType.DEVELOP:
116
+ return this.handleDevelopCommand(command);
117
+ case protocol_1.CommandType.CANCEL:
118
+ return this.handleCancelCommand(command);
119
+ case protocol_1.CommandType.RETRY:
120
+ return this.handleRetryCommand(command);
121
+ case protocol_1.CommandType.CONFIRM:
122
+ return this.handleConfirmCommand(command);
123
+ case protocol_1.CommandType.LIST:
124
+ return this.handleListCommand(command);
125
+ case protocol_1.CommandType.PROGRESS: {
126
+ const taskId = command.args.taskId;
127
+ if (!taskId) {
128
+ return { success: false, message: 'taskId is required' };
129
+ }
130
+ const task = this.taskRepo.findById(taskId);
131
+ if (!task) {
132
+ return { success: false, message: `Task #${taskId} not found` };
133
+ }
134
+ return { success: true, task };
135
+ }
136
+ case protocol_1.CommandType.LOGS: {
137
+ const taskId = command.args.taskId;
138
+ if (!taskId) {
139
+ return { success: false, message: 'taskId is required' };
140
+ }
141
+ const task = this.taskRepo.findById(taskId);
142
+ if (!task) {
143
+ return { success: false, message: `Task #${taskId} not found` };
144
+ }
145
+ return { success: true, task };
146
+ }
147
+ case protocol_1.CommandType.DRAIN:
148
+ return this.handleDrainCommand(command, true);
149
+ case protocol_1.CommandType.UNDRAIN:
150
+ return this.handleDrainCommand(command, false);
151
+ case protocol_1.CommandType.WORKSPACE: {
152
+ const taskId = command.args.taskId;
153
+ if (!taskId) {
154
+ return { success: false, message: 'taskId is required' };
155
+ }
156
+ const task = this.taskRepo.findById(taskId);
157
+ if (!task) {
158
+ return { success: false, message: `Task #${taskId} not found` };
159
+ }
160
+ return { success: true, task };
161
+ }
162
+ default:
163
+ return { success: false, message: `Unknown command type: ${command.type}` };
164
+ }
165
+ }
166
+ // ---------------------------------------------------------------------------
167
+ // Command handlers
168
+ // ---------------------------------------------------------------------------
169
+ async handleDevelopCommand(command) {
170
+ const { description, project: projectKeyOrAlias } = command.args;
171
+ if (!description) {
172
+ return { success: false, message: 'Description is required' };
173
+ }
174
+ const request = {
175
+ description,
176
+ projectKey: projectKeyOrAlias ?? '',
177
+ machineId: command.args.machine,
178
+ developerId: command.user.id,
179
+ };
180
+ try {
181
+ const task = await this.createTask(request, command.user.id, {
182
+ platform: command.platform,
183
+ msgId: command.source.msgId,
184
+ chatId: command.source.chatId,
185
+ appId: command.source.appId,
186
+ });
187
+ return { success: true, task };
188
+ }
189
+ catch (err) {
190
+ if (err instanceof common_1.ConflictException) {
191
+ // Fingerprint dedup — check if there is a pending confirm
192
+ const body = err.getResponse();
193
+ if (body && typeof body === 'object' && body['confirmRequired']) {
194
+ return {
195
+ success: false,
196
+ confirmRequired: body['confirmRequired'],
197
+ };
198
+ }
199
+ }
200
+ throw err;
201
+ }
202
+ }
203
+ async handleCancelCommand(command) {
204
+ const taskId = command.args.taskId;
205
+ if (!taskId) {
206
+ return { success: false, message: 'taskId is required' };
207
+ }
208
+ await this.cancelTask(taskId, command.user.id);
209
+ const task = this.taskRepo.findById(taskId);
210
+ return { success: true, task, message: `Task #${taskId} cancelled` };
211
+ }
212
+ async handleRetryCommand(command) {
213
+ const taskId = command.args.taskId;
214
+ if (!taskId) {
215
+ return { success: false, message: 'taskId is required' };
216
+ }
217
+ try {
218
+ const task = await this.retryTask(taskId, command.user.id);
219
+ return { success: true, task, message: `Task #${taskId} retried` };
220
+ }
221
+ catch (err) {
222
+ if (err instanceof common_1.ConflictException) {
223
+ const body = err.getResponse();
224
+ if (body && typeof body === 'object' && body['confirmRequired']) {
225
+ return {
226
+ success: false,
227
+ confirmRequired: body['confirmRequired'],
228
+ };
229
+ }
230
+ }
231
+ throw err;
232
+ }
233
+ }
234
+ async handleConfirmCommand(command) {
235
+ const taskId = command.args.taskId;
236
+ if (!taskId) {
237
+ return { success: false, message: 'taskId is required' };
238
+ }
239
+ // Resolve pending confirm from Redis.
240
+ // Priority: reply-to msgId association, fallback to #taskId lookup.
241
+ const pendingData = await this.resolvePendingConfirm(taskId, command.user.id, command.source.msgId);
242
+ if (!pendingData) {
243
+ return { success: false, message: 'No pending confirmation found or expired' };
244
+ }
245
+ if (pendingData.action === 'create_task' && pendingData.request) {
246
+ // Confirmed dedup override — create the task anyway
247
+ const task = await this.createTaskInternal(pendingData.request, pendingData.createdBy, pendingData.source, true);
248
+ return { success: true, task, message: 'Task created (dedup override)' };
249
+ }
250
+ if (pendingData.action === 'retry' && pendingData.taskId) {
251
+ // Confirmed retry with existing MR
252
+ const task = await this.executeRetry(pendingData.taskId, pendingData.createdBy);
253
+ return { success: true, task, message: `Task #${pendingData.taskId} retried (MR override)` };
254
+ }
255
+ return { success: false, message: 'Unknown confirm action' };
256
+ }
257
+ handleListCommand(command) {
258
+ const projectKey = command.args.project;
259
+ const tasks = this.taskRepo.findByStatus([
260
+ protocol_1.TaskStatus.QUEUED,
261
+ protocol_1.TaskStatus.ASSIGNED,
262
+ protocol_1.TaskStatus.RUNNING,
263
+ protocol_1.TaskStatus.SUSPENDED,
264
+ ]);
265
+ const filtered = projectKey
266
+ ? tasks.filter((t) => t.projectKey === projectKey)
267
+ : tasks;
268
+ return { success: true, tasks: filtered };
269
+ }
270
+ handleDrainCommand(command, drain) {
271
+ const machineName = command.args.machine;
272
+ if (!machineName) {
273
+ return { success: false, message: 'Machine name is required' };
274
+ }
275
+ const machine = this.machineRepo.findByName(machineName);
276
+ if (!machine) {
277
+ return { success: false, message: `Machine '${machineName}' not found` };
278
+ }
279
+ const developer = this.developerRepo.findById(command.user.id);
280
+ if (!developer || developer.role !== protocol_1.DeveloperRole.ADMIN) {
281
+ return { success: false, message: 'Only admins can drain/undrain machines' };
282
+ }
283
+ const newStatus = drain ? protocol_1.MachineStatus.DRAINING : protocol_1.MachineStatus.ONLINE;
284
+ this.machineRepo.updateStatus(machine.id, newStatus);
285
+ this.auditLogRepo.create({
286
+ userId: command.user.id,
287
+ action: drain ? 'machine_drain' : 'machine_undrain',
288
+ resource: `machine:${machine.id}`,
289
+ detail: `Machine '${machine.name}' ${drain ? 'drained' : 'undrained'}`,
290
+ });
291
+ return { success: true, message: `Machine '${machine.name}' ${drain ? 'drained' : 'undrained'}` };
292
+ }
293
+ // ---------------------------------------------------------------------------
294
+ // createTask
295
+ // ---------------------------------------------------------------------------
296
+ async createTask(request, createdBy, source) {
297
+ return this.createTaskInternal(request, createdBy, source, false);
298
+ }
299
+ async createTaskInternal(request, createdBy, source, skipDedup = false) {
300
+ // 1. Find project by key or alias
301
+ const project = this.resolveProject(request.projectKey);
302
+ if (!project) {
303
+ throw new common_1.NotFoundException(`Project '${request.projectKey}' not found`);
304
+ }
305
+ // 2. Authorization: check developer is a project member (or admin bypass)
306
+ const developer = this.developerRepo.findById(createdBy);
307
+ if (!developer) {
308
+ throw new common_1.NotFoundException(`Developer #${createdBy} not found`);
309
+ }
310
+ if (developer.role !== protocol_1.DeveloperRole.ADMIN) {
311
+ const membership = this.projectMemberRepo.findByProjectAndDeveloper(project.key, createdBy);
312
+ if (!membership) {
313
+ throw new common_1.ForbiddenException(`Developer '${developer.name}' is not a member of project '${project.key}'`);
314
+ }
315
+ }
316
+ // 3. Compute fingerprint and check dedup
317
+ const fingerprint = this.dedupService.computeFingerprint(project.key, request.description);
318
+ if (!skipDedup) {
319
+ const existingTask = this.taskRepo.findActiveByFingerprint(fingerprint);
320
+ if (existingTask) {
321
+ // Store pending confirm in Redis
322
+ const confirmKey = `pending_confirm:dedup:${crypto.randomUUID()}`;
323
+ const pendingData = JSON.stringify({
324
+ action: 'create_task',
325
+ request: { ...request, projectKey: project.key },
326
+ createdBy,
327
+ source,
328
+ existingTaskId: existingTask.id,
329
+ });
330
+ await this.redis.setex(confirmKey, protocol_1.CONFIRM_PENDING_TTL_SEC, pendingData);
331
+ // Store index from taskId to confirmKey for lookup during confirm
332
+ await this.redis.setex(`pending_confirm_idx:task:${existingTask.id}`, protocol_1.CONFIRM_PENDING_TTL_SEC, confirmKey);
333
+ throw new common_1.ConflictException({
334
+ message: `A similar task already exists: #${existingTask.id} (${existingTask.status})`,
335
+ confirmRequired: {
336
+ reason: 'duplicate_fingerprint',
337
+ message: `Task #${existingTask.id} with the same description is already ${existingTask.status}. Create anyway?`,
338
+ pendingAction: 'create_task',
339
+ taskId: existingTask.id,
340
+ confirmKey,
341
+ },
342
+ });
343
+ }
344
+ }
345
+ // 4. Snapshot config (14 fields from project)
346
+ const configSnapshot = {
347
+ repoUrl: project.repoUrl,
348
+ gitPlatform: project.gitPlatform,
349
+ defaultBranch: project.defaultBranch,
350
+ workspaceRoot: project.workspaceRoot,
351
+ setupCommands: project.setupCommands,
352
+ testCommand: project.testCommand,
353
+ agentType: project.agentType,
354
+ agentCommand: project.agentCommand,
355
+ agentEnv: project.agentEnv,
356
+ allowedTools: project.allowedTools,
357
+ maxTurns: project.maxTurns,
358
+ skillsPath: project.skillsPath,
359
+ pipeline: project.pipeline,
360
+ ptyOutputFilter: project.ptyOutputFilter,
361
+ };
362
+ // 5. Insert task (QUEUED)
363
+ let task;
364
+ try {
365
+ task = this.taskRepo.create({
366
+ description: request.description,
367
+ fingerprint,
368
+ projectKey: project.key,
369
+ machineId: request.machineId,
370
+ developerId: request.developerId ?? createdBy,
371
+ configSnapshot: JSON.stringify(configSnapshot),
372
+ sourcePlatform: source?.platform,
373
+ sourceMsgId: source?.msgId,
374
+ sourceChatId: source?.chatId,
375
+ sourceAppId: source?.appId,
376
+ createdBy,
377
+ });
378
+ }
379
+ catch (err) {
380
+ // Catch UNIQUE constraint violation on fingerprint (concurrent create)
381
+ const errMsg = err instanceof Error ? err.message : String(err);
382
+ if (errMsg.includes('UNIQUE') || errMsg.includes('unique')) {
383
+ throw new common_1.ConflictException('A task with the same fingerprint was just created concurrently');
384
+ }
385
+ throw err;
386
+ }
387
+ // 6. Enqueue to BullMQ
388
+ await this.schedulerService.enqueueTask(task.id);
389
+ this.logger.log(`Task #${task.id} created for project '${project.key}' by developer #${createdBy}`);
390
+ this.auditLogRepo.create({
391
+ userId: createdBy,
392
+ action: 'task_create',
393
+ resource: `task:${task.id}`,
394
+ detail: `Created task for project '${project.key}': ${request.description.slice(0, 100)}`,
395
+ });
396
+ return task;
397
+ }
398
+ // ---------------------------------------------------------------------------
399
+ // cancelTask
400
+ // ---------------------------------------------------------------------------
401
+ async cancelTask(taskId, cancelledBy) {
402
+ const task = this.taskRepo.findById(taskId);
403
+ if (!task) {
404
+ throw new common_1.NotFoundException(`Task #${taskId} not found`);
405
+ }
406
+ // Validate transition
407
+ state_machine_1.TaskStateMachine.assertTransition(task.status, protocol_1.TaskStatus.CANCELLED);
408
+ switch (task.status) {
409
+ case protocol_1.TaskStatus.QUEUED: {
410
+ // Remove from BullMQ — task stays QUEUED until we set CANCELLED below
411
+ this.updateTaskStatusWithRetry(taskId, protocol_1.TaskStatus.CANCELLED, task.revision);
412
+ break;
413
+ }
414
+ case protocol_1.TaskStatus.RUNNING: {
415
+ // Send cancel to Worker via WS, wait ack with timeout
416
+ if (task.machineId) {
417
+ const acked = await this.sendCancelToWorkerWithAck(task.machineId, taskId);
418
+ if (!acked) {
419
+ this.logger.warn(`Cancel ack timeout for task #${taskId} on machine ${task.machineId}, forcing cancel`);
420
+ }
421
+ }
422
+ // Reload task to get latest revision (may have changed during ack wait)
423
+ const freshTask = this.taskRepo.findById(taskId);
424
+ if (freshTask && freshTask.status !== protocol_1.TaskStatus.CANCELLED) {
425
+ this.updateTaskStatusWithRetry(taskId, protocol_1.TaskStatus.CANCELLED, freshTask.revision, { completedAt: new Date().toISOString() });
426
+ }
427
+ break;
428
+ }
429
+ case protocol_1.TaskStatus.ASSIGNED: {
430
+ // Re-read to catch ASSIGNED->RUNNING race between initial read and CAS
431
+ const freshAssigned = this.taskRepo.findById(taskId);
432
+ if (!freshAssigned || freshAssigned.status === protocol_1.TaskStatus.CANCELLED) {
433
+ break;
434
+ }
435
+ state_machine_1.TaskStateMachine.assertTransition(freshAssigned.status, protocol_1.TaskStatus.CANCELLED);
436
+ // Send cancel to Worker if assigned, set CANCELLED
437
+ if (freshAssigned.machineId) {
438
+ this.sendCancelToWorker(freshAssigned.machineId, taskId);
439
+ }
440
+ this.updateTaskStatusWithRetry(taskId, protocol_1.TaskStatus.CANCELLED, freshAssigned.revision, { completedAt: new Date().toISOString() });
441
+ break;
442
+ }
443
+ case protocol_1.TaskStatus.SUSPENDED: {
444
+ // Direct set CANCELLED — no Worker ack needed
445
+ this.updateTaskStatusWithRetry(taskId, protocol_1.TaskStatus.CANCELLED, task.revision, { completedAt: new Date().toISOString() });
446
+ break;
447
+ }
448
+ default:
449
+ throw new common_1.BadRequestException(`Cannot cancel task #${taskId} in status ${task.status}`);
450
+ }
451
+ this.auditLogRepo.create({
452
+ userId: cancelledBy,
453
+ action: 'task_cancel',
454
+ resource: `task:${taskId}`,
455
+ detail: `Cancelled task #${taskId} from status ${task.status}`,
456
+ });
457
+ // Schedule workspace cleanup for the cancelled task
458
+ if (task.machineId) {
459
+ const workspace = this.workspaceRepo.findByTaskId(taskId);
460
+ if (workspace) {
461
+ this.cleanupService.scheduleCleanup(taskId, task.machineId, workspace.path).catch((err) => {
462
+ this.logger.error(`Failed to schedule cleanup for cancelled task #${taskId}: ${err instanceof Error ? err.message : String(err)}`);
463
+ });
464
+ }
465
+ }
466
+ this.logger.log(`Task #${taskId} cancelled by developer #${cancelledBy}`);
467
+ }
468
+ // ---------------------------------------------------------------------------
469
+ // retryTask
470
+ // ---------------------------------------------------------------------------
471
+ async retryTask(taskId, retriedBy) {
472
+ const task = this.taskRepo.findById(taskId);
473
+ if (!task) {
474
+ throw new common_1.NotFoundException(`Task #${taskId} not found`);
475
+ }
476
+ if (task.status !== protocol_1.TaskStatus.FAILED) {
477
+ throw new common_1.BadRequestException(`Cannot retry task #${taskId}: status is ${task.status}, expected FAILED`);
478
+ }
479
+ // Validate transition
480
+ state_machine_1.TaskStateMachine.assertTransition(task.status, protocol_1.TaskStatus.QUEUED);
481
+ // Check fingerprint conflict — another active task with same fingerprint
482
+ if (task.fingerprint) {
483
+ const conflicting = this.taskRepo.findActiveByFingerprint(task.fingerprint);
484
+ if (conflicting && conflicting.id !== taskId) {
485
+ throw new common_1.ConflictException(`Cannot retry: active task #${conflicting.id} has the same fingerprint`);
486
+ }
487
+ }
488
+ // Check for existing MR/PR on the branch (if branch exists)
489
+ if (task.branch && task.configSnapshot) {
490
+ const mrCheckResult = await this.checkExistingMergeRequest(task);
491
+ if (mrCheckResult) {
492
+ // Store pending confirm for retry with existing MR
493
+ const confirmKey = `pending_confirm:retry:${crypto.randomUUID()}`;
494
+ const pendingData = JSON.stringify({
495
+ action: 'retry',
496
+ taskId,
497
+ createdBy: retriedBy,
498
+ });
499
+ await this.redis.setex(confirmKey, protocol_1.CONFIRM_PENDING_TTL_SEC, pendingData);
500
+ // Store index from taskId to confirmKey for lookup during confirm
501
+ await this.redis.setex(`pending_confirm_idx:task:${taskId}`, protocol_1.CONFIRM_PENDING_TTL_SEC, confirmKey);
502
+ throw new common_1.ConflictException({
503
+ message: `Task #${taskId} branch '${task.branch}' has an open MR/PR`,
504
+ confirmRequired: {
505
+ reason: 'existing_mr',
506
+ message: `Task #${taskId} branch '${task.branch}' has an open MR/PR (${mrCheckResult}). Retry will force push to the same branch. Continue?`,
507
+ pendingAction: 'retry',
508
+ taskId,
509
+ mrUrl: mrCheckResult,
510
+ confirmKey,
511
+ },
512
+ });
513
+ }
514
+ }
515
+ return this.executeRetry(taskId, retriedBy);
516
+ }
517
+ async executeRetry(taskId, retriedBy) {
518
+ const task = this.taskRepo.findById(taskId);
519
+ if (!task) {
520
+ throw new common_1.NotFoundException(`Task #${taskId} not found`);
521
+ }
522
+ // CAS update: FAILED -> QUEUED
523
+ const updated = this.updateTaskStatusWithRetry(taskId, protocol_1.TaskStatus.QUEUED, task.revision, {
524
+ retryCount: task.retryCount + 1,
525
+ errorMessage: null,
526
+ machineId: null,
527
+ assignedAt: null,
528
+ startedAt: null,
529
+ completedAt: null,
530
+ currentStage: null,
531
+ currentSessionId: null,
532
+ });
533
+ if (!updated) {
534
+ throw new common_1.ConflictException(`Failed to retry task #${taskId}: concurrent modification`);
535
+ }
536
+ // Re-enqueue to BullMQ
537
+ await this.schedulerService.enqueueTask(taskId);
538
+ this.auditLogRepo.create({
539
+ userId: retriedBy,
540
+ action: 'task_retry',
541
+ resource: `task:${taskId}`,
542
+ detail: `Retried task #${taskId} (attempt ${task.retryCount + 1})`,
543
+ });
544
+ this.logger.log(`Task #${taskId} retried by developer #${retriedBy}`);
545
+ return this.taskRepo.findById(taskId);
546
+ }
547
+ // ---------------------------------------------------------------------------
548
+ // confirmStage — forward interactive stage confirm result to Worker
549
+ // ---------------------------------------------------------------------------
550
+ async confirmStage(taskId, stageIndex, result) {
551
+ const task = this.taskRepo.findById(taskId);
552
+ if (!task) {
553
+ throw new common_1.NotFoundException(`Task #${taskId} not found`);
554
+ }
555
+ if (task.status !== protocol_1.TaskStatus.RUNNING &&
556
+ task.status !== protocol_1.TaskStatus.SUSPENDED) {
557
+ throw new common_1.BadRequestException(`Task #${taskId} is not in a confirmable state (${task.status})`);
558
+ }
559
+ if (!task.machineId) {
560
+ throw new common_1.BadRequestException(`Task #${taskId} has no assigned machine`);
561
+ }
562
+ // Forward confirm response to Worker via WS
563
+ const frame = {
564
+ type: 'stage_confirm_response',
565
+ taskId,
566
+ stageIndex,
567
+ result,
568
+ };
569
+ const ws = this.workerConnectionManager.getConnection(task.machineId);
570
+ if (!ws || ws.readyState !== 1 /* WebSocket.OPEN */) {
571
+ throw new common_1.BadRequestException(`Worker for task #${taskId} is not connected`);
572
+ }
573
+ ws.send(JSON.stringify(frame));
574
+ // Set a short TTL on the stage confirm key instead of deleting immediately.
575
+ // This gives a 60-second window for retry if the worker doesn't process the message,
576
+ // while still ensuring eventual cleanup.
577
+ const stageConfirmKey = `confirm:stage:${taskId}:${stageIndex}`;
578
+ await this.redis.expire(stageConfirmKey, 60);
579
+ this.logger.log(`Stage confirm forwarded to Worker for task #${taskId}, stage ${stageIndex}`);
580
+ }
581
+ // ---------------------------------------------------------------------------
582
+ // getTask / listTasks
583
+ // ---------------------------------------------------------------------------
584
+ getTask(taskId) {
585
+ return this.taskRepo.findById(taskId);
586
+ }
587
+ listTasks(query) {
588
+ return this.taskRepo.listPaginated(query);
589
+ }
590
+ // ---------------------------------------------------------------------------
591
+ // registerWorker
592
+ // ---------------------------------------------------------------------------
593
+ async registerWorker(request) {
594
+ // 1. Parse token format: ovw_<id>_<secret>
595
+ const { tokenId, secret } = this.parseWorkerToken(request.token);
596
+ // 2. Find token by id, verify it's active and unused
597
+ const token = this.workerTokenRepo.findActiveUnused(tokenId);
598
+ if (!token) {
599
+ throw new common_1.UnauthorizedException('Invalid or already used worker token');
600
+ }
601
+ // 3. Verify bcrypt(secret, hash)
602
+ const secretValid = await this.cryptoService.comparePassword(secret, token.tokenHash);
603
+ if (!secretValid) {
604
+ throw new common_1.UnauthorizedException('Invalid worker token secret');
605
+ }
606
+ // 4. Validate protocol version (major must match)
607
+ this.validateProtocolVersion(request.protocolVersion);
608
+ // 5. Mark token as used
609
+ this.workerTokenRepo.markUsed(tokenId);
610
+ // 6. Create or update machine record
611
+ const machineId = request.machineId ?? crypto.randomUUID();
612
+ const isFirstRegistration = !request.machineId;
613
+ let machine = this.machineRepo.findById(machineId);
614
+ if (machine) {
615
+ // Update existing machine
616
+ this.machineRepo.updateStatus(machineId, protocol_1.MachineStatus.ONLINE);
617
+ this.machineRepo.updateTokenId(machineId, tokenId);
618
+ }
619
+ else {
620
+ // Create new machine
621
+ machine = this.machineRepo.create({
622
+ id: machineId,
623
+ name: request.machineName,
624
+ host: request.host,
625
+ port: request.port,
626
+ tokenId,
627
+ os: request.os,
628
+ cpuCores: request.cpuCores,
629
+ memoryGb: request.memoryGb,
630
+ capabilities: request.capabilities,
631
+ tags: request.tags,
632
+ protocolVersion: request.protocolVersion,
633
+ });
634
+ }
635
+ // 7. Generate recovery secret on first registration
636
+ let recoverySecret;
637
+ if (isFirstRegistration) {
638
+ recoverySecret = crypto.randomBytes(32).toString('hex');
639
+ const recoveryHash = await this.cryptoService.hashPassword(recoverySecret);
640
+ this.machineRepo.updateRecoverySecretHash(machineId, recoveryHash);
641
+ }
642
+ // 8. Sign Worker JWT
643
+ const jwt = this.authService.signWorkerJwt(machineId, tokenId);
644
+ this.auditLogRepo.create({
645
+ action: 'worker_register',
646
+ resource: `machine:${machineId}`,
647
+ detail: `Worker '${request.machineName}' registered (token #${tokenId}, first=${isFirstRegistration})`,
648
+ });
649
+ this.logger.log(`Worker '${request.machineName}' registered as ${machineId}`);
650
+ return {
651
+ jwt,
652
+ machineId,
653
+ recoverySecret,
654
+ };
655
+ }
656
+ // ---------------------------------------------------------------------------
657
+ // refreshWorkerToken
658
+ // ---------------------------------------------------------------------------
659
+ async refreshWorkerToken(request, currentJwt) {
660
+ const { machineId } = request;
661
+ // Verify tokenId matches the current JWT's tokenId
662
+ const machine = this.machineRepo.findById(machineId);
663
+ if (!machine) {
664
+ throw new common_1.NotFoundException(`Machine '${machineId}' not found`);
665
+ }
666
+ if (machine.tokenId !== currentJwt.tokenId) {
667
+ throw new common_1.UnauthorizedException('Token ID mismatch — token may have been revoked');
668
+ }
669
+ // Verify the token hasn't been revoked
670
+ const token = this.workerTokenRepo.findById(currentJwt.tokenId);
671
+ if (!token || token.status === 'revoked') {
672
+ throw new common_1.UnauthorizedException('Worker token has been revoked');
673
+ }
674
+ // Sign new Worker JWT
675
+ const jwt = this.authService.signWorkerJwt(machineId, currentJwt.tokenId);
676
+ this.logger.debug(`Worker JWT refreshed for machine ${machineId}`);
677
+ return { jwt };
678
+ }
679
+ // ---------------------------------------------------------------------------
680
+ // recoverWorker
681
+ // ---------------------------------------------------------------------------
682
+ async recoverWorker(request) {
683
+ const { machineId, machineName, recoverySecret } = request;
684
+ // 1. Find machine
685
+ const machine = this.machineRepo.findById(machineId);
686
+ if (!machine) {
687
+ throw new common_1.NotFoundException(`Machine '${machineId}' not found`);
688
+ }
689
+ // 2. Validate machine name matches
690
+ if (machine.name !== machineName) {
691
+ throw new common_1.UnauthorizedException('Machine name does not match the registered name');
692
+ }
693
+ // 3. Validate recovery secret hash exists
694
+ if (!machine.recoverySecretHash) {
695
+ throw new common_1.UnauthorizedException('No recovery secret configured for this machine');
696
+ }
697
+ // 4. Validate recovery secret
698
+ const secretValid = await this.cryptoService.comparePassword(recoverySecret, machine.recoverySecretHash);
699
+ if (!secretValid) {
700
+ throw new common_1.UnauthorizedException('Invalid recovery secret');
701
+ }
702
+ // 5. Atomically claim the recovery token via Redis GETDEL to prevent
703
+ // race conditions where two concurrent recovery requests consume
704
+ // the same token.
705
+ const recoveryClaimKey = `recovery_claim:${machineId}`;
706
+ // Set the claim key if not already present (first recovery wins)
707
+ const claimed = await this.redis.getClient().set(recoveryClaimKey, '1', 'EX', 30, 'NX');
708
+ if (claimed === null) {
709
+ throw new common_1.ConflictException('Recovery already in progress for this machine');
710
+ }
711
+ try {
712
+ // 6. Wrap SQLite updates in a transaction for atomicity
713
+ const db = this.databaseService.getDb();
714
+ const runRecoveryTransaction = db.transaction(() => {
715
+ // Update machine status to online
716
+ db.prepare('UPDATE machines SET status = ? WHERE id = ?')
717
+ .run(protocol_1.MachineStatus.ONLINE, machineId);
718
+ // Record audit log
719
+ db.prepare(`INSERT INTO audit_logs (user_id, action, resource, detail)
720
+ VALUES (?, ?, ?, ?)`).run(null, 'worker_recover', `machine:${machineId}`, `Worker '${machineName}' recovered via recovery secret`);
721
+ });
722
+ runRecoveryTransaction();
723
+ // 7. Issue new token — sign Worker JWT with existing tokenId
724
+ const jwt = this.authService.signWorkerJwt(machineId, machine.tokenId);
725
+ this.logger.log(`Worker '${machineName}' (${machineId}) recovered via recovery secret`);
726
+ return { jwt, machineId };
727
+ }
728
+ finally {
729
+ // Clean up the claim key
730
+ await this.redis.del(recoveryClaimKey);
731
+ }
732
+ }
733
+ // ---------------------------------------------------------------------------
734
+ // issuePtyToken
735
+ // ---------------------------------------------------------------------------
736
+ issuePtyToken(taskId, aud) {
737
+ const task = this.taskRepo.findById(taskId);
738
+ if (!task) {
739
+ throw new common_1.NotFoundException(`Task #${taskId} not found`);
740
+ }
741
+ return this.authService.signChannelToken(taskId, aud);
742
+ }
743
+ // ---------------------------------------------------------------------------
744
+ // Helpers
745
+ // ---------------------------------------------------------------------------
746
+ resolveProject(keyOrAlias) {
747
+ if (!keyOrAlias) {
748
+ // Try default project
749
+ return this.projectRepo.findDefault();
750
+ }
751
+ // Try by key first
752
+ const byKey = this.projectRepo.findByKey(keyOrAlias);
753
+ if (byKey)
754
+ return byKey;
755
+ // Try by alias
756
+ return this.projectRepo.findByAlias(keyOrAlias);
757
+ }
758
+ parseWorkerToken(raw) {
759
+ // Format: ovw_<id>_<secret>
760
+ const match = /^ovw_(\d+)_(.+)$/.exec(raw);
761
+ if (!match) {
762
+ throw new common_1.UnauthorizedException('Invalid token format. Expected: ovw_<id>_<secret>');
763
+ }
764
+ return {
765
+ tokenId: parseInt(match[1], 10),
766
+ secret: match[2],
767
+ };
768
+ }
769
+ validateProtocolVersion(workerVersion) {
770
+ const [serverMajor] = protocol_1.PROTOCOL_VERSION.split('.');
771
+ const [workerMajor] = workerVersion.split('.');
772
+ if (serverMajor !== workerMajor) {
773
+ throw new common_1.BadRequestException(`Protocol version mismatch: server=${protocol_1.PROTOCOL_VERSION}, worker=${workerVersion}. Major version must match.`);
774
+ }
775
+ if (workerVersion !== protocol_1.PROTOCOL_VERSION) {
776
+ this.logger.warn(`Protocol minor version mismatch: server=${protocol_1.PROTOCOL_VERSION}, worker=${workerVersion}`);
777
+ }
778
+ }
779
+ /**
780
+ * Resolve a pending confirmation from Redis.
781
+ * Tries reply-to msgId lookup first, then taskId-based index.
782
+ * Validates that the confirming user matches the original creator.
783
+ * Consumes (deletes) the pending confirm on success.
784
+ */
785
+ async resolvePendingConfirm(taskId, userId, _msgId) {
786
+ // Look up the confirm key via taskId index (non-destructive read)
787
+ const indexKey = `pending_confirm_idx:task:${taskId}`;
788
+ const confirmKey = await this.redis.get(indexKey);
789
+ if (!confirmKey)
790
+ return null;
791
+ // Atomically consume the confirm data (GETDEL prevents double-confirm race)
792
+ const raw = await this.redis.getdel(confirmKey);
793
+ if (!raw)
794
+ return null;
795
+ let data;
796
+ try {
797
+ data = JSON.parse(raw);
798
+ }
799
+ catch {
800
+ return null;
801
+ }
802
+ // Validate user matches original creator.
803
+ // If auth fails the confirm data is already consumed (intentional —
804
+ // prevents an unauthorized user from probing indefinitely, and the
805
+ // confirm would have expired via TTL anyway).
806
+ if (data.createdBy !== userId) {
807
+ this.logger.warn(`Confirm attempt by user #${userId} but pending confirm belongs to user #${data.createdBy}`);
808
+ return null;
809
+ }
810
+ // Clean up the index key
811
+ await this.redis.del(indexKey);
812
+ return data;
813
+ }
814
+ /**
815
+ * Update task status with CAS retry (up to CAS_MAX_RETRIES).
816
+ * Returns true if successful, throws ConflictException on exhaustion.
817
+ */
818
+ updateTaskStatusWithRetry(taskId, status, initialRevision, extra) {
819
+ let revision = initialRevision;
820
+ for (let attempt = 0; attempt < protocol_1.CAS_MAX_RETRIES; attempt++) {
821
+ const success = this.taskRepo.updateStatus(taskId, status, revision, extra);
822
+ if (success)
823
+ return true;
824
+ // Reload task to get fresh revision
825
+ const freshTask = this.taskRepo.findById(taskId);
826
+ if (!freshTask) {
827
+ throw new common_1.NotFoundException(`Task #${taskId} not found during CAS retry`);
828
+ }
829
+ // If task already reached target status, that's fine
830
+ if (freshTask.status === status)
831
+ return true;
832
+ revision = freshTask.revision;
833
+ }
834
+ throw new common_1.ConflictException(`CAS retry exhausted for task #${taskId} after ${protocol_1.CAS_MAX_RETRIES} attempts`);
835
+ }
836
+ /**
837
+ * Send cancel frame to Worker and wait for ack within CANCEL_ACK_TIMEOUT_MS.
838
+ */
839
+ async sendCancelToWorkerWithAck(machineId, taskId) {
840
+ const msgId = crypto.randomUUID();
841
+ try {
842
+ await this.workerConnectionManager
843
+ .sendWithAck(machineId, { type: 'cancel', msgId, taskId }, protocol_1.CANCEL_ACK_TIMEOUT_MS);
844
+ return true;
845
+ }
846
+ catch {
847
+ return false;
848
+ }
849
+ }
850
+ /**
851
+ * Send cancel frame to Worker (fire and forget).
852
+ */
853
+ sendCancelToWorker(machineId, taskId) {
854
+ const msgId = crypto.randomUUID();
855
+ this.workerConnectionManager.send(machineId, {
856
+ type: 'cancel',
857
+ msgId,
858
+ taskId,
859
+ });
860
+ }
861
+ /**
862
+ * Check if the task's branch has an existing open MR/PR on the Git platform.
863
+ * Returns the MR/PR URL if found, null otherwise.
864
+ * On timeout or error, returns null (allows retry to proceed).
865
+ *
866
+ * Supports GitLab and GitHub APIs. Extracts the project path from the repo URL
867
+ * and queries the platform API for open merge/pull requests on the branch.
868
+ */
869
+ async checkExistingMergeRequest(task) {
870
+ if (!task.branch || !task.configSnapshot)
871
+ return null;
872
+ try {
873
+ const config = JSON.parse(task.configSnapshot);
874
+ const { repoUrl, gitPlatform } = config;
875
+ if (!repoUrl)
876
+ return null;
877
+ // Extract the project path from the repo URL.
878
+ const projectPath = this.extractProjectPath(repoUrl);
879
+ if (!projectPath) {
880
+ this.logger.warn(`Cannot extract project path from repo URL '${repoUrl}' for task #${task.id}`);
881
+ return task.mrUrl ?? null;
882
+ }
883
+ // Extract the base URL for API calls
884
+ const baseUrl = this.extractGitBaseUrl(repoUrl);
885
+ if (!baseUrl) {
886
+ this.logger.warn(`Cannot extract base URL from repo URL '${repoUrl}' for task #${task.id}`);
887
+ return task.mrUrl ?? null;
888
+ }
889
+ // Look up the git token. Since the DB schema does not currently include
890
+ // git_token_encrypted, use the GIT_TOKEN environment variable as fallback.
891
+ const gitToken = process.env.GIT_TOKEN || '';
892
+ if (!gitToken) {
893
+ this.logger.debug(`No GIT_TOKEN configured; skipping MR/PR API check for task #${task.id}`);
894
+ return task.mrUrl ?? null;
895
+ }
896
+ const abortController = new AbortController();
897
+ const timeout = setTimeout(() => abortController.abort(), 10_000);
898
+ try {
899
+ let mrUrl = null;
900
+ if (gitPlatform === 'gitlab') {
901
+ mrUrl = await this.checkGitLabMergeRequest(baseUrl, projectPath, task.branch, gitToken, abortController.signal);
902
+ }
903
+ else if (gitPlatform === 'github') {
904
+ mrUrl = await this.checkGitHubPullRequest(baseUrl, projectPath, task.branch, gitToken, abortController.signal);
905
+ }
906
+ else {
907
+ this.logger.debug(`Unsupported git platform '${gitPlatform}' for MR/PR check on task #${task.id}`);
908
+ return task.mrUrl ?? null;
909
+ }
910
+ return mrUrl;
911
+ }
912
+ finally {
913
+ clearTimeout(timeout);
914
+ }
915
+ }
916
+ catch (err) {
917
+ this.logger.warn(`MR/PR check failed for task #${task.id}: ${err instanceof Error ? err.message : String(err)}`);
918
+ // On error, allow retry to proceed
919
+ return null;
920
+ }
921
+ }
922
+ /**
923
+ * Query GitLab API for open merge requests on a branch.
924
+ * GET /api/v4/projects/:id/merge_requests?source_branch=...&state=opened
925
+ */
926
+ async checkGitLabMergeRequest(baseUrl, projectPath, branch, token, signal) {
927
+ const encodedPath = encodeURIComponent(projectPath);
928
+ const url = `${baseUrl}/api/v4/projects/${encodedPath}/merge_requests?source_branch=${encodeURIComponent(branch)}&state=opened&per_page=1`;
929
+ const response = await fetch(url, {
930
+ headers: {
931
+ 'PRIVATE-TOKEN': token,
932
+ 'Accept': 'application/json',
933
+ },
934
+ signal,
935
+ });
936
+ if (!response.ok) {
937
+ this.logger.warn(`GitLab MR check returned ${response.status} for project '${projectPath}', branch '${branch}'`);
938
+ return null;
939
+ }
940
+ const data = (await response.json());
941
+ if (Array.isArray(data) && data.length > 0 && data[0].web_url) {
942
+ this.logger.debug(`Found open GitLab MR for branch '${branch}': ${data[0].web_url}`);
943
+ return data[0].web_url;
944
+ }
945
+ return null;
946
+ }
947
+ /**
948
+ * Query GitHub API for open pull requests on a branch.
949
+ * GET /repos/:owner/:repo/pulls?head=owner:branch&state=open
950
+ */
951
+ async checkGitHubPullRequest(baseUrl, projectPath, branch, token, signal) {
952
+ const owner = projectPath.split('/')[0];
953
+ const apiBase = baseUrl === 'https://github.com'
954
+ ? 'https://api.github.com'
955
+ : `${baseUrl}/api/v3`;
956
+ const url = `${apiBase}/repos/${projectPath}/pulls?head=${encodeURIComponent(`${owner}:${branch}`)}&state=open&per_page=1`;
957
+ const response = await fetch(url, {
958
+ headers: {
959
+ 'Authorization': `Bearer ${token}`,
960
+ 'Accept': 'application/vnd.github+json',
961
+ 'X-GitHub-Api-Version': '2022-11-28',
962
+ },
963
+ signal,
964
+ });
965
+ if (!response.ok) {
966
+ this.logger.warn(`GitHub PR check returned ${response.status} for project '${projectPath}', branch '${branch}'`);
967
+ return null;
968
+ }
969
+ const data = (await response.json());
970
+ if (Array.isArray(data) && data.length > 0 && data[0].html_url) {
971
+ this.logger.debug(`Found open GitHub PR for branch '${branch}': ${data[0].html_url}`);
972
+ return data[0].html_url;
973
+ }
974
+ return null;
975
+ }
976
+ /**
977
+ * Extract the project path (e.g. "group/project") from a git repo URL.
978
+ * Supports HTTPS and SSH formats.
979
+ */
980
+ extractProjectPath(repoUrl) {
981
+ // SSH format: git@host:path.git
982
+ const sshMatch = /^git@[^:]+:(.+?)(?:\.git)?$/.exec(repoUrl);
983
+ if (sshMatch)
984
+ return sshMatch[1];
985
+ // HTTPS format: https://host/path.git or https://host/path
986
+ try {
987
+ const parsed = new URL(repoUrl);
988
+ let path = parsed.pathname;
989
+ path = path.replace(/^\//, '').replace(/\.git$/, '');
990
+ return path || null;
991
+ }
992
+ catch {
993
+ return null;
994
+ }
995
+ }
996
+ /**
997
+ * Extract the base URL (scheme + host) from a git repo URL.
998
+ */
999
+ extractGitBaseUrl(repoUrl) {
1000
+ // SSH format: git@host:path -> https://host
1001
+ const sshMatch = /^git@([^:]+):/.exec(repoUrl);
1002
+ if (sshMatch)
1003
+ return `https://${sshMatch[1]}`;
1004
+ // HTTPS format
1005
+ try {
1006
+ const parsed = new URL(repoUrl);
1007
+ return `${parsed.protocol}//${parsed.host}`;
1008
+ }
1009
+ catch {
1010
+ return null;
1011
+ }
1012
+ }
1013
+ };
1014
+ exports.DispatcherService = DispatcherService;
1015
+ exports.DispatcherService = DispatcherService = DispatcherService_1 = __decorate([
1016
+ (0, common_1.Injectable)(),
1017
+ __metadata("design:paramtypes", [task_repository_1.TaskRepository,
1018
+ machine_repository_1.MachineRepository,
1019
+ project_repository_1.ProjectRepository,
1020
+ project_member_repository_1.ProjectMemberRepository,
1021
+ worker_token_repository_1.WorkerTokenRepository,
1022
+ audit_log_repository_1.AuditLogRepository,
1023
+ developer_repository_1.DeveloperRepository,
1024
+ database_service_1.DatabaseService,
1025
+ auth_service_1.AuthService,
1026
+ crypto_service_1.CryptoService,
1027
+ redis_service_1.RedisService,
1028
+ dedup_service_1.DedupService,
1029
+ scheduler_service_1.SchedulerService,
1030
+ worker_connection_manager_1.WorkerConnectionManager,
1031
+ cleanup_service_1.CleanupService,
1032
+ workspace_repository_1.WorkspaceRepository])
1033
+ ], DispatcherService);
1034
+ //# sourceMappingURL=dispatcher.service.js.map