automagik-forge 0.1.13 → 0.1.14

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 (301) hide show
  1. package/README.md +143 -447
  2. package/dist/linux-x64/automagik-forge-mcp.zip +0 -0
  3. package/{npx-cli/automagik-forge-0.0.55.tgz → dist/linux-x64/automagik-forge.zip} +0 -0
  4. package/package.json +13 -23
  5. package/.cargo/config.toml +0 -13
  6. package/.claude/commands/commit.md +0 -376
  7. package/.claude/commands/prompt.md +0 -871
  8. package/.env.example +0 -20
  9. package/.github/actions/setup-node/action.yml +0 -29
  10. package/.github/images/automagik-logo.png +0 -0
  11. package/.github/workflows/pre-release.yml +0 -470
  12. package/.github/workflows/publish.yml +0 -145
  13. package/.github/workflows/test.yml +0 -63
  14. package/.mcp.json +0 -57
  15. package/AGENT.md +0 -40
  16. package/CLAUDE.md +0 -40
  17. package/CODE-OF-CONDUCT.md +0 -89
  18. package/Cargo.toml +0 -19
  19. package/Dockerfile +0 -43
  20. package/LICENSE +0 -201
  21. package/Makefile +0 -97
  22. package/backend/.sqlx/query-01b7e2bac1261d8be3d03c03df3e5220590da6c31c77f161074fc62752d63881.json +0 -12
  23. package/backend/.sqlx/query-03f2b02ba6dc5ea2b3cf6b1004caea0ad6bcc10ebd63f441d321a389f026e263.json +0 -12
  24. package/backend/.sqlx/query-0923b77d137a29fc54d399a873ff15fc4af894490bc65a4d344a7575cb0d8643.json +0 -12
  25. package/backend/.sqlx/query-0f808bcdb63c5f180836e448dd64c435c51758b2fc54a52ce9e67495b1ab200e.json +0 -68
  26. package/backend/.sqlx/query-1268afe9ca849daa6722e3df7ca8e9e61f0d37052e782bb5452ab8e1018d9b63.json +0 -12
  27. package/backend/.sqlx/query-1b082630a9622f8667ee7a9aba2c2d3176019a68c6bb83d33008594821415a57.json +0 -12
  28. package/backend/.sqlx/query-1c7b06ba1e112abf6b945a2ff08a0b40ec23f3738c2e7399f067b558cf8d490e.json +0 -12
  29. package/backend/.sqlx/query-1f619f01f46859a64ded531dd0ef61abacfe62e758abe7030a6aa745140b95ca.json +0 -104
  30. package/backend/.sqlx/query-1fca1ce14b4b20205364cd1f1f45ebe1d2e30cd745e59e189d56487b5639dfbb.json +0 -12
  31. package/backend/.sqlx/query-212828320e8d871ab9d83705a040b23bcf0393dc7252177fc539a74657f578ef.json +0 -32
  32. package/backend/.sqlx/query-290ce5c152be8d36e58ff42570f9157beb07ab9e77a03ec6fc30b4f56f9b8f6b.json +0 -56
  33. package/backend/.sqlx/query-2b471d2c2e8ffbe0cd42d2a91b814c0d79f9d09200f147e3cea33ba4ce673c8a.json +0 -68
  34. package/backend/.sqlx/query-354a48c705bb9bb2048c1b7f10fcb714e23f9db82b7a4ea6932486197b2ede6a.json +0 -92
  35. package/backend/.sqlx/query-36c9e3dd10648e94b949db5c91a774ecb1e10a899ef95da74066eccedca4d8b2.json +0 -12
  36. package/backend/.sqlx/query-36e4ba7bbd81b402d5a20b6005755eafbb174c8dda442081823406ac32809a94.json +0 -56
  37. package/backend/.sqlx/query-3a5b3c98a55ca183ab20c74708e3d7e579dda37972c059e7515c4ceee4bd8dd3.json +0 -62
  38. package/backend/.sqlx/query-3d0a1cabf2a52e9d90cdfd29c509ca89aeb448d0c1d2446c65cd43db40735e86.json +0 -62
  39. package/backend/.sqlx/query-3d6bd16fbce59efe30b7f67ea342e0e4ea6d1432389c02468ad79f1f742d4031.json +0 -56
  40. package/backend/.sqlx/query-4049ca413b285a05aca6b25385e9c8185575f01e9069e4e8581aa45d713f612f.json +0 -32
  41. package/backend/.sqlx/query-412bacd3477d86369082e90f52240407abce436cb81292d42b2dbe1e5c18eea1.json +0 -104
  42. package/backend/.sqlx/query-417a8b1ff4e51de82aea0159a3b97932224dc325b23476cb84153d690227fd8b.json +0 -62
  43. package/backend/.sqlx/query-461cc1b0bb6fd909afc9dd2246e8526b3771cfbb0b22ae4b5d17b51af587b9e2.json +0 -56
  44. package/backend/.sqlx/query-58408c7a8cdeeda0bef359f1f9bd91299a339dc2b191462fc58c9736a56d5227.json +0 -92
  45. package/backend/.sqlx/query-5a886026d75d515c01f347cc203c8d99dd04c61dc468e2e4c5aa548436d13834.json +0 -62
  46. package/backend/.sqlx/query-5b902137b11022d2e1a5c4f6a9c83fec1a856c6a710aff831abd2382ede76b43.json +0 -12
  47. package/backend/.sqlx/query-5ed1238e52e59bb5f76c0f153fd99a14093f7ce2585bf9843585608f17ec575b.json +0 -104
  48. package/backend/.sqlx/query-6e8b860b14decfc2227dc57213f38442943d3fbef5c8418fd6b634c6e0f5e2ea.json +0 -104
  49. package/backend/.sqlx/query-6ec414276994c4ccb2433eaa5b1b342168557d17ddf5a52dac84cb1b59b9de8f.json +0 -68
  50. package/backend/.sqlx/query-6ecfa16d0cf825aacf233544b5baf151e9adfdca26c226ad71020d291fd802d5.json +0 -62
  51. package/backend/.sqlx/query-72509d252c39fce77520aa816cb2acbc1fb35dc2605e7be893610599b2427f2e.json +0 -62
  52. package/backend/.sqlx/query-75239b2da188f749707d77f3c1544332ca70db3d6d6743b2601dc0d167536437.json +0 -62
  53. package/backend/.sqlx/query-83d10e29f8478aff33434f9ac67068e013b888b953a2657e2bb72a6f619d04f2.json +0 -50
  54. package/backend/.sqlx/query-8610803360ea18b9b9d078a6981ea56abfbfe84e6354fc1d5ae4c622e01410ed.json +0 -68
  55. package/backend/.sqlx/query-86d03eb70eef39c59296416867f2ee66c9f7cd8b7f961fbda2f89fc0a1c442c2.json +0 -12
  56. package/backend/.sqlx/query-87d0feb5a6b442bad9c60068ea7569599cc6fc91a0e2692ecb42e93b03201b9d.json +0 -68
  57. package/backend/.sqlx/query-8a67b3b3337248f06a57bdf8a908f7ef23177431eaed82dc08c94c3e5944340e.json +0 -12
  58. package/backend/.sqlx/query-8f01ebd64bdcde6a090479f14810d73ba23020e76fd70854ac57f2da251702c3.json +0 -12
  59. package/backend/.sqlx/query-90fd607fcb2dca72239ff25e618e21e174b195991eaa33722cbf5f76da84cfab.json +0 -62
  60. package/backend/.sqlx/query-92e8bdbcd80c5ff3db7a35cf79492048803ef305cbdef0d0a1fe5dc881ca8c71.json +0 -104
  61. package/backend/.sqlx/query-93a1605f90e9672dad29b472b6ad85fa9a55ea3ffa5abcb8724b09d61be254ca.json +0 -20
  62. package/backend/.sqlx/query-9472c8fb477958167f5fae40b85ac44252468c5226b2cdd7770f027332eed6d7.json +0 -104
  63. package/backend/.sqlx/query-96036c4f9e0f48bdc5a4a4588f0c5f288ac7aaa5425cac40fc33f337e1a351f2.json +0 -56
  64. package/backend/.sqlx/query-9edb2c01e91fd0f0fe7b56e988c7ae0393150f50be3f419a981e035c0121dfc7.json +0 -104
  65. package/backend/.sqlx/query-a157cf00616f703bfba21927f1eb1c9eec2a81c02da15f66efdba0b6c375de1b.json +0 -26
  66. package/backend/.sqlx/query-a31fff84f3b8e532fd1160447d89d700f06ae08821fee00c9a5b60492b05259c.json +0 -62
  67. package/backend/.sqlx/query-a5ba908419fb3e456bdd2daca41ba06cc3212ffffb8520fc7dbbcc8b60ada314.json +0 -12
  68. package/backend/.sqlx/query-a6d2961718dbc3b1a925e549f49a159c561bef58c105529275f274b27e2eba5b.json +0 -104
  69. package/backend/.sqlx/query-a9e93d5b09b29faf66e387e4d7596a792d81e75c4d3726e83c2963e8d7c9b56f.json +0 -104
  70. package/backend/.sqlx/query-ac5247c8d7fb86e4650c4b0eb9420031614c831b7b085083bac20c1af314c538.json +0 -12
  71. package/backend/.sqlx/query-afef9467be74c411c4cb119a8b2b1aea53049877dfc30cc60b486134ba4b4c9f.json +0 -68
  72. package/backend/.sqlx/query-b2b2c6b4d0b1a347b5c4cb63c3a46a265d4db53be9554989a814b069d0af82f2.json +0 -62
  73. package/backend/.sqlx/query-c50d2ff0b12e5bcc81e371089ee2d007e233e7db93aefba4fef08e7aa68f5ab7.json +0 -20
  74. package/backend/.sqlx/query-c614e6056b244ca07f1b9d44e7edc9d5819225c6f8d9e077070c6e518a17f50b.json +0 -12
  75. package/backend/.sqlx/query-c67259be8bf4ee0cfd32167b2aa3b7fe9192809181a8171bf1c2d6df731967ae.json +0 -12
  76. package/backend/.sqlx/query-d2d0a1b985ebbca6a2b3e882a221a219f3199890fa640afc946ef1a792d6d8de.json +0 -12
  77. package/backend/.sqlx/query-d30aa5786757f32bf2b9c5fe51a45e506c71c28c5994e430d9b0546adb15ffa2.json +0 -20
  78. package/backend/.sqlx/query-d3b9ea1de1576af71b312924ce7f4ea8ae5dbe2ac138ea3b4470f2d5cd734846.json +0 -12
  79. package/backend/.sqlx/query-ed8456646fa69ddd412441955f06ff22bfb790f29466450735e0b8bb1bc4ec94.json +0 -12
  80. package/backend/Cargo.toml +0 -71
  81. package/backend/build.rs +0 -32
  82. package/backend/migrations/20250617183714_init.sql +0 -44
  83. package/backend/migrations/20250620212427_execution_processes.sql +0 -25
  84. package/backend/migrations/20250620214100_remove_stdout_stderr_from_task_attempts.sql +0 -28
  85. package/backend/migrations/20250621120000_relate_activities_to_execution_processes.sql +0 -23
  86. package/backend/migrations/20250623120000_executor_sessions.sql +0 -17
  87. package/backend/migrations/20250623130000_add_executor_type_to_execution_processes.sql +0 -4
  88. package/backend/migrations/20250625000000_add_dev_script_to_projects.sql +0 -4
  89. package/backend/migrations/20250701000000_add_branch_to_task_attempts.sql +0 -2
  90. package/backend/migrations/20250701000001_add_pr_tracking_to_task_attempts.sql +0 -5
  91. package/backend/migrations/20250701120000_add_assistant_message_to_executor_sessions.sql +0 -2
  92. package/backend/migrations/20250708000000_add_base_branch_to_task_attempts.sql +0 -2
  93. package/backend/migrations/20250709000000_add_worktree_deleted_flag.sql +0 -2
  94. package/backend/migrations/20250710000000_add_setup_completion.sql +0 -3
  95. package/backend/migrations/20250715154859_add_task_templates.sql +0 -25
  96. package/backend/migrations/20250716143725_add_default_templates.sql +0 -174
  97. package/backend/migrations/20250716161432_update_executor_names_to_kebab_case.sql +0 -20
  98. package/backend/migrations/20250716170000_add_parent_task_to_tasks.sql +0 -7
  99. package/backend/migrations/20250717000000_drop_task_attempt_activities.sql +0 -9
  100. package/backend/migrations/20250719000000_add_cleanup_script_to_projects.sql +0 -2
  101. package/backend/migrations/20250720000000_add_cleanupscript_to_process_type_constraint.sql +0 -25
  102. package/backend/migrations/20250723000000_add_wish_to_tasks.sql +0 -7
  103. package/backend/migrations/20250724000000_remove_unique_wish_constraint.sql +0 -5
  104. package/backend/scripts/toast-notification.ps1 +0 -23
  105. package/backend/sounds/abstract-sound1.wav +0 -0
  106. package/backend/sounds/abstract-sound2.wav +0 -0
  107. package/backend/sounds/abstract-sound3.wav +0 -0
  108. package/backend/sounds/abstract-sound4.wav +0 -0
  109. package/backend/sounds/cow-mooing.wav +0 -0
  110. package/backend/sounds/phone-vibration.wav +0 -0
  111. package/backend/sounds/rooster.wav +0 -0
  112. package/backend/src/app_state.rs +0 -218
  113. package/backend/src/bin/generate_types.rs +0 -189
  114. package/backend/src/bin/mcp_task_server.rs +0 -191
  115. package/backend/src/execution_monitor.rs +0 -1193
  116. package/backend/src/executor.rs +0 -1053
  117. package/backend/src/executors/amp.rs +0 -697
  118. package/backend/src/executors/ccr.rs +0 -91
  119. package/backend/src/executors/charm_opencode.rs +0 -113
  120. package/backend/src/executors/claude.rs +0 -887
  121. package/backend/src/executors/cleanup_script.rs +0 -124
  122. package/backend/src/executors/dev_server.rs +0 -53
  123. package/backend/src/executors/echo.rs +0 -79
  124. package/backend/src/executors/gemini/config.rs +0 -67
  125. package/backend/src/executors/gemini/streaming.rs +0 -363
  126. package/backend/src/executors/gemini.rs +0 -765
  127. package/backend/src/executors/mod.rs +0 -23
  128. package/backend/src/executors/opencode_ai.rs +0 -113
  129. package/backend/src/executors/setup_script.rs +0 -130
  130. package/backend/src/executors/sst_opencode/filter.rs +0 -184
  131. package/backend/src/executors/sst_opencode/tools.rs +0 -139
  132. package/backend/src/executors/sst_opencode.rs +0 -756
  133. package/backend/src/lib.rs +0 -45
  134. package/backend/src/main.rs +0 -324
  135. package/backend/src/mcp/mod.rs +0 -1
  136. package/backend/src/mcp/task_server.rs +0 -850
  137. package/backend/src/middleware/mod.rs +0 -3
  138. package/backend/src/middleware/model_loaders.rs +0 -242
  139. package/backend/src/models/api_response.rs +0 -36
  140. package/backend/src/models/config.rs +0 -375
  141. package/backend/src/models/execution_process.rs +0 -430
  142. package/backend/src/models/executor_session.rs +0 -225
  143. package/backend/src/models/mod.rs +0 -12
  144. package/backend/src/models/project.rs +0 -356
  145. package/backend/src/models/task.rs +0 -345
  146. package/backend/src/models/task_attempt.rs +0 -1214
  147. package/backend/src/models/task_template.rs +0 -146
  148. package/backend/src/openapi.rs +0 -93
  149. package/backend/src/routes/auth.rs +0 -297
  150. package/backend/src/routes/config.rs +0 -385
  151. package/backend/src/routes/filesystem.rs +0 -228
  152. package/backend/src/routes/health.rs +0 -16
  153. package/backend/src/routes/mod.rs +0 -9
  154. package/backend/src/routes/projects.rs +0 -562
  155. package/backend/src/routes/stream.rs +0 -244
  156. package/backend/src/routes/task_attempts.rs +0 -1172
  157. package/backend/src/routes/task_templates.rs +0 -229
  158. package/backend/src/routes/tasks.rs +0 -353
  159. package/backend/src/services/analytics.rs +0 -216
  160. package/backend/src/services/git_service.rs +0 -1321
  161. package/backend/src/services/github_service.rs +0 -307
  162. package/backend/src/services/mod.rs +0 -13
  163. package/backend/src/services/notification_service.rs +0 -263
  164. package/backend/src/services/pr_monitor.rs +0 -214
  165. package/backend/src/services/process_service.rs +0 -940
  166. package/backend/src/utils/path.rs +0 -96
  167. package/backend/src/utils/shell.rs +0 -19
  168. package/backend/src/utils/text.rs +0 -24
  169. package/backend/src/utils/worktree_manager.rs +0 -578
  170. package/backend/src/utils.rs +0 -125
  171. package/backend/test.db +0 -0
  172. package/build-npm-package.sh +0 -61
  173. package/dev_assets_seed/config.json +0 -19
  174. package/frontend/.eslintrc.json +0 -25
  175. package/frontend/.prettierrc.json +0 -8
  176. package/frontend/components.json +0 -17
  177. package/frontend/index.html +0 -19
  178. package/frontend/package-lock.json +0 -7321
  179. package/frontend/package.json +0 -61
  180. package/frontend/postcss.config.js +0 -6
  181. package/frontend/public/android-chrome-192x192.png +0 -0
  182. package/frontend/public/android-chrome-512x512.png +0 -0
  183. package/frontend/public/apple-touch-icon.png +0 -0
  184. package/frontend/public/automagik-forge-logo-dark.svg +0 -3
  185. package/frontend/public/automagik-forge-logo.svg +0 -3
  186. package/frontend/public/automagik-forge-screenshot-overview.png +0 -0
  187. package/frontend/public/favicon-16x16.png +0 -0
  188. package/frontend/public/favicon-32x32.png +0 -0
  189. package/frontend/public/favicon.ico +0 -0
  190. package/frontend/public/site.webmanifest +0 -1
  191. package/frontend/public/viba-kanban-favicon.png +0 -0
  192. package/frontend/src/App.tsx +0 -157
  193. package/frontend/src/components/DisclaimerDialog.tsx +0 -106
  194. package/frontend/src/components/GitHubLoginDialog.tsx +0 -314
  195. package/frontend/src/components/OnboardingDialog.tsx +0 -185
  196. package/frontend/src/components/PrivacyOptInDialog.tsx +0 -130
  197. package/frontend/src/components/ProvidePatDialog.tsx +0 -98
  198. package/frontend/src/components/TaskTemplateManager.tsx +0 -336
  199. package/frontend/src/components/config-provider.tsx +0 -119
  200. package/frontend/src/components/context/TaskDetailsContextProvider.tsx +0 -470
  201. package/frontend/src/components/context/taskDetailsContext.ts +0 -125
  202. package/frontend/src/components/keyboard-shortcuts-demo.tsx +0 -35
  203. package/frontend/src/components/layout/navbar.tsx +0 -86
  204. package/frontend/src/components/logo.tsx +0 -44
  205. package/frontend/src/components/projects/ProjectCard.tsx +0 -155
  206. package/frontend/src/components/projects/project-detail.tsx +0 -251
  207. package/frontend/src/components/projects/project-form-fields.tsx +0 -238
  208. package/frontend/src/components/projects/project-form.tsx +0 -301
  209. package/frontend/src/components/projects/project-list.tsx +0 -200
  210. package/frontend/src/components/projects/projects-page.tsx +0 -20
  211. package/frontend/src/components/tasks/BranchSelector.tsx +0 -169
  212. package/frontend/src/components/tasks/DeleteFileConfirmationDialog.tsx +0 -94
  213. package/frontend/src/components/tasks/EditorSelectionDialog.tsx +0 -119
  214. package/frontend/src/components/tasks/TaskCard.tsx +0 -154
  215. package/frontend/src/components/tasks/TaskDetails/CollapsibleToolbar.tsx +0 -33
  216. package/frontend/src/components/tasks/TaskDetails/DiffCard.tsx +0 -109
  217. package/frontend/src/components/tasks/TaskDetails/DiffChunkSection.tsx +0 -135
  218. package/frontend/src/components/tasks/TaskDetails/DiffFile.tsx +0 -296
  219. package/frontend/src/components/tasks/TaskDetails/DiffTab.tsx +0 -32
  220. package/frontend/src/components/tasks/TaskDetails/DisplayConversationEntry.tsx +0 -392
  221. package/frontend/src/components/tasks/TaskDetails/LogsTab/Conversation.tsx +0 -256
  222. package/frontend/src/components/tasks/TaskDetails/LogsTab/ConversationEntry.tsx +0 -56
  223. package/frontend/src/components/tasks/TaskDetails/LogsTab/NormalizedConversationViewer.tsx +0 -92
  224. package/frontend/src/components/tasks/TaskDetails/LogsTab/Prompt.tsx +0 -22
  225. package/frontend/src/components/tasks/TaskDetails/LogsTab/SetupScriptRunning.tsx +0 -49
  226. package/frontend/src/components/tasks/TaskDetails/LogsTab.tsx +0 -186
  227. package/frontend/src/components/tasks/TaskDetails/ProcessesTab.tsx +0 -288
  228. package/frontend/src/components/tasks/TaskDetails/RelatedTasksTab.tsx +0 -216
  229. package/frontend/src/components/tasks/TaskDetails/TabNavigation.tsx +0 -93
  230. package/frontend/src/components/tasks/TaskDetailsHeader.tsx +0 -169
  231. package/frontend/src/components/tasks/TaskDetailsPanel.tsx +0 -126
  232. package/frontend/src/components/tasks/TaskDetailsToolbar.tsx +0 -302
  233. package/frontend/src/components/tasks/TaskFollowUpSection.tsx +0 -130
  234. package/frontend/src/components/tasks/TaskFormDialog.tsx +0 -400
  235. package/frontend/src/components/tasks/TaskKanbanBoard.tsx +0 -180
  236. package/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx +0 -259
  237. package/frontend/src/components/tasks/Toolbar/CreatePRDialog.tsx +0 -243
  238. package/frontend/src/components/tasks/Toolbar/CurrentAttempt.tsx +0 -899
  239. package/frontend/src/components/tasks/index.ts +0 -2
  240. package/frontend/src/components/theme-provider.tsx +0 -82
  241. package/frontend/src/components/theme-toggle.tsx +0 -36
  242. package/frontend/src/components/ui/alert.tsx +0 -59
  243. package/frontend/src/components/ui/auto-expanding-textarea.tsx +0 -70
  244. package/frontend/src/components/ui/badge.tsx +0 -36
  245. package/frontend/src/components/ui/button.tsx +0 -56
  246. package/frontend/src/components/ui/card.tsx +0 -86
  247. package/frontend/src/components/ui/checkbox.tsx +0 -44
  248. package/frontend/src/components/ui/chip.tsx +0 -25
  249. package/frontend/src/components/ui/dialog.tsx +0 -124
  250. package/frontend/src/components/ui/dropdown-menu.tsx +0 -198
  251. package/frontend/src/components/ui/file-search-textarea.tsx +0 -292
  252. package/frontend/src/components/ui/folder-picker.tsx +0 -279
  253. package/frontend/src/components/ui/input.tsx +0 -25
  254. package/frontend/src/components/ui/label.tsx +0 -24
  255. package/frontend/src/components/ui/loader.tsx +0 -26
  256. package/frontend/src/components/ui/markdown-renderer.tsx +0 -75
  257. package/frontend/src/components/ui/select.tsx +0 -160
  258. package/frontend/src/components/ui/separator.tsx +0 -31
  259. package/frontend/src/components/ui/shadcn-io/kanban/index.tsx +0 -185
  260. package/frontend/src/components/ui/table.tsx +0 -117
  261. package/frontend/src/components/ui/tabs.tsx +0 -53
  262. package/frontend/src/components/ui/textarea.tsx +0 -22
  263. package/frontend/src/components/ui/tooltip.tsx +0 -28
  264. package/frontend/src/hooks/useNormalizedConversation.ts +0 -440
  265. package/frontend/src/index.css +0 -225
  266. package/frontend/src/lib/api.ts +0 -630
  267. package/frontend/src/lib/keyboard-shortcuts.ts +0 -266
  268. package/frontend/src/lib/responsive-config.ts +0 -70
  269. package/frontend/src/lib/types.ts +0 -39
  270. package/frontend/src/lib/utils.ts +0 -10
  271. package/frontend/src/main.tsx +0 -50
  272. package/frontend/src/pages/McpServers.tsx +0 -418
  273. package/frontend/src/pages/Settings.tsx +0 -610
  274. package/frontend/src/pages/project-tasks.tsx +0 -575
  275. package/frontend/src/pages/projects.tsx +0 -18
  276. package/frontend/src/vite-env.d.ts +0 -1
  277. package/frontend/tailwind.config.js +0 -125
  278. package/frontend/tsconfig.json +0 -26
  279. package/frontend/tsconfig.node.json +0 -10
  280. package/frontend/vite.config.ts +0 -33
  281. package/npx-cli/README.md +0 -159
  282. package/npx-cli/automagik-forge-0.1.0.tgz +0 -0
  283. package/npx-cli/automagik-forge-0.1.10.tgz +0 -0
  284. package/npx-cli/package.json +0 -17
  285. package/npx-cli/vibe-kanban-0.0.55.tgz +0 -0
  286. package/pnpm-workspace.yaml +0 -2
  287. package/rust-toolchain.toml +0 -11
  288. package/rustfmt.toml +0 -3
  289. package/scripts/load-env.js +0 -43
  290. package/scripts/mcp_test.js +0 -374
  291. package/scripts/prepare-db.js +0 -45
  292. package/scripts/setup-dev-environment.js +0 -274
  293. package/scripts/start-mcp-sse.js +0 -70
  294. package/scripts/test-debug.js +0 -32
  295. package/scripts/test-mcp-sse.js +0 -138
  296. package/scripts/test-simple.js +0 -44
  297. package/scripts/test-wish-final.js +0 -179
  298. package/scripts/test-wish-system.js +0 -221
  299. package/shared/types.ts +0 -182
  300. package/test-npm-package.sh +0 -42
  301. /package/{npx-cli/bin → bin}/cli.js +0 -0
