automagik-forge 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/README.md +143 -447
  2. package/dist/linux-x64/automagik-forge-mcp.zip +0 -0
  3. package/{npx-cli/automagik-forge-0.0.55.tgz → dist/linux-x64/automagik-forge.zip} +0 -0
  4. package/package.json +13 -23
  5. package/.cargo/config.toml +0 -13
  6. package/.claude/commands/commit.md +0 -376
  7. package/.claude/commands/prompt.md +0 -871
  8. package/.env.example +0 -20
  9. package/.github/actions/setup-node/action.yml +0 -29
  10. package/.github/images/automagik-logo.png +0 -0
  11. package/.github/workflows/pre-release.yml +0 -470
  12. package/.github/workflows/publish.yml +0 -145
  13. package/.github/workflows/test.yml +0 -63
  14. package/.mcp.json +0 -57
  15. package/AGENT.md +0 -40
  16. package/CLAUDE.md +0 -40
  17. package/CODE-OF-CONDUCT.md +0 -89
  18. package/Cargo.toml +0 -19
  19. package/Dockerfile +0 -43
  20. package/LICENSE +0 -201
  21. package/Makefile +0 -97
  22. package/backend/.sqlx/query-01b7e2bac1261d8be3d03c03df3e5220590da6c31c77f161074fc62752d63881.json +0 -12
  23. package/backend/.sqlx/query-03f2b02ba6dc5ea2b3cf6b1004caea0ad6bcc10ebd63f441d321a389f026e263.json +0 -12
  24. package/backend/.sqlx/query-0923b77d137a29fc54d399a873ff15fc4af894490bc65a4d344a7575cb0d8643.json +0 -12
  25. package/backend/.sqlx/query-0f808bcdb63c5f180836e448dd64c435c51758b2fc54a52ce9e67495b1ab200e.json +0 -68
  26. package/backend/.sqlx/query-1268afe9ca849daa6722e3df7ca8e9e61f0d37052e782bb5452ab8e1018d9b63.json +0 -12
  27. package/backend/.sqlx/query-1b082630a9622f8667ee7a9aba2c2d3176019a68c6bb83d33008594821415a57.json +0 -12
  28. package/backend/.sqlx/query-1c7b06ba1e112abf6b945a2ff08a0b40ec23f3738c2e7399f067b558cf8d490e.json +0 -12
  29. package/backend/.sqlx/query-1f619f01f46859a64ded531dd0ef61abacfe62e758abe7030a6aa745140b95ca.json +0 -104
  30. package/backend/.sqlx/query-1fca1ce14b4b20205364cd1f1f45ebe1d2e30cd745e59e189d56487b5639dfbb.json +0 -12
  31. package/backend/.sqlx/query-212828320e8d871ab9d83705a040b23bcf0393dc7252177fc539a74657f578ef.json +0 -32
  32. package/backend/.sqlx/query-290ce5c152be8d36e58ff42570f9157beb07ab9e77a03ec6fc30b4f56f9b8f6b.json +0 -56
  33. package/backend/.sqlx/query-2b471d2c2e8ffbe0cd42d2a91b814c0d79f9d09200f147e3cea33ba4ce673c8a.json +0 -68
  34. package/backend/.sqlx/query-354a48c705bb9bb2048c1b7f10fcb714e23f9db82b7a4ea6932486197b2ede6a.json +0 -92
  35. package/backend/.sqlx/query-36c9e3dd10648e94b949db5c91a774ecb1e10a899ef95da74066eccedca4d8b2.json +0 -12
  36. package/backend/.sqlx/query-36e4ba7bbd81b402d5a20b6005755eafbb174c8dda442081823406ac32809a94.json +0 -56
  37. package/backend/.sqlx/query-3a5b3c98a55ca183ab20c74708e3d7e579dda37972c059e7515c4ceee4bd8dd3.json +0 -62
  38. package/backend/.sqlx/query-3d0a1cabf2a52e9d90cdfd29c509ca89aeb448d0c1d2446c65cd43db40735e86.json +0 -62
  39. package/backend/.sqlx/query-3d6bd16fbce59efe30b7f67ea342e0e4ea6d1432389c02468ad79f1f742d4031.json +0 -56
  40. package/backend/.sqlx/query-4049ca413b285a05aca6b25385e9c8185575f01e9069e4e8581aa45d713f612f.json +0 -32
  41. package/backend/.sqlx/query-412bacd3477d86369082e90f52240407abce436cb81292d42b2dbe1e5c18eea1.json +0 -104
  42. package/backend/.sqlx/query-417a8b1ff4e51de82aea0159a3b97932224dc325b23476cb84153d690227fd8b.json +0 -62
  43. package/backend/.sqlx/query-461cc1b0bb6fd909afc9dd2246e8526b3771cfbb0b22ae4b5d17b51af587b9e2.json +0 -56
  44. package/backend/.sqlx/query-58408c7a8cdeeda0bef359f1f9bd91299a339dc2b191462fc58c9736a56d5227.json +0 -92
  45. package/backend/.sqlx/query-5a886026d75d515c01f347cc203c8d99dd04c61dc468e2e4c5aa548436d13834.json +0 -62
  46. package/backend/.sqlx/query-5b902137b11022d2e1a5c4f6a9c83fec1a856c6a710aff831abd2382ede76b43.json +0 -12
  47. package/backend/.sqlx/query-5ed1238e52e59bb5f76c0f153fd99a14093f7ce2585bf9843585608f17ec575b.json +0 -104
  48. package/backend/.sqlx/query-6e8b860b14decfc2227dc57213f38442943d3fbef5c8418fd6b634c6e0f5e2ea.json +0 -104
  49. package/backend/.sqlx/query-6ec414276994c4ccb2433eaa5b1b342168557d17ddf5a52dac84cb1b59b9de8f.json +0 -68
  50. package/backend/.sqlx/query-6ecfa16d0cf825aacf233544b5baf151e9adfdca26c226ad71020d291fd802d5.json +0 -62
  51. package/backend/.sqlx/query-72509d252c39fce77520aa816cb2acbc1fb35dc2605e7be893610599b2427f2e.json +0 -62
  52. package/backend/.sqlx/query-75239b2da188f749707d77f3c1544332ca70db3d6d6743b2601dc0d167536437.json +0 -62
  53. package/backend/.sqlx/query-83d10e29f8478aff33434f9ac67068e013b888b953a2657e2bb72a6f619d04f2.json +0 -50
  54. package/backend/.sqlx/query-8610803360ea18b9b9d078a6981ea56abfbfe84e6354fc1d5ae4c622e01410ed.json +0 -68
  55. package/backend/.sqlx/query-86d03eb70eef39c59296416867f2ee66c9f7cd8b7f961fbda2f89fc0a1c442c2.json +0 -12
  56. package/backend/.sqlx/query-87d0feb5a6b442bad9c60068ea7569599cc6fc91a0e2692ecb42e93b03201b9d.json +0 -68
  57. package/backend/.sqlx/query-8a67b3b3337248f06a57bdf8a908f7ef23177431eaed82dc08c94c3e5944340e.json +0 -12
  58. package/backend/.sqlx/query-8f01ebd64bdcde6a090479f14810d73ba23020e76fd70854ac57f2da251702c3.json +0 -12
  59. package/backend/.sqlx/query-90fd607fcb2dca72239ff25e618e21e174b195991eaa33722cbf5f76da84cfab.json +0 -62
  60. package/backend/.sqlx/query-92e8bdbcd80c5ff3db7a35cf79492048803ef305cbdef0d0a1fe5dc881ca8c71.json +0 -104
  61. package/backend/.sqlx/query-93a1605f90e9672dad29b472b6ad85fa9a55ea3ffa5abcb8724b09d61be254ca.json +0 -20
  62. package/backend/.sqlx/query-9472c8fb477958167f5fae40b85ac44252468c5226b2cdd7770f027332eed6d7.json +0 -104
  63. package/backend/.sqlx/query-96036c4f9e0f48bdc5a4a4588f0c5f288ac7aaa5425cac40fc33f337e1a351f2.json +0 -56
  64. package/backend/.sqlx/query-9edb2c01e91fd0f0fe7b56e988c7ae0393150f50be3f419a981e035c0121dfc7.json +0 -104
  65. package/backend/.sqlx/query-a157cf00616f703bfba21927f1eb1c9eec2a81c02da15f66efdba0b6c375de1b.json +0 -26
  66. package/backend/.sqlx/query-a31fff84f3b8e532fd1160447d89d700f06ae08821fee00c9a5b60492b05259c.json +0 -62
  67. package/backend/.sqlx/query-a5ba908419fb3e456bdd2daca41ba06cc3212ffffb8520fc7dbbcc8b60ada314.json +0 -12
  68. package/backend/.sqlx/query-a6d2961718dbc3b1a925e549f49a159c561bef58c105529275f274b27e2eba5b.json +0 -104
  69. package/backend/.sqlx/query-a9e93d5b09b29faf66e387e4d7596a792d81e75c4d3726e83c2963e8d7c9b56f.json +0 -104
  70. package/backend/.sqlx/query-ac5247c8d7fb86e4650c4b0eb9420031614c831b7b085083bac20c1af314c538.json +0 -12
  71. package/backend/.sqlx/query-afef9467be74c411c4cb119a8b2b1aea53049877dfc30cc60b486134ba4b4c9f.json +0 -68
  72. package/backend/.sqlx/query-b2b2c6b4d0b1a347b5c4cb63c3a46a265d4db53be9554989a814b069d0af82f2.json +0 -62
  73. package/backend/.sqlx/query-c50d2ff0b12e5bcc81e371089ee2d007e233e7db93aefba4fef08e7aa68f5ab7.json +0 -20
  74. package/backend/.sqlx/query-c614e6056b244ca07f1b9d44e7edc9d5819225c6f8d9e077070c6e518a17f50b.json +0 -12
  75. package/backend/.sqlx/query-c67259be8bf4ee0cfd32167b2aa3b7fe9192809181a8171bf1c2d6df731967ae.json +0 -12
  76. package/backend/.sqlx/query-d2d0a1b985ebbca6a2b3e882a221a219f3199890fa640afc946ef1a792d6d8de.json +0 -12
  77. package/backend/.sqlx/query-d30aa5786757f32bf2b9c5fe51a45e506c71c28c5994e430d9b0546adb15ffa2.json +0 -20
  78. package/backend/.sqlx/query-d3b9ea1de1576af71b312924ce7f4ea8ae5dbe2ac138ea3b4470f2d5cd734846.json +0 -12
  79. package/backend/.sqlx/query-ed8456646fa69ddd412441955f06ff22bfb790f29466450735e0b8bb1bc4ec94.json +0 -12
  80. package/backend/Cargo.toml +0 -71
  81. package/backend/build.rs +0 -32
  82. package/backend/migrations/20250617183714_init.sql +0 -44
  83. package/backend/migrations/20250620212427_execution_processes.sql +0 -25
  84. package/backend/migrations/20250620214100_remove_stdout_stderr_from_task_attempts.sql +0 -28
  85. package/backend/migrations/20250621120000_relate_activities_to_execution_processes.sql +0 -23
  86. package/backend/migrations/20250623120000_executor_sessions.sql +0 -17
  87. package/backend/migrations/20250623130000_add_executor_type_to_execution_processes.sql +0 -4
  88. package/backend/migrations/20250625000000_add_dev_script_to_projects.sql +0 -4
  89. package/backend/migrations/20250701000000_add_branch_to_task_attempts.sql +0 -2
  90. package/backend/migrations/20250701000001_add_pr_tracking_to_task_attempts.sql +0 -5
  91. package/backend/migrations/20250701120000_add_assistant_message_to_executor_sessions.sql +0 -2
  92. package/backend/migrations/20250708000000_add_base_branch_to_task_attempts.sql +0 -2
  93. package/backend/migrations/20250709000000_add_worktree_deleted_flag.sql +0 -2
  94. package/backend/migrations/20250710000000_add_setup_completion.sql +0 -3
  95. package/backend/migrations/20250715154859_add_task_templates.sql +0 -25
  96. package/backend/migrations/20250716143725_add_default_templates.sql +0 -174
  97. package/backend/migrations/20250716161432_update_executor_names_to_kebab_case.sql +0 -20
  98. package/backend/migrations/20250716170000_add_parent_task_to_tasks.sql +0 -7
  99. package/backend/migrations/20250717000000_drop_task_attempt_activities.sql +0 -9
  100. package/backend/migrations/20250719000000_add_cleanup_script_to_projects.sql +0 -2
  101. package/backend/migrations/20250720000000_add_cleanupscript_to_process_type_constraint.sql +0 -25
  102. package/backend/migrations/20250723000000_add_wish_to_tasks.sql +0 -7
  103. package/backend/migrations/20250724000000_remove_unique_wish_constraint.sql +0 -5
  104. package/backend/scripts/toast-notification.ps1 +0 -23
  105. package/backend/sounds/abstract-sound1.wav +0 -0
  106. package/backend/sounds/abstract-sound2.wav +0 -0
  107. package/backend/sounds/abstract-sound3.wav +0 -0
  108. package/backend/sounds/abstract-sound4.wav +0 -0
  109. package/backend/sounds/cow-mooing.wav +0 -0
  110. package/backend/sounds/phone-vibration.wav +0 -0
  111. package/backend/sounds/rooster.wav +0 -0
  112. package/backend/src/app_state.rs +0 -218
  113. package/backend/src/bin/generate_types.rs +0 -189
  114. package/backend/src/bin/mcp_task_server.rs +0 -191
  115. package/backend/src/execution_monitor.rs +0 -1193
  116. package/backend/src/executor.rs +0 -1053
  117. package/backend/src/executors/amp.rs +0 -697
  118. package/backend/src/executors/ccr.rs +0 -91
  119. package/backend/src/executors/charm_opencode.rs +0 -113
  120. package/backend/src/executors/claude.rs +0 -887
  121. package/backend/src/executors/cleanup_script.rs +0 -124
  122. package/backend/src/executors/dev_server.rs +0 -53
  123. package/backend/src/executors/echo.rs +0 -79
  124. package/backend/src/executors/gemini/config.rs +0 -67
  125. package/backend/src/executors/gemini/streaming.rs +0 -363
  126. package/backend/src/executors/gemini.rs +0 -765
  127. package/backend/src/executors/mod.rs +0 -23
  128. package/backend/src/executors/opencode_ai.rs +0 -113
  129. package/backend/src/executors/setup_script.rs +0 -130
  130. package/backend/src/executors/sst_opencode/filter.rs +0 -184
  131. package/backend/src/executors/sst_opencode/tools.rs +0 -139
  132. package/backend/src/executors/sst_opencode.rs +0 -756
  133. package/backend/src/lib.rs +0 -45
  134. package/backend/src/main.rs +0 -324
  135. package/backend/src/mcp/mod.rs +0 -1
  136. package/backend/src/mcp/task_server.rs +0 -850
  137. package/backend/src/middleware/mod.rs +0 -3
  138. package/backend/src/middleware/model_loaders.rs +0 -242
  139. package/backend/src/models/api_response.rs +0 -36
  140. package/backend/src/models/config.rs +0 -375
  141. package/backend/src/models/execution_process.rs +0 -430
  142. package/backend/src/models/executor_session.rs +0 -225
  143. package/backend/src/models/mod.rs +0 -12
  144. package/backend/src/models/project.rs +0 -356
  145. package/backend/src/models/task.rs +0 -345
  146. package/backend/src/models/task_attempt.rs +0 -1214
  147. package/backend/src/models/task_template.rs +0 -146
  148. package/backend/src/openapi.rs +0 -93
  149. package/backend/src/routes/auth.rs +0 -297
  150. package/backend/src/routes/config.rs +0 -385
  151. package/backend/src/routes/filesystem.rs +0 -228
  152. package/backend/src/routes/health.rs +0 -16
  153. package/backend/src/routes/mod.rs +0 -9
  154. package/backend/src/routes/projects.rs +0 -562
  155. package/backend/src/routes/stream.rs +0 -244
  156. package/backend/src/routes/task_attempts.rs +0 -1172
  157. package/backend/src/routes/task_templates.rs +0 -229
  158. package/backend/src/routes/tasks.rs +0 -353
  159. package/backend/src/services/analytics.rs +0 -216
  160. package/backend/src/services/git_service.rs +0 -1321
  161. package/backend/src/services/github_service.rs +0 -307
  162. package/backend/src/services/mod.rs +0 -13
  163. package/backend/src/services/notification_service.rs +0 -263
  164. package/backend/src/services/pr_monitor.rs +0 -214
  165. package/backend/src/services/process_service.rs +0 -940
  166. package/backend/src/utils/path.rs +0 -96
  167. package/backend/src/utils/shell.rs +0 -19
  168. package/backend/src/utils/text.rs +0 -24
  169. package/backend/src/utils/worktree_manager.rs +0 -578
  170. package/backend/src/utils.rs +0 -125
  171. package/backend/test.db +0 -0
  172. package/build-npm-package.sh +0 -61
  173. package/dev_assets_seed/config.json +0 -19
  174. package/frontend/.eslintrc.json +0 -25
  175. package/frontend/.prettierrc.json +0 -8
  176. package/frontend/components.json +0 -17
  177. package/frontend/index.html +0 -19
  178. package/frontend/package-lock.json +0 -7321
  179. package/frontend/package.json +0 -61
  180. package/frontend/postcss.config.js +0 -6
  181. package/frontend/public/android-chrome-192x192.png +0 -0
  182. package/frontend/public/android-chrome-512x512.png +0 -0
  183. package/frontend/public/apple-touch-icon.png +0 -0
  184. package/frontend/public/automagik-forge-logo-dark.svg +0 -3
  185. package/frontend/public/automagik-forge-logo.svg +0 -3
  186. package/frontend/public/automagik-forge-screenshot-overview.png +0 -0
  187. package/frontend/public/favicon-16x16.png +0 -0
  188. package/frontend/public/favicon-32x32.png +0 -0
  189. package/frontend/public/favicon.ico +0 -0
  190. package/frontend/public/site.webmanifest +0 -1
  191. package/frontend/public/viba-kanban-favicon.png +0 -0
  192. package/frontend/src/App.tsx +0 -157
  193. package/frontend/src/components/DisclaimerDialog.tsx +0 -106
  194. package/frontend/src/components/GitHubLoginDialog.tsx +0 -314
  195. package/frontend/src/components/OnboardingDialog.tsx +0 -185
  196. package/frontend/src/components/PrivacyOptInDialog.tsx +0 -130
  197. package/frontend/src/components/ProvidePatDialog.tsx +0 -98
  198. package/frontend/src/components/TaskTemplateManager.tsx +0 -336
  199. package/frontend/src/components/config-provider.tsx +0 -119
  200. package/frontend/src/components/context/TaskDetailsContextProvider.tsx +0 -470
  201. package/frontend/src/components/context/taskDetailsContext.ts +0 -125
  202. package/frontend/src/components/keyboard-shortcuts-demo.tsx +0 -35
  203. package/frontend/src/components/layout/navbar.tsx +0 -86
  204. package/frontend/src/components/logo.tsx +0 -44
  205. package/frontend/src/components/projects/ProjectCard.tsx +0 -155
  206. package/frontend/src/components/projects/project-detail.tsx +0 -251
  207. package/frontend/src/components/projects/project-form-fields.tsx +0 -238
  208. package/frontend/src/components/projects/project-form.tsx +0 -301
  209. package/frontend/src/components/projects/project-list.tsx +0 -200
  210. package/frontend/src/components/projects/projects-page.tsx +0 -20
  211. package/frontend/src/components/tasks/BranchSelector.tsx +0 -169
  212. package/frontend/src/components/tasks/DeleteFileConfirmationDialog.tsx +0 -94
  213. package/frontend/src/components/tasks/EditorSelectionDialog.tsx +0 -119
  214. package/frontend/src/components/tasks/TaskCard.tsx +0 -154
  215. package/frontend/src/components/tasks/TaskDetails/CollapsibleToolbar.tsx +0 -33
  216. package/frontend/src/components/tasks/TaskDetails/DiffCard.tsx +0 -109
  217. package/frontend/src/components/tasks/TaskDetails/DiffChunkSection.tsx +0 -135
  218. package/frontend/src/components/tasks/TaskDetails/DiffFile.tsx +0 -296
  219. package/frontend/src/components/tasks/TaskDetails/DiffTab.tsx +0 -32
  220. package/frontend/src/components/tasks/TaskDetails/DisplayConversationEntry.tsx +0 -392
  221. package/frontend/src/components/tasks/TaskDetails/LogsTab/Conversation.tsx +0 -256
  222. package/frontend/src/components/tasks/TaskDetails/LogsTab/ConversationEntry.tsx +0 -56
  223. package/frontend/src/components/tasks/TaskDetails/LogsTab/NormalizedConversationViewer.tsx +0 -92
  224. package/frontend/src/components/tasks/TaskDetails/LogsTab/Prompt.tsx +0 -22
  225. package/frontend/src/components/tasks/TaskDetails/LogsTab/SetupScriptRunning.tsx +0 -49
  226. package/frontend/src/components/tasks/TaskDetails/LogsTab.tsx +0 -186
  227. package/frontend/src/components/tasks/TaskDetails/ProcessesTab.tsx +0 -288
  228. package/frontend/src/components/tasks/TaskDetails/RelatedTasksTab.tsx +0 -216
  229. package/frontend/src/components/tasks/TaskDetails/TabNavigation.tsx +0 -93
  230. package/frontend/src/components/tasks/TaskDetailsHeader.tsx +0 -169
  231. package/frontend/src/components/tasks/TaskDetailsPanel.tsx +0 -126
  232. package/frontend/src/components/tasks/TaskDetailsToolbar.tsx +0 -302
  233. package/frontend/src/components/tasks/TaskFollowUpSection.tsx +0 -130
  234. package/frontend/src/components/tasks/TaskFormDialog.tsx +0 -400
  235. package/frontend/src/components/tasks/TaskKanbanBoard.tsx +0 -180
  236. package/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx +0 -259
  237. package/frontend/src/components/tasks/Toolbar/CreatePRDialog.tsx +0 -243
  238. package/frontend/src/components/tasks/Toolbar/CurrentAttempt.tsx +0 -899
  239. package/frontend/src/components/tasks/index.ts +0 -2
  240. package/frontend/src/components/theme-provider.tsx +0 -82
  241. package/frontend/src/components/theme-toggle.tsx +0 -36
  242. package/frontend/src/components/ui/alert.tsx +0 -59
  243. package/frontend/src/components/ui/auto-expanding-textarea.tsx +0 -70
  244. package/frontend/src/components/ui/badge.tsx +0 -36
  245. package/frontend/src/components/ui/button.tsx +0 -56
  246. package/frontend/src/components/ui/card.tsx +0 -86
  247. package/frontend/src/components/ui/checkbox.tsx +0 -44
  248. package/frontend/src/components/ui/chip.tsx +0 -25
  249. package/frontend/src/components/ui/dialog.tsx +0 -124
  250. package/frontend/src/components/ui/dropdown-menu.tsx +0 -198
  251. package/frontend/src/components/ui/file-search-textarea.tsx +0 -292
  252. package/frontend/src/components/ui/folder-picker.tsx +0 -279
  253. package/frontend/src/components/ui/input.tsx +0 -25
  254. package/frontend/src/components/ui/label.tsx +0 -24
  255. package/frontend/src/components/ui/loader.tsx +0 -26
  256. package/frontend/src/components/ui/markdown-renderer.tsx +0 -75
  257. package/frontend/src/components/ui/select.tsx +0 -160
  258. package/frontend/src/components/ui/separator.tsx +0 -31
  259. package/frontend/src/components/ui/shadcn-io/kanban/index.tsx +0 -185
  260. package/frontend/src/components/ui/table.tsx +0 -117
  261. package/frontend/src/components/ui/tabs.tsx +0 -53
  262. package/frontend/src/components/ui/textarea.tsx +0 -22
  263. package/frontend/src/components/ui/tooltip.tsx +0 -28
  264. package/frontend/src/hooks/useNormalizedConversation.ts +0 -440
  265. package/frontend/src/index.css +0 -225
  266. package/frontend/src/lib/api.ts +0 -630
  267. package/frontend/src/lib/keyboard-shortcuts.ts +0 -266
  268. package/frontend/src/lib/responsive-config.ts +0 -70
  269. package/frontend/src/lib/types.ts +0 -39
  270. package/frontend/src/lib/utils.ts +0 -10
  271. package/frontend/src/main.tsx +0 -50
  272. package/frontend/src/pages/McpServers.tsx +0 -418
  273. package/frontend/src/pages/Settings.tsx +0 -610
  274. package/frontend/src/pages/project-tasks.tsx +0 -575
  275. package/frontend/src/pages/projects.tsx +0 -18
  276. package/frontend/src/vite-env.d.ts +0 -1
  277. package/frontend/tailwind.config.js +0 -125
  278. package/frontend/tsconfig.json +0 -26
  279. package/frontend/tsconfig.node.json +0 -10
  280. package/frontend/vite.config.ts +0 -33
  281. package/npx-cli/README.md +0 -159
  282. package/npx-cli/automagik-forge-0.1.0.tgz +0 -0
  283. package/npx-cli/automagik-forge-0.1.10.tgz +0 -0
  284. package/npx-cli/package.json +0 -17
  285. package/npx-cli/vibe-kanban-0.0.55.tgz +0 -0
  286. package/pnpm-workspace.yaml +0 -2
  287. package/rust-toolchain.toml +0 -11
  288. package/rustfmt.toml +0 -3
  289. package/scripts/load-env.js +0 -43
  290. package/scripts/mcp_test.js +0 -374
  291. package/scripts/prepare-db.js +0 -45
  292. package/scripts/setup-dev-environment.js +0 -274
  293. package/scripts/start-mcp-sse.js +0 -70
  294. package/scripts/test-debug.js +0 -32
  295. package/scripts/test-mcp-sse.js +0 -138
  296. package/scripts/test-simple.js +0 -44
  297. package/scripts/test-wish-final.js +0 -179
  298. package/scripts/test-wish-system.js +0 -221
  299. package/shared/types.ts +0 -182
  300. package/test-npm-package.sh +0 -42
  301. /package/{npx-cli/bin → bin}/cli.js +0 -0
