Files
vibe-kanban/crates/db/.sqlx/query-b74d74a66fa902092d8ccbcb68dc22954b7f2a59f21b79c15cd6159654f4a8b5.json

21 lines
810 B
JSON
Raw Normal View History

Refactor task_attempts to workspaces and sessions (Vibe Kanban) (#1569) * All tasks are complete. Here's a summary of the refactoring that was done: The `task_attempts` table has been refactored into `workspaces` and `sessions`: - Created new migration `20251216142123_refactor_task_attempts_to_workspaces_sessions.sql` - Renamed `task_attempts` table to `workspaces` (removed `executor` column) - Created new `sessions` table with `(id, workspace_id, executor, created_at, updated_at)` - Renamed `executor_sessions` to `coding_agent_turns` (dropped redundant `task_attempt_id`) - Changed `execution_processes.task_attempt_id` to `session_id` - Changed `merges.task_attempt_id` to `workspace_id` - Changed `tasks.parent_task_attempt` to `parent_workspace_id` - Renamed `crates/db/src/models/task_attempt.rs` → `workspace.rs` - Created new `crates/db/src/models/session.rs` - Renamed `executor_session.rs` → `coding_agent_turn.rs` - Updated `execution_process.rs`: - Changed `task_attempt_id` field to `session_id` - Added workspace-level query methods (`find_latest_by_workspace_and_run_reason`, `latest_executor_profile_for_workspace`, `find_latest_external_session_id_by_workspace`, `find_running_dev_servers_by_workspace`, `has_running_non_dev_server_processes_for_workspace`) - Updated `merge.rs`: Changed `task_attempt_id` to `workspace_id` - Updated `task.rs`: Changed `parent_task_attempt` to `parent_workspace_id` - Updated `container.rs`: Changed `start_execution` to take 4 parameters (workspace, session, action, reason) - Updated `events/`: Changed event types from `TaskAttempt` to `Workspace` - Updated `pr_monitor.rs`: Changed to use `Workspace` instead of `TaskAttempt` - Updated `workspace_manager.rs`: Added support for `DbWorkspace` alias - Updated `task_attempts.rs`: All handlers now create/use sessions for execution processes - Updated `tasks.rs`: Removed `executor` from `CreateWorkspace`, fixed error type - Updated setup helpers (codex, cursor, gh_cli) to create sessions - Updated `error.rs`: Added `SessionError` handling Cleanup script changes for task attempt 2f672d73-cb46-4515-b1c7-3da8f4402858 Done. All 3 `sqlx::query_scalar()` function calls have been converted to `sqlx::query_scalar!()` macros: 1. `has_running_non_dev_server_processes()` - lines 332-341 2. `has_running_non_dev_server_processes_for_workspace()` - lines 350-361 3. `find_prev_after_head_commit()` - lines 619-634 Key changes: - Converted `?` placeholders to `$1, $2, $3` positional parameters (sqlx macro style) - Added `as "count!: i64"` type annotation for COUNT queries - Used `.flatten()` for the nullable `after_head_commit` column to avoid `Option<Option<String>>` The one in `crates/remote/src/db/mod.rs` uses dynamic SQL for PostgreSQL's `ALTER ROLE` and cannot be converted to a macro. Done. Removed the 4 unused functions from `execution_process.rs`: - `find_running_dev_servers_by_session` - `has_running_non_dev_server_processes` (the per-session version) - `delete_by_session_id` - `latest_executor_profile_for_session` The workspace-level versions (`has_running_non_dev_server_processes_for_workspace`, `find_running_dev_servers_by_workspace`, `latest_executor_profile_for_workspace`) are still available. Everything builds successfully. Rewbase Revert event rename * All done. Here's a summary of the changes: Created a `TaskAttempt` helper struct in the backend that maintains backwards compatibility with the frontend: 1. **`crates/db/src/models/workspace.rs`** - Added `TaskAttempt` struct that combines `Workspace` fields + `executor` from the latest `Session` - Added `TaskAttempt::from_workspace()` to convert a `Workspace` by fetching its latest session's executor - Added `TaskAttempt::fetch_all()` for API responses 2. **`crates/db/src/models/task.rs`** - Changed `TaskRelationships.current_workspace` → `current_attempt: TaskAttempt` 3. **`crates/server/src/routes/task_attempts.rs`** - Updated `get_task_attempts` to return `Vec<TaskAttempt>` - Updated `get_task_attempt` to return `TaskAttempt` - Updated `create_task_attempt` to return `TaskAttempt` 4. **`crates/server/src/bin/generate_types.rs`** - Added `TaskAttempt::decl()` to the type generation list Fixed references from `parent_task_attempt` to `parent_workspace_id` in: - `TaskCard.tsx` - `NoServerContent.tsx` - `TaskPanel.tsx` - `TaskFormDialog.tsx` - `CreateAttemptDialog.tsx` - `useTaskMutations.ts` - `ProjectTasks.tsx` - Frontend now gets `TaskAttempt` type with the `executor` field - The `TaskRelationships.current_attempt` field provides `TaskAttempt` with executor - Backend still uses `Workspace` + `Session` internally - All type checks pass Fixed. The issue was that the frontend was sending `task_attempt_id` as the query parameter, but the backend expected `workspace_id`. Now the execution processes WebSocket stream should work correctly and the "Loading History" should resolve. * All done. Here's a summary of the changes: 1. **Added sessions endpoint** (`GET /api/sessions?workspace_id={uuid}`) - Created `crates/server/src/routes/sessions.rs` - Registered in `crates/server/src/routes/mod.rs` - Added `Session::decl()` to `generate_types.rs` 2. **Removed TaskAttempt from backend** - Removed `TaskAttempt` struct and methods from `crates/db/src/models/workspace.rs` - Changed `TaskRelationships.current_attempt` back to `current_workspace: Workspace` in `task.rs` - Updated route handlers to return `Workspace` instead of `TaskAttempt` 1. **Created `TaskAttempt` type** in `frontend/src/types/attempt.ts` - Defines `TaskAttempt = Workspace & { executor: string }` - Exports `createTaskAttempt()` helper function 2. **Updated `api.ts`** - Added `sessionsApi` for fetching sessions - Added `workspaceToTaskAttempt()` helper that fetches sessions and constructs TaskAttempt - Updated `attemptsApi.getAll()`, `get()`, and `create()` to internally convert Workspace → TaskAttempt 3. **Updated 20+ frontend files** to import `TaskAttempt` from `@/types/attempt` instead of `shared/types` - Backend returns `Workspace` directly (no executor field) - Backend has new `/api/sessions` endpoint to fetch sessions by workspace - Frontend constructs `TaskAttempt` by combining `Workspace` + latest `Session.executor` - All type checks pass * The refactoring is complete. Here's a summary of what was done: - `TaskAttempt` is now just a type alias for `Workspace` (no executor needed for most uses) - Added `WorkspaceWithSession = Workspace & { executor: string }` for components that need executor - Added `createWorkspaceWithSession()` helper function - `attemptsApi.get()`, `getAll()`, `create()` now return `Workspace` directly (as `TaskAttempt`) - Added `attemptsApi.getWithSession()` - fetches workspace + session to get executor - Added `sessionsApi.getByWorkspace()` for fetching sessions - Added `useTaskAttemptWithSession()` - for single attempt with executor - Added `useTaskAttemptsWithSessions()` - for list of attempts with executor Files that access `.executor` now use `WorkspaceWithSession`: - `TaskPanel.tsx` - Uses `useTaskAttemptsWithSessions` and `useTaskAttemptWithSession` - `NextActionCard.tsx` - Uses `attemptsApi.getWithSession()` - `UserMessage.tsx` - Receives `WorkspaceWithSession` prop - `RetryEditorInline.tsx` - Receives `WorkspaceWithSession` prop - `DisplayConversationEntry.tsx` - Passes `WorkspaceWithSession` to children - `VirtualizedList.tsx` - Receives `WorkspaceWithSession` prop - `TaskAttemptPanel.tsx` - Receives `WorkspaceWithSession` prop - `ProjectTasks.tsx` - Uses `useTaskAttemptWithSession` - `FullAttemptLogs.tsx` - Uses `useTaskAttemptWithSession` - `CreateAttemptDialog.tsx` - Uses `useTaskAttemptsWithSessions` The 32 other files that only use Workspace fields continue to use `TaskAttempt` (which is just `Workspace`) without any changes needed. Done! I've simplified the migration from 191 lines down to 106 lines by: 1. **Removed workspaces table rebuild** → simple `ALTER TABLE workspaces DROP COLUMN executor` 2. **Removed merges table rebuild** → simple `ALTER TABLE merges RENAME COLUMN task_attempt_id TO workspace_id` 3. **Removed tasks table rebuild** → simple `ALTER TABLE tasks RENAME COLUMN parent_task_attempt TO parent_workspace_id` 4. **Removed attempt_repos rebuild entirely** → FK auto-updates when `task_attempts` is renamed to `workspaces` The only table rebuilds remaining are for: - `execution_processes` - because the FK target changes from workspaces to sessions - `executor_sessions` → `coding_agent_turns` - because we're dropping the `task_attempt_id` column All done: - **npm run lint** - passed (fixed 4 unused import errors) - **cargo build** - passed - **npm run build** - passed - **tsc --noEmit** - passed Prep db * Done. I've renamed `TaskAttempt` to `Workspace` in all places where it was used as a type annotation. Here's the summary: - `types/attempt.ts` - Removed `export type TaskAttempt = Workspace;` **API Layer:** - `lib/api.ts` - Changed return types from `TaskAttempt` to `Workspace` **Hooks:** - `hooks/useTaskAttempts.ts` - `TaskAttempt[]` → `Workspace[]` - `hooks/useAttemptCreation.ts` - `TaskAttempt` → `Workspace` - `hooks/useConversationHistory.ts` - `TaskAttempt` → `Workspace` - `hooks/useAttempt.ts` - `TaskAttempt` → `Workspace` **Components:** - `contexts/ClickedElementsProvider.tsx` - `components/DiffCard.tsx` - `components/panels/AttemptHeaderActions.tsx` - `components/panels/DiffsPanel.tsx` - `components/tasks/Toolbar/GitOperations.tsx` - `components/dialogs/tasks/GitActionsDialog.tsx` - `components/dialogs/tasks/ViewRelatedTasksDialog.tsx` - `components/dialogs/tasks/CreatePRDialog.tsx` - `components/ui/actions-dropdown.tsx` **Type Definitions:** - `types/modal-args.d.ts` - `types/modals.ts` **Pages:** - `pages/ProjectTasks.tsx` TypeScript check passes with no errors. Cleanup script changes for task attempt 2f672d73-cb46-4515-b1c7-3da8f4402858 * The Session type now has `executor: string | null`, which is correct. All changes are complete: 1. **Migration**: `executor TEXT` (no `NOT NULL`) 2. **Rust model**: `executor: Option<String>` 3. **sqlx queries**: Using just `executor` without type annotation (sqlx infers nullable correctly) 4. **All usages**: Wrapped string values in `Some()` where executor is set 5. **TypeScript types**: `executor: string | null` Everything builds and lints successfully. Done. Removed `Session::parent_workspace` and its unused `Workspace` import. * Done. The migration has been updated with the sqlx workaround: 1. Added `COMMIT TRANSACTION;` after `PRAGMA foreign_keys = OFF;` to end sqlx's auto-transaction 2. Added `BEGIN TRANSACTION;` to start our own controlled transaction 3. Added `PRAGMA foreign_key_check;` before committing to verify FK integrity 4. Added `COMMIT;` to commit our transaction 5. Added final `BEGIN TRANSACTION;` for sqlx to close gracefully This ensures the `PRAGMA foreign_keys = OFF` actually takes effect, preventing potential cascade deletes of `execution_process_logs` when `DROP TABLE execution_processes` runs on existing databases with data. Fixed. The migration now has the correct order: 1. `COMMIT TRANSACTION;` - ends sqlx's auto-transaction 2. `PRAGMA foreign_keys = OFF;` - now takes effect (outside transaction) 3. `BEGIN TRANSACTION;` - starts our controlled transaction This ensures foreign keys are actually disabled before the table operations, preventing potential cascade deletes of `execution_process_logs` when `DROP TABLE execution_processes` runs. Done. The migration now only disables foreign keys for the section that needs it (steps 7-8, the table rebuilds with DROP TABLE). Steps 1-6 run in sqlx's normal auto-transaction with FK enforcement on. Fix slow statement (vibe-kanban b695d0ad) i think we are missing an index on something, seeing these logs: 2025-12-17T15:23:25.297852Z  WARN sqlx::query: slow statement: execution time exceeded alert threshold *summary*="SELECT t.id AS \\"id!: …" *db.statement*="\\n\\nSELECT\\n  t.id                            AS \\"id!: Uuid\\",\\n  t.project\_id                    AS \\"project\_id!: Uuid\\",\\n  t.title,\\n  t.description,\\n  t.status                        AS \\"status!: TaskStatus\\",\\n  t.parent\_workspace\_id           AS \\"parent\_workspace\_id: Uuid\\",\\n  t.shared\_task\_id                AS \\"shared\_task\_id: Uuid\\",\\n  t.created\_at                    AS \\"created\_at!: DateTime<Utc>\\",\\n  t.updated\_at                    AS \\"updated\_at!: DateTime<Utc>\\",\\n\\n  CASE WHEN EXISTS (\\n    SELECT 1\\n      FROM workspaces w\\n      JOIN sessions s ON s.workspace\_id = w.id\\n      JOIN execution\_processes ep ON ep.session\_id = s.id\\n     WHERE w.task\_id       = t.id\\n       AND ep.status        = 'running'\\n       AND ep.run\_reason IN ('setupscript','cleanupscript','codingagent')\\n     LIMIT 1\\n  ) THEN 1 ELSE 0 END            AS \\"has\_in\_progress\_attempt!: i64\\",\\n\\n  CASE WHEN (\\n    SELECT ep.status\\n      FROM workspaces w\\n      JOIN sessions s ON s.workspace\_id = w.id\\n      JOIN execution\_processes ep ON ep.session\_id = s.id\\n     WHERE w.task\_id       = t.id\\n     AND ep.run\_reason IN ('setupscript','cleanupscript','codingagent')\\n     ORDER BY ep.created\_at DESC\\n     LIMIT 1\\n  ) IN ('failed','killed') THEN 1 ELSE 0 END\\n                                 AS \\"last\_attempt\_failed!: i64\\",\\n\\n  ( SELECT s.executor\\n      FROM workspaces w\\n      JOIN sessions s ON s.workspace\_id = w.id\\n      WHERE w.task\_id = t.id\\n     ORDER BY s.created\_at DESC\\n      LIMIT 1\\n    )                               AS \\"executor!: String\\"\\n\\nFROM tasks t\\nWHERE t.project\_id = $1\\nORDER BY t.created\_at DESC\\n" *rows\_affected*=0 *rows\_returned*=202 *elapsed*=1.281210542s *elapsed\_secs*=1.281210542 *slow\_threshold*=1s 2025-12-17T15:23:25.350788Z  WARN sqlx::query: slow statement: execution time exceeded alert threshold *summary*="SELECT t.id AS \\"id!: …" *db.statement*="\\n\\nSELECT\\n  t.id                            AS \\"id!: Uuid\\",\\n  t.project\_id                    AS \\"project\_id!: Uuid\\",\\n  t.title,\\n  t.description,\\n  t.status                        AS \\"status!: TaskStatus\\",\\n  t.parent\_workspace\_id           AS \\"parent\_workspace\_id: Uuid\\",\\n  t.shared\_task\_id                AS \\"shared\_task\_id: Uuid\\",\\n  t.created\_at                    AS \\"created\_at!: DateTime<Utc>\\",\\n  t.updated\_at                    AS \\"updated\_at!: DateTime<Utc>\\",\\n\\n  CASE WHEN EXISTS (\\n    SELECT 1\\n      FROM workspaces w\\n      JOIN sessions s ON s.workspace\_id = w.id\\n      JOIN execution\_processes ep ON ep.session\_id = s.id\\n     WHERE w.task\_id       = t.id\\n       AND ep.status        = 'running'\\n       AND ep.run\_reason IN ('setupscript','cleanupscript','codingagent')\\n     LIMIT 1\\n  ) THEN 1 ELSE 0 END            AS \\"has\_in\_progress\_attempt!: i64\\",\\n\\n  CASE WHEN (\\n    SELECT ep.status\\n      FROM workspaces w\\n      JOIN sessions s ON s.workspace\_id = w.id\\n      JOIN execution\_processes ep ON ep.session\_id = s.id\\n     WHERE w.task\_id       = t.id\\n     AND ep.run\_reason IN ('setupscript','cleanupscript','codingagent')\\n     ORDER BY ep.created\_at DESC\\n     LIMIT 1\\n  ) IN ('failed','killed') THEN 1 ELSE 0 END\\n                                 AS \\"last\_attempt\_failed!: i64\\",\\n\\n  ( SELECT s.executor\\n      FROM workspaces w\\n      JOIN sessions s ON s.workspace\_id = w.id\\n      WHERE w.task\_id = t.id\\n     ORDER BY s.created\_at DESC\\n      LIMIT 1\\n    )                               AS \\"executor!: String\\"\\n\\nFROM tasks t\\nWHERE t.project\_id = $1\\nORDER BY t.created\_at DESC\\n" *rows\_affected*=0 *rows\_returned*=202 *elapsed*=1.333812833s *elapsed\_secs*=1.333812833 *slow\_threshold*=1s 2025-12-17T15:23:25.401326Z  WARN sqlx::query: slow statement: execution time exceeded alert threshold *summary*="INSERT INTO execution\_processes ( …" *db.statement*="\\n\\nINSERT INTO execution\_processes (\\n                    id, session\_id, run\_reason, executor\_action,\\n                    status, exit\_code, started\_at, completed\_at, created\_at, updated\_at\\n                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\\n" *rows\_affected*=1 *rows\_returned*=0 *elapsed*=1.383690208s *elapsed\_secs*=1.383690208 *slow\_threshold*=1s * Address feedback (vibe-kanban 81d8dbfa) A PR opened by your colleague (https://github.com/BloopAI/vibe-kanban/pull/1569) got some feedback, let's address it. ```gh-comment { "id": "2627479232", "comment_type": "review", "author": "ggordonhall", "body": "```suggestion\r\n-- 3. Migrate data: create one session per workspace\r\nINSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\r\nSELECT gen_random_uuid(), id, executor, created_at, updated_at FROM workspaces;\r\n```\r\n", "created_at": "2025-12-17T15:17:50Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627479232", "path": "crates/db/migrations/20251216142123_refactor_task_attempts_to_workspaces_sessions.sql", "line": 26, "diff_hunk": "@@ -0,0 +1,121 @@\n+-- Refactor task_attempts into workspaces and sessions\n+-- - Rename task_attempts -> workspaces (keeps workspace-related fields)\n+-- - Create sessions table (executor moves here)\n+-- - Update execution_processes.task_attempt_id -> session_id\n+-- - Rename executor_sessions -> coding_agent_turns (drop redundant task_attempt_id)\n+-- - Rename merges.task_attempt_id -> workspace_id\n+-- - Rename tasks.parent_task_attempt -> parent_workspace_id\n+\n+-- 1. Rename task_attempts to workspaces (FK refs auto-update in schema)\n+ALTER TABLE task_attempts RENAME TO workspaces;\n+\n+-- 2. Create sessions table\n+CREATE TABLE sessions (\n+ id BLOB PRIMARY KEY,\n+ workspace_id BLOB NOT NULL,\n+ executor TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE\n+);\n+\n+CREATE INDEX idx_sessions_workspace_id ON sessions(workspace_id);\n+\n+-- 3. Migrate data: create one session per workspace (using workspace.id as session.id for simplicity)\n+INSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\n+SELECT id, id, executor, created_at, updated_at FROM workspaces;" } ``` ```gh-comment { "id": "2627515578", "comment_type": "review", "author": "ggordonhall", "body": "Why not rename `attempt_repos` to `workspace_repos` here now that `attempt` is a legacy concept?", "created_at": "2025-12-17T15:27:21Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627515578", "path": "crates/db/migrations/20251216142123_refactor_task_attempts_to_workspaces_sessions.sql", "line": 118, "diff_hunk": "@@ -0,0 +1,121 @@\n+-- Refactor task_attempts into workspaces and sessions\n+-- - Rename task_attempts -> workspaces (keeps workspace-related fields)\n+-- - Create sessions table (executor moves here)\n+-- - Update execution_processes.task_attempt_id -> session_id\n+-- - Rename executor_sessions -> coding_agent_turns (drop redundant task_attempt_id)\n+-- - Rename merges.task_attempt_id -> workspace_id\n+-- - Rename tasks.parent_task_attempt -> parent_workspace_id\n+\n+-- 1. Rename task_attempts to workspaces (FK refs auto-update in schema)\n+ALTER TABLE task_attempts RENAME TO workspaces;\n+\n+-- 2. Create sessions table\n+CREATE TABLE sessions (\n+ id BLOB PRIMARY KEY,\n+ workspace_id BLOB NOT NULL,\n+ executor TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE\n+);\n+\n+CREATE INDEX idx_sessions_workspace_id ON sessions(workspace_id);\n+\n+-- 3. Migrate data: create one session per workspace (using workspace.id as session.id for simplicity)\n+INSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\n+SELECT id, id, executor, created_at, updated_at FROM workspaces;\n+\n+-- 4. Drop executor column from workspaces\n+ALTER TABLE workspaces DROP COLUMN executor;\n+\n+-- 5. Rename merges.task_attempt_id to workspace_id\n+DROP INDEX idx_merges_task_attempt_id;\n+DROP INDEX idx_merges_open_pr;\n+ALTER TABLE merges RENAME COLUMN task_attempt_id TO workspace_id;\n+CREATE INDEX idx_merges_workspace_id ON merges(workspace_id);\n+CREATE INDEX idx_merges_open_pr ON merges(workspace_id, pr_status)\n+WHERE merge_type = 'pr' AND pr_status = 'open';\n+\n+-- 6. Rename tasks.parent_task_attempt to parent_workspace_id\n+DROP INDEX IF EXISTS idx_tasks_parent_task_attempt;\n+ALTER TABLE tasks RENAME COLUMN parent_task_attempt TO parent_workspace_id;\n+CREATE INDEX idx_tasks_parent_workspace_id ON tasks(parent_workspace_id);\n+\n+-- Steps 7-8 need FK disabled to avoid cascade deletes during DROP TABLE\n+-- sqlx workaround: end auto-transaction to allow PRAGMA to take effect\n+-- https://github.com/launchbadge/sqlx/issues/2085#issuecomment-1499859906\n+COMMIT;\n+\n+PRAGMA foreign_keys = OFF;\n+\n+BEGIN TRANSACTION;\n+\n+-- 7. Update execution_processes to reference session_id instead of task_attempt_id\n+-- (needs rebuild because FK target changes from workspaces to sessions)\n+DROP INDEX IF EXISTS idx_execution_processes_task_attempt_created_at;\n+DROP INDEX IF EXISTS idx_execution_processes_task_attempt_type_created;\n+\n+CREATE TABLE execution_processes_new (\n+ id BLOB PRIMARY KEY,\n+ session_id BLOB NOT NULL,\n+ run_reason TEXT NOT NULL DEFAULT 'setupscript'\n+ CHECK (run_reason IN ('setupscript','codingagent','devserver','cleanupscript')),\n+ executor_action TEXT NOT NULL DEFAULT '{}',\n+ status TEXT NOT NULL DEFAULT 'running'\n+ CHECK (status IN ('running','completed','failed','killed')),\n+ exit_code INTEGER,\n+ dropped INTEGER NOT NULL DEFAULT 0,\n+ started_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ completed_at TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE\n+);\n+\n+-- Since we used workspace.id as session.id, the task_attempt_id values map directly\n+INSERT INTO execution_processes_new (id, session_id, run_reason, executor_action, status, exit_code, dropped, started_at, completed_at, created_at, updated_at)\n+SELECT id, task_attempt_id, run_reason, executor_action, status, exit_code, dropped, started_at, completed_at, created_at, updated_at\n+FROM execution_processes;\n+\n+DROP TABLE execution_processes;\n+ALTER TABLE execution_processes_new RENAME TO execution_processes;\n+\n+-- Recreate execution_processes indexes\n+CREATE INDEX idx_execution_processes_session_id ON execution_processes(session_id);\n+CREATE INDEX idx_execution_processes_status ON execution_processes(status);\n+CREATE INDEX idx_execution_processes_run_reason ON execution_processes(run_reason);\n+\n+-- 8. Rename executor_sessions to coding_agent_turns and drop task_attempt_id\n+-- (needs rebuild to drop the redundant task_attempt_id column)\n+CREATE TABLE coding_agent_turns (\n+ id BLOB PRIMARY KEY,\n+ execution_process_id BLOB NOT NULL,\n+ session_id TEXT,\n+ prompt TEXT,\n+ summary TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (execution_process_id) REFERENCES execution_processes(id) ON DELETE CASCADE\n+);\n+\n+INSERT INTO coding_agent_turns (id, execution_process_id, session_id, prompt, summary, created_at, updated_at)\n+SELECT id, execution_process_id, session_id, prompt, summary, created_at, updated_at\n+FROM executor_sessions;\n+\n+DROP TABLE executor_sessions;\n+\n+-- Recreate coding_agent_turns indexes\n+CREATE INDEX idx_coding_agent_turns_execution_process_id ON coding_agent_turns(execution_process_id);\n+CREATE INDEX idx_coding_agent_turns_session_id ON coding_agent_turns(session_id);\n+\n+-- 9. attempt_repos: no changes needed - FK auto-updated when task_attempts renamed to workspaces" } ``` ```gh-comment { "id": "2627694792", "comment_type": "review", "author": "ggordonhall", "body": "Maybe there's a better name than `external_session_id` here? `agent_session_id`? ", "created_at": "2025-12-17T16:16:24Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627694792", "path": "crates/db/src/models/execution_process.rs", "line": 685, "diff_hunk": "@@ -618,4 +680,34 @@ impl ExecutionProcess {\n )),\n }\n }\n+\n+ /// Find latest coding_agent_turn session_id by workspace (across all sessions)\n+ pub async fn find_latest_external_session_id_by_workspace(" } ``` ```gh-comment { "id": "2627707446", "comment_type": "review", "author": "ggordonhall", "body": "```suggestion\r\n pub async fn cleanup_workspace(db: &DBService, workspace: &Workspace) {\r\n```", "created_at": "2025-12-17T16:19:31Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627707446", "path": "crates/local-deployment/src/container.rs", "line": 146, "diff_hunk": "@@ -142,20 +143,20 @@ impl LocalContainerService {\n map.remove(id)\n }\n \n- pub async fn cleanup_attempt_workspace(db: &DBService, attempt: &TaskAttempt) {\n- let Some(container_ref) = &attempt.container_ref else {\n+ pub async fn cleanup_workspace_container(db: &DBService, workspace: &Workspace) {" } ``` ```gh-comment { "id": "2627756192", "comment_type": "review", "author": "ggordonhall", "body": "Update `mcp` nomenclature", "created_at": "2025-12-17T16:31:49Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627756192", "path": "crates/server/src/mcp/task_server.rs", "line": 352, "diff_hunk": "@@ -350,10 +349,9 @@ impl TaskServer {\n project_id: ctx.project.id,\n task_id: ctx.task.id,\n task_title: ctx.task.title,\n- attempt_id: ctx.task_attempt.id,\n- attempt_branch: ctx.task_attempt.branch,\n+ attempt_id: ctx.workspace.id," } ``` ```gh-comment { "id": "2628161769", "comment_type": "review", "author": "ggordonhall", "body": "update, and similar in other events", "created_at": "2025-12-17T18:27:47Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628161769", "path": "crates/server/src/routes/task_attempts.rs", "line": 1335, "diff_hunk": "@@ -1295,7 +1332,7 @@ pub async fn start_dev_server(\n serde_json::json!({\n \"task_id\": task.id.to_string(),\n \"project_id\": project.id.to_string(),\n- \"attempt_id\": task_attempt.id.to_string(),\n+ \"attempt_id\": workspace.id.to_string()," } ``` ```gh-comment { "id": "2628194289", "comment_type": "review", "author": "ggordonhall", "body": "Ugly, but we should rename this struct to avoid confusion with the more general concept of a workspace. Ideas...\r\n\r\n- `WorktreeContainer`\r\n...\r\n...\r\n\r\nChatGPT?", "created_at": "2025-12-17T18:36:30Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628194289", "path": "crates/services/src/services/workspace_manager.rs", "line": 3, "diff_hunk": "@@ -1,6 +1,6 @@\n use std::path::{Path, PathBuf};\n \n-use db::models::{repo::Repo, task_attempt::TaskAttempt};\n+use db::models::{repo::Repo, workspace::Workspace as DbWorkspace};" } ``` ```gh-comment { "id": "2628198036", "comment_type": "review", "author": "ggordonhall", "body": "We could add a BE route for this, and similar hooks where we're aggregating this information on the fly", "created_at": "2025-12-17T18:37:46Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628198036", "path": "frontend/src/hooks/useTaskAttempts.ts", "line": 43, "diff_hunk": "@@ -16,10 +20,36 @@ export function useTaskAttempts(taskId?: string, opts?: Options) {\n const enabled = (opts?.enabled ?? true) && !!taskId;\n const refetchInterval = opts?.refetchInterval ?? 5000;\n \n- return useQuery<TaskAttempt[]>({\n+ return useQuery<Workspace[]>({\n queryKey: taskAttemptKeys.byTask(taskId),\n queryFn: () => attemptsApi.getAll(taskId!),\n enabled,\n refetchInterval,\n });\n }\n+\n+/**\n+ * Hook for components that need executor field for all attempts.\n+ * Fetches all attempts and their sessions in parallel.\n+ */\n+export function useTaskAttemptsWithSessions(taskId?: string, opts?: Options) {\n+ const enabled = (opts?.enabled ?? true) && !!taskId;\n+ const refetchInterval = opts?.refetchInterval ?? 5000;\n+\n+ return useQuery<WorkspaceWithSession[]>({\n+ queryKey: taskAttemptKeys.byTaskWithSessions(taskId),\n+ queryFn: async () => {\n+ const attempts = await attemptsApi.getAll(taskId!);\n+ // Fetch sessions for all attempts in parallel" } ``` * Address feedback (vibe-kanban 81d8dbfa) A PR opened by your colleague (https://github.com/BloopAI/vibe-kanban/pull/1569) got some feedback, let's address it. ```gh-comment { "id": "2627479232", "comment_type": "review", "author": "ggordonhall", "body": "```suggestion\r\n-- 3. Migrate data: create one session per workspace\r\nINSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\r\nSELECT gen_random_uuid(), id, executor, created_at, updated_at FROM workspaces;\r\n```\r\n", "created_at": "2025-12-17T15:17:50Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627479232", "path": "crates/db/migrations/20251216142123_refactor_task_attempts_to_workspaces_sessions.sql", "line": 26, "diff_hunk": "@@ -0,0 +1,121 @@\n+-- Refactor task_attempts into workspaces and sessions\n+-- - Rename task_attempts -> workspaces (keeps workspace-related fields)\n+-- - Create sessions table (executor moves here)\n+-- - Update execution_processes.task_attempt_id -> session_id\n+-- - Rename executor_sessions -> coding_agent_turns (drop redundant task_attempt_id)\n+-- - Rename merges.task_attempt_id -> workspace_id\n+-- - Rename tasks.parent_task_attempt -> parent_workspace_id\n+\n+-- 1. Rename task_attempts to workspaces (FK refs auto-update in schema)\n+ALTER TABLE task_attempts RENAME TO workspaces;\n+\n+-- 2. Create sessions table\n+CREATE TABLE sessions (\n+ id BLOB PRIMARY KEY,\n+ workspace_id BLOB NOT NULL,\n+ executor TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE\n+);\n+\n+CREATE INDEX idx_sessions_workspace_id ON sessions(workspace_id);\n+\n+-- 3. Migrate data: create one session per workspace (using workspace.id as session.id for simplicity)\n+INSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\n+SELECT id, id, executor, created_at, updated_at FROM workspaces;" } ``` ```gh-comment { "id": "2627515578", "comment_type": "review", "author": "ggordonhall", "body": "Why not rename `attempt_repos` to `workspace_repos` here now that `attempt` is a legacy concept?", "created_at": "2025-12-17T15:27:21Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627515578", "path": "crates/db/migrations/20251216142123_refactor_task_attempts_to_workspaces_sessions.sql", "line": 118, "diff_hunk": "@@ -0,0 +1,121 @@\n+-- Refactor task_attempts into workspaces and sessions\n+-- - Rename task_attempts -> workspaces (keeps workspace-related fields)\n+-- - Create sessions table (executor moves here)\n+-- - Update execution_processes.task_attempt_id -> session_id\n+-- - Rename executor_sessions -> coding_agent_turns (drop redundant task_attempt_id)\n+-- - Rename merges.task_attempt_id -> workspace_id\n+-- - Rename tasks.parent_task_attempt -> parent_workspace_id\n+\n+-- 1. Rename task_attempts to workspaces (FK refs auto-update in schema)\n+ALTER TABLE task_attempts RENAME TO workspaces;\n+\n+-- 2. Create sessions table\n+CREATE TABLE sessions (\n+ id BLOB PRIMARY KEY,\n+ workspace_id BLOB NOT NULL,\n+ executor TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE\n+);\n+\n+CREATE INDEX idx_sessions_workspace_id ON sessions(workspace_id);\n+\n+-- 3. Migrate data: create one session per workspace (using workspace.id as session.id for simplicity)\n+INSERT INTO sessions (id, workspace_id, executor, created_at, updated_at)\n+SELECT id, id, executor, created_at, updated_at FROM workspaces;\n+\n+-- 4. Drop executor column from workspaces\n+ALTER TABLE workspaces DROP COLUMN executor;\n+\n+-- 5. Rename merges.task_attempt_id to workspace_id\n+DROP INDEX idx_merges_task_attempt_id;\n+DROP INDEX idx_merges_open_pr;\n+ALTER TABLE merges RENAME COLUMN task_attempt_id TO workspace_id;\n+CREATE INDEX idx_merges_workspace_id ON merges(workspace_id);\n+CREATE INDEX idx_merges_open_pr ON merges(workspace_id, pr_status)\n+WHERE merge_type = 'pr' AND pr_status = 'open';\n+\n+-- 6. Rename tasks.parent_task_attempt to parent_workspace_id\n+DROP INDEX IF EXISTS idx_tasks_parent_task_attempt;\n+ALTER TABLE tasks RENAME COLUMN parent_task_attempt TO parent_workspace_id;\n+CREATE INDEX idx_tasks_parent_workspace_id ON tasks(parent_workspace_id);\n+\n+-- Steps 7-8 need FK disabled to avoid cascade deletes during DROP TABLE\n+-- sqlx workaround: end auto-transaction to allow PRAGMA to take effect\n+-- https://github.com/launchbadge/sqlx/issues/2085#issuecomment-1499859906\n+COMMIT;\n+\n+PRAGMA foreign_keys = OFF;\n+\n+BEGIN TRANSACTION;\n+\n+-- 7. Update execution_processes to reference session_id instead of task_attempt_id\n+-- (needs rebuild because FK target changes from workspaces to sessions)\n+DROP INDEX IF EXISTS idx_execution_processes_task_attempt_created_at;\n+DROP INDEX IF EXISTS idx_execution_processes_task_attempt_type_created;\n+\n+CREATE TABLE execution_processes_new (\n+ id BLOB PRIMARY KEY,\n+ session_id BLOB NOT NULL,\n+ run_reason TEXT NOT NULL DEFAULT 'setupscript'\n+ CHECK (run_reason IN ('setupscript','codingagent','devserver','cleanupscript')),\n+ executor_action TEXT NOT NULL DEFAULT '{}',\n+ status TEXT NOT NULL DEFAULT 'running'\n+ CHECK (status IN ('running','completed','failed','killed')),\n+ exit_code INTEGER,\n+ dropped INTEGER NOT NULL DEFAULT 0,\n+ started_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ completed_at TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE\n+);\n+\n+-- Since we used workspace.id as session.id, the task_attempt_id values map directly\n+INSERT INTO execution_processes_new (id, session_id, run_reason, executor_action, status, exit_code, dropped, started_at, completed_at, created_at, updated_at)\n+SELECT id, task_attempt_id, run_reason, executor_action, status, exit_code, dropped, started_at, completed_at, created_at, updated_at\n+FROM execution_processes;\n+\n+DROP TABLE execution_processes;\n+ALTER TABLE execution_processes_new RENAME TO execution_processes;\n+\n+-- Recreate execution_processes indexes\n+CREATE INDEX idx_execution_processes_session_id ON execution_processes(session_id);\n+CREATE INDEX idx_execution_processes_status ON execution_processes(status);\n+CREATE INDEX idx_execution_processes_run_reason ON execution_processes(run_reason);\n+\n+-- 8. Rename executor_sessions to coding_agent_turns and drop task_attempt_id\n+-- (needs rebuild to drop the redundant task_attempt_id column)\n+CREATE TABLE coding_agent_turns (\n+ id BLOB PRIMARY KEY,\n+ execution_process_id BLOB NOT NULL,\n+ session_id TEXT,\n+ prompt TEXT,\n+ summary TEXT,\n+ created_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ updated_at TEXT NOT NULL DEFAULT (datetime('now', 'subsec')),\n+ FOREIGN KEY (execution_process_id) REFERENCES execution_processes(id) ON DELETE CASCADE\n+);\n+\n+INSERT INTO coding_agent_turns (id, execution_process_id, session_id, prompt, summary, created_at, updated_at)\n+SELECT id, execution_process_id, session_id, prompt, summary, created_at, updated_at\n+FROM executor_sessions;\n+\n+DROP TABLE executor_sessions;\n+\n+-- Recreate coding_agent_turns indexes\n+CREATE INDEX idx_coding_agent_turns_execution_process_id ON coding_agent_turns(execution_process_id);\n+CREATE INDEX idx_coding_agent_turns_session_id ON coding_agent_turns(session_id);\n+\n+-- 9. attempt_repos: no changes needed - FK auto-updated when task_attempts renamed to workspaces" } ``` ```gh-comment { "id": "2627694792", "comment_type": "review", "author": "ggordonhall", "body": "Maybe there's a better name than `external_session_id` here? `agent_session_id`? ", "created_at": "2025-12-17T16:16:24Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627694792", "path": "crates/db/src/models/execution_process.rs", "line": 685, "diff_hunk": "@@ -618,4 +680,34 @@ impl ExecutionProcess {\n )),\n }\n }\n+\n+ /// Find latest coding_agent_turn session_id by workspace (across all sessions)\n+ pub async fn find_latest_external_session_id_by_workspace(" } ``` ```gh-comment { "id": "2627707446", "comment_type": "review", "author": "ggordonhall", "body": "```suggestion\r\n pub async fn cleanup_workspace(db: &DBService, workspace: &Workspace) {\r\n```", "created_at": "2025-12-17T16:19:31Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627707446", "path": "crates/local-deployment/src/container.rs", "line": 146, "diff_hunk": "@@ -142,20 +143,20 @@ impl LocalContainerService {\n map.remove(id)\n }\n \n- pub async fn cleanup_attempt_workspace(db: &DBService, attempt: &TaskAttempt) {\n- let Some(container_ref) = &attempt.container_ref else {\n+ pub async fn cleanup_workspace_container(db: &DBService, workspace: &Workspace) {" } ``` ```gh-comment { "id": "2627756192", "comment_type": "review", "author": "ggordonhall", "body": "Update `mcp` nomenclature", "created_at": "2025-12-17T16:31:49Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2627756192", "path": "crates/server/src/mcp/task_server.rs", "line": 352, "diff_hunk": "@@ -350,10 +349,9 @@ impl TaskServer {\n project_id: ctx.project.id,\n task_id: ctx.task.id,\n task_title: ctx.task.title,\n- attempt_id: ctx.task_attempt.id,\n- attempt_branch: ctx.task_attempt.branch,\n+ attempt_id: ctx.workspace.id," } ``` ```gh-comment { "id": "2628161769", "comment_type": "review", "author": "ggordonhall", "body": "update, and similar in other events", "created_at": "2025-12-17T18:27:47Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628161769", "path": "crates/server/src/routes/task_attempts.rs", "line": 1335, "diff_hunk": "@@ -1295,7 +1332,7 @@ pub async fn start_dev_server(\n serde_json::json!({\n \"task_id\": task.id.to_string(),\n \"project_id\": project.id.to_string(),\n- \"attempt_id\": task_attempt.id.to_string(),\n+ \"attempt_id\": workspace.id.to_string()," } ``` ```gh-comment { "id": "2628194289", "comment_type": "review", "author": "ggordonhall", "body": "Ugly, but we should rename this struct to avoid confusion with the more general concept of a workspace. Ideas...\r\n\r\n- `WorktreeContainer`\r\n...\r\n...\r\n\r\nChatGPT?", "created_at": "2025-12-17T18:36:30Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628194289", "path": "crates/services/src/services/workspace_manager.rs", "line": 3, "diff_hunk": "@@ -1,6 +1,6 @@\n use std::path::{Path, PathBuf};\n \n-use db::models::{repo::Repo, task_attempt::TaskAttempt};\n+use db::models::{repo::Repo, workspace::Workspace as DbWorkspace};" } ``` ```gh-comment { "id": "2628198036", "comment_type": "review", "author": "ggordonhall", "body": "We could add a BE route for this, and similar hooks where we're aggregating this information on the fly", "created_at": "2025-12-17T18:37:46Z", "url": "https://github.com/BloopAI/vibe-kanban/pull/1569#discussion_r2628198036", "path": "frontend/src/hooks/useTaskAttempts.ts", "line": 43, "diff_hunk": "@@ -16,10 +20,36 @@ export function useTaskAttempts(taskId?: string, opts?: Options) {\n const enabled = (opts?.enabled ?? true) && !!taskId;\n const refetchInterval = opts?.refetchInterval ?? 5000;\n \n- return useQuery<TaskAttempt[]>({\n+ return useQuery<Workspace[]>({\n queryKey: taskAttemptKeys.byTask(taskId),\n queryFn: () => attemptsApi.getAll(taskId!),\n enabled,\n refetchInterval,\n });\n }\n+\n+/**\n+ * Hook for components that need executor field for all attempts.\n+ * Fetches all attempts and their sessions in parallel.\n+ */\n+export function useTaskAttemptsWithSessions(taskId?: string, opts?: Options) {\n+ const enabled = (opts?.enabled ?? true) && !!taskId;\n+ const refetchInterval = opts?.refetchInterval ?? 5000;\n+\n+ return useQuery<WorkspaceWithSession[]>({\n+ queryKey: taskAttemptKeys.byTaskWithSessions(taskId),\n+ queryFn: async () => {\n+ const attempts = await attemptsApi.getAll(taskId!);\n+ // Fetch sessions for all attempts in parallel" } ```
2025-12-18 14:45:10 +00:00
{
"db_name": "SQLite",
"query": "SELECT cat.agent_session_id\n FROM execution_processes ep\n JOIN coding_agent_turns cat ON ep.id = cat.execution_process_id\n JOIN sessions s ON ep.session_id = s.id\n WHERE s.workspace_id = $1\n AND ep.run_reason = 'codingagent'\n AND ep.dropped = FALSE\n AND cat.agent_session_id IS NOT NULL\n ORDER BY ep.created_at DESC\n LIMIT 1",
"describe": {
"columns": [
{
"name": "agent_session_id",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true
]
},
"hash": "b74d74a66fa902092d8ccbcb68dc22954b7f2a59f21b79c15cd6159654f4a8b5"
}