automagik-forge 0.1.11 → 0.1.13

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/.cargo/config.toml +13 -0
  2. package/.claude/commands/commit.md +376 -0
  3. package/.claude/commands/prompt.md +871 -0
  4. package/.env.example +20 -0
  5. package/.github/actions/setup-node/action.yml +29 -0
  6. package/.github/images/automagik-logo.png +0 -0
  7. package/.github/workflows/pre-release.yml +470 -0
  8. package/.github/workflows/publish.yml +145 -0
  9. package/.github/workflows/test.yml +63 -0
  10. package/.mcp.json +57 -0
  11. package/AGENT.md +40 -0
  12. package/CLAUDE.md +40 -0
  13. package/CODE-OF-CONDUCT.md +89 -0
  14. package/Cargo.toml +19 -0
  15. package/Dockerfile +43 -0
  16. package/LICENSE +201 -0
  17. package/Makefile +97 -0
  18. package/README.md +447 -143
  19. package/backend/.sqlx/query-01b7e2bac1261d8be3d03c03df3e5220590da6c31c77f161074fc62752d63881.json +12 -0
  20. package/backend/.sqlx/query-03f2b02ba6dc5ea2b3cf6b1004caea0ad6bcc10ebd63f441d321a389f026e263.json +12 -0
  21. package/backend/.sqlx/query-0923b77d137a29fc54d399a873ff15fc4af894490bc65a4d344a7575cb0d8643.json +12 -0
  22. package/backend/.sqlx/query-0f808bcdb63c5f180836e448dd64c435c51758b2fc54a52ce9e67495b1ab200e.json +68 -0
  23. package/backend/.sqlx/query-1268afe9ca849daa6722e3df7ca8e9e61f0d37052e782bb5452ab8e1018d9b63.json +12 -0
  24. package/backend/.sqlx/query-1b082630a9622f8667ee7a9aba2c2d3176019a68c6bb83d33008594821415a57.json +12 -0
  25. package/backend/.sqlx/query-1c7b06ba1e112abf6b945a2ff08a0b40ec23f3738c2e7399f067b558cf8d490e.json +12 -0
  26. package/backend/.sqlx/query-1f619f01f46859a64ded531dd0ef61abacfe62e758abe7030a6aa745140b95ca.json +104 -0
  27. package/backend/.sqlx/query-1fca1ce14b4b20205364cd1f1f45ebe1d2e30cd745e59e189d56487b5639dfbb.json +12 -0
  28. package/backend/.sqlx/query-212828320e8d871ab9d83705a040b23bcf0393dc7252177fc539a74657f578ef.json +32 -0
  29. package/backend/.sqlx/query-290ce5c152be8d36e58ff42570f9157beb07ab9e77a03ec6fc30b4f56f9b8f6b.json +56 -0
  30. package/backend/.sqlx/query-2b471d2c2e8ffbe0cd42d2a91b814c0d79f9d09200f147e3cea33ba4ce673c8a.json +68 -0
  31. package/backend/.sqlx/query-354a48c705bb9bb2048c1b7f10fcb714e23f9db82b7a4ea6932486197b2ede6a.json +92 -0
  32. package/backend/.sqlx/query-36c9e3dd10648e94b949db5c91a774ecb1e10a899ef95da74066eccedca4d8b2.json +12 -0
  33. package/backend/.sqlx/query-36e4ba7bbd81b402d5a20b6005755eafbb174c8dda442081823406ac32809a94.json +56 -0
  34. package/backend/.sqlx/query-3a5b3c98a55ca183ab20c74708e3d7e579dda37972c059e7515c4ceee4bd8dd3.json +62 -0
  35. package/backend/.sqlx/query-3d0a1cabf2a52e9d90cdfd29c509ca89aeb448d0c1d2446c65cd43db40735e86.json +62 -0
  36. package/backend/.sqlx/query-3d6bd16fbce59efe30b7f67ea342e0e4ea6d1432389c02468ad79f1f742d4031.json +56 -0
  37. package/backend/.sqlx/query-4049ca413b285a05aca6b25385e9c8185575f01e9069e4e8581aa45d713f612f.json +32 -0
  38. package/backend/.sqlx/query-412bacd3477d86369082e90f52240407abce436cb81292d42b2dbe1e5c18eea1.json +104 -0
  39. package/backend/.sqlx/query-417a8b1ff4e51de82aea0159a3b97932224dc325b23476cb84153d690227fd8b.json +62 -0
  40. package/backend/.sqlx/query-461cc1b0bb6fd909afc9dd2246e8526b3771cfbb0b22ae4b5d17b51af587b9e2.json +56 -0
  41. package/backend/.sqlx/query-58408c7a8cdeeda0bef359f1f9bd91299a339dc2b191462fc58c9736a56d5227.json +92 -0
  42. package/backend/.sqlx/query-5a886026d75d515c01f347cc203c8d99dd04c61dc468e2e4c5aa548436d13834.json +62 -0
  43. package/backend/.sqlx/query-5b902137b11022d2e1a5c4f6a9c83fec1a856c6a710aff831abd2382ede76b43.json +12 -0
  44. package/backend/.sqlx/query-5ed1238e52e59bb5f76c0f153fd99a14093f7ce2585bf9843585608f17ec575b.json +104 -0
  45. package/backend/.sqlx/query-6e8b860b14decfc2227dc57213f38442943d3fbef5c8418fd6b634c6e0f5e2ea.json +104 -0
  46. package/backend/.sqlx/query-6ec414276994c4ccb2433eaa5b1b342168557d17ddf5a52dac84cb1b59b9de8f.json +68 -0
  47. package/backend/.sqlx/query-6ecfa16d0cf825aacf233544b5baf151e9adfdca26c226ad71020d291fd802d5.json +62 -0
  48. package/backend/.sqlx/query-72509d252c39fce77520aa816cb2acbc1fb35dc2605e7be893610599b2427f2e.json +62 -0
  49. package/backend/.sqlx/query-75239b2da188f749707d77f3c1544332ca70db3d6d6743b2601dc0d167536437.json +62 -0
  50. package/backend/.sqlx/query-83d10e29f8478aff33434f9ac67068e013b888b953a2657e2bb72a6f619d04f2.json +50 -0
  51. package/backend/.sqlx/query-8610803360ea18b9b9d078a6981ea56abfbfe84e6354fc1d5ae4c622e01410ed.json +68 -0
  52. package/backend/.sqlx/query-86d03eb70eef39c59296416867f2ee66c9f7cd8b7f961fbda2f89fc0a1c442c2.json +12 -0
  53. package/backend/.sqlx/query-87d0feb5a6b442bad9c60068ea7569599cc6fc91a0e2692ecb42e93b03201b9d.json +68 -0
  54. package/backend/.sqlx/query-8a67b3b3337248f06a57bdf8a908f7ef23177431eaed82dc08c94c3e5944340e.json +12 -0
  55. package/backend/.sqlx/query-8f01ebd64bdcde6a090479f14810d73ba23020e76fd70854ac57f2da251702c3.json +12 -0
  56. package/backend/.sqlx/query-90fd607fcb2dca72239ff25e618e21e174b195991eaa33722cbf5f76da84cfab.json +62 -0
  57. package/backend/.sqlx/query-92e8bdbcd80c5ff3db7a35cf79492048803ef305cbdef0d0a1fe5dc881ca8c71.json +104 -0
  58. package/backend/.sqlx/query-93a1605f90e9672dad29b472b6ad85fa9a55ea3ffa5abcb8724b09d61be254ca.json +20 -0
  59. package/backend/.sqlx/query-9472c8fb477958167f5fae40b85ac44252468c5226b2cdd7770f027332eed6d7.json +104 -0
  60. package/backend/.sqlx/query-96036c4f9e0f48bdc5a4a4588f0c5f288ac7aaa5425cac40fc33f337e1a351f2.json +56 -0
  61. package/backend/.sqlx/query-9edb2c01e91fd0f0fe7b56e988c7ae0393150f50be3f419a981e035c0121dfc7.json +104 -0
  62. package/backend/.sqlx/query-a157cf00616f703bfba21927f1eb1c9eec2a81c02da15f66efdba0b6c375de1b.json +26 -0
  63. package/backend/.sqlx/query-a31fff84f3b8e532fd1160447d89d700f06ae08821fee00c9a5b60492b05259c.json +62 -0
  64. package/backend/.sqlx/query-a5ba908419fb3e456bdd2daca41ba06cc3212ffffb8520fc7dbbcc8b60ada314.json +12 -0
  65. package/backend/.sqlx/query-a6d2961718dbc3b1a925e549f49a159c561bef58c105529275f274b27e2eba5b.json +104 -0
  66. package/backend/.sqlx/query-a9e93d5b09b29faf66e387e4d7596a792d81e75c4d3726e83c2963e8d7c9b56f.json +104 -0
  67. package/backend/.sqlx/query-ac5247c8d7fb86e4650c4b0eb9420031614c831b7b085083bac20c1af314c538.json +12 -0
  68. package/backend/.sqlx/query-afef9467be74c411c4cb119a8b2b1aea53049877dfc30cc60b486134ba4b4c9f.json +68 -0
  69. package/backend/.sqlx/query-b2b2c6b4d0b1a347b5c4cb63c3a46a265d4db53be9554989a814b069d0af82f2.json +62 -0
  70. package/backend/.sqlx/query-c50d2ff0b12e5bcc81e371089ee2d007e233e7db93aefba4fef08e7aa68f5ab7.json +20 -0
  71. package/backend/.sqlx/query-c614e6056b244ca07f1b9d44e7edc9d5819225c6f8d9e077070c6e518a17f50b.json +12 -0
  72. package/backend/.sqlx/query-c67259be8bf4ee0cfd32167b2aa3b7fe9192809181a8171bf1c2d6df731967ae.json +12 -0
  73. package/backend/.sqlx/query-d2d0a1b985ebbca6a2b3e882a221a219f3199890fa640afc946ef1a792d6d8de.json +12 -0
  74. package/backend/.sqlx/query-d30aa5786757f32bf2b9c5fe51a45e506c71c28c5994e430d9b0546adb15ffa2.json +20 -0
  75. package/backend/.sqlx/query-d3b9ea1de1576af71b312924ce7f4ea8ae5dbe2ac138ea3b4470f2d5cd734846.json +12 -0
  76. package/backend/.sqlx/query-ed8456646fa69ddd412441955f06ff22bfb790f29466450735e0b8bb1bc4ec94.json +12 -0
  77. package/backend/Cargo.toml +71 -0
  78. package/backend/build.rs +32 -0
  79. package/backend/migrations/20250617183714_init.sql +44 -0
  80. package/backend/migrations/20250620212427_execution_processes.sql +25 -0
  81. package/backend/migrations/20250620214100_remove_stdout_stderr_from_task_attempts.sql +28 -0
  82. package/backend/migrations/20250621120000_relate_activities_to_execution_processes.sql +23 -0
  83. package/backend/migrations/20250623120000_executor_sessions.sql +17 -0
  84. package/backend/migrations/20250623130000_add_executor_type_to_execution_processes.sql +4 -0
  85. package/backend/migrations/20250625000000_add_dev_script_to_projects.sql +4 -0
  86. package/backend/migrations/20250701000000_add_branch_to_task_attempts.sql +2 -0
  87. package/backend/migrations/20250701000001_add_pr_tracking_to_task_attempts.sql +5 -0
  88. package/backend/migrations/20250701120000_add_assistant_message_to_executor_sessions.sql +2 -0
  89. package/backend/migrations/20250708000000_add_base_branch_to_task_attempts.sql +2 -0
  90. package/backend/migrations/20250709000000_add_worktree_deleted_flag.sql +2 -0
  91. package/backend/migrations/20250710000000_add_setup_completion.sql +3 -0
  92. package/backend/migrations/20250715154859_add_task_templates.sql +25 -0
  93. package/backend/migrations/20250716143725_add_default_templates.sql +174 -0
  94. package/backend/migrations/20250716161432_update_executor_names_to_kebab_case.sql +20 -0
  95. package/backend/migrations/20250716170000_add_parent_task_to_tasks.sql +7 -0
  96. package/backend/migrations/20250717000000_drop_task_attempt_activities.sql +9 -0
  97. package/backend/migrations/20250719000000_add_cleanup_script_to_projects.sql +2 -0
  98. package/backend/migrations/20250720000000_add_cleanupscript_to_process_type_constraint.sql +25 -0
  99. package/backend/migrations/20250723000000_add_wish_to_tasks.sql +7 -0
  100. package/backend/migrations/20250724000000_remove_unique_wish_constraint.sql +5 -0
  101. package/backend/scripts/toast-notification.ps1 +23 -0
  102. package/backend/sounds/abstract-sound1.wav +0 -0
  103. package/backend/sounds/abstract-sound2.wav +0 -0
  104. package/backend/sounds/abstract-sound3.wav +0 -0
  105. package/backend/sounds/abstract-sound4.wav +0 -0
  106. package/backend/sounds/cow-mooing.wav +0 -0
  107. package/backend/sounds/phone-vibration.wav +0 -0
  108. package/backend/sounds/rooster.wav +0 -0
  109. package/backend/src/app_state.rs +218 -0
  110. package/backend/src/bin/generate_types.rs +189 -0
  111. package/backend/src/bin/mcp_task_server.rs +191 -0
  112. package/backend/src/execution_monitor.rs +1193 -0
  113. package/backend/src/executor.rs +1053 -0
  114. package/backend/src/executors/amp.rs +697 -0
  115. package/backend/src/executors/ccr.rs +91 -0
  116. package/backend/src/executors/charm_opencode.rs +113 -0
  117. package/backend/src/executors/claude.rs +887 -0
  118. package/backend/src/executors/cleanup_script.rs +124 -0
  119. package/backend/src/executors/dev_server.rs +53 -0
  120. package/backend/src/executors/echo.rs +79 -0
  121. package/backend/src/executors/gemini/config.rs +67 -0
  122. package/backend/src/executors/gemini/streaming.rs +363 -0
  123. package/backend/src/executors/gemini.rs +765 -0
  124. package/backend/src/executors/mod.rs +23 -0
  125. package/backend/src/executors/opencode_ai.rs +113 -0
  126. package/backend/src/executors/setup_script.rs +130 -0
  127. package/backend/src/executors/sst_opencode/filter.rs +184 -0
  128. package/backend/src/executors/sst_opencode/tools.rs +139 -0
  129. package/backend/src/executors/sst_opencode.rs +756 -0
  130. package/backend/src/lib.rs +45 -0
  131. package/backend/src/main.rs +324 -0
  132. package/backend/src/mcp/mod.rs +1 -0
  133. package/backend/src/mcp/task_server.rs +850 -0
  134. package/backend/src/middleware/mod.rs +3 -0
  135. package/backend/src/middleware/model_loaders.rs +242 -0
  136. package/backend/src/models/api_response.rs +36 -0
  137. package/backend/src/models/config.rs +375 -0
  138. package/backend/src/models/execution_process.rs +430 -0
  139. package/backend/src/models/executor_session.rs +225 -0
  140. package/backend/src/models/mod.rs +12 -0
  141. package/backend/src/models/project.rs +356 -0
  142. package/backend/src/models/task.rs +345 -0
  143. package/backend/src/models/task_attempt.rs +1214 -0
  144. package/backend/src/models/task_template.rs +146 -0
  145. package/backend/src/openapi.rs +93 -0
  146. package/backend/src/routes/auth.rs +297 -0
  147. package/backend/src/routes/config.rs +385 -0
  148. package/backend/src/routes/filesystem.rs +228 -0
  149. package/backend/src/routes/health.rs +16 -0
  150. package/backend/src/routes/mod.rs +9 -0
  151. package/backend/src/routes/projects.rs +562 -0
  152. package/backend/src/routes/stream.rs +244 -0
  153. package/backend/src/routes/task_attempts.rs +1172 -0
  154. package/backend/src/routes/task_templates.rs +229 -0
  155. package/backend/src/routes/tasks.rs +353 -0
  156. package/backend/src/services/analytics.rs +216 -0
  157. package/backend/src/services/git_service.rs +1321 -0
  158. package/backend/src/services/github_service.rs +307 -0
  159. package/backend/src/services/mod.rs +13 -0
  160. package/backend/src/services/notification_service.rs +263 -0
  161. package/backend/src/services/pr_monitor.rs +214 -0
  162. package/backend/src/services/process_service.rs +940 -0
  163. package/backend/src/utils/path.rs +96 -0
  164. package/backend/src/utils/shell.rs +19 -0
  165. package/backend/src/utils/text.rs +24 -0
  166. package/backend/src/utils/worktree_manager.rs +578 -0
  167. package/backend/src/utils.rs +125 -0
  168. package/backend/test.db +0 -0
  169. package/build-npm-package.sh +61 -0
  170. package/dev_assets_seed/config.json +19 -0
  171. package/frontend/.eslintrc.json +25 -0
  172. package/frontend/.prettierrc.json +8 -0
  173. package/frontend/components.json +17 -0
  174. package/frontend/index.html +19 -0
  175. package/frontend/package-lock.json +7321 -0
  176. package/frontend/package.json +61 -0
  177. package/frontend/postcss.config.js +6 -0
  178. package/frontend/public/android-chrome-192x192.png +0 -0
  179. package/frontend/public/android-chrome-512x512.png +0 -0
  180. package/frontend/public/apple-touch-icon.png +0 -0
  181. package/frontend/public/automagik-forge-logo-dark.svg +3 -0
  182. package/frontend/public/automagik-forge-logo.svg +3 -0
  183. package/frontend/public/automagik-forge-screenshot-overview.png +0 -0
  184. package/frontend/public/favicon-16x16.png +0 -0
  185. package/frontend/public/favicon-32x32.png +0 -0
  186. package/frontend/public/favicon.ico +0 -0
  187. package/frontend/public/site.webmanifest +1 -0
  188. package/frontend/public/viba-kanban-favicon.png +0 -0
  189. package/frontend/src/App.tsx +157 -0
  190. package/frontend/src/components/DisclaimerDialog.tsx +106 -0
  191. package/frontend/src/components/GitHubLoginDialog.tsx +314 -0
  192. package/frontend/src/components/OnboardingDialog.tsx +185 -0
  193. package/frontend/src/components/PrivacyOptInDialog.tsx +130 -0
  194. package/frontend/src/components/ProvidePatDialog.tsx +98 -0
  195. package/frontend/src/components/TaskTemplateManager.tsx +336 -0
  196. package/frontend/src/components/config-provider.tsx +119 -0
  197. package/frontend/src/components/context/TaskDetailsContextProvider.tsx +470 -0
  198. package/frontend/src/components/context/taskDetailsContext.ts +125 -0
  199. package/frontend/src/components/keyboard-shortcuts-demo.tsx +35 -0
  200. package/frontend/src/components/layout/navbar.tsx +86 -0
  201. package/frontend/src/components/logo.tsx +44 -0
  202. package/frontend/src/components/projects/ProjectCard.tsx +155 -0
  203. package/frontend/src/components/projects/project-detail.tsx +251 -0
  204. package/frontend/src/components/projects/project-form-fields.tsx +238 -0
  205. package/frontend/src/components/projects/project-form.tsx +301 -0
  206. package/frontend/src/components/projects/project-list.tsx +200 -0
  207. package/frontend/src/components/projects/projects-page.tsx +20 -0
  208. package/frontend/src/components/tasks/BranchSelector.tsx +169 -0
  209. package/frontend/src/components/tasks/DeleteFileConfirmationDialog.tsx +94 -0
  210. package/frontend/src/components/tasks/EditorSelectionDialog.tsx +119 -0
  211. package/frontend/src/components/tasks/TaskCard.tsx +154 -0
  212. package/frontend/src/components/tasks/TaskDetails/CollapsibleToolbar.tsx +33 -0
  213. package/frontend/src/components/tasks/TaskDetails/DiffCard.tsx +109 -0
  214. package/frontend/src/components/tasks/TaskDetails/DiffChunkSection.tsx +135 -0
  215. package/frontend/src/components/tasks/TaskDetails/DiffFile.tsx +296 -0
  216. package/frontend/src/components/tasks/TaskDetails/DiffTab.tsx +32 -0
  217. package/frontend/src/components/tasks/TaskDetails/DisplayConversationEntry.tsx +392 -0
  218. package/frontend/src/components/tasks/TaskDetails/LogsTab/Conversation.tsx +256 -0
  219. package/frontend/src/components/tasks/TaskDetails/LogsTab/ConversationEntry.tsx +56 -0
  220. package/frontend/src/components/tasks/TaskDetails/LogsTab/NormalizedConversationViewer.tsx +92 -0
  221. package/frontend/src/components/tasks/TaskDetails/LogsTab/Prompt.tsx +22 -0
  222. package/frontend/src/components/tasks/TaskDetails/LogsTab/SetupScriptRunning.tsx +49 -0
  223. package/frontend/src/components/tasks/TaskDetails/LogsTab.tsx +186 -0
  224. package/frontend/src/components/tasks/TaskDetails/ProcessesTab.tsx +288 -0
  225. package/frontend/src/components/tasks/TaskDetails/RelatedTasksTab.tsx +216 -0
  226. package/frontend/src/components/tasks/TaskDetails/TabNavigation.tsx +93 -0
  227. package/frontend/src/components/tasks/TaskDetailsHeader.tsx +169 -0
  228. package/frontend/src/components/tasks/TaskDetailsPanel.tsx +126 -0
  229. package/frontend/src/components/tasks/TaskDetailsToolbar.tsx +302 -0
  230. package/frontend/src/components/tasks/TaskFollowUpSection.tsx +130 -0
  231. package/frontend/src/components/tasks/TaskFormDialog.tsx +400 -0
  232. package/frontend/src/components/tasks/TaskKanbanBoard.tsx +180 -0
  233. package/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx +259 -0
  234. package/frontend/src/components/tasks/Toolbar/CreatePRDialog.tsx +243 -0
  235. package/frontend/src/components/tasks/Toolbar/CurrentAttempt.tsx +899 -0
  236. package/frontend/src/components/tasks/index.ts +2 -0
  237. package/frontend/src/components/theme-provider.tsx +82 -0
  238. package/frontend/src/components/theme-toggle.tsx +36 -0
  239. package/frontend/src/components/ui/alert.tsx +59 -0
  240. package/frontend/src/components/ui/auto-expanding-textarea.tsx +70 -0
  241. package/frontend/src/components/ui/badge.tsx +36 -0
  242. package/frontend/src/components/ui/button.tsx +56 -0
  243. package/frontend/src/components/ui/card.tsx +86 -0
  244. package/frontend/src/components/ui/checkbox.tsx +44 -0
  245. package/frontend/src/components/ui/chip.tsx +25 -0
  246. package/frontend/src/components/ui/dialog.tsx +124 -0
  247. package/frontend/src/components/ui/dropdown-menu.tsx +198 -0
  248. package/frontend/src/components/ui/file-search-textarea.tsx +292 -0
  249. package/frontend/src/components/ui/folder-picker.tsx +279 -0
  250. package/frontend/src/components/ui/input.tsx +25 -0
  251. package/frontend/src/components/ui/label.tsx +24 -0
  252. package/frontend/src/components/ui/loader.tsx +26 -0
  253. package/frontend/src/components/ui/markdown-renderer.tsx +75 -0
  254. package/frontend/src/components/ui/select.tsx +160 -0
  255. package/frontend/src/components/ui/separator.tsx +31 -0
  256. package/frontend/src/components/ui/shadcn-io/kanban/index.tsx +185 -0
  257. package/frontend/src/components/ui/table.tsx +117 -0
  258. package/frontend/src/components/ui/tabs.tsx +53 -0
  259. package/frontend/src/components/ui/textarea.tsx +22 -0
  260. package/frontend/src/components/ui/tooltip.tsx +28 -0
  261. package/frontend/src/hooks/useNormalizedConversation.ts +440 -0
  262. package/frontend/src/index.css +225 -0
  263. package/frontend/src/lib/api.ts +630 -0
  264. package/frontend/src/lib/keyboard-shortcuts.ts +266 -0
  265. package/frontend/src/lib/responsive-config.ts +70 -0
  266. package/frontend/src/lib/types.ts +39 -0
  267. package/frontend/src/lib/utils.ts +10 -0
  268. package/frontend/src/main.tsx +50 -0
  269. package/frontend/src/pages/McpServers.tsx +418 -0
  270. package/frontend/src/pages/Settings.tsx +610 -0
  271. package/frontend/src/pages/project-tasks.tsx +575 -0
  272. package/frontend/src/pages/projects.tsx +18 -0
  273. package/frontend/src/vite-env.d.ts +1 -0
  274. package/frontend/tailwind.config.js +125 -0
  275. package/frontend/tsconfig.json +26 -0
  276. package/frontend/tsconfig.node.json +10 -0
  277. package/frontend/vite.config.ts +33 -0
  278. package/npx-cli/README.md +159 -0
  279. package/npx-cli/automagik-forge-0.0.55.tgz +0 -0
  280. package/npx-cli/automagik-forge-0.1.0.tgz +0 -0
  281. package/{dist/linux-x64/automagik-forge.zip → npx-cli/automagik-forge-0.1.10.tgz} +0 -0
  282. package/npx-cli/package.json +17 -0
  283. package/npx-cli/vibe-kanban-0.0.55.tgz +0 -0
  284. package/package.json +23 -13
  285. package/pnpm-workspace.yaml +2 -0
  286. package/rust-toolchain.toml +11 -0
  287. package/rustfmt.toml +3 -0
  288. package/scripts/load-env.js +43 -0
  289. package/scripts/mcp_test.js +374 -0
  290. package/scripts/prepare-db.js +45 -0
  291. package/scripts/setup-dev-environment.js +274 -0
  292. package/scripts/start-mcp-sse.js +70 -0
  293. package/scripts/test-debug.js +32 -0
  294. package/scripts/test-mcp-sse.js +138 -0
  295. package/scripts/test-simple.js +44 -0
  296. package/scripts/test-wish-final.js +179 -0
  297. package/scripts/test-wish-system.js +221 -0
  298. package/shared/types.ts +182 -0
  299. package/test-npm-package.sh +42 -0
  300. package/dist/linux-x64/automagik-forge-mcp.zip +0 -0
  301. /package/{bin → npx-cli/bin}/cli.js +0 -0