@@ -1,1172 +0,0 @@
1
- use axum::{
2
- extract::{Query, State},
3
- http::StatusCode,
4
- middleware::from_fn_with_state,
5
- response::Json as ResponseJson,
6
- routing::get,
7
- Extension, Json, Router,
8
- };
9
- use serde::{Deserialize, Serialize};
10
- use sqlx::SqlitePool;
11
- use ts_rs::TS;
12
- use utoipa;
13
- use uuid::Uuid;
14
-
15
- use crate::{
16
- app_state::AppState,
17
- executor::{
18
- ActionType, ExecutorConfig, NormalizedConversation, NormalizedEntry, NormalizedEntryType,
19
- },
20
- middleware::{load_execution_process_with_context_middleware, load_task_attempt_middleware},
21
- models::{
22
- config::Config,
23
- execution_process::{
24
- ExecutionProcess, ExecutionProcessStatus, ExecutionProcessSummary, ExecutionProcessType,
25
- },
26
- project::Project,
27
- task::{Task, TaskStatus},
28
- task_attempt::{
29
- BranchStatus, CreateFollowUpAttempt, CreatePrParams, CreateTaskAttempt, TaskAttempt,
30
- TaskAttemptState, WorktreeDiff,
31
- },
32
- ApiResponse,
33
- },
34
- };
35
-
36
- #[derive(Debug, Deserialize, Serialize)]
37
- pub struct RebaseTaskAttemptRequest {
38
- pub new_base_branch: Option<String>,
39
- }
40
-
41
- #[derive(Debug, Deserialize, Serialize)]
42
- pub struct CreateGitHubPRRequest {
43
- pub title: String,
44
- pub body: Option<String>,
45
- pub base_branch: Option<String>,
46
- }
47
-
48
- #[derive(Debug, Serialize)]
49
- pub struct FollowUpResponse {
50
- pub message: String,
51
- pub actual_attempt_id: Uuid,
52
- pub created_new_attempt: bool,
53
- }
54
-
55
- #[derive(Debug, Serialize, TS)]
56
- #[ts(export)]
57
- pub struct ProcessLogsResponse {
58
- pub id: Uuid,
59
- pub process_type: ExecutionProcessType,
60
- pub command: String,
61
- pub executor_type: Option<String>,
62
- pub status: ExecutionProcessStatus,
63
- pub normalized_conversation: NormalizedConversation,
64
- }
65
-
66
- // Helper to normalize logs for a process (extracted from get_execution_process_normalized_logs)
67
- async fn normalize_process_logs(
68
- db_pool: &SqlitePool,
69
- process: &ExecutionProcess,
70
- ) -> NormalizedConversation {
71
- use crate::models::{
72
- execution_process::ExecutionProcessType, executor_session::ExecutorSession,
73
- };
74
- let executor_session = ExecutorSession::find_by_execution_process_id(db_pool, process.id)
75
- .await
76
- .ok()
77
- .flatten();
78
-
79
- let has_stdout = process
80
- .stdout
81
- .as_ref()
82
- .map(|s| !s.trim().is_empty())
83
- .unwrap_or(false);
84
- let has_stderr = process
85
- .stderr
86
- .as_ref()
87
- .map(|s| !s.trim().is_empty())
88
- .unwrap_or(false);
89
-
90
- if !has_stdout && !has_stderr {
91
- return NormalizedConversation {
92
- entries: vec![],
93
- session_id: None,
94
- executor_type: process
95
- .executor_type
96
- .clone()
97
- .unwrap_or("unknown".to_string()),
98
- prompt: executor_session.as_ref().and_then(|s| s.prompt.clone()),
99
- summary: executor_session.as_ref().and_then(|s| s.summary.clone()),
100
- };
101
- }
102
-
103
- // Parse stdout as JSONL using executor normalization
104
- let mut stdout_entries = Vec::new();
105
- if let Some(stdout) = &process.stdout {
106
- if !stdout.trim().is_empty() {
107
- let executor_type = process.executor_type.as_deref().unwrap_or("unknown");
108
- let executor_config = if process.process_type == ExecutionProcessType::SetupScript {
109
- ExecutorConfig::SetupScript {
110
- script: executor_session
111
- .as_ref()
112
- .and_then(|s| s.prompt.clone())
113
- .unwrap_or_else(|| "setup script".to_string()),
114
- }
115
- } else {
116
- match executor_type.to_string().parse() {
117
- Ok(config) => config,
118
- Err(_) => {
119
- return NormalizedConversation {
120
- entries: vec![],
121
- session_id: None,
122
- executor_type: executor_type.to_string(),
123
- prompt: executor_session.as_ref().and_then(|s| s.prompt.clone()),
124
- summary: executor_session.as_ref().and_then(|s| s.summary.clone()),
125
- };
126
- }
127
- }
128
- };
129
- let executor = executor_config.create_executor();
130
- let working_dir_path = match std::fs::canonicalize(&process.working_directory) {
131
- Ok(canonical_path) => canonical_path.to_string_lossy().to_string(),
132
- Err(_) => process.working_directory.clone(),
133
- };
134
- if let Ok(normalized) = executor.normalize_logs(stdout, &working_dir_path) {
135
- stdout_entries = normalized.entries;
136
- }
137
- }
138
- }
139
- // Parse stderr chunks separated by boundary markers
140
- let mut stderr_entries = Vec::new();
141
- if let Some(stderr) = &process.stderr {
142
- let trimmed = stderr.trim();
143
- if !trimmed.is_empty() {
144
- let chunks: Vec<&str> = trimmed.split("---STDERR_CHUNK_BOUNDARY---").collect();
145
- for chunk in chunks {
146
- let chunk_trimmed = chunk.trim();
147
- if !chunk_trimmed.is_empty() {
148
- let filtered_content = chunk_trimmed.replace("---STDERR_CHUNK_BOUNDARY---", "");
149
- if !filtered_content.trim().is_empty() {
150
- stderr_entries.push(NormalizedEntry {
151
- timestamp: Some(chrono::Utc::now().to_rfc3339()),
152
- entry_type: NormalizedEntryType::ErrorMessage,
153
- content: filtered_content.trim().to_string(),
154
- metadata: None,
155
- });
156
- }
157
- }
158
- }
159
- }
160
- }
161
- let mut all_entries = Vec::new();
162
- all_entries.extend(stdout_entries);
163
- all_entries.extend(stderr_entries);
164
- all_entries.sort_by(|a, b| match (&a.timestamp, &b.timestamp) {
165
- (Some(a_ts), Some(b_ts)) => a_ts.cmp(b_ts),
166
- (Some(_), None) => std::cmp::Ordering::Less,
167
- (None, Some(_)) => std::cmp::Ordering::Greater,
168
- (None, None) => std::cmp::Ordering::Equal,
169
- });
170
- let executor_type = if process.process_type == ExecutionProcessType::SetupScript {
171
- "setup-script".to_string()
172
- } else {
173
- process
174
- .executor_type
175
- .clone()
176
- .unwrap_or("unknown".to_string())
177
- };
178
- NormalizedConversation {
179
- entries: all_entries,
180
- session_id: None,
181
- executor_type,
182
- prompt: executor_session.as_ref().and_then(|s| s.prompt.clone()),
183
- summary: executor_session.as_ref().and_then(|s| s.summary.clone()),
184
- }
185
- }
186
-
187
- /// Get all normalized logs for all execution processes of a task attempt
188
- pub async fn get_task_attempt_all_logs(
189
- Extension(_project): Extension<Project>,
190
- Extension(_task): Extension<Task>,
191
- Extension(task_attempt): Extension<TaskAttempt>,
192
- State(app_state): State<AppState>,
193
- ) -> Result<Json<ApiResponse<Vec<ProcessLogsResponse>>>, StatusCode> {
194
- // Fetch all execution processes for this attempt
195
- let processes = match ExecutionProcess::find_by_task_attempt_id(
196
- &app_state.db_pool,
197
- task_attempt.id,
198
- )
199
- .await
200
- {
201
- Ok(list) => list,
202
- Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
203
- };
204
- // For each process, normalize logs
205
- let mut result = Vec::new();
206
- for process in processes {
207
- let normalized_conversation = normalize_process_logs(&app_state.db_pool, &process).await;
208
- result.push(ProcessLogsResponse {
209
- id: process.id,
210
- process_type: process.process_type.clone(),
211
- command: process.command.clone(),
212
- executor_type: process.executor_type.clone(),
213
- status: process.status.clone(),
214
- normalized_conversation,
215
- });
216
- }
217
- Ok(Json(ApiResponse::success(result)))
218
- }
219
-
220
- #[utoipa::path(
221
- get,
222
- path = "/api/projects/{project_id}/tasks/{task_id}/attempts",
223
- params(
224
- ("project_id" = String, Path, description = "Project ID"),
225
- ("task_id" = String, Path, description = "Task ID")
226
- ),
227
- responses(
228
- (status = 200, description = "List all task attempts", body = ApiResponse<Vec<TaskAttempt>>),
229
- (status = 404, description = "Project or task not found"),
230
- (status = 500, description = "Internal server error")
231
- ),
232
- tag = "task_attempts"
233
- )]
234
- pub async fn get_task_attempts(
235
- Extension(_project): Extension<Project>,
236
- Extension(task): Extension<Task>,
237
- State(app_state): State<AppState>,
238
- ) -> Result<ResponseJson<ApiResponse<Vec<TaskAttempt>>>, StatusCode> {
239
- match TaskAttempt::find_by_task_id(&app_state.db_pool, task.id).await {
240
- Ok(attempts) => Ok(ResponseJson(ApiResponse::success(attempts))),
241
- Err(e) => {
242
- tracing::error!("Failed to fetch task attempts for task {}: {}", task.id, e);
243
- Err(StatusCode::INTERNAL_SERVER_ERROR)
244
- }
245
- }
246
- }
247
-
248
- #[utoipa::path(
249
- post,
250
- path = "/api/projects/{project_id}/tasks/{task_id}/attempts",
251
- params(
252
- ("project_id" = String, Path, description = "Project ID"),
253
- ("task_id" = String, Path, description = "Task ID")
254
- ),
255
- request_body = CreateTaskAttempt,
256
- responses(
257
- (status = 200, description = "Task attempt created successfully", body = ApiResponse<TaskAttempt>),
258
- (status = 404, description = "Project or task not found"),
259
- (status = 400, description = "Invalid input"),
260
- (status = 500, description = "Internal server error")
261
- ),
262
- tag = "task_attempts"
263
- )]
264
- pub async fn create_task_attempt(
265
- Extension(_project): Extension<Project>,
266
- Extension(task): Extension<Task>,
267
- State(app_state): State<AppState>,
268
- Json(payload): Json<CreateTaskAttempt>,
269
- ) -> Result<ResponseJson<ApiResponse<TaskAttempt>>, StatusCode> {
270
- let executor_string = payload.executor.as_ref().map(|exec| exec.to_string());
271
-
272
- match TaskAttempt::create(&app_state.db_pool, &payload, task.id).await {
273
- Ok(attempt) => {
274
- app_state
275
- .track_analytics_event(
276
- "task_attempt_started",
277
- Some(serde_json::json!({
278
- "task_id": task.id.to_string(),
279
- "executor_type": executor_string.as_deref().unwrap_or("default"),
280
- "attempt_id": attempt.id.to_string(),
281
- })),
282
- )
283
- .await;
284
-
285
- // Start execution asynchronously (don't block the response)
286
- let app_state_clone = app_state.clone();
287
- let attempt_id = attempt.id;
288
- let task_id = task.id;
289
- let project_id = _project.id;
290
- tokio::spawn(async move {
291
- if let Err(e) = TaskAttempt::start_execution(
292
- &app_state_clone.db_pool,
293
- &app_state_clone,
294
- attempt_id,
295
- task_id,
296
- project_id,
297
- )
298
- .await
299
- {
300
- tracing::error!(
301
- "Failed to start execution for task attempt {}: {}",
302
- attempt_id,
303
- e
304
- );
305
- }
306
- });
307
-
308
- Ok(ResponseJson(ApiResponse::success(attempt)))
309
- }
310
- Err(e) => {
311
- tracing::error!("Failed to create task attempt: {}", e);
312
- Err(StatusCode::INTERNAL_SERVER_ERROR)
313
- }
314
- }
315
- }
316
-
317
- pub async fn get_task_attempt_diff(
318
- Extension(project): Extension<Project>,
319
- Extension(task): Extension<Task>,
320
- Extension(task_attempt): Extension<TaskAttempt>,
321
- State(app_state): State<AppState>,
322
- ) -> Result<ResponseJson<ApiResponse<WorktreeDiff>>, StatusCode> {
323
- match TaskAttempt::get_diff(&app_state.db_pool, task_attempt.id, task.id, project.id).await {
324
- Ok(diff) => Ok(ResponseJson(ApiResponse::success(diff))),
325
- Err(e) => {
326
- tracing::error!(
327
- "Failed to get diff for task attempt {}: {}",
328
- task_attempt.id,
329
- e
330
- );
331
- Err(StatusCode::INTERNAL_SERVER_ERROR)
332
- }
333
- }
334
- }
335
-
336
- #[axum::debug_handler]
337
- pub async fn merge_task_attempt(
338
- Extension(project): Extension<Project>,
339
- Extension(task): Extension<Task>,
340
- Extension(task_attempt): Extension<TaskAttempt>,
341
- State(app_state): State<AppState>,
342
- ) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
343
- match TaskAttempt::merge_changes(&app_state.db_pool, task_attempt.id, task.id, project.id).await
344
- {
345
- Ok(_) => {
346
- // Update task status to Done
347
- if let Err(e) = Task::update_status(
348
- &app_state.db_pool,
349
- task.id,
350
- project.id,
351
- crate::models::task::TaskStatus::Done,
352
- )
353
- .await
354
- {
355
- tracing::error!("Failed to update task status to Done after merge: {}", e);
356
- return Err(StatusCode::INTERNAL_SERVER_ERROR);
357
- }
358
-
359
- // Track task attempt merged event
360
- app_state
361
- .track_analytics_event(
362
- "task_attempt_merged",
363
- Some(serde_json::json!({
364
- "task_id": task.id.to_string(),
365
- "project_id": project.id.to_string(),
366
- "attempt_id": task_attempt.id.to_string(),
367
- })),
368
- )
369
- .await;
370
-
371
- Ok(ResponseJson(ApiResponse::success(())))
372
- }
373
- Err(e) => {
374
- tracing::error!("Failed to merge task attempt {}: {}", task_attempt.id, e);
375
- Ok(ResponseJson(ApiResponse::error(&format!(
376
- "Failed to merge: {}",
377
- e
378
- ))))
379
- }
380
- }
381
- }
382
-
383
- pub async fn create_github_pr(
384
- Extension(project): Extension<Project>,
385
- Extension(task): Extension<Task>,
386
- Extension(task_attempt): Extension<TaskAttempt>,
387
- State(app_state): State<AppState>,
388
- Json(request): Json<CreateGitHubPRRequest>,
389
- ) -> Result<ResponseJson<ApiResponse<String>>, StatusCode> {
390
- // Load the user's GitHub configuration
391
- let config = match Config::load(&crate::utils::config_path()) {
392
- Ok(config) => config,
393
- Err(e) => {
394
- tracing::error!("Failed to load config: {}", e);
395
- return Err(StatusCode::INTERNAL_SERVER_ERROR);
396
- }
397
- };
398
-
399
- let github_token = match config.github.token {
400
- Some(token) => token,
401
- None => {
402
- return Ok(ResponseJson(ApiResponse::error(
403
- "GitHub authentication not configured. Please sign in with GitHub.",
404
- )));
405
- }
406
- };
407
-
408
- // Get the task attempt to access the stored base branch
409
- let attempt = &task_attempt;
410
-
411
- let base_branch = request.base_branch.unwrap_or_else(|| {
412
- // Use the stored base branch from the task attempt as the default
413
- // Fall back to config default or "main" only if stored base branch is somehow invalid
414
- if !attempt.base_branch.trim().is_empty() {
415
- attempt.base_branch.clone()
416
- } else {
417
- config
418
- .github
419
- .default_pr_base
420
- .unwrap_or_else(|| "main".to_string())
421
- }
422
- });
423
-
424
- match TaskAttempt::create_github_pr(
425
- &app_state.db_pool,
426
- CreatePrParams {
427
- attempt_id: task_attempt.id,
428
- task_id: task.id,
429
- project_id: project.id,
430
- github_token: &config.github.pat.unwrap_or(github_token),
431
- title: &request.title,
432
- body: request.body.as_deref(),
433
- base_branch: Some(&base_branch),
434
- },
435
- )
436
- .await
437
- {
438
- Ok(pr_url) => {
439
- app_state
440
- .track_analytics_event(
441
- "github_pr_created",
442
- Some(serde_json::json!({
443
- "task_id": task.id.to_string(),
444
- "project_id": project.id.to_string(),
445
- "attempt_id": task_attempt.id.to_string(),
446
- })),
447
- )
448
- .await;
449
-
450
- Ok(ResponseJson(ApiResponse::success(pr_url)))
451
- }
452
- Err(e) => {
453
- tracing::error!(
454
- "Failed to create GitHub PR for attempt {}: {}",
455
- task_attempt.id,
456
- e
457
- );
458
- let message = match &e {
459
- crate::models::task_attempt::TaskAttemptError::GitHubService(
460
- crate::services::GitHubServiceError::TokenInvalid,
461
- ) => Some("github_token_invalid".to_string()),
462
- crate::models::task_attempt::TaskAttemptError::GitService(
463
- crate::services::git_service::GitServiceError::Git(err),
464
- ) if err
465
- .message()
466
- .contains("too many redirects or authentication replays") =>
467
- {
468
- Some("insufficient_github_permissions".to_string()) // PAT is invalid
469
- }
470
- crate::models::task_attempt::TaskAttemptError::GitService(
471
- crate::services::git_service::GitServiceError::Git(err),
472
- ) if err.message().contains("status code: 403") => {
473
- Some("insufficient_github_permissions".to_string())
474
- }
475
- crate::models::task_attempt::TaskAttemptError::GitService(
476
- crate::services::git_service::GitServiceError::Git(err),
477
- ) if err.message().contains("status code: 404") => {
478
- Some("github_repo_not_found_or_no_access".to_string())
479
- }
480
- _ => Some(format!("Failed to create PR: {}", e)),
481
- };
482
- Ok(ResponseJson(ApiResponse::error(
483
- message.as_deref().unwrap_or("Unknown error"),
484
- )))
485
- }
486
- }
487
- }
488
-
489
- #[derive(serde::Deserialize)]
490
- pub struct OpenEditorRequest {
491
- editor_type: Option<String>,
492
- }
493
-
494
- pub async fn open_task_attempt_in_editor(
495
- Extension(_project): Extension<Project>,
496
- Extension(_task): Extension<Task>,
497
- Extension(task_attempt): Extension<TaskAttempt>,
498
- State(app_state): State<AppState>,
499
- Json(payload): Json<Option<OpenEditorRequest>>,
500
- ) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
501
- // Get the task attempt to access the worktree path
502
- let attempt = &task_attempt;
503
-
504
- // Get editor command from config or override
505
- let editor_command = {
506
- let config_guard = app_state.get_config().read().await;
507
- if let Some(ref request) = payload {
508
- if let Some(ref editor_type) = request.editor_type {
509
- // Create a temporary editor config with the override
510
- use crate::models::config::{EditorConfig, EditorType};
511
- let override_editor_type = match editor_type.as_str() {
512
- "vscode" => EditorType::VSCode,
513
- "cursor" => EditorType::Cursor,
514
- "windsurf" => EditorType::Windsurf,
515
- "intellij" => EditorType::IntelliJ,
516
- "zed" => EditorType::Zed,
517
- "custom" => EditorType::Custom,
518
- _ => config_guard.editor.editor_type.clone(),
519
- };
520
- let temp_config = EditorConfig {
521
- editor_type: override_editor_type,
522
- custom_command: config_guard.editor.custom_command.clone(),
523
- };
524
- temp_config.get_command()
525
- } else {
526
- config_guard.editor.get_command()
527
- }
528
- } else {
529
- config_guard.editor.get_command()
530
- }
531
- };
532
-
533
- // Open editor in the worktree directory
534
- let mut cmd = std::process::Command::new(&editor_command[0]);
535
- for arg in &editor_command[1..] {
536
- cmd.arg(arg);
537
- }
538
- cmd.arg(&attempt.worktree_path);
539
-
540
- match cmd.spawn() {
541
- Ok(_) => {
542
- tracing::info!(
543
- "Opened editor ({}) for task attempt {} at path: {}",
544
- editor_command.join(" "),
545
- task_attempt.id,
546
- attempt.worktree_path
547
- );
548
- Ok(ResponseJson(ApiResponse::success(())))
549
- }
550
- Err(e) => {
551
- tracing::error!(
552
- "Failed to open editor ({}) for attempt {}: {}",
553
- editor_command.join(" "),
554
- task_attempt.id,
555
- e
556
- );
557
- Err(StatusCode::INTERNAL_SERVER_ERROR)
558
- }
559
- }
560
- }
561
-
562
- pub async fn get_task_attempt_branch_status(
563
- Extension(project): Extension<Project>,
564
- Extension(task): Extension<Task>,
565
- Extension(task_attempt): Extension<TaskAttempt>,
566
- State(app_state): State<AppState>,
567
- ) -> Result<ResponseJson<ApiResponse<BranchStatus>>, StatusCode> {
568
- match TaskAttempt::get_branch_status(&app_state.db_pool, task_attempt.id, task.id, project.id)
569
- .await
570
- {
571
- Ok(status) => Ok(ResponseJson(ApiResponse::success(status))),
572
- Err(e) => {
573
- tracing::error!(
574
- "Failed to get branch status for task attempt {}: {}",
575
- task_attempt.id,
576
- e
577
- );
578
- Err(StatusCode::INTERNAL_SERVER_ERROR)
579
- }
580
- }
581
- }
582
-
583
- #[axum::debug_handler]
584
- pub async fn rebase_task_attempt(
585
- Extension(project): Extension<Project>,
586
- Extension(task): Extension<Task>,
587
- Extension(task_attempt): Extension<TaskAttempt>,
588
- State(app_state): State<AppState>,
589
- request_body: Option<Json<RebaseTaskAttemptRequest>>,
590
- ) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
591
- // Extract new base branch from request body if provided
592
- let new_base_branch = request_body.and_then(|body| body.new_base_branch.clone());
593
-
594
- match TaskAttempt::rebase_attempt(
595
- &app_state.db_pool,
596
- task_attempt.id,
597
- task.id,
598
- project.id,
599
- new_base_branch,
600
- )
601
- .await
602
- {
603
- Ok(_new_base_commit) => Ok(ResponseJson(ApiResponse::success(()))),
604
- Err(e) => {
605
- tracing::error!("Failed to rebase task attempt {}: {}", task_attempt.id, e);
606
- Ok(ResponseJson(ApiResponse::error(&e.to_string())))
607
- }
608
- }
609
- }
610
-
611
- pub async fn get_task_attempt_execution_processes(
612
- Extension(_project): Extension<Project>,
613
- Extension(_task): Extension<Task>,
614
- Extension(task_attempt): Extension<TaskAttempt>,
615
- State(app_state): State<AppState>,
616
- ) -> Result<ResponseJson<ApiResponse<Vec<ExecutionProcessSummary>>>, StatusCode> {
617
- match ExecutionProcess::find_summaries_by_task_attempt_id(&app_state.db_pool, task_attempt.id)
618
- .await
619
- {
620
- Ok(processes) => Ok(ResponseJson(ApiResponse::success(processes))),
621
- Err(e) => {
622
- tracing::error!(
623
- "Failed to fetch execution processes for attempt {}: {}",
624
- task_attempt.id,
625
- e
626
- );
627
- Err(StatusCode::INTERNAL_SERVER_ERROR)
628
- }
629
- }
630
- }
631
-
632
- pub async fn get_execution_process(
633
- Extension(execution_process): Extension<ExecutionProcess>,
634
- ) -> Result<ResponseJson<ApiResponse<ExecutionProcess>>, StatusCode> {
635
- Ok(ResponseJson(ApiResponse::success(execution_process)))
636
- }
637
-
638
- #[axum::debug_handler]
639
- pub async fn stop_all_execution_processes(
640
- Extension(_project): Extension<Project>,
641
- Extension(_task): Extension<Task>,
642
- Extension(task_attempt): Extension<TaskAttempt>,
643
- State(app_state): State<AppState>,
644
- ) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
645
- // Get all execution processes for the task attempt
646
- let processes = match ExecutionProcess::find_by_task_attempt_id(
647
- &app_state.db_pool,
648
- task_attempt.id,
649
- )
650
- .await
651
- {
652
- Ok(processes) => processes,
653
- Err(e) => {
654
- tracing::error!(
655
- "Failed to fetch execution processes for attempt {}: {}",
656
- task_attempt.id,
657
- e
658
- );
659
- return Err(StatusCode::INTERNAL_SERVER_ERROR);
660
- }
661
- };
662
-
663
- let mut stopped_count = 0;
664
- let mut errors = Vec::new();
665
-
666
- // Stop all running processes
667
- for process in processes {
668
- match app_state.stop_running_execution_by_id(process.id).await {
669
- Ok(true) => {
670
- stopped_count += 1;
671
-
672
- // Update the execution process status in the database
673
- if let Err(e) = ExecutionProcess::update_completion(
674
- &app_state.db_pool,
675
- process.id,
676
- crate::models::execution_process::ExecutionProcessStatus::Killed,
677
- None,
678
- )
679
- .await
680
- {
681
- tracing::error!("Failed to update execution process status: {}", e);
682
- errors.push(format!("Failed to update process {} status", process.id));
683
- } else {
684
- // Process stopped successfully
685
- }
686
- }
687
- Ok(false) => {
688
- // Process was not running, which is fine
689
- }
690
- Err(e) => {
691
- tracing::error!("Failed to stop execution process {}: {}", process.id, e);
692
- errors.push(format!("Failed to stop process {}: {}", process.id, e));
693
- }
694
- }
695
- }
696
-
697
- if !errors.is_empty() {
698
- return Ok(ResponseJson(ApiResponse::error(&format!(
699
- "Stopped {} processes, but encountered errors: {}",
700
- stopped_count,
701
- errors.join(", ")
702
- ))));
703
- }
704
-
705
- if stopped_count == 0 {
706
- return Ok(ResponseJson(ApiResponse::success(())));
707
- }
708
-
709
- Ok(ResponseJson(ApiResponse::success(())))
710
- }
711
-
712
- #[axum::debug_handler]
713
- pub async fn stop_execution_process(
714
- Extension(_project): Extension<Project>,
715
- Extension(_task): Extension<Task>,
716
- Extension(_task_attempt): Extension<TaskAttempt>,
717
- Extension(execution_process): Extension<ExecutionProcess>,
718
- State(app_state): State<AppState>,
719
- ) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
720
- // Stop the specific execution process
721
- let stopped = match app_state
722
- .stop_running_execution_by_id(execution_process.id)
723
- .await
724
- {
725
- Ok(stopped) => stopped,
726
- Err(e) => {
727
- tracing::error!(
728
- "Failed to stop execution process {}: {}",
729
- execution_process.id,
730
- e
731
- );
732
- return Err(StatusCode::INTERNAL_SERVER_ERROR);
733
- }
734
- };
735
-
736
- if !stopped {
737
- return Ok(ResponseJson(ApiResponse::success(())));
738
- }
739
-
740
- // Update the execution process status in the database
741
- if let Err(e) = ExecutionProcess::update_completion(
742
- &app_state.db_pool,
743
- execution_process.id,
744
- crate::models::execution_process::ExecutionProcessStatus::Killed,
745
- None,
746
- )
747
- .await
748
- {
749
- tracing::error!("Failed to update execution process status: {}", e);
750
- return Err(StatusCode::INTERNAL_SERVER_ERROR);
751
- }
752
-
753
- // Process stopped successfully
754
-
755
- Ok(ResponseJson(ApiResponse::success(())))
756
- }
757
-
758
- #[derive(serde::Deserialize)]
759
- pub struct DeleteFileQuery {
760
- file_path: String,
761
- }
762
-
763
- #[axum::debug_handler]
764
- pub async fn delete_task_attempt_file(
765
- Extension(project): Extension<Project>,
766
- Extension(task): Extension<Task>,
767
- Extension(task_attempt): Extension<TaskAttempt>,
768
- Query(query): Query<DeleteFileQuery>,
769
- State(app_state): State<AppState>,
770
- ) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
771
- match TaskAttempt::delete_file(
772
- &app_state.db_pool,
773
- task_attempt.id,
774
- task.id,
775
- project.id,
776
- &query.file_path,
777
- )
778
- .await
779
- {
780
- Ok(_commit_id) => Ok(ResponseJson(ApiResponse::success(()))),
781
- Err(e) => {
782
- tracing::error!(
783
- "Failed to delete file '{}' from task attempt {}: {}",
784
- query.file_path,
785
- task_attempt.id,
786
- e
787
- );
788
- Ok(ResponseJson(ApiResponse::error(&e.to_string())))
789
- }
790
- }
791
- }
792
-
793
- pub async fn create_followup_attempt(
794
- Extension(project): Extension<Project>,
795
- Extension(task): Extension<Task>,
796
- Extension(task_attempt): Extension<TaskAttempt>,
797
- State(app_state): State<AppState>,
798
- Json(payload): Json<CreateFollowUpAttempt>,
799
- ) -> Result<ResponseJson<ApiResponse<FollowUpResponse>>, StatusCode> {
800
- // Start follow-up execution synchronously to catch errors
801
- match TaskAttempt::start_followup_execution(
802
- &app_state.db_pool,
803
- &app_state,
804
- task_attempt.id,
805
- task.id,
806
- project.id,
807
- &payload.prompt,
808
- )
809
- .await
810
- {
811
- Ok(actual_attempt_id) => {
812
- let created_new_attempt = actual_attempt_id != task_attempt.id;
813
- let message = if created_new_attempt {
814
- format!(
815
- "Follow-up execution started on new attempt {} (original worktree was deleted)",
816
- actual_attempt_id
817
- )
818
- } else {
819
- "Follow-up execution started successfully".to_string()
820
- };
821
-
822
- Ok(ResponseJson(ApiResponse::success(FollowUpResponse {
823
- message: message.clone(),
824
- actual_attempt_id,
825
- created_new_attempt,
826
- })))
827
- }
828
- Err(e) => {
829
- tracing::error!(
830
- "Failed to start follow-up execution for task attempt {}: {}",
831
- task_attempt.id,
832
- e
833
- );
834
- Err(StatusCode::INTERNAL_SERVER_ERROR)
835
- }
836
- }
837
- }
838
-
839
- pub async fn start_dev_server(
840
- Extension(project): Extension<Project>,
841
- Extension(task): Extension<Task>,
842
- Extension(task_attempt): Extension<TaskAttempt>,
843
- State(app_state): State<AppState>,
844
- ) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
845
- // Stop any existing dev servers for this project
846
- let existing_dev_servers =
847
- match ExecutionProcess::find_running_dev_servers_by_project(&app_state.db_pool, project.id)
848
- .await
849
- {
850
- Ok(servers) => servers,
851
- Err(e) => {
852
- tracing::error!(
853
- "Failed to find running dev servers for project {}: {}",
854
- project.id,
855
- e
856
- );
857
- return Err(StatusCode::INTERNAL_SERVER_ERROR);
858
- }
859
- };
860
-
861
- for dev_server in existing_dev_servers {
862
- tracing::info!(
863
- "Stopping existing dev server {} for project {}",
864
- dev_server.id,
865
- project.id
866
- );
867
-
868
- // Stop the running process
869
- if let Err(e) = app_state.stop_running_execution_by_id(dev_server.id).await {
870
- tracing::error!("Failed to stop dev server {}: {}", dev_server.id, e);
871
- } else {
872
- // Update the execution process status in the database
873
- if let Err(e) = ExecutionProcess::update_completion(
874
- &app_state.db_pool,
875
- dev_server.id,
876
- crate::models::execution_process::ExecutionProcessStatus::Killed,
877
- None,
878
- )
879
- .await
880
- {
881
- tracing::error!(
882
- "Failed to update dev server {} status: {}",
883
- dev_server.id,
884
- e
885
- );
886
- }
887
- }
888
- }
889
-
890
- // Start dev server execution
891
- match TaskAttempt::start_dev_server(
892
- &app_state.db_pool,
893
- &app_state,
894
- task_attempt.id,
895
- task.id,
896
- project.id,
897
- )
898
- .await
899
- {
900
- Ok(_) => Ok(ResponseJson(ApiResponse::success(()))),
901
- Err(e) => {
902
- tracing::error!(
903
- "Failed to start dev server for task attempt {}: {}",
904
- task_attempt.id,
905
- e
906
- );
907
- Ok(ResponseJson(ApiResponse::error(&e.to_string())))
908
- }
909
- }
910
- }
911
-
912
- pub async fn get_task_attempt_execution_state(
913
- Extension(project): Extension<Project>,
914
- Extension(task): Extension<Task>,
915
- Extension(task_attempt): Extension<TaskAttempt>,
916
- State(app_state): State<AppState>,
917
- ) -> Result<ResponseJson<ApiResponse<TaskAttemptState>>, StatusCode> {
918
- // Get the execution state
919
- match TaskAttempt::get_execution_state(&app_state.db_pool, task_attempt.id, task.id, project.id)
920
- .await
921
- {
922
- Ok(state) => Ok(ResponseJson(ApiResponse::success(state))),
923
- Err(e) => {
924
- tracing::error!(
925
- "Failed to get execution state for task attempt {}: {}",
926
- task_attempt.id,
927
- e
928
- );
929
- Err(StatusCode::INTERNAL_SERVER_ERROR)
930
- }
931
- }
932
- }
933
-
934
- /// Find plan content with context by searching through multiple processes in the same attempt
935
- async fn find_plan_content_with_context(
936
- pool: &SqlitePool,
937
- attempt_id: Uuid,
938
- ) -> Result<String, StatusCode> {
939
- // Get all execution processes for this attempt
940
- let execution_processes =
941
- match ExecutionProcess::find_by_task_attempt_id(pool, attempt_id).await {
942
- Ok(processes) => processes,
943
- Err(e) => {
944
- tracing::error!(
945
- "Failed to fetch execution processes for attempt {}: {}",
946
- attempt_id,
947
- e
948
- );
949
- return Err(StatusCode::INTERNAL_SERVER_ERROR);
950
- }
951
- };
952
-
953
- // Look for claudeplan processes (most recent first)
954
- for claudeplan_process in execution_processes
955
- .iter()
956
- .rev()
957
- .filter(|p| p.executor_type.as_deref() == Some("claude-plan"))
958
- {
959
- if let Some(stdout) = &claudeplan_process.stdout {
960
- if !stdout.trim().is_empty() {
961
- // Create executor and normalize logs
962
- let executor_config = ExecutorConfig::ClaudePlan;
963
- let executor = executor_config.create_executor();
964
-
965
- // Use working directory for normalization
966
- let working_dir_path =
967
- match std::fs::canonicalize(&claudeplan_process.working_directory) {
968
- Ok(canonical_path) => canonical_path.to_string_lossy().to_string(),
969
- Err(_) => claudeplan_process.working_directory.clone(),
970
- };
971
-
972
- // Normalize logs and extract plan content
973
- match executor.normalize_logs(stdout, &working_dir_path) {
974
- Ok(normalized_conversation) => {
975
- // Search for plan content in the normalized conversation
976
- if let Some(plan_content) = normalized_conversation
977
- .entries
978
- .iter()
979
- .rev()
980
- .find_map(|entry| {
981
- if let NormalizedEntryType::ToolUse {
982
- action_type: ActionType::PlanPresentation { plan },
983
- ..
984
- } = &entry.entry_type
985
- {
986
- Some(plan.clone())
987
- } else {
988
- None
989
- }
990
- })
991
- {
992
- return Ok(plan_content);
993
- }
994
- }
995
- Err(_) => {
996
- continue;
997
- }
998
- }
999
- }
1000
- }
1001
- }
1002
-
1003
- tracing::error!(
1004
- "No claudeplan content found in any process in attempt {}",
1005
- attempt_id
1006
- );
1007
- Err(StatusCode::NOT_FOUND)
1008
- }
1009
-
1010
- pub async fn approve_plan(
1011
- Extension(project): Extension<Project>,
1012
- Extension(task): Extension<Task>,
1013
- Extension(task_attempt): Extension<TaskAttempt>,
1014
- State(app_state): State<AppState>,
1015
- ) -> Result<ResponseJson<ApiResponse<FollowUpResponse>>, StatusCode> {
1016
- let current_task = &task;
1017
-
1018
- // Find plan content with context across the task hierarchy
1019
- let plan_content = find_plan_content_with_context(&app_state.db_pool, task_attempt.id).await?;
1020
-
1021
- use crate::models::task::CreateTask;
1022
- let new_task_id = Uuid::new_v4();
1023
- let create_task_data = CreateTask {
1024
- project_id: project.id,
1025
- title: format!("Execute Plan: {}", current_task.title),
1026
- description: Some(plan_content),
1027
- wish_id: current_task.wish_id.clone(),
1028
- parent_task_attempt: Some(task_attempt.id),
1029
- };
1030
-
1031
- let new_task = match Task::create(&app_state.db_pool, &create_task_data, new_task_id).await {
1032
- Ok(task) => task,
1033
- Err(e) => {
1034
- tracing::error!("Failed to create new task: {}", e);
1035
- return Err(StatusCode::INTERNAL_SERVER_ERROR);
1036
- }
1037
- };
1038
-
1039
- // Mark original task as completed since it now has children
1040
- if let Err(e) =
1041
- Task::update_status(&app_state.db_pool, task.id, project.id, TaskStatus::Done).await
1042
- {
1043
- tracing::error!("Failed to update original task status to Done: {}", e);
1044
- return Err(StatusCode::INTERNAL_SERVER_ERROR);
1045
- } else {
1046
- tracing::info!(
1047
- "Original task {} marked as Done after plan approval (has children)",
1048
- task.id
1049
- );
1050
- }
1051
-
1052
- Ok(ResponseJson(ApiResponse::success(FollowUpResponse {
1053
- message: format!("Plan approved and new task created: {}", new_task.title),
1054
- actual_attempt_id: new_task_id, // Return the new task ID
1055
- created_new_attempt: true,
1056
- })))
1057
- }
1058
-
1059
- pub async fn get_task_attempt_details(
1060
- Extension(task_attempt): Extension<TaskAttempt>,
1061
- ) -> Result<ResponseJson<ApiResponse<TaskAttempt>>, StatusCode> {
1062
- Ok(ResponseJson(ApiResponse::success(task_attempt)))
1063
- }
1064
-
1065
- pub async fn get_task_attempt_children(
1066
- Extension(task_attempt): Extension<TaskAttempt>,
1067
- Extension(project): Extension<Project>,
1068
- State(app_state): State<AppState>,
1069
- ) -> Result<ResponseJson<ApiResponse<Vec<Task>>>, StatusCode> {
1070
- match Task::find_related_tasks_by_attempt_id(&app_state.db_pool, task_attempt.id, project.id)
1071
- .await
1072
- {
1073
- Ok(related_tasks) => Ok(ResponseJson(ApiResponse::success(related_tasks))),
1074
- Err(e) => {
1075
- tracing::error!(
1076
- "Failed to fetch children for task attempt {}: {}",
1077
- task_attempt.id,
1078
- e
1079
- );
1080
- Err(StatusCode::INTERNAL_SERVER_ERROR)
1081
- }
1082
- }
1083
- }
1084
-
1085
- pub fn task_attempts_list_router(_state: AppState) -> Router<AppState> {
1086
- Router::new().route(
1087
- "/projects/:project_id/tasks/:task_id/attempts",
1088
- get(get_task_attempts).post(create_task_attempt),
1089
- )
1090
- }
1091
-
1092
- pub fn task_attempts_with_id_router(_state: AppState) -> Router<AppState> {
1093
- use axum::routing::post;
1094
-
1095
- Router::new()
1096
- .route(
1097
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/diff",
1098
- get(get_task_attempt_diff),
1099
- )
1100
- .route(
1101
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/merge",
1102
- post(merge_task_attempt),
1103
- )
1104
- .route(
1105
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/branch-status",
1106
- get(get_task_attempt_branch_status),
1107
- )
1108
- .route(
1109
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/rebase",
1110
- post(rebase_task_attempt),
1111
- )
1112
- .route(
1113
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/open-editor",
1114
- post(open_task_attempt_in_editor),
1115
- )
1116
- .route(
1117
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/delete-file",
1118
- post(delete_task_attempt_file),
1119
- )
1120
- .route(
1121
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/create-pr",
1122
- post(create_github_pr),
1123
- )
1124
- .route(
1125
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/execution-processes",
1126
- get(get_task_attempt_execution_processes),
1127
- )
1128
- .route(
1129
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/stop",
1130
- post(stop_all_execution_processes),
1131
- )
1132
- .merge(
1133
- Router::new()
1134
- .route(
1135
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/execution-processes/:process_id/stop",
1136
- post(stop_execution_process),
1137
- )
1138
- .route_layer(from_fn_with_state(_state.clone(), load_execution_process_with_context_middleware))
1139
- )
1140
- .route(
1141
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/logs",
1142
- get(get_task_attempt_all_logs),
1143
- )
1144
- .route(
1145
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/follow-up",
1146
- post(create_followup_attempt),
1147
- )
1148
- .route(
1149
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/start-dev-server",
1150
- post(start_dev_server),
1151
- )
1152
- .route(
1153
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id",
1154
- get(get_task_attempt_execution_state),
1155
- )
1156
- .route(
1157
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/approve-plan",
1158
- post(approve_plan),
1159
- )
1160
- .route(
1161
- "/projects/:project_id/tasks/:task_id/attempts/:attempt_id/children",
1162
- get(get_task_attempt_children),
1163
- )
1164
- .merge(
1165
- Router::new()
1166
- .route(
1167
- "/attempts/:attempt_id/details",
1168
- get(get_task_attempt_details),
1169
- )
1170
- .route_layer(from_fn_with_state(_state.clone(), load_task_attempt_middleware))
1171
- )
1172
- }