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,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
|
-
}
|