From 5d3e3c7053243568b01d04b580b7bc89b8b7824b Mon Sep 17 00:00:00 2001 From: danielroytel <107309800+danielroytel@users.noreply.github.com> Date: Sun, 7 Jun 2026 23:33:17 +1000 Subject: [PATCH] feat(tasks): assign folder='Tasks' at creation + backfill migration (#2834) * feat: assign folder='Tasks' to task sessions at creation Task sessions (LLM, action, research) now set folder='Tasks' on their DbSession row, matching the pattern used by the Assistant folder. This enables sidebar lens filtering without changing existing session behaviour. Co-Authored-By: Claude Opus 4.6 * feat: add backfill script for task session folders One-shot script to set folder='Tasks' on existing [Task]/[Research] sessions that predate the folder assignment in task_scheduler.py. Co-Authored-By: Claude Opus 4.6 * refactor: replace standalone backfill script with automatic migration Convert scripts/backfill_task_folders.py into _migrate_backfill_task_folders() in core/database.py, called from init_db(). The migration is idempotent (only touches rows where folder IS NULL/empty) and runs automatically on upgrade, so operators no longer need a manual step to tag pre-existing task sessions. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- core/database.py | 27 +++++++++++++++++++++++++++ src/task_scheduler.py | 3 +++ tests/test_task_session_folder.py | 27 +++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 tests/test_task_session_folder.py diff --git a/core/database.py b/core/database.py index a559f55c5..241f3892b 100644 --- a/core/database.py +++ b/core/database.py @@ -1631,6 +1631,33 @@ def init_db(): _migrate_encrypt_email_passwords() _migrate_encrypt_signatures() _migrate_encrypt_endpoint_keys() + _migrate_backfill_task_folders() + + +def _migrate_backfill_task_folders(): + """Backfill folder='Tasks' on pre-existing task/research sessions. + + Sessions created by the task scheduler (LLM tasks, action tasks, research + runs) now set folder='Tasks' at creation time. This migration tags any + older sessions that predate that assignment. Idempotent — only touches + rows where folder is NULL or empty and the title matches known prefixes. + """ + try: + with engine.connect() as conn: + cols = [r[1] for r in conn.execute(text("PRAGMA table_info(sessions)"))] + if "folder" not in cols: + return + res = conn.execute(text( + "UPDATE sessions SET folder = 'Tasks' " + "WHERE (folder IS NULL OR folder = '') " + "AND (name LIKE '[Task] %' OR name LIKE '[Research] %')" + )) + conn.commit() + if res.rowcount: + logging.getLogger(__name__).info( + f"Backfilled folder='Tasks' on {res.rowcount} task/research sessions") + except Exception as e: + logging.getLogger(__name__).warning(f"task folder backfill: {e}") def _migrate_chat_messages_fts(): diff --git a/src/task_scheduler.py b/src/task_scheduler.py index 5cc0e717a..69336d2dd 100644 --- a/src/task_scheduler.py +++ b/src/task_scheduler.py @@ -1315,6 +1315,7 @@ class TaskScheduler: endpoint_url=endpoint_url, model=model, owner=task.owner, + folder="Tasks", created_at=_utcnow(), updated_at=_utcnow(), ) @@ -1463,6 +1464,7 @@ class TaskScheduler: endpoint_url=endpoint_url or "", model=model_name or "", owner=task.owner, + folder="Tasks", created_at=_utcnow(), updated_at=_utcnow(), ) @@ -1755,6 +1757,7 @@ class TaskScheduler: endpoint_url=endpoint_url, model=model, owner=task.owner, + folder="Tasks", created_at=_utcnow(), updated_at=_utcnow(), ) diff --git a/tests/test_task_session_folder.py b/tests/test_task_session_folder.py new file mode 100644 index 000000000..4b49ab321 --- /dev/null +++ b/tests/test_task_session_folder.py @@ -0,0 +1,27 @@ +"""Task sessions must be assigned folder='Tasks' at creation time.""" +import inspect +from src.task_scheduler import TaskScheduler + + +def test_llm_task_session_gets_tasks_folder(): + """_execute_llm_task must create sessions with folder='Tasks'.""" + source = inspect.getsource(TaskScheduler._execute_llm_task) + assert 'folder="Tasks"' in source or "folder='Tasks'" in source, ( + "LLM task session creation must set folder='Tasks'" + ) + + +def test_action_task_session_gets_tasks_folder(): + """_deliver_task_result must create sessions with folder='Tasks'.""" + source = inspect.getsource(TaskScheduler._deliver_task_result) + assert 'folder="Tasks"' in source or "folder='Tasks'" in source, ( + "Action task session delivery must set folder='Tasks'" + ) + + +def test_research_task_session_gets_tasks_folder(): + """_execute_research_task must create sessions with folder='Tasks'.""" + source = inspect.getsource(TaskScheduler._execute_research_task) + assert 'folder="Tasks"' in source or "folder='Tasks'" in source, ( + "Research task session creation must set folder='Tasks'" + )