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,392 @@
1
+ import { useContext, useMemo, useState } from 'react';
2
+ import { DiffCard } from './DiffCard';
3
+ import MarkdownRenderer from '@/components/ui/markdown-renderer.tsx';
4
+ import {
5
+ AlertCircle,
6
+ Bot,
7
+ Brain,
8
+ CheckSquare,
9
+ ChevronRight,
10
+ ChevronUp,
11
+ Edit,
12
+ Eye,
13
+ Globe,
14
+ Plus,
15
+ Search,
16
+ Settings,
17
+ Terminal,
18
+ User,
19
+ } from 'lucide-react';
20
+ import {
21
+ NormalizedEntry,
22
+ type NormalizedEntryType,
23
+ type WorktreeDiff,
24
+ } from 'shared/types.ts';
25
+ import { TaskDiffContext } from '@/components/context/taskDetailsContext.ts';
26
+
27
+ type Props = {
28
+ entry: NormalizedEntry;
29
+ index: number;
30
+ diffDeletable?: boolean;
31
+ };
32
+
33
+ const getEntryIcon = (entryType: NormalizedEntryType) => {
34
+ if (entryType.type === 'user_message') {
35
+ return <User className="h-4 w-4 text-blue-600" />;
36
+ }
37
+ if (entryType.type === 'assistant_message') {
38
+ return <Bot className="h-4 w-4 text-green-600" />;
39
+ }
40
+ if (entryType.type === 'system_message') {
41
+ return <Settings className="h-4 w-4 text-gray-600" />;
42
+ }
43
+ if (entryType.type === 'thinking') {
44
+ return <Brain className="h-4 w-4 text-purple-600" />;
45
+ }
46
+ if (entryType.type === 'error_message') {
47
+ return <AlertCircle className="h-4 w-4 text-red-600" />;
48
+ }
49
+ if (entryType.type === 'tool_use') {
50
+ const { action_type, tool_name } = entryType;
51
+
52
+ // Special handling for TODO tools
53
+ if (
54
+ tool_name &&
55
+ (tool_name.toLowerCase() === 'todowrite' ||
56
+ tool_name.toLowerCase() === 'todoread' ||
57
+ tool_name.toLowerCase() === 'todo_write' ||
58
+ tool_name.toLowerCase() === 'todo_read')
59
+ ) {
60
+ return <CheckSquare className="h-4 w-4 text-purple-600" />;
61
+ }
62
+
63
+ if (action_type.action === 'file_read') {
64
+ return <Eye className="h-4 w-4 text-orange-600" />;
65
+ }
66
+ if (action_type.action === 'file_write') {
67
+ return <Edit className="h-4 w-4 text-red-600" />;
68
+ }
69
+ if (action_type.action === 'command_run') {
70
+ return <Terminal className="h-4 w-4 text-yellow-600" />;
71
+ }
72
+ if (action_type.action === 'search') {
73
+ return <Search className="h-4 w-4 text-indigo-600" />;
74
+ }
75
+ if (action_type.action === 'web_fetch') {
76
+ return <Globe className="h-4 w-4 text-cyan-600" />;
77
+ }
78
+ if (action_type.action === 'task_create') {
79
+ return <Plus className="h-4 w-4 text-teal-600" />;
80
+ }
81
+ if (action_type.action === 'plan_presentation') {
82
+ return <CheckSquare className="h-4 w-4 text-blue-600" />;
83
+ }
84
+ return <Settings className="h-4 w-4 text-gray-600" />;
85
+ }
86
+ return <Settings className="h-4 w-4 text-gray-400" />;
87
+ };
88
+
89
+ const getContentClassName = (entryType: NormalizedEntryType) => {
90
+ const baseClasses = 'text-sm whitespace-pre-wrap break-words';
91
+
92
+ if (
93
+ entryType.type === 'tool_use' &&
94
+ entryType.action_type.action === 'command_run'
95
+ ) {
96
+ return `${baseClasses} font-mono`;
97
+ }
98
+
99
+ if (entryType.type === 'error_message') {
100
+ return `${baseClasses} text-red-600 font-mono bg-red-50 dark:bg-red-950/20 px-2 py-1 rounded`;
101
+ }
102
+
103
+ // Special styling for TODO lists
104
+ if (
105
+ entryType.type === 'tool_use' &&
106
+ entryType.tool_name &&
107
+ (entryType.tool_name.toLowerCase() === 'todowrite' ||
108
+ entryType.tool_name.toLowerCase() === 'todoread' ||
109
+ entryType.tool_name.toLowerCase() === 'todo_write' ||
110
+ entryType.tool_name.toLowerCase() === 'todo_read')
111
+ ) {
112
+ return `${baseClasses} font-mono text-purple-700 dark:text-purple-300 bg-purple-50 dark:bg-purple-950/20 px-2 py-1 rounded`;
113
+ }
114
+
115
+ // Special styling for plan presentations
116
+ if (
117
+ entryType.type === 'tool_use' &&
118
+ entryType.action_type.action === 'plan_presentation'
119
+ ) {
120
+ return `${baseClasses} text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-950/20 px-3 py-2 rounded-md border-l-4 border-blue-400`;
121
+ }
122
+
123
+ return baseClasses;
124
+ };
125
+
126
+ // Parse file path from content (handles various formats)
127
+ const parseFilePathFromContent = (content: string): string | null => {
128
+ // Try to extract path from backticks: `path/to/file.ext`
129
+ const backtickMatch = content.match(/`([^`]+)`/);
130
+ if (backtickMatch) {
131
+ return backtickMatch[1];
132
+ }
133
+
134
+ // Try to extract from common patterns like "Edit file: path" or "Write file: path"
135
+ const actionMatch = content.match(
136
+ /(?:Edit|Write|Create)\s+file:\s*([^\s\n]+)/i
137
+ );
138
+ if (actionMatch) {
139
+ return actionMatch[1];
140
+ }
141
+
142
+ return null;
143
+ };
144
+
145
+ // Helper function to determine if a tool call modifies files
146
+ const isFileModificationToolCall = (
147
+ entryType: NormalizedEntryType
148
+ ): boolean => {
149
+ if (entryType.type !== 'tool_use') {
150
+ return false;
151
+ }
152
+
153
+ // Check for direct file write action
154
+ if (entryType.action_type.action === 'file_write') {
155
+ return true;
156
+ }
157
+
158
+ // Check for "other" actions that are file modification tools
159
+ if (entryType.action_type.action === 'other') {
160
+ const fileModificationTools = [
161
+ 'edit',
162
+ 'write',
163
+ 'create_file',
164
+ 'multiedit',
165
+ 'edit_file',
166
+ ];
167
+ return fileModificationTools.includes(
168
+ entryType.tool_name?.toLowerCase() || ''
169
+ );
170
+ }
171
+
172
+ return false;
173
+ };
174
+
175
+ // Extract file path from tool call
176
+ const extractFilePathFromToolCall = (entry: NormalizedEntry): string | null => {
177
+ if (entry.entry_type.type !== 'tool_use') {
178
+ return null;
179
+ }
180
+
181
+ const { action_type, tool_name } = entry.entry_type;
182
+
183
+ // Direct path extraction from action_type
184
+ if (action_type.action === 'file_write') {
185
+ return action_type.path || null;
186
+ }
187
+
188
+ // For "other" actions, check if it's a known file modification tool
189
+ if (action_type.action === 'other') {
190
+ const fileModificationTools = [
191
+ 'edit',
192
+ 'write',
193
+ 'create_file',
194
+ 'multiedit',
195
+ 'edit_file',
196
+ ];
197
+
198
+ if (fileModificationTools.includes(tool_name.toLowerCase())) {
199
+ // Parse file path from content field
200
+ return parseFilePathFromContent(entry.content);
201
+ }
202
+ }
203
+
204
+ return null;
205
+ };
206
+
207
+ // Create filtered diff showing only specific files
208
+ const createIncrementalDiff = (
209
+ fullDiff: WorktreeDiff | null,
210
+ targetFilePaths: string[]
211
+ ): WorktreeDiff | null => {
212
+ if (!fullDiff || targetFilePaths.length === 0) {
213
+ return null;
214
+ }
215
+
216
+ // Filter files to only include the target file paths
217
+ const filteredFiles = fullDiff.files.filter((file) =>
218
+ targetFilePaths.some(
219
+ (targetPath) =>
220
+ file.path === targetPath ||
221
+ file.path.endsWith('/' + targetPath) ||
222
+ targetPath.endsWith('/' + file.path)
223
+ )
224
+ );
225
+
226
+ if (filteredFiles.length === 0) {
227
+ return null;
228
+ }
229
+
230
+ return {
231
+ ...fullDiff,
232
+ files: filteredFiles,
233
+ };
234
+ };
235
+
236
+ // Helper function to determine if content should be rendered as markdown
237
+ const shouldRenderMarkdown = (entryType: NormalizedEntryType) => {
238
+ // Render markdown for assistant messages, plan presentations, and tool outputs that contain backticks
239
+ return (
240
+ entryType.type === 'assistant_message' ||
241
+ (entryType.type === 'tool_use' &&
242
+ entryType.action_type.action === 'plan_presentation') ||
243
+ (entryType.type === 'tool_use' &&
244
+ entryType.tool_name &&
245
+ (entryType.tool_name.toLowerCase() === 'todowrite' ||
246
+ entryType.tool_name.toLowerCase() === 'todoread' ||
247
+ entryType.tool_name.toLowerCase() === 'todo_write' ||
248
+ entryType.tool_name.toLowerCase() === 'todo_read' ||
249
+ entryType.tool_name.toLowerCase() === 'glob' ||
250
+ entryType.tool_name.toLowerCase() === 'ls' ||
251
+ entryType.tool_name.toLowerCase() === 'list_directory' ||
252
+ entryType.tool_name.toLowerCase() === 'read' ||
253
+ entryType.tool_name.toLowerCase() === 'read_file' ||
254
+ entryType.tool_name.toLowerCase() === 'write' ||
255
+ entryType.tool_name.toLowerCase() === 'create_file' ||
256
+ entryType.tool_name.toLowerCase() === 'edit' ||
257
+ entryType.tool_name.toLowerCase() === 'edit_file' ||
258
+ entryType.tool_name.toLowerCase() === 'multiedit' ||
259
+ entryType.tool_name.toLowerCase() === 'bash' ||
260
+ entryType.tool_name.toLowerCase() === 'run_command' ||
261
+ entryType.tool_name.toLowerCase() === 'grep' ||
262
+ entryType.tool_name.toLowerCase() === 'search' ||
263
+ entryType.tool_name.toLowerCase() === 'webfetch' ||
264
+ entryType.tool_name.toLowerCase() === 'web_fetch' ||
265
+ entryType.tool_name.toLowerCase() === 'task'))
266
+ );
267
+ };
268
+
269
+ function DisplayConversationEntry({ entry, index, diffDeletable }: Props) {
270
+ const { diff } = useContext(TaskDiffContext);
271
+ const [expandedErrors, setExpandedErrors] = useState<Set<number>>(new Set());
272
+
273
+ const toggleErrorExpansion = (index: number) => {
274
+ setExpandedErrors((prev) => {
275
+ const newSet = new Set(prev);
276
+ if (newSet.has(index)) {
277
+ newSet.delete(index);
278
+ } else {
279
+ newSet.add(index);
280
+ }
281
+ return newSet;
282
+ });
283
+ };
284
+
285
+ const isErrorMessage = entry.entry_type.type === 'error_message';
286
+ const isExpanded = expandedErrors.has(index);
287
+ const hasMultipleLines = isErrorMessage && entry.content.includes('\n');
288
+ const isFileModification = useMemo(
289
+ () => isFileModificationToolCall(entry.entry_type),
290
+ [entry.entry_type]
291
+ );
292
+
293
+ // Extract file path from this specific tool call
294
+ const modifiedFilePath = useMemo(
295
+ () => (isFileModification ? extractFilePathFromToolCall(entry) : null),
296
+ [isFileModification, entry]
297
+ );
298
+
299
+ // Create incremental diff showing only the files modified by this specific tool call
300
+ const incrementalDiff = useMemo(
301
+ () =>
302
+ modifiedFilePath && diff
303
+ ? createIncrementalDiff(diff, [modifiedFilePath])
304
+ : null,
305
+ [modifiedFilePath, diff]
306
+ );
307
+
308
+ // Show incremental diff for this specific file modification
309
+ const shouldShowDiff =
310
+ isFileModification && incrementalDiff && incrementalDiff.files.length > 0;
311
+
312
+ return (
313
+ <div key={index}>
314
+ <div className="flex items-start gap-3">
315
+ <div className="flex-shrink-0 mt-1">
316
+ {isErrorMessage && hasMultipleLines ? (
317
+ <button
318
+ onClick={() => toggleErrorExpansion(index)}
319
+ className="transition-colors hover:opacity-70"
320
+ >
321
+ {getEntryIcon(entry.entry_type)}
322
+ </button>
323
+ ) : (
324
+ getEntryIcon(entry.entry_type)
325
+ )}
326
+ </div>
327
+ <div className="flex-1 min-w-0">
328
+ {isErrorMessage && hasMultipleLines ? (
329
+ <div className={isExpanded ? 'space-y-2' : ''}>
330
+ <div className={getContentClassName(entry.entry_type)}>
331
+ {isExpanded ? (
332
+ shouldRenderMarkdown(entry.entry_type) ? (
333
+ <MarkdownRenderer
334
+ content={entry.content}
335
+ className="whitespace-pre-wrap break-words"
336
+ />
337
+ ) : (
338
+ entry.content
339
+ )
340
+ ) : (
341
+ <>
342
+ {entry.content.split('\n')[0]}
343
+ <button
344
+ onClick={() => toggleErrorExpansion(index)}
345
+ className="ml-2 inline-flex items-center gap-1 text-xs text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 transition-colors"
346
+ >
347
+ <ChevronRight className="h-3 w-3" />
348
+ Show more
349
+ </button>
350
+ </>
351
+ )}
352
+ </div>
353
+ {isExpanded && (
354
+ <button
355
+ onClick={() => toggleErrorExpansion(index)}
356
+ className="flex items-center gap-1 text-xs text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 transition-colors"
357
+ >
358
+ <ChevronUp className="h-3 w-3" />
359
+ Show less
360
+ </button>
361
+ )}
362
+ </div>
363
+ ) : (
364
+ <div className={getContentClassName(entry.entry_type)}>
365
+ {shouldRenderMarkdown(entry.entry_type) ? (
366
+ <MarkdownRenderer
367
+ content={entry.content}
368
+ className="whitespace-pre-wrap break-words"
369
+ />
370
+ ) : (
371
+ entry.content
372
+ )}
373
+ </div>
374
+ )}
375
+ </div>
376
+ </div>
377
+
378
+ {/* Render incremental diff card inline after file modification entries */}
379
+ {shouldShowDiff && incrementalDiff && (
380
+ <div className="mt-4 mb-2">
381
+ <DiffCard
382
+ diff={incrementalDiff}
383
+ deletable={diffDeletable}
384
+ compact={true}
385
+ />
386
+ </div>
387
+ )}
388
+ </div>
389
+ );
390
+ }
391
+
392
+ export default DisplayConversationEntry;
@@ -0,0 +1,256 @@
1
+ import { NormalizedConversationViewer } from '@/components/tasks/TaskDetails/LogsTab/NormalizedConversationViewer.tsx';
2
+ import {
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+ import { TaskAttemptDataContext } from '@/components/context/taskDetailsContext.ts';
11
+ import { Loader } from '@/components/ui/loader.tsx';
12
+ import { Button } from '@/components/ui/button';
13
+ import Prompt from './Prompt';
14
+ import ConversationEntry from './ConversationEntry';
15
+ import { ConversationEntryDisplayType } from '@/lib/types';
16
+
17
+ function Conversation() {
18
+ const { attemptData } = useContext(TaskAttemptDataContext);
19
+ const [shouldAutoScrollLogs, setShouldAutoScrollLogs] = useState(true);
20
+ const [conversationUpdateTrigger, setConversationUpdateTrigger] = useState(0);
21
+ const [visibleCount, setVisibleCount] = useState(100);
22
+ const [visibleRunningEntriesCount, setVisibleRunningEntriesCount] =
23
+ useState(0);
24
+
25
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
26
+
27
+ // Callback to trigger auto-scroll when conversation updates
28
+ const handleConversationUpdate = useCallback(() => {
29
+ setConversationUpdateTrigger((prev) => prev + 1);
30
+ }, []);
31
+
32
+ useEffect(() => {
33
+ if (shouldAutoScrollLogs && scrollContainerRef.current) {
34
+ scrollContainerRef.current.scrollTop =
35
+ scrollContainerRef.current.scrollHeight;
36
+ }
37
+ }, [attemptData.allLogs, conversationUpdateTrigger, shouldAutoScrollLogs]);
38
+
39
+ const handleLogsScroll = useCallback(() => {
40
+ if (scrollContainerRef.current) {
41
+ const { scrollTop, scrollHeight, clientHeight } =
42
+ scrollContainerRef.current;
43
+ const isAtBottom = scrollTop + clientHeight >= scrollHeight - 5;
44
+
45
+ if (isAtBottom && !shouldAutoScrollLogs) {
46
+ setShouldAutoScrollLogs(true);
47
+ } else if (!isAtBottom && shouldAutoScrollLogs) {
48
+ setShouldAutoScrollLogs(false);
49
+ }
50
+ }
51
+ }, [shouldAutoScrollLogs]);
52
+
53
+ // Find main and follow-up processes from allLogs
54
+ const mainCodingAgentLog = useMemo(
55
+ () =>
56
+ attemptData.allLogs.find(
57
+ (log) =>
58
+ log.process_type.toLowerCase() === 'codingagent' &&
59
+ log.command === 'executor'
60
+ ),
61
+ [attemptData.allLogs]
62
+ );
63
+ const followUpLogs = useMemo(
64
+ () =>
65
+ attemptData.allLogs.filter(
66
+ (log) =>
67
+ log.process_type.toLowerCase() === 'codingagent' &&
68
+ log.command === 'followup_executor'
69
+ ),
70
+ [attemptData.allLogs]
71
+ );
72
+
73
+ // Combine all logs in order (main first, then follow-ups)
74
+ const allProcessLogs = useMemo(
75
+ () =>
76
+ [mainCodingAgentLog, ...followUpLogs].filter(Boolean) as Array<
77
+ NonNullable<typeof mainCodingAgentLog>
78
+ >,
79
+ [mainCodingAgentLog, followUpLogs]
80
+ );
81
+
82
+ // Flatten all entries, keeping process info for each entry
83
+ const allEntries = useMemo(() => {
84
+ const entries: Array<ConversationEntryDisplayType> = [];
85
+ allProcessLogs.forEach((log, processIndex) => {
86
+ if (!log) return;
87
+ if (log.status === 'running') return; // Skip static entries for running processes
88
+ const processId = String(log.id); // Ensure string
89
+ const processPrompt = log.normalized_conversation.prompt || undefined; // Ensure undefined, not null
90
+ const entriesArr = log.normalized_conversation.entries || [];
91
+ entriesArr.forEach((entry, entryIndex) => {
92
+ entries.push({
93
+ entry,
94
+ processId,
95
+ processPrompt,
96
+ processStatus: log.status,
97
+ processIsRunning: false, // Only completed processes here
98
+ process: log,
99
+ isFirstInProcess: entryIndex === 0,
100
+ processIndex,
101
+ entryIndex,
102
+ });
103
+ });
104
+ });
105
+ // Sort by timestamp (entries without timestamp go last)
106
+ entries.sort((a, b) => {
107
+ if (a.entry.timestamp && b.entry.timestamp) {
108
+ return a.entry.timestamp.localeCompare(b.entry.timestamp);
109
+ }
110
+ if (a.entry.timestamp) return -1;
111
+ if (b.entry.timestamp) return 1;
112
+ return 0;
113
+ });
114
+ return entries;
115
+ }, [allProcessLogs]);
116
+
117
+ // Identify running processes (main + follow-ups)
118
+ const runningProcessLogs = useMemo(
119
+ () => allProcessLogs.filter((log) => log.status === 'running'),
120
+ [allProcessLogs]
121
+ );
122
+
123
+ // Paginate: show only the last visibleCount entries
124
+ const visibleEntries = useMemo(
125
+ () => allEntries.slice(-(visibleCount - visibleRunningEntriesCount)),
126
+ [allEntries, visibleCount, visibleRunningEntriesCount]
127
+ );
128
+
129
+ const renderedVisibleEntries = useMemo(
130
+ () =>
131
+ visibleEntries.map((entry, index) => (
132
+ <ConversationEntry
133
+ key={entry.entry.timestamp || index}
134
+ idx={index}
135
+ item={entry}
136
+ handleConversationUpdate={handleConversationUpdate}
137
+ visibleEntriesLength={visibleEntries.length}
138
+ runningProcessDetails={attemptData.runningProcessDetails}
139
+ />
140
+ )),
141
+ [
142
+ visibleEntries,
143
+ handleConversationUpdate,
144
+ attemptData.runningProcessDetails,
145
+ ]
146
+ );
147
+
148
+ const renderedRunningProcessLogs = useMemo(() => {
149
+ return runningProcessLogs.map((log, i) => {
150
+ const runningProcess = attemptData.runningProcessDetails[String(log.id)];
151
+ if (!runningProcess) return null;
152
+ // Show prompt only if this is the first entry in the process (i.e., no completed entries for this process)
153
+ const showPrompt =
154
+ log.normalized_conversation.prompt &&
155
+ !allEntries.some((e) => e.processId === String(log.id));
156
+ return (
157
+ <div key={String(log.id)} className={i > 0 ? 'mt-8' : ''}>
158
+ {showPrompt && (
159
+ <Prompt prompt={log.normalized_conversation.prompt || ''} />
160
+ )}
161
+ <NormalizedConversationViewer
162
+ executionProcess={runningProcess}
163
+ onConversationUpdate={handleConversationUpdate}
164
+ diffDeletable
165
+ visibleEntriesNum={visibleCount}
166
+ onDisplayEntriesChange={setVisibleRunningEntriesCount}
167
+ />
168
+ </div>
169
+ );
170
+ });
171
+ }, [
172
+ runningProcessLogs,
173
+ attemptData.runningProcessDetails,
174
+ handleConversationUpdate,
175
+ allEntries,
176
+ visibleCount,
177
+ ]);
178
+
179
+ // Check if we should show the status banner - only if the most recent process failed/stopped
180
+ const getMostRecentProcess = () => {
181
+ if (followUpLogs.length > 0) {
182
+ // Sort by creation time or use last in array as most recent
183
+ return followUpLogs[followUpLogs.length - 1];
184
+ }
185
+ return mainCodingAgentLog;
186
+ };
187
+
188
+ const mostRecentProcess = getMostRecentProcess();
189
+ const showStatusBanner =
190
+ mostRecentProcess &&
191
+ (mostRecentProcess.status === 'failed' ||
192
+ mostRecentProcess.status === 'killed');
193
+
194
+ return (
195
+ <div
196
+ ref={scrollContainerRef}
197
+ onScroll={handleLogsScroll}
198
+ className="h-full overflow-y-auto"
199
+ >
200
+ {visibleCount - visibleRunningEntriesCount < allEntries.length && (
201
+ <div className="flex justify-center mb-4">
202
+ <Button
203
+ variant="outline"
204
+ className="w-full"
205
+ onClick={() => setVisibleCount((c) => c + 100)}
206
+ >
207
+ Load previous logs
208
+ </Button>
209
+ </div>
210
+ )}
211
+ {visibleEntries.length > 0 && (
212
+ <div className="space-y-2">{renderedVisibleEntries}</div>
213
+ )}
214
+ {/* Render live viewers for running processes (after paginated list) */}
215
+ {renderedRunningProcessLogs}
216
+ {/* If nothing to show at all, show loader */}
217
+ {visibleEntries.length === 0 && runningProcessLogs.length === 0 && (
218
+ <Loader
219
+ message={
220
+ <>
221
+ Coding Agent Starting
222
+ <br />
223
+ Initializing conversation...
224
+ </>
225
+ }
226
+ size={48}
227
+ className="py-8"
228
+ />
229
+ )}
230
+
231
+ {/* Status banner for failed/stopped states - shown at bottom */}
232
+ {showStatusBanner && mostRecentProcess && (
233
+ <div className="mt-4 p-4 rounded-lg border">
234
+ <p
235
+ className={`text-lg font-semibold mb-2 ${
236
+ mostRecentProcess.status === 'failed'
237
+ ? 'text-destructive'
238
+ : 'text-orange-600'
239
+ }`}
240
+ >
241
+ {mostRecentProcess.status === 'failed'
242
+ ? 'Coding Agent Failed'
243
+ : 'Coding Agent Stopped'}
244
+ </p>
245
+ <p className="text-muted-foreground">
246
+ {mostRecentProcess.status === 'failed'
247
+ ? 'The coding agent encountered an error.'
248
+ : 'The coding agent was stopped.'}
249
+ </p>
250
+ </div>
251
+ )}
252
+ </div>
253
+ );
254
+ }
255
+
256
+ export default Conversation;
@@ -0,0 +1,56 @@
1
+ import { ConversationEntryDisplayType } from '@/lib/types';
2
+ import DisplayConversationEntry from '../DisplayConversationEntry';
3
+ import { NormalizedConversationViewer } from './NormalizedConversationViewer';
4
+ import Prompt from './Prompt';
5
+ import { Loader } from '@/components/ui/loader.tsx';
6
+ import { ExecutionProcess } from 'shared/types';
7
+
8
+ type Props = {
9
+ item: ConversationEntryDisplayType;
10
+ idx: number;
11
+ handleConversationUpdate: () => void;
12
+ visibleEntriesLength: number;
13
+ runningProcessDetails: Record<string, ExecutionProcess>;
14
+ };
15
+
16
+ const ConversationEntry = ({
17
+ item,
18
+ idx,
19
+ handleConversationUpdate,
20
+ visibleEntriesLength,
21
+ runningProcessDetails,
22
+ }: Props) => {
23
+ const showPrompt = item.isFirstInProcess && item.processPrompt;
24
+ // For running processes, render the live viewer below the static entries
25
+ if (item.processIsRunning && idx === visibleEntriesLength - 1) {
26
+ // Only render the live viewer for the last entry of a running process
27
+ const runningProcess = runningProcessDetails[item.processId];
28
+ if (runningProcess) {
29
+ return (
30
+ <div key={item.entry.timestamp || idx}>
31
+ {showPrompt && <Prompt prompt={item.processPrompt || ''} />}
32
+ <NormalizedConversationViewer
33
+ executionProcess={runningProcess}
34
+ onConversationUpdate={handleConversationUpdate}
35
+ diffDeletable
36
+ />
37
+ </div>
38
+ );
39
+ }
40
+ // Fallback: show loading if not found
41
+ return <Loader message="Loading live logs..." size={24} className="py-4" />;
42
+ } else {
43
+ return (
44
+ <div key={item.entry.timestamp || idx}>
45
+ {showPrompt && <Prompt prompt={item.processPrompt || ''} />}
46
+ <DisplayConversationEntry
47
+ entry={item.entry}
48
+ index={idx}
49
+ diffDeletable
50
+ />
51
+ </div>
52
+ );
53
+ }
54
+ };
55
+
56
+ export default ConversationEntry;