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