@@ -0,0 +1,899 @@
1
+ import {
2
+ Check,
3
+ ExternalLink,
4
+ GitBranch as GitBranchIcon,
5
+ GitPullRequest,
6
+ History,
7
+ Play,
8
+ Plus,
9
+ RefreshCw,
10
+ Settings,
11
+ StopCircle,
12
+ } from 'lucide-react';
13
+ import { is_planning_executor_type } from '@/lib/utils';
14
+ import {
15
+ Tooltip,
16
+ TooltipContent,
17
+ TooltipProvider,
18
+ TooltipTrigger,
19
+ } from '@/components/ui/tooltip.tsx';
20
+ import { Button } from '@/components/ui/button.tsx';
21
+ import {
22
+ DropdownMenu,
23
+ DropdownMenuContent,
24
+ DropdownMenuItem,
25
+ DropdownMenuTrigger,
26
+ } from '@/components/ui/dropdown-menu.tsx';
27
+ import {
28
+ Dialog,
29
+ DialogContent,
30
+ DialogDescription,
31
+ DialogFooter,
32
+ DialogHeader,
33
+ DialogTitle,
34
+ } from '@/components/ui/dialog.tsx';
35
+ import BranchSelector from '@/components/tasks/BranchSelector.tsx';
36
+ import {
37
+ attemptsApi,
38
+ executionProcessesApi,
39
+ makeRequest,
40
+ FollowUpResponse,
41
+ ApiResponse,
42
+ } from '@/lib/api.ts';
43
+ import {
44
+ Dispatch,
45
+ SetStateAction,
46
+ useCallback,
47
+ useContext,
48
+ useEffect,
49
+ useMemo,
50
+ useState,
51
+ } from 'react';
52
+ import type {
53
+ BranchStatus,
54
+ ExecutionProcess,
55
+ GitBranch,
56
+ TaskAttempt,
57
+ } from 'shared/types.ts';
58
+ import {
59
+ TaskAttemptDataContext,
60
+ TaskAttemptStoppingContext,
61
+ TaskDetailsContext,
62
+ TaskExecutionStateContext,
63
+ TaskRelatedTasksContext,
64
+ TaskSelectedAttemptContext,
65
+ } from '@/components/context/taskDetailsContext.ts';
66
+ import { useConfig } from '@/components/config-provider.tsx';
67
+ import { useKeyboardShortcuts } from '@/lib/keyboard-shortcuts.ts';
68
+ import { useNavigate } from 'react-router-dom';
69
+
70
+ // Helper function to get the display name for different editor types
71
+ function getEditorDisplayName(editorType: string): string {
72
+ switch (editorType) {
73
+ case 'vscode':
74
+ return 'Visual Studio Code';
75
+ case 'cursor':
76
+ return 'Cursor';
77
+ case 'windsurf':
78
+ return 'Windsurf';
79
+ case 'intellij':
80
+ return 'IntelliJ IDEA';
81
+ case 'zed':
82
+ return 'Zed';
83
+ case 'custom':
84
+ return 'Custom Editor';
85
+ default:
86
+ return 'Editor';
87
+ }
88
+ }
89
+
90
+ type Props = {
91
+ setError: Dispatch<SetStateAction<string | null>>;
92
+ setShowCreatePRDialog: Dispatch<SetStateAction<boolean>>;
93
+ selectedBranch: string | null;
94
+ selectedAttempt: TaskAttempt;
95
+ taskAttempts: TaskAttempt[];
96
+ creatingPR: boolean;
97
+ handleEnterCreateAttemptMode: () => void;
98
+ availableExecutors: {
99
+ id: string;
100
+ name: string;
101
+ }[];
102
+ branches: GitBranch[];
103
+ };
104
+
105
+ function CurrentAttempt({
106
+ setError,
107
+ setShowCreatePRDialog,
108
+ selectedBranch,
109
+ selectedAttempt,
110
+ taskAttempts,
111
+ creatingPR,
112
+ handleEnterCreateAttemptMode,
113
+ availableExecutors,
114
+ branches,
115
+ }: Props) {
116
+ const { task, projectId, handleOpenInEditor, projectHasDevScript } =
117
+ useContext(TaskDetailsContext);
118
+ const { config } = useConfig();
119
+ const { setSelectedAttempt } = useContext(TaskSelectedAttemptContext);
120
+ const navigate = useNavigate();
121
+ const { isStopping, setIsStopping } = useContext(TaskAttemptStoppingContext);
122
+ const { attemptData, fetchAttemptData, isAttemptRunning } = useContext(
123
+ TaskAttemptDataContext
124
+ );
125
+ const { relatedTasks } = useContext(TaskRelatedTasksContext);
126
+ const { executionState, fetchExecutionState } = useContext(
127
+ TaskExecutionStateContext
128
+ );
129
+
130
+ const [isStartingDevServer, setIsStartingDevServer] = useState(false);
131
+ const [merging, setMerging] = useState(false);
132
+ const [rebasing, setRebasing] = useState(false);
133
+ const [devServerDetails, setDevServerDetails] =
134
+ useState<ExecutionProcess | null>(null);
135
+ const [isHoveringDevServer, setIsHoveringDevServer] = useState(false);
136
+ const [branchStatus, setBranchStatus] = useState<BranchStatus | null>(null);
137
+ const [branchStatusLoading, setBranchStatusLoading] = useState(false);
138
+ const [showRebaseDialog, setShowRebaseDialog] = useState(false);
139
+ const [selectedRebaseBranch, setSelectedRebaseBranch] = useState<string>('');
140
+ const [showStopConfirmation, setShowStopConfirmation] = useState(false);
141
+ const [isApprovingPlan, setIsApprovingPlan] = useState(false);
142
+ const [copied, setCopied] = useState(false);
143
+
144
+ const processedDevServerLogs = useMemo(() => {
145
+ if (!devServerDetails) return 'No output yet...';
146
+
147
+ const stdout = devServerDetails.stdout || '';
148
+ const stderr = devServerDetails.stderr || '';
149
+ const allOutput = stdout + (stderr ? '\n' + stderr : '');
150
+ const lines = allOutput.split('\n').filter((line) => line.trim());
151
+ const lastLines = lines.slice(-10);
152
+ return lastLines.length > 0 ? lastLines.join('\n') : 'No output yet...';
153
+ }, [devServerDetails]);
154
+
155
+ // Find running dev server in current project
156
+ const runningDevServer = useMemo(() => {
157
+ return attemptData.processes.find(
158
+ (process) =>
159
+ process.process_type === 'devserver' && process.status === 'running'
160
+ );
161
+ }, [attemptData.processes]);
162
+
163
+ // Check if plan approval is needed
164
+ const isPlanTask = useMemo(() => {
165
+ return !!(
166
+ selectedAttempt.executor &&
167
+ is_planning_executor_type(selectedAttempt.executor)
168
+ );
169
+ }, [selectedAttempt.executor]);
170
+
171
+ const fetchDevServerDetails = useCallback(async () => {
172
+ if (!runningDevServer || !task || !selectedAttempt) return;
173
+
174
+ try {
175
+ const result = await executionProcessesApi.getDetails(
176
+ runningDevServer.id
177
+ );
178
+ setDevServerDetails(result);
179
+ } catch (err) {
180
+ console.error('Failed to fetch dev server details:', err);
181
+ }
182
+ }, [runningDevServer, task, selectedAttempt, projectId]);
183
+
184
+ useEffect(() => {
185
+ if (!isHoveringDevServer || !runningDevServer) {
186
+ setDevServerDetails(null);
187
+ return;
188
+ }
189
+
190
+ fetchDevServerDetails();
191
+ const interval = setInterval(fetchDevServerDetails, 2000);
192
+ return () => clearInterval(interval);
193
+ }, [isHoveringDevServer, runningDevServer, fetchDevServerDetails]);
194
+
195
+ const startDevServer = async () => {
196
+ if (!task || !selectedAttempt) return;
197
+
198
+ setIsStartingDevServer(true);
199
+
200
+ try {
201
+ await attemptsApi.startDevServer(
202
+ projectId,
203
+ selectedAttempt.task_id,
204
+ selectedAttempt.id
205
+ );
206
+ fetchAttemptData(selectedAttempt.id, selectedAttempt.task_id);
207
+ } catch (err) {
208
+ console.error('Failed to start dev server:', err);
209
+ } finally {
210
+ setIsStartingDevServer(false);
211
+ }
212
+ };
213
+
214
+ const stopDevServer = async () => {
215
+ if (!task || !selectedAttempt || !runningDevServer) return;
216
+
217
+ setIsStartingDevServer(true);
218
+
219
+ try {
220
+ await attemptsApi.stopExecutionProcess(
221
+ projectId,
222
+ selectedAttempt.task_id,
223
+ selectedAttempt.id,
224
+ runningDevServer.id
225
+ );
226
+ fetchAttemptData(selectedAttempt.id, selectedAttempt.task_id);
227
+ } catch (err) {
228
+ console.error('Failed to stop dev server:', err);
229
+ } finally {
230
+ setIsStartingDevServer(false);
231
+ }
232
+ };
233
+
234
+ const stopAllExecutions = useCallback(async () => {
235
+ if (!task || !selectedAttempt || !isAttemptRunning) return;
236
+
237
+ try {
238
+ setIsStopping(true);
239
+ await attemptsApi.stop(
240
+ projectId,
241
+ selectedAttempt.task_id,
242
+ selectedAttempt.id
243
+ );
244
+ await fetchAttemptData(selectedAttempt.id, selectedAttempt.task_id);
245
+ setTimeout(() => {
246
+ fetchAttemptData(selectedAttempt.id, selectedAttempt.task_id);
247
+ }, 1000);
248
+ } catch (err) {
249
+ console.error('Failed to stop executions:', err);
250
+ } finally {
251
+ setIsStopping(false);
252
+ }
253
+ }, [
254
+ task,
255
+ selectedAttempt,
256
+ projectId,
257
+ fetchAttemptData,
258
+ setIsStopping,
259
+ isAttemptRunning,
260
+ ]);
261
+
262
+ useKeyboardShortcuts({
263
+ stopExecution: () => setShowStopConfirmation(true),
264
+ newAttempt: !isAttemptRunning ? handleEnterCreateAttemptMode : () => {},
265
+ hasOpenDialog: showStopConfirmation,
266
+ closeDialog: () => setShowStopConfirmation(false),
267
+ onEnter: () => {
268
+ setShowStopConfirmation(false);
269
+ stopAllExecutions();
270
+ },
271
+ });
272
+
273
+ const handleAttemptChange = useCallback(
274
+ (attempt: TaskAttempt) => {
275
+ setSelectedAttempt(attempt);
276
+ fetchAttemptData(attempt.id, attempt.task_id);
277
+ fetchExecutionState(attempt.id, attempt.task_id);
278
+ },
279
+ [fetchAttemptData, fetchExecutionState, setSelectedAttempt]
280
+ );
281
+
282
+ const handleMergeClick = async () => {
283
+ if (!projectId || !selectedAttempt?.id || !selectedAttempt?.task_id) return;
284
+
285
+ // Directly perform merge without checking branch status
286
+ await performMerge();
287
+ };
288
+
289
+ const fetchBranchStatus = useCallback(async () => {
290
+ if (!projectId || !selectedAttempt?.id || !selectedAttempt?.task_id) return;
291
+
292
+ try {
293
+ setBranchStatusLoading(true);
294
+ const result = await attemptsApi.getBranchStatus(
295
+ projectId,
296
+ selectedAttempt.task_id,
297
+ selectedAttempt.id
298
+ );
299
+ setBranchStatus((prev) => {
300
+ if (JSON.stringify(prev) === JSON.stringify(result)) return prev;
301
+ return result;
302
+ });
303
+ } catch (err) {
304
+ setError('Failed to load branch status');
305
+ } finally {
306
+ setBranchStatusLoading(false);
307
+ }
308
+ }, [projectId, selectedAttempt?.id, selectedAttempt?.task_id, setError]);
309
+
310
+ // Fetch branch status when selected attempt changes
311
+ useEffect(() => {
312
+ if (selectedAttempt) {
313
+ fetchBranchStatus();
314
+ }
315
+ }, [selectedAttempt, fetchBranchStatus]);
316
+
317
+ const performMerge = async () => {
318
+ if (!projectId || !selectedAttempt?.id || !selectedAttempt?.task_id) return;
319
+
320
+ try {
321
+ setMerging(true);
322
+ await attemptsApi.merge(
323
+ projectId,
324
+ selectedAttempt.task_id,
325
+ selectedAttempt.id
326
+ );
327
+ // Refetch branch status to show updated state
328
+ fetchBranchStatus();
329
+ } catch (error) {
330
+ console.error('Failed to merge changes:', error);
331
+ // @ts-expect-error it is type ApiError
332
+ setError(error.message || 'Failed to merge changes');
333
+ } finally {
334
+ setMerging(false);
335
+ }
336
+ };
337
+
338
+ const handleRebaseClick = async () => {
339
+ if (!projectId || !selectedAttempt?.id || !selectedAttempt?.task_id) return;
340
+
341
+ try {
342
+ setRebasing(true);
343
+ await attemptsApi.rebase(
344
+ projectId,
345
+ selectedAttempt.task_id,
346
+ selectedAttempt.id
347
+ );
348
+ // Refresh branch status after rebase
349
+ fetchBranchStatus();
350
+ } catch (err) {
351
+ setError(err instanceof Error ? err.message : 'Failed to rebase branch');
352
+ } finally {
353
+ setRebasing(false);
354
+ }
355
+ };
356
+
357
+ const handleRebaseWithNewBranch = async (newBaseBranch: string) => {
358
+ if (!projectId || !selectedAttempt?.id || !selectedAttempt?.task_id) return;
359
+
360
+ try {
361
+ setRebasing(true);
362
+ await attemptsApi.rebase(
363
+ projectId,
364
+ selectedAttempt.task_id,
365
+ selectedAttempt.id,
366
+ newBaseBranch
367
+ );
368
+ // Refresh branch status after rebase
369
+ fetchBranchStatus();
370
+ setShowRebaseDialog(false);
371
+ } catch (err) {
372
+ setError(err instanceof Error ? err.message : 'Failed to rebase branch');
373
+ } finally {
374
+ setRebasing(false);
375
+ }
376
+ };
377
+
378
+ const handleRebaseDialogConfirm = () => {
379
+ if (selectedRebaseBranch) {
380
+ handleRebaseWithNewBranch(selectedRebaseBranch);
381
+ }
382
+ };
383
+
384
+ const handleRebaseDialogOpen = () => {
385
+ setSelectedRebaseBranch('');
386
+ setShowRebaseDialog(true);
387
+ };
388
+
389
+ const handleCreatePRClick = async () => {
390
+ if (!projectId || !selectedAttempt?.id || !selectedAttempt?.task_id) return;
391
+
392
+ // If PR already exists, open it
393
+ if (selectedAttempt.pr_url) {
394
+ window.open(selectedAttempt.pr_url, '_blank');
395
+ return;
396
+ }
397
+
398
+ setShowCreatePRDialog(true);
399
+ };
400
+
401
+ const handlePlanApproval = async () => {
402
+ if (!task || !selectedAttempt || !isPlanTask) return;
403
+
404
+ setIsApprovingPlan(true);
405
+ try {
406
+ const response = await makeRequest(
407
+ `/api/projects/${projectId}/tasks/${task.id}/attempts/${selectedAttempt.id}/approve-plan`,
408
+ {
409
+ method: 'POST',
410
+ // No body needed - endpoint only handles approval now
411
+ }
412
+ );
413
+
414
+ if (response.ok) {
415
+ const result: ApiResponse<FollowUpResponse> = await response.json();
416
+ if (result.success && result.data) {
417
+ console.log('Plan approved successfully:', result.message);
418
+
419
+ // If a new task was created, navigate to it
420
+ if (result.data.created_new_attempt) {
421
+ const newTaskId = result.data.actual_attempt_id;
422
+ console.log('Navigating to new task:', newTaskId);
423
+ navigate(`/projects/${projectId}/tasks/${newTaskId}`);
424
+ } else {
425
+ // Otherwise, just refresh the current task data
426
+ fetchAttemptData(selectedAttempt.id, selectedAttempt.task_id);
427
+ }
428
+ } else {
429
+ setError(`Failed to approve plan: ${result.message}`);
430
+ }
431
+ } else {
432
+ setError('Failed to approve plan');
433
+ }
434
+ } catch (error) {
435
+ setError(
436
+ `Error approving plan: ${error instanceof Error ? error.message : 'Unknown error'}`
437
+ );
438
+ } finally {
439
+ setIsApprovingPlan(false);
440
+ }
441
+ };
442
+
443
+ // Get display name for selected branch
444
+ const selectedBranchDisplayName = useMemo(() => {
445
+ if (!selectedBranch) return 'current';
446
+
447
+ // For remote branches, show just the branch name without the remote prefix
448
+ if (selectedBranch.includes('/')) {
449
+ const parts = selectedBranch.split('/');
450
+ return parts[parts.length - 1];
451
+ }
452
+ return selectedBranch;
453
+ }, [selectedBranch]);
454
+
455
+ // Get display name for the configured editor
456
+ const editorDisplayName = useMemo(() => {
457
+ if (!config?.editor?.editor_type) return 'Editor';
458
+ return getEditorDisplayName(config.editor.editor_type);
459
+ }, [config?.editor?.editor_type]);
460
+
461
+ const handleCopyWorktreePath = useCallback(async () => {
462
+ try {
463
+ await navigator.clipboard.writeText(selectedAttempt.worktree_path);
464
+ setCopied(true);
465
+ setTimeout(() => setCopied(false), 2000);
466
+ } catch (err) {
467
+ console.error('Failed to copy worktree path:', err);
468
+ }
469
+ }, [selectedAttempt.worktree_path]);
470
+
471
+ return (
472
+ <div className="space-y-2">
473
+ <div className="grid grid-cols-4 gap-3 items-start">
474
+ <div>
475
+ <div className="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-1">
476
+ Started
477
+ </div>
478
+ <div className="text-sm font-medium">
479
+ {new Date(selectedAttempt.created_at).toLocaleDateString()}{' '}
480
+ {new Date(selectedAttempt.created_at).toLocaleTimeString([], {
481
+ hour: '2-digit',
482
+ minute: '2-digit',
483
+ })}
484
+ </div>
485
+ </div>
486
+
487
+ <div>
488
+ <div className="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-1">
489
+ Agent
490
+ </div>
491
+ <div className="text-sm font-medium">
492
+ {availableExecutors.find((e) => e.id === selectedAttempt.executor)
493
+ ?.name ||
494
+ selectedAttempt.executor ||
495
+ 'Unknown'}
496
+ </div>
497
+ </div>
498
+
499
+ <div>
500
+ <div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground uppercase tracking-wide mb-1">
501
+ <span>Base Branch</span>
502
+ <TooltipProvider>
503
+ <Tooltip>
504
+ <TooltipTrigger asChild>
505
+ <Button
506
+ variant="ghost"
507
+ size="sm"
508
+ onClick={handleRebaseDialogOpen}
509
+ disabled={
510
+ rebasing ||
511
+ branchStatusLoading ||
512
+ isAttemptRunning ||
513
+ isPlanTask
514
+ }
515
+ className="h-4 w-4 p-0 hover:bg-muted"
516
+ >
517
+ <Settings className="h-3 w-3" />
518
+ </Button>
519
+ </TooltipTrigger>
520
+ <TooltipContent>
521
+ <p>Change base branch</p>
522
+ </TooltipContent>
523
+ </Tooltip>
524
+ </TooltipProvider>
525
+ </div>
526
+ <div className="flex items-center gap-1.5">
527
+ <GitBranchIcon className="h-3 w-3 text-muted-foreground" />
528
+ <span className="text-sm font-medium">
529
+ {branchStatus?.base_branch_name || selectedBranchDisplayName}
530
+ </span>
531
+ </div>
532
+ </div>
533
+
534
+ <div>
535
+ <div className="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-1">
536
+ {isPlanTask ? 'Plan Status' : 'Merge Status'}
537
+ </div>
538
+ <div className="flex items-center gap-1.5">
539
+ {isPlanTask ? (
540
+ // Plan status for planning tasks
541
+ relatedTasks && relatedTasks.length > 0 ? (
542
+ <div className="flex items-center gap-1.5">
543
+ <div className="h-2 w-2 bg-green-500 rounded-full" />
544
+ <span className="text-sm font-medium text-green-700">
545
+ Task Created
546
+ </span>
547
+ </div>
548
+ ) : (
549
+ <div className="flex items-center gap-1.5">
550
+ <div className="h-2 w-2 bg-gray-500 rounded-full" />
551
+ <span className="text-sm font-medium text-gray-700">
552
+ Draft
553
+ </span>
554
+ </div>
555
+ )
556
+ ) : // Merge status for regular tasks
557
+ selectedAttempt.merge_commit ? (
558
+ <div className="flex items-center gap-1.5">
559
+ <div className="h-2 w-2 bg-green-500 rounded-full" />
560
+ <span className="text-sm font-medium text-green-700">
561
+ Merged
562
+ </span>
563
+ <span className="text-xs font-mono text-muted-foreground">
564
+ ({selectedAttempt.merge_commit.slice(0, 8)})
565
+ </span>
566
+ </div>
567
+ ) : (
568
+ <div className="flex items-center gap-1.5">
569
+ <div className="h-2 w-2 bg-yellow-500 rounded-full" />
570
+ <span className="text-sm font-medium text-yellow-700">
571
+ Not merged
572
+ </span>
573
+ </div>
574
+ )}
575
+ </div>
576
+ </div>
577
+ </div>
578
+
579
+ <div className="col-span-4">
580
+ <div className="flex items-center gap-1.5 mb-1">
581
+ <div className="text-xs font-medium text-muted-foreground uppercase tracking-wide mb-1">
582
+ Worktree Path
583
+ </div>
584
+ <Button
585
+ variant="ghost"
586
+ size="sm"
587
+ onClick={() => handleOpenInEditor()}
588
+ className="h-6 px-2 text-xs hover:bg-muted gap-1"
589
+ >
590
+ <ExternalLink className="h-3 w-3" />
591
+ Open in {editorDisplayName}
592
+ </Button>
593
+ </div>
594
+ <div
595
+ className={`text-xs font-mono px-2 py-1 rounded break-all cursor-pointer transition-all duration-300 flex items-center gap-2 ${
596
+ copied
597
+ ? 'bg-green-100 text-green-800 border border-green-300'
598
+ : 'text-muted-foreground bg-muted hover:bg-muted/80'
599
+ }`}
600
+ onClick={handleCopyWorktreePath}
601
+ title={copied ? 'Copied!' : 'Click to copy worktree path'}
602
+ >
603
+ {copied && <Check className="h-3 w-3 text-green-600" />}
604
+ <span className={copied ? 'text-green-800' : ''}>
605
+ {selectedAttempt.worktree_path}
606
+ </span>
607
+ {copied && (
608
+ <span className="text-green-700 font-medium">Copied!</span>
609
+ )}
610
+ </div>
611
+ </div>
612
+
613
+ <div className="col-span-4 flex flex-wrap items-center justify-between gap-2">
614
+ <div className="flex items-center gap-2 flex-wrap">
615
+ <TooltipProvider>
616
+ <Tooltip>
617
+ <TooltipTrigger asChild>
618
+ <div
619
+ className={!projectHasDevScript ? 'cursor-not-allowed' : ''}
620
+ onMouseEnter={() => setIsHoveringDevServer(true)}
621
+ onMouseLeave={() => setIsHoveringDevServer(false)}
622
+ >
623
+ <Button
624
+ variant={runningDevServer ? 'destructive' : 'outline'}
625
+ size="sm"
626
+ onClick={runningDevServer ? stopDevServer : startDevServer}
627
+ disabled={isStartingDevServer || !projectHasDevScript}
628
+ className="gap-1"
629
+ >
630
+ {runningDevServer ? (
631
+ <>
632
+ <StopCircle className="h-3 w-3" />
633
+ Stop Dev
634
+ </>
635
+ ) : (
636
+ <>
637
+ <Play className="h-3 w-3" />
638
+ Dev Server
639
+ </>
640
+ )}
641
+ </Button>
642
+ </div>
643
+ </TooltipTrigger>
644
+ <TooltipContent
645
+ className={runningDevServer ? 'max-w-2xl p-4' : ''}
646
+ side="top"
647
+ align="center"
648
+ avoidCollisions={true}
649
+ >
650
+ {!projectHasDevScript ? (
651
+ <p>
652
+ Add a dev server script in project settings to enable this
653
+ feature
654
+ </p>
655
+ ) : runningDevServer && devServerDetails ? (
656
+ <div className="space-y-2">
657
+ <p className="text-sm font-medium">
658
+ Dev Server Logs (Last 10 lines):
659
+ </p>
660
+ <pre className="text-xs bg-muted p-2 rounded max-h-64 overflow-y-auto whitespace-pre-wrap">
661
+ {processedDevServerLogs}
662
+ </pre>
663
+ </div>
664
+ ) : runningDevServer ? (
665
+ <p>Stop the running dev server</p>
666
+ ) : (
667
+ <p>Start the dev server</p>
668
+ )}
669
+ </TooltipContent>
670
+ </Tooltip>
671
+ </TooltipProvider>
672
+ </div>
673
+
674
+ <div className="flex items-center gap-2 flex-wrap">
675
+ {taskAttempts.length > 1 && (
676
+ <DropdownMenu>
677
+ <TooltipProvider>
678
+ <Tooltip>
679
+ <TooltipTrigger asChild>
680
+ <DropdownMenuTrigger asChild>
681
+ <Button variant="outline" size="sm" className="gap-2">
682
+ <History className="h-4 w-4" />
683
+ History
684
+ </Button>
685
+ </DropdownMenuTrigger>
686
+ </TooltipTrigger>
687
+ <TooltipContent>
688
+ <p>View attempt history</p>
689
+ </TooltipContent>
690
+ </Tooltip>
691
+ </TooltipProvider>
692
+ <DropdownMenuContent align="start" className="w-64">
693
+ {taskAttempts.map((attempt) => (
694
+ <DropdownMenuItem
695
+ key={attempt.id}
696
+ onClick={() => handleAttemptChange(attempt)}
697
+ className={
698
+ selectedAttempt?.id === attempt.id ? 'bg-accent' : ''
699
+ }
700
+ >
701
+ <div className="flex flex-col w-full">
702
+ <span className="font-medium text-sm">
703
+ {new Date(attempt.created_at).toLocaleDateString()}{' '}
704
+ {new Date(attempt.created_at).toLocaleTimeString()}
705
+ </span>
706
+ <span className="text-xs text-muted-foreground">
707
+ {attempt.executor || 'executor'}
708
+ </span>
709
+ </div>
710
+ </DropdownMenuItem>
711
+ ))}
712
+ </DropdownMenuContent>
713
+ </DropdownMenu>
714
+ )}
715
+
716
+ {/* Git Operations */}
717
+ {selectedAttempt && branchStatus && (
718
+ <>
719
+ {branchStatus.is_behind &&
720
+ !branchStatus.merged &&
721
+ !isPlanTask && (
722
+ <Button
723
+ onClick={handleRebaseClick}
724
+ disabled={
725
+ rebasing || branchStatusLoading || isAttemptRunning
726
+ }
727
+ variant="outline"
728
+ size="sm"
729
+ className="border-orange-300 text-orange-700 hover:bg-orange-50 gap-1"
730
+ >
731
+ <RefreshCw
732
+ className={`h-3 w-3 ${rebasing ? 'animate-spin' : ''}`}
733
+ />
734
+ {rebasing ? 'Rebasing...' : `Rebase`}
735
+ </Button>
736
+ )}
737
+ {isPlanTask ? (
738
+ // Plan tasks: show approval button
739
+ <Button
740
+ onClick={handlePlanApproval}
741
+ disabled={
742
+ isAttemptRunning ||
743
+ executionState?.execution_state === 'CodingAgentFailed' ||
744
+ executionState?.execution_state === 'SetupFailed'
745
+ }
746
+ size="sm"
747
+ className="bg-green-600 hover:bg-green-700 disabled:bg-gray-400 gap-1"
748
+ >
749
+ <GitBranchIcon className="h-3 w-3" />
750
+ {isApprovingPlan ? 'Approving...' : 'Create Task'}
751
+ </Button>
752
+ ) : (
753
+ // Normal merge and PR buttons for regular tasks
754
+ !branchStatus.merged && (
755
+ <>
756
+ <Button
757
+ onClick={handleCreatePRClick}
758
+ disabled={
759
+ creatingPR ||
760
+ Boolean(branchStatus.is_behind) ||
761
+ isAttemptRunning
762
+ }
763
+ variant="outline"
764
+ size="sm"
765
+ className="border-blue-300 text-blue-700 hover:bg-blue-50 gap-1"
766
+ >
767
+ <GitPullRequest className="h-3 w-3" />
768
+ {selectedAttempt.pr_url
769
+ ? 'Open PR'
770
+ : creatingPR
771
+ ? 'Creating...'
772
+ : 'Create PR'}
773
+ </Button>
774
+ <Button
775
+ onClick={handleMergeClick}
776
+ disabled={
777
+ merging ||
778
+ Boolean(branchStatus.is_behind) ||
779
+ isAttemptRunning
780
+ }
781
+ size="sm"
782
+ className="bg-green-600 hover:bg-green-700 disabled:bg-gray-400 gap-1"
783
+ >
784
+ <GitBranchIcon className="h-3 w-3" />
785
+ {merging ? 'Merging...' : 'Merge'}
786
+ </Button>
787
+ </>
788
+ )
789
+ )}
790
+ </>
791
+ )}
792
+
793
+ {isStopping || isAttemptRunning ? (
794
+ <Button
795
+ variant="destructive"
796
+ size="sm"
797
+ onClick={stopAllExecutions}
798
+ disabled={isStopping}
799
+ className="gap-2"
800
+ >
801
+ <StopCircle className="h-4 w-4" />
802
+ {isStopping ? 'Stopping...' : 'Stop Attempt'}
803
+ </Button>
804
+ ) : (
805
+ <Button
806
+ variant="outline"
807
+ size="sm"
808
+ onClick={handleEnterCreateAttemptMode}
809
+ className="gap-2"
810
+ >
811
+ <Plus className="h-4 w-4" />
812
+ New Attempt
813
+ </Button>
814
+ )}
815
+ </div>
816
+ </div>
817
+
818
+ {/* Rebase Dialog */}
819
+ <Dialog open={showRebaseDialog} onOpenChange={setShowRebaseDialog}>
820
+ <DialogContent className="sm:max-w-md">
821
+ <DialogHeader>
822
+ <DialogTitle>Rebase Task Attempt</DialogTitle>
823
+ <DialogDescription>
824
+ Choose a new base branch to rebase this task attempt onto.
825
+ </DialogDescription>
826
+ </DialogHeader>
827
+
828
+ <div className="space-y-4">
829
+ <div className="space-y-2">
830
+ <label htmlFor="base-branch" className="text-sm font-medium">
831
+ Base Branch
832
+ </label>
833
+ <BranchSelector
834
+ branches={branches}
835
+ selectedBranch={selectedRebaseBranch}
836
+ onBranchSelect={setSelectedRebaseBranch}
837
+ placeholder="Select a base branch"
838
+ excludeCurrentBranch={false}
839
+ />
840
+ </div>
841
+ </div>
842
+
843
+ <DialogFooter>
844
+ <Button
845
+ variant="outline"
846
+ onClick={() => setShowRebaseDialog(false)}
847
+ disabled={rebasing}
848
+ >
849
+ Cancel
850
+ </Button>
851
+ <Button
852
+ onClick={handleRebaseDialogConfirm}
853
+ disabled={rebasing || !selectedRebaseBranch}
854
+ >
855
+ {rebasing ? 'Rebasing...' : 'Rebase'}
856
+ </Button>
857
+ </DialogFooter>
858
+ </DialogContent>
859
+ </Dialog>
860
+
861
+ {/* Stop Execution Confirmation Dialog */}
862
+ <Dialog
863
+ open={showStopConfirmation}
864
+ onOpenChange={setShowStopConfirmation}
865
+ >
866
+ <DialogContent className="sm:max-w-md">
867
+ <DialogHeader>
868
+ <DialogTitle>Stop Current Attempt?</DialogTitle>
869
+ <DialogDescription>
870
+ Are you sure you want to stop the current execution? This action
871
+ cannot be undone.
872
+ </DialogDescription>
873
+ </DialogHeader>
874
+ <DialogFooter>
875
+ <Button
876
+ variant="outline"
877
+ onClick={() => setShowStopConfirmation(false)}
878
+ disabled={isStopping}
879
+ >
880
+ Cancel
881
+ </Button>
882
+ <Button
883
+ variant="destructive"
884
+ onClick={async () => {
885
+ setShowStopConfirmation(false);
886
+ await stopAllExecutions();
887
+ }}
888
+ disabled={isStopping}
889
+ >
890
+ {isStopping ? 'Stopping...' : 'Stop'}
891
+ </Button>
892
+ </DialogFooter>
893
+ </DialogContent>
894
+ </Dialog>
895
+ </div>
896
+ );
897
+ }
898
+
899
+ export default CurrentAttempt;