@@ -1,124 +0,0 @@
1
- use async_trait::async_trait;
2
- use command_group::{AsyncCommandGroup, AsyncGroupChild};
3
- use tokio::process::Command;
4
- use uuid::Uuid;
5
-
6
- use crate::{
7
- executor::{Executor, ExecutorError},
8
- models::{project::Project, task::Task},
9
- utils::shell::get_shell_command,
10
- };
11
-
12
- /// Executor for running project cleanup scripts
13
- pub struct CleanupScriptExecutor {
14
- pub script: String,
15
- }
16
-
17
- #[async_trait]
18
- impl Executor for CleanupScriptExecutor {
19
- async fn spawn(
20
- &self,
21
- pool: &sqlx::SqlitePool,
22
- task_id: Uuid,
23
- worktree_path: &str,
24
- ) -> Result<AsyncGroupChild, ExecutorError> {
25
- // Validate the task and project exist
26
- let task = Task::find_by_id(pool, task_id)
27
- .await?
28
- .ok_or(ExecutorError::TaskNotFound)?;
29
-
30
- let _project = Project::find_by_id(pool, task.project_id)
31
- .await?
32
- .ok_or(ExecutorError::TaskNotFound)?; // Reuse TaskNotFound for simplicity
33
-
34
- let (shell_cmd, shell_arg) = get_shell_command();
35
- let mut command = Command::new(shell_cmd);
36
- command
37
- .kill_on_drop(true)
38
- .stdout(std::process::Stdio::piped())
39
- .stderr(std::process::Stdio::piped())
40
- .arg(shell_arg)
41
- .arg(&self.script)
42
- .current_dir(worktree_path);
43
-
44
- let child = command.group_spawn().map_err(|e| {
45
- crate::executor::SpawnContext::from_command(&command, "CleanupScript")
46
- .with_task(task_id, Some(task.title.clone()))
47
- .with_context("Cleanup script execution")
48
- .spawn_error(e)
49
- })?;
50
-
51
- Ok(child)
52
- }
53
-
54
- /// Normalize cleanup script logs into a readable format
55
- fn normalize_logs(
56
- &self,
57
- logs: &str,
58
- _worktree_path: &str,
59
- ) -> Result<crate::executor::NormalizedConversation, String> {
60
- let mut entries = Vec::new();
61
-
62
- // Add script command as first entry
63
- entries.push(crate::executor::NormalizedEntry {
64
- timestamp: None,
65
- entry_type: crate::executor::NormalizedEntryType::SystemMessage,
66
- content: format!("Executing cleanup script:\n{}", self.script),
67
- metadata: None,
68
- });
69
-
70
- // Process the logs - split by lines and create entries
71
- if !logs.trim().is_empty() {
72
- let lines: Vec<&str> = logs.lines().collect();
73
- let mut current_chunk = String::new();
74
-
75
- for line in lines {
76
- current_chunk.push_str(line);
77
- current_chunk.push('\n');
78
-
79
- // Create entry for every 10 lines or when we encounter an error-like line
80
- if current_chunk.lines().count() >= 10
81
- || line.to_lowercase().contains("error")
82
- || line.to_lowercase().contains("failed")
83
- || line.to_lowercase().contains("exception")
84
- {
85
- let entry_type = if line.to_lowercase().contains("error")
86
- || line.to_lowercase().contains("failed")
87
- || line.to_lowercase().contains("exception")
88
- {
89
- crate::executor::NormalizedEntryType::ErrorMessage
90
- } else {
91
- crate::executor::NormalizedEntryType::SystemMessage
92
- };
93
-
94
- entries.push(crate::executor::NormalizedEntry {
95
- timestamp: Some(chrono::Utc::now().to_rfc3339()),
96
- entry_type,
97
- content: current_chunk.trim().to_string(),
98
- metadata: None,
99
- });
100
-
101
- current_chunk.clear();
102
- }
103
- }
104
-
105
- // Add any remaining content
106
- if !current_chunk.trim().is_empty() {
107
- entries.push(crate::executor::NormalizedEntry {
108
- timestamp: Some(chrono::Utc::now().to_rfc3339()),
109
- entry_type: crate::executor::NormalizedEntryType::SystemMessage,
110
- content: current_chunk.trim().to_string(),
111
- metadata: None,
112
- });
113
- }
114
- }
115
-
116
- Ok(crate::executor::NormalizedConversation {
117
- entries,
118
- session_id: None,
119
- executor_type: "cleanup-script".to_string(),
120
- prompt: Some(self.script.clone()),
121
- summary: None,
122
- })
123
- }
124
- }
@@ -1,53 +0,0 @@
1
- use async_trait::async_trait;
2
- use command_group::{AsyncCommandGroup, AsyncGroupChild};
3
- use tokio::process::Command;
4
- use uuid::Uuid;
5
-
6
- use crate::{
7
- executor::{Executor, ExecutorError},
8
- models::{project::Project, task::Task},
9
- utils::shell::get_shell_command,
10
- };
11
-
12
- /// Executor for running project dev server scripts
13
- pub struct DevServerExecutor {
14
- pub script: String,
15
- }
16
-
17
- #[async_trait]
18
- impl Executor for DevServerExecutor {
19
- async fn spawn(
20
- &self,
21
- pool: &sqlx::SqlitePool,
22
- task_id: Uuid,
23
- worktree_path: &str,
24
- ) -> Result<AsyncGroupChild, ExecutorError> {
25
- // Validate the task and project exist
26
- let task = Task::find_by_id(pool, task_id)
27
- .await?
28
- .ok_or(ExecutorError::TaskNotFound)?;
29
-
30
- let _project = Project::find_by_id(pool, task.project_id)
31
- .await?
32
- .ok_or(ExecutorError::TaskNotFound)?; // Reuse TaskNotFound for simplicity
33
-
34
- let (shell_cmd, shell_arg) = get_shell_command();
35
- let mut command = Command::new(shell_cmd);
36
- command
37
- .kill_on_drop(true)
38
- .stdout(std::process::Stdio::piped())
39
- .stderr(std::process::Stdio::piped())
40
- .arg(shell_arg)
41
- .arg(&self.script)
42
- .current_dir(worktree_path);
43
-
44
- let child = command.group_spawn().map_err(|e| {
45
- crate::executor::SpawnContext::from_command(&command, "DevServer")
46
- .with_task(task_id, Some(task.title.clone()))
47
- .with_context("Development server execution")
48
- .spawn_error(e)
49
- })?;
50
-
51
- Ok(child)
52
- }
53
- }
@@ -1,79 +0,0 @@
1
- use async_trait::async_trait;
2
- use command_group::{AsyncCommandGroup, AsyncGroupChild};
3
- use tokio::process::Command;
4
- use uuid::Uuid;
5
-
6
- use crate::{
7
- executor::{Executor, ExecutorError},
8
- models::task::Task,
9
- utils::shell::get_shell_command,
10
- };
11
-
12
- /// A dummy executor that echoes the task title and description
13
- pub struct EchoExecutor;
14
-
15
- #[async_trait]
16
- impl Executor for EchoExecutor {
17
- async fn spawn(
18
- &self,
19
- pool: &sqlx::SqlitePool,
20
- task_id: Uuid,
21
- _worktree_path: &str,
22
- ) -> Result<AsyncGroupChild, ExecutorError> {
23
- // Get the task to fetch its description
24
- let task = Task::find_by_id(pool, task_id)
25
- .await?
26
- .ok_or(ExecutorError::TaskNotFound)?;
27
-
28
- let _message = format!(
29
- "Executing task: {} - {}",
30
- task.title,
31
- task.description.as_deref().unwrap_or("No description")
32
- );
33
-
34
- // For demonstration of streaming, we can use a shell command that outputs multiple lines
35
- let (shell_cmd, shell_arg) = get_shell_command();
36
- let script = if shell_cmd == "cmd" {
37
- // Windows batch script
38
- format!(
39
- r#"echo Starting task: {}
40
- for /l %%i in (1,1,50) do (
41
- echo Progress line %%i
42
- timeout /t 1 /nobreak > nul
43
- )
44
- echo Task completed: {}"#,
45
- task.title, task.title
46
- )
47
- } else {
48
- // Unix shell script (bash/sh)
49
- format!(
50
- r#"echo "Starting task: {}"
51
- for i in {{1..50}}; do
52
- echo "Progress line $i"
53
- sleep 1
54
- done
55
- echo "Task completed: {}""#,
56
- task.title, task.title
57
- )
58
- };
59
-
60
- let mut command = Command::new(shell_cmd);
61
- command
62
- .kill_on_drop(true)
63
- .stdout(std::process::Stdio::piped())
64
- .stderr(std::process::Stdio::piped())
65
- .arg(shell_arg)
66
- .arg(&script);
67
-
68
- let child = command
69
- .group_spawn() // Create new process group so we can kill entire tree
70
- .map_err(|e| {
71
- crate::executor::SpawnContext::from_command(&command, "Echo")
72
- .with_task(task_id, Some(task.title.clone()))
73
- .with_context("Shell script execution for echo demo")
74
- .spawn_error(e)
75
- })?;
76
-
77
- Ok(child)
78
- }
79
- }
@@ -1,67 +0,0 @@
1
- //! Gemini executor configuration and environment variable resolution
2
- //!
3
- //! This module contains configuration structures and functions for the Gemini executor,
4
- //! including environment variable resolution for runtime parameters.
5
-
6
- /// Configuration for Gemini WAL compaction and DB chunking
7
- #[derive(Debug, Clone)]
8
- pub struct GeminiStreamConfig {
9
- pub max_db_chunk_size: usize,
10
- pub wal_compaction_threshold: usize,
11
- pub wal_compaction_size: usize,
12
- pub wal_compaction_interval_ms: u64,
13
- pub max_wal_batches: usize,
14
- pub max_wal_total_size: usize,
15
- }
16
-
17
- impl Default for GeminiStreamConfig {
18
- fn default() -> Self {
19
- Self {
20
- max_db_chunk_size: max_message_size(),
21
- wal_compaction_threshold: 40,
22
- wal_compaction_size: max_message_size() * 2,
23
- wal_compaction_interval_ms: 30000,
24
- max_wal_batches: 100,
25
- max_wal_total_size: 1024 * 1024, // 1MB per process
26
- }
27
- }
28
- }
29
-
30
- // Constants for configuration
31
- /// Size-based streaming configuration
32
- pub const DEFAULT_MAX_CHUNK_SIZE: usize = 5120; // bytes (read buffer size)
33
- pub const DEFAULT_MAX_DISPLAY_SIZE: usize = 2000; // bytes (SSE emission threshold for smooth UI)
34
- pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 8000; // bytes (message boundary for new assistant entries)
35
- pub const DEFAULT_MAX_LATENCY_MS: u64 = 50; // milliseconds
36
-
37
- /// Resolve MAX_CHUNK_SIZE from env or fallback
38
- pub fn max_chunk_size() -> usize {
39
- std::env::var("GEMINI_CLI_MAX_CHUNK_SIZE")
40
- .ok()
41
- .and_then(|v| v.parse::<usize>().ok())
42
- .unwrap_or(DEFAULT_MAX_CHUNK_SIZE)
43
- }
44
-
45
- /// Resolve MAX_DISPLAY_SIZE from env or fallback
46
- pub fn max_display_size() -> usize {
47
- std::env::var("GEMINI_CLI_MAX_DISPLAY_SIZE")
48
- .ok()
49
- .and_then(|v| v.parse::<usize>().ok())
50
- .unwrap_or(DEFAULT_MAX_DISPLAY_SIZE)
51
- }
52
-
53
- /// Resolve MAX_MESSAGE_SIZE from env or fallback
54
- pub fn max_message_size() -> usize {
55
- std::env::var("GEMINI_CLI_MAX_MESSAGE_SIZE")
56
- .ok()
57
- .and_then(|v| v.parse::<usize>().ok())
58
- .unwrap_or(DEFAULT_MAX_MESSAGE_SIZE)
59
- }
60
-
61
- /// Resolve MAX_LATENCY_MS from env or fallback
62
- pub fn max_latency_ms() -> u64 {
63
- std::env::var("GEMINI_CLI_MAX_LATENCY_MS")
64
- .ok()
65
- .and_then(|v| v.parse::<u64>().ok())
66
- .unwrap_or(DEFAULT_MAX_LATENCY_MS)
67
- }
@@ -1,363 +0,0 @@
1
- //! Gemini streaming functionality with WAL and chunked storage
2
- //!
3
- //! This module provides real-time streaming support for Gemini execution processes
4
- //! with Write-Ahead Log (WAL) capabilities for resumable streaming.
5
-
6
- use std::{collections::HashMap, sync::Mutex, time::Instant};
7
-
8
- use json_patch::{patch, Patch, PatchOperation};
9
- use serde::{Deserialize, Serialize};
10
- use serde_json::Value;
11
- use uuid::Uuid;
12
-
13
- use super::config::GeminiStreamConfig;
14
- use crate::{
15
- executor::{NormalizedEntry, NormalizedEntryType},
16
- models::execution_process::ExecutionProcess,
17
- };
18
-
19
- lazy_static::lazy_static! {
20
- /// Write-Ahead Log: Maps execution_process_id → WAL state (Gemini-specific)
21
- static ref GEMINI_WAL_MAP: Mutex<HashMap<Uuid, GeminiWalState>> = Mutex::new(HashMap::new());
22
- }
23
-
24
- /// A batch of JSON patches for Gemini streaming
25
- #[derive(Debug, Clone, Serialize, Deserialize)]
26
- pub struct GeminiPatchBatch {
27
- /// Monotonic batch identifier for cursor-based streaming
28
- pub batch_id: u64,
29
- /// Array of JSON Patch operations (RFC 6902 format)
30
- pub patches: Vec<Value>,
31
- /// ISO 8601 timestamp when this batch was created
32
- pub timestamp: String,
33
- /// Total content length after applying all patches in this batch
34
- pub content_length: usize,
35
- }
36
-
37
- /// WAL state for a single Gemini execution process
38
- #[derive(Debug)]
39
- pub struct GeminiWalState {
40
- pub batches: Vec<GeminiPatchBatch>,
41
- pub total_content_length: usize,
42
- pub next_batch_id: u64,
43
- pub last_compaction: Instant,
44
- pub last_db_flush: Instant,
45
- pub last_access: Instant,
46
- }
47
-
48
- impl Default for GeminiWalState {
49
- fn default() -> Self {
50
- Self::new()
51
- }
52
- }
53
-
54
- impl GeminiWalState {
55
- pub fn new() -> Self {
56
- let now = Instant::now();
57
- Self {
58
- batches: Vec::new(),
59
- total_content_length: 0,
60
- next_batch_id: 1,
61
- last_compaction: now,
62
- last_db_flush: now,
63
- last_access: now,
64
- }
65
- }
66
- }
67
-
68
- /// Gemini streaming utilities
69
- pub struct GeminiStreaming;
70
-
71
- impl GeminiStreaming {
72
- /// Push patches to the Gemini WAL system
73
- pub fn push_patch(execution_process_id: Uuid, patches: Vec<Value>, content_length: usize) {
74
- let mut wal_map = GEMINI_WAL_MAP.lock().unwrap();
75
- let wal_state = wal_map.entry(execution_process_id).or_default();
76
- let config = GeminiStreamConfig::default();
77
-
78
- // Update access time for orphan cleanup
79
- wal_state.last_access = Instant::now();
80
-
81
- // Enforce size limits - force compaction instead of clearing to prevent data loss
82
- if wal_state.batches.len() >= config.max_wal_batches
83
- || wal_state.total_content_length >= config.max_wal_total_size
84
- {
85
- tracing::warn!(
86
- "WAL size limits exceeded for process {} (batches: {}, size: {}), forcing compaction",
87
- execution_process_id,
88
- wal_state.batches.len(),
89
- wal_state.total_content_length
90
- );
91
-
92
- // Force compaction to preserve data instead of losing it
93
- Self::compact_wal(wal_state);
94
-
95
- // If still over limits after compaction, keep only the most recent batches
96
- if wal_state.batches.len() >= config.max_wal_batches {
97
- let keep_count = config.max_wal_batches / 2; // Keep half
98
- let remove_count = wal_state.batches.len() - keep_count;
99
- wal_state.batches.drain(..remove_count);
100
- tracing::warn!(
101
- "After compaction still over limit, kept {} most recent batches",
102
- keep_count
103
- );
104
- }
105
- }
106
-
107
- let batch = GeminiPatchBatch {
108
- batch_id: wal_state.next_batch_id,
109
- patches,
110
- timestamp: chrono::Utc::now().to_rfc3339(),
111
- content_length,
112
- };
113
-
114
- wal_state.next_batch_id += 1;
115
- wal_state.batches.push(batch);
116
- wal_state.total_content_length = content_length;
117
-
118
- // Check if compaction is needed
119
- if Self::should_compact(wal_state, &config) {
120
- Self::compact_wal(wal_state);
121
- }
122
- }
123
-
124
- /// Get WAL batches for an execution process, optionally filtering by cursor
125
- pub fn get_wal_batches(
126
- execution_process_id: Uuid,
127
- after_batch_id: Option<u64>,
128
- ) -> Option<Vec<GeminiPatchBatch>> {
129
- GEMINI_WAL_MAP.lock().ok().and_then(|mut wal_map| {
130
- wal_map.get_mut(&execution_process_id).map(|wal_state| {
131
- // Update access time when WAL is retrieved
132
- wal_state.last_access = Instant::now();
133
-
134
- match after_batch_id {
135
- Some(cursor) => {
136
- // Return only batches with batch_id > cursor
137
- wal_state
138
- .batches
139
- .iter()
140
- .filter(|batch| batch.batch_id > cursor)
141
- .cloned()
142
- .collect()
143
- }
144
- None => {
145
- // Return all batches
146
- wal_state.batches.clone()
147
- }
148
- }
149
- })
150
- })
151
- }
152
-
153
- /// Clean up WAL when execution process finishes
154
- pub async fn finalize_execution(
155
- pool: &sqlx::SqlitePool,
156
- execution_process_id: Uuid,
157
- final_buffer: &str,
158
- ) {
159
- // Flush any remaining content to database
160
- if !final_buffer.trim().is_empty() {
161
- Self::store_chunk_to_db(pool, execution_process_id, final_buffer).await;
162
- }
163
-
164
- // Remove WAL entry
165
- Self::purge_wal(execution_process_id);
166
- }
167
-
168
- /// Remove WAL entry for a specific execution process
169
- pub fn purge_wal(execution_process_id: Uuid) {
170
- if let Ok(mut wal_map) = GEMINI_WAL_MAP.lock() {
171
- wal_map.remove(&execution_process_id);
172
- tracing::debug!(
173
- "Cleaned up WAL for execution process {}",
174
- execution_process_id
175
- );
176
- }
177
- }
178
-
179
- /// Find the best boundary to split a chunk (newline preferred, sentence fallback)
180
- pub fn find_chunk_boundary(buffer: &str, max_size: usize) -> usize {
181
- if buffer.len() <= max_size {
182
- return buffer.len();
183
- }
184
-
185
- let search_window = &buffer[..max_size];
186
-
187
- // First preference: newline boundary
188
- if let Some(pos) = search_window.rfind('\n') {
189
- return pos + 1; // Include the newline
190
- }
191
-
192
- // Second preference: sentence boundary (., !, ?)
193
- if let Some(pos) = search_window.rfind(&['.', '!', '?'][..]) {
194
- if pos + 1 < search_window.len() {
195
- return pos + 1;
196
- }
197
- }
198
-
199
- // Fallback: word boundary
200
- if let Some(pos) = search_window.rfind(' ') {
201
- return pos + 1;
202
- }
203
-
204
- // Last resort: split at max_size
205
- max_size
206
- }
207
-
208
- /// Store a chunk to the database
209
- async fn store_chunk_to_db(pool: &sqlx::SqlitePool, execution_process_id: Uuid, content: &str) {
210
- if content.trim().is_empty() {
211
- return;
212
- }
213
-
214
- let entry = NormalizedEntry {
215
- timestamp: Some(chrono::Utc::now().to_rfc3339()),
216
- entry_type: NormalizedEntryType::AssistantMessage,
217
- content: content.to_string(),
218
- metadata: None,
219
- };
220
-
221
- match serde_json::to_string(&entry) {
222
- Ok(jsonl_line) => {
223
- let formatted_line = format!("{}\n", jsonl_line);
224
- if let Err(e) =
225
- ExecutionProcess::append_stdout(pool, execution_process_id, &formatted_line)
226
- .await
227
- {
228
- tracing::error!("Failed to store chunk to database: {}", e);
229
- } else {
230
- tracing::debug!("Stored {}B chunk to database", content.len());
231
- }
232
- }
233
- Err(e) => {
234
- tracing::error!("Failed to serialize chunk: {}", e);
235
- }
236
- }
237
- }
238
-
239
- /// Conditionally flush accumulated content to database in chunks
240
- pub async fn maybe_flush_chunk(
241
- pool: &sqlx::SqlitePool,
242
- execution_process_id: Uuid,
243
- buffer: &mut String,
244
- config: &GeminiStreamConfig,
245
- ) {
246
- if buffer.len() < config.max_db_chunk_size {
247
- return;
248
- }
249
-
250
- // Find the best split point (newline preferred, sentence boundary fallback)
251
- let split_point = Self::find_chunk_boundary(buffer, config.max_db_chunk_size);
252
-
253
- if split_point > 0 {
254
- let chunk = buffer[..split_point].to_string();
255
- buffer.drain(..split_point);
256
-
257
- // Store chunk to database
258
- Self::store_chunk_to_db(pool, execution_process_id, &chunk).await;
259
-
260
- // Update WAL flush time
261
- if let Ok(mut wal_map) = GEMINI_WAL_MAP.lock() {
262
- if let Some(wal_state) = wal_map.get_mut(&execution_process_id) {
263
- wal_state.last_db_flush = Instant::now();
264
- }
265
- }
266
- }
267
- }
268
-
269
- /// Check if WAL compaction is needed based on configured thresholds
270
- fn should_compact(wal_state: &GeminiWalState, config: &GeminiStreamConfig) -> bool {
271
- wal_state.batches.len() >= config.wal_compaction_threshold
272
- || wal_state.total_content_length >= config.wal_compaction_size
273
- || wal_state.last_compaction.elapsed().as_millis() as u64
274
- >= config.wal_compaction_interval_ms
275
- }
276
-
277
- /// Compact WAL by losslessly merging older patches into a snapshot
278
- fn compact_wal(wal_state: &mut GeminiWalState) {
279
- // Need at least a few batches to make compaction worthwhile
280
- if wal_state.batches.len() <= 5 {
281
- return;
282
- }
283
-
284
- // Keep the most recent 3 batches for smooth incremental updates
285
- let recent_count = 3;
286
- let compact_count = wal_state.batches.len() - recent_count;
287
-
288
- if compact_count <= 1 {
289
- return; // Not enough to compact
290
- }
291
-
292
- // Start with an empty conversation and apply all patches sequentially
293
- let mut conversation_value = serde_json::json!({
294
- "entries": [],
295
- "session_id": null,
296
- "executor_type": "gemini",
297
- "prompt": null,
298
- "summary": null
299
- });
300
-
301
- let mut total_content_length = 0;
302
- let oldest_batch_id = wal_state.batches[0].batch_id;
303
- let compact_timestamp = chrono::Utc::now().to_rfc3339();
304
-
305
- // Apply patches from oldest to newest (excluding recent ones) using json-patch crate
306
- for batch in &wal_state.batches[..compact_count] {
307
- // Convert Vec<Value> to json_patch::Patch
308
- let patch_operations: Result<Vec<PatchOperation>, _> = batch
309
- .patches
310
- .iter()
311
- .map(|p| serde_json::from_value(p.clone()))
312
- .collect();
313
-
314
- match patch_operations {
315
- Ok(ops) => {
316
- let patch_obj = Patch(ops);
317
- if let Err(e) = patch(&mut conversation_value, &patch_obj) {
318
- tracing::warn!("Failed to apply patch during compaction: {}, skipping", e);
319
- continue;
320
- }
321
- }
322
- Err(e) => {
323
- tracing::warn!("Failed to deserialize patch operations: {}, skipping", e);
324
- continue;
325
- }
326
- }
327
- total_content_length = batch.content_length; // Use the final length
328
- }
329
-
330
- // Extract the final entries array for the snapshot
331
- let final_entries = conversation_value
332
- .get("entries")
333
- .and_then(|v| v.as_array())
334
- .cloned()
335
- .unwrap_or_default();
336
-
337
- // Create a single snapshot patch that replaces the entire entries array
338
- let snapshot_patch = GeminiPatchBatch {
339
- batch_id: oldest_batch_id, // Use the oldest batch_id to maintain cursor compatibility
340
- patches: vec![serde_json::json!({
341
- "op": "replace",
342
- "path": "/entries",
343
- "value": final_entries
344
- })],
345
- timestamp: compact_timestamp,
346
- content_length: total_content_length,
347
- };
348
-
349
- // Replace old batches with snapshot + keep recent batches
350
- let mut new_batches = vec![snapshot_patch];
351
- new_batches.extend_from_slice(&wal_state.batches[compact_count..]);
352
- wal_state.batches = new_batches;
353
-
354
- wal_state.last_compaction = Instant::now();
355
-
356
- tracing::info!(
357
- "Losslessly compacted WAL: {} batches → {} (1 snapshot + {} recent), preserving all content",
358
- compact_count + recent_count,
359
- wal_state.batches.len(),
360
- recent_count
361
- );
362
- }
363
- }