From a130dc29f55b26752fa3a5efdf3990c72b7c3d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 16 Apr 2026 21:53:12 -0400 Subject: [PATCH] fix(tasks): route updateTaskSession/getTaskSession through task_sessions table Stop writing new session data back to the legacy tasks.session_json column; updateTaskSession now upserts the active task_sessions row (updating the session pointed to by tasks.active_session_id, creating one when absent), and getTaskSession reads from the same row so session data has a single source of truth. Adds migration 27 to backfill pre-existing tasks.session_json values into task_sessions idempotently via INSERT OR IGNORE guarded on task_id. The legacy column is kept for read-only history and is not dropped. --- src/main/database/schema.ts | 42 ++++++++++++++++++++++ src/main/database/tasks-crud.ts | 64 ++++++++++++++++++++++++++------- 2 files changed, 93 insertions(+), 13 deletions(-) diff --git a/src/main/database/schema.ts b/src/main/database/schema.ts index 1779d7de..98f3cbf9 100644 --- a/src/main/database/schema.ts +++ b/src/main/database/schema.ts @@ -365,6 +365,48 @@ export function runMigrations( version: 26, name: "add_source_run_id_for_causal_binding", sql: `ALTER TABLE documents ADD COLUMN source_run_id TEXT; ALTER TABLE knowledge_base ADD COLUMN source_run_id TEXT`, }, + { + version: 27, name: "backfill_task_sessions_from_tasks_session_json", + fn: () => { + const legacyTasks = readAll<{ + id: string; + companyId: string; + assigneeAgentId: string | null; + sessionJson: string; + sessionDisplayId: string | null; + updatedAt: string; + connectorId: string | null; + }>( + `SELECT t.id AS id, t.company_id AS companyId, t.assignee_agent_id AS assigneeAgentId, + t.session_json AS sessionJson, t.session_display_id AS sessionDisplayId, + t.updated_at AS updatedAt, a.connector_id AS connectorId + FROM tasks t + LEFT JOIN agents a ON a.id = t.assignee_agent_id + WHERE t.session_json IS NOT NULL + AND t.id NOT IN (SELECT task_id FROM task_sessions)`, + ); + for (const task of legacyTasks) { + const sessionId = randomUUID(); + db.prepare( + `INSERT OR IGNORE INTO task_sessions (id, company_id, task_id, agent_id, adapter_type, session_params_json, session_display_id, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + ).run( + sessionId, + task.companyId, + task.id, + task.assigneeAgentId ?? "unassigned", + task.connectorId ?? "unknown", + task.sessionJson, + task.sessionDisplayId, + task.updatedAt, + task.updatedAt, + ); + db.prepare( + "UPDATE tasks SET active_session_id = ? WHERE id = ? AND active_session_id IS NULL", + ).run(sessionId, task.id); + } + }, + }, ]; for (const migration of migrations) { diff --git a/src/main/database/tasks-crud.ts b/src/main/database/tasks-crud.ts index 8eb7947a..7059277d 100644 --- a/src/main/database/tasks-crud.ts +++ b/src/main/database/tasks-crud.ts @@ -185,36 +185,74 @@ export function buildTaskCrudMethods( sessionJson: Record | null, sessionDisplayId: string | null, ): void { + const ts = nowIso(); + const sessionParamsJson = sessionJson ? JSON.stringify(sessionJson) : null; + const taskRow = getDb() + .prepare( + "select company_id as companyId, assignee_agent_id as assigneeAgentId, active_session_id as activeSessionId from tasks where id = ?", + ) + .get(taskId) as + | { companyId: string; assigneeAgentId: string | null; activeSessionId: string | null } + | undefined; + if (!taskRow) throw new Error(`Task not found: ${taskId}`); + if (taskRow.activeSessionId) { + getDb() + .prepare( + "update task_sessions set session_params_json = ?, session_display_id = ?, updated_at = ? where id = ?", + ) + .run(sessionParamsJson, sessionDisplayId, ts, taskRow.activeSessionId); + return; + } + if (sessionParamsJson === null && sessionDisplayId === null) return; + const agentId = taskRow.assigneeAgentId ?? "unassigned"; + const agent = taskRow.assigneeAgentId + ? (getDb() + .prepare("select connector_id as connectorId from agents where id = ?") + .get(taskRow.assigneeAgentId) as { connectorId: string } | undefined) + : undefined; + const adapterType = agent?.connectorId ?? "unknown"; + const sessionId = randomUUID(); getDb() .prepare( - "update tasks set session_json = ?, session_display_id = ?, updated_at = ? where id = ?", + `insert into task_sessions (id, company_id, task_id, agent_id, adapter_type, session_params_json, session_display_id, created_at, updated_at) + values (?, ?, ?, ?, ?, ?, ?, ?, ?)`, ) .run( - sessionJson ? JSON.stringify(sessionJson) : null, - sessionDisplayId, - nowIso(), + sessionId, + taskRow.companyId, taskId, + agentId, + adapterType, + sessionParamsJson, + sessionDisplayId, + ts, + ts, ); + getDb() + .prepare("update tasks set active_session_id = ?, updated_at = ? where id = ?") + .run(sessionId, ts, taskId); }, getTaskSession(taskId: string): { sessionParams: Record | null; sessionDisplayId: string | null; } { + const taskRow = getDb() + .prepare("select active_session_id as activeSessionId from tasks where id = ?") + .get(taskId) as { activeSessionId: string | null } | undefined; + if (!taskRow?.activeSessionId) { + return { sessionParams: null, sessionDisplayId: null }; + } const row = getDb() .prepare( - "select session_json as sessionJson, session_display_id as sessionDisplayId from tasks where id = ?", + "select session_params_json as sessionParamsJson, session_display_id as sessionDisplayId from task_sessions where id = ?", ) - .get(taskId) as - | { sessionJson: string | null; sessionDisplayId: string | null } + .get(taskRow.activeSessionId) as + | { sessionParamsJson: string | null; sessionDisplayId: string | null } | undefined; let sessionParams: Record | null = null; - if (row?.sessionJson) { - try { - sessionParams = JSON.parse(row.sessionJson) as Record; - } catch { - sessionParams = null; - } + if (row?.sessionParamsJson) { + sessionParams = JSON.parse(row.sessionParamsJson) as Record; } return { sessionParams,