diff --git a/src/main/database-base.ts b/src/main/database-base.ts index 3e384979..253c3e0f 100644 --- a/src/main/database-base.ts +++ b/src/main/database-base.ts @@ -339,16 +339,13 @@ export class AppDatabaseBase { saveTask(input: { id?: string | null; companyId: string; projectId?: string | null; goalId?: string | null; parentId?: string | null; title: string; description: string; assigneeAgentId?: string | null; workspaceId?: string | null; priority: string; status: string }) { return this.tasks.saveTask(input); } getTask(id: string) { return this.entityCrud.getTask(id); } deleteTask(id: string, companyId: string) { return this.tasks.deleteTask(id, companyId); } - updateTaskSession(taskId: string, sessionJson: Record | null, sessionDisplayId: string | null) { return this.tasks.updateTaskSession(taskId, sessionJson, sessionDisplayId); } - getTaskSession(taskId: string) { return this.tasks.getTaskSession(taskId); } - getActiveTaskSession(taskId: string) { return this.tasks.getActiveTaskSession(taskId); } + updateSessionParams(sessionId: string, sessionParams: Record | null, sessionDisplayId: string | null) { return this.tasks.updateSessionParams(sessionId, sessionParams, sessionDisplayId); } getSessionById(sessionId: string) { return this.tasks.getSessionById(sessionId); } incrementSessionUsage(sessionId: string, inputTokens: number, outputTokens: number, runId: string) { return this.tasks.incrementSessionUsage(sessionId, inputTokens, outputTokens, runId); } archiveSession(sessionId: string, reason: string, handoffMarkdown: string) { return this.tasks.archiveSession(sessionId, reason, handoffMarkdown); } - createNewSession(input: { taskId: string; companyId: string; agentId: string; adapterType: string; sessionParamsJson: string | null; sessionDisplayId: string | null }) { return this.tasks.createNewSession(input); } + createNewSession(input: { companyId: string; agentId: string; adapterType: string; sessionParamsJson: string | null; sessionDisplayId: string | null }) { return this.tasks.createNewSession(input); } ensureAgentSession(input: { agentId: string; companyId: string; adapterType: string }) { return this.tasks.ensureAgentSession(input); } setAgentSessionId(agentId: string, sessionId: string) { return this.tasks.setAgentSessionId(agentId, sessionId); } - updateTasksActiveSessionForAgent(agentId: string, sessionId: string) { return this.tasks.updateTasksActiveSessionForAgent(agentId, sessionId); } getRecentRunsForTask(taskId: string, limit: number) { return this.tasks.getRecentRunsForTask(taskId, limit); } getRecentActivityForTask(taskId: string, limit: number) { return this.tasks.getRecentActivityForTask(taskId, limit); } checkoutTask(taskId: string, runId: string) { return this.tasks.checkoutTask(taskId, runId); } diff --git a/src/main/database/entity-crud.ts b/src/main/database/entity-crud.ts index 7725939c..1c819fe1 100644 --- a/src/main/database/entity-crud.ts +++ b/src/main/database/entity-crud.ts @@ -49,7 +49,7 @@ export function buildEntityCrudMethods( getTask(id: string): TaskRecord { const row = getDb() .prepare( - "select id, company_id as companyId, project_id as projectId, goal_id as goalId, parent_id as parentId, title, description, assignee_agent_id as assigneeAgentId, workspace_id as workspaceId, status, priority, active_run_id as activeRunId, session_json as sessionJson, session_display_id as sessionDisplayId, cost_usd as costUsd, task_type as taskType, requires_user_review as requiresUserReview, started_at as startedAt, completed_at as completedAt, cancelled_at as cancelledAt, created_at as createdAt, updated_at as updatedAt from tasks where id = ?", + "select id, company_id as companyId, project_id as projectId, goal_id as goalId, parent_id as parentId, title, description, assignee_agent_id as assigneeAgentId, workspace_id as workspaceId, status, priority, active_run_id as activeRunId, cost_usd as costUsd, task_type as taskType, requires_user_review as requiresUserReview, started_at as startedAt, completed_at as completedAt, cancelled_at as cancelledAt, created_at as createdAt, updated_at as updatedAt from tasks where id = ?", ) .get(id) as TaskRecord | undefined; if (!row) throw new Error(`Task not found: ${id}`); diff --git a/src/main/database/migrations/v41-v41.ts b/src/main/database/migrations/v41-v41.ts new file mode 100644 index 00000000..e6500c13 --- /dev/null +++ b/src/main/database/migrations/v41-v41.ts @@ -0,0 +1,173 @@ +import log from "electron-log/main.js"; +import type { Migration, MigrationDeps } from "./types"; +import { columnExists, tableExists } from "./types"; + +/** + * Drop dead columns left behind by the agent-scoped session refactor. + * + * Pre-v34, a session belonged to a task; the schema reflected that + * with `tasks.active_session_id`, `tasks.session_json`, + * `tasks.session_display_id`, and a `task_sessions.task_id` FK back to + * tasks. Since v34, sessions are agent-scoped — `agents.session_id` + * is the canonical pointer, and every runtime read goes through that + * or through the session's primary key. The four columns have been + * dead storage ever since, and every touch of session code had to + * pretend they were still meaningful. + * + * This migration rebuilds both tables without those columns. SQLite + * has no `ALTER TABLE DROP COLUMN` that drops multiple columns + * atomically and preserves every constraint; the "copy to new table, + * swap, reindex" pattern is the only safe route. `PRAGMA foreign_keys` + * is disabled for the swap window and restored afterward so child + * tables are not cascade-deleted mid-rebuild. + * + * The migration is idempotent: each table rebuild checks whether the + * dead columns are still present and skips the work otherwise. + */ +export function createMigrationsV41V41(deps: MigrationDeps): Migration[] { + const { db } = deps; + return [ + { + version: 41, + name: "drop_dead_session_columns", + fn: () => { + if (!tableExists(db, "tasks") || !tableExists(db, "task_sessions")) { + log.info("[migration v41] tasks or task_sessions table missing; skipping"); + return; + } + + const tasksHasDeadCols = + columnExists(db, "tasks", "session_json") || + columnExists(db, "tasks", "session_display_id") || + columnExists(db, "tasks", "active_session_id"); + const sessionsHasDeadCol = columnExists(db, "task_sessions", "task_id"); + + if (!tasksHasDeadCols && !sessionsHasDeadCol) { + log.info("[migration v41] dead columns already absent; skipping"); + return; + } + + // The `ensureOptionalColumns` helper in schema.ts normally + // backfills `task_type`, `requires_user_review`, `retry_count`, + // `last_failure_at` on existing installs — but that runs AFTER + // migrations, not before. Rebuild the table here with the same + // shape as the post-ensureOptionalColumns schema so the old data + // is preserved and the new table stays consistent whether + // initializeDatabase has reached ensureOptionalColumns yet or not. + const ensureTaskCol = (column: string, typeSql: string) => { + if (!columnExists(db, "tasks", column)) { + db.prepare(`ALTER TABLE tasks ADD COLUMN ${column} ${typeSql}`).run(); + } + }; + ensureTaskCol("task_type", "TEXT NOT NULL DEFAULT 'general'"); + ensureTaskCol("requires_user_review", "INTEGER NOT NULL DEFAULT 0"); + ensureTaskCol("retry_count", "INTEGER NOT NULL DEFAULT 0"); + ensureTaskCol("last_failure_at", "TEXT"); + ensureTaskCol("checkout_run_id", "TEXT"); + ensureTaskCol("execution_locked_at", "TEXT"); + ensureTaskCol("started_at", "TEXT"); + ensureTaskCol("completed_at", "TEXT"); + ensureTaskCol("cancelled_at", "TEXT"); + + db.exec("PRAGMA foreign_keys = OFF"); + try { + if (tasksHasDeadCols) { + db.exec(` + CREATE TABLE tasks_new ( + id TEXT PRIMARY KEY, + company_id TEXT NOT NULL, + project_id TEXT, + goal_id TEXT, + parent_id TEXT, + title TEXT NOT NULL, + description TEXT NOT NULL, + assignee_agent_id TEXT, + workspace_id TEXT, + status TEXT NOT NULL, + priority TEXT NOT NULL, + active_run_id TEXT, + cost_usd REAL NOT NULL DEFAULT 0, + task_type TEXT NOT NULL DEFAULT 'general', + requires_user_review INTEGER NOT NULL DEFAULT 0, + retry_count INTEGER NOT NULL DEFAULT 0, + last_failure_at TEXT, + checkout_run_id TEXT, + execution_locked_at TEXT, + started_at TEXT, + completed_at TEXT, + cancelled_at TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + INSERT INTO tasks_new ( + id, company_id, project_id, goal_id, parent_id, title, description, + assignee_agent_id, workspace_id, status, priority, active_run_id, + cost_usd, task_type, requires_user_review, retry_count, last_failure_at, + checkout_run_id, execution_locked_at, started_at, completed_at, + cancelled_at, created_at, updated_at + ) + SELECT + id, company_id, project_id, goal_id, parent_id, title, description, + assignee_agent_id, workspace_id, status, priority, active_run_id, + cost_usd, task_type, requires_user_review, retry_count, last_failure_at, + checkout_run_id, execution_locked_at, started_at, completed_at, + cancelled_at, created_at, updated_at + FROM tasks; + DROP TABLE tasks; + ALTER TABLE tasks_new RENAME TO tasks; + CREATE INDEX IF NOT EXISTS idx_tasks_company ON tasks(company_id); + CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee_agent_id); + CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id); + CREATE INDEX IF NOT EXISTS idx_tasks_company_status ON tasks(company_id, status); + `); + log.info("[migration v41] rebuilt tasks without session_json/session_display_id/active_session_id"); + } + + if (sessionsHasDeadCol) { + db.exec(` + CREATE TABLE task_sessions_new ( + id TEXT PRIMARY KEY, + company_id TEXT NOT NULL, + agent_id TEXT NOT NULL, + adapter_type TEXT NOT NULL, + session_params_json TEXT, + session_display_id TEXT, + last_run_id TEXT, + last_error TEXT, + run_count INTEGER NOT NULL DEFAULT 0, + total_input_tokens INTEGER NOT NULL DEFAULT 0, + total_output_tokens INTEGER NOT NULL DEFAULT 0, + rotated_at TEXT, + rotation_reason TEXT, + handoff_markdown TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + INSERT INTO task_sessions_new ( + id, company_id, agent_id, adapter_type, session_params_json, + session_display_id, last_run_id, last_error, run_count, + total_input_tokens, total_output_tokens, rotated_at, rotation_reason, + handoff_markdown, created_at, updated_at + ) + SELECT + id, company_id, agent_id, adapter_type, session_params_json, + session_display_id, last_run_id, last_error, run_count, + total_input_tokens, total_output_tokens, rotated_at, rotation_reason, + handoff_markdown, created_at, updated_at + FROM task_sessions; + DROP TABLE task_sessions; + ALTER TABLE task_sessions_new RENAME TO task_sessions; + CREATE INDEX IF NOT EXISTS idx_task_sessions_company_agent + ON task_sessions(company_id, agent_id, updated_at); + CREATE INDEX IF NOT EXISTS idx_task_sessions_lookup + ON task_sessions(company_id, agent_id, adapter_type); + `); + log.info("[migration v41] rebuilt task_sessions without task_id"); + } + } finally { + db.exec("PRAGMA foreign_keys = ON"); + } + }, + }, + ]; +} diff --git a/src/main/database/schema.ts b/src/main/database/schema.ts index 93e1afe2..263cda12 100644 --- a/src/main/database/schema.ts +++ b/src/main/database/schema.ts @@ -5,6 +5,7 @@ import { createMigrationsV26V34 } from "./migrations/v26-v34"; import { createMigrationsV35V37 } from "./migrations/v35-v37"; import { createMigrationsV38V39 } from "./migrations/v38-v39"; import { createMigrationsV40V40 } from "./migrations/v40-v40"; +import { createMigrationsV41V41 } from "./migrations/v41-v41"; import { columnExists, nowIso, @@ -176,6 +177,7 @@ export function runMigrations( ...createMigrationsV35V37(deps), ...createMigrationsV38V39(deps), ...createMigrationsV40V40(deps), + ...createMigrationsV41V41(deps), ]; for (const migration of migrations) { diff --git a/src/main/database/snapshot.ts b/src/main/database/snapshot.ts index 71cb8c57..831ec85a 100644 --- a/src/main/database/snapshot.ts +++ b/src/main/database/snapshot.ts @@ -75,7 +75,7 @@ export function buildSnapshotMethods( ...companyArgs, ), tasks: readAll( - `select id, company_id as companyId, project_id as projectId, goal_id as goalId, parent_id as parentId, title, description, assignee_agent_id as assigneeAgentId, workspace_id as workspaceId, status, priority, active_run_id as activeRunId, session_json as sessionJson, session_display_id as sessionDisplayId, cost_usd as costUsd, task_type as taskType, requires_user_review as requiresUserReview, started_at as startedAt, completed_at as completedAt, cancelled_at as cancelledAt, created_at as createdAt, updated_at as updatedAt from tasks${companyWhere} order by created_at desc limit 2000`, + `select id, company_id as companyId, project_id as projectId, goal_id as goalId, parent_id as parentId, title, description, assignee_agent_id as assigneeAgentId, workspace_id as workspaceId, status, priority, active_run_id as activeRunId, cost_usd as costUsd, task_type as taskType, requires_user_review as requiresUserReview, started_at as startedAt, completed_at as completedAt, cancelled_at as cancelledAt, created_at as createdAt, updated_at as updatedAt from tasks${companyWhere} order by created_at desc limit 2000`, ...companyArgs, ).map((record) => ({ ...record, requiresUserReview: toBoolean(record.requiresUserReview), startedAt: record.startedAt ?? null, completedAt: record.completedAt ?? null, cancelledAt: record.cancelledAt ?? null })) as TaskRecord[], approvals: readAll( diff --git a/src/main/database/task-queries.ts b/src/main/database/task-queries.ts index 4a57edb1..7168a8f0 100644 --- a/src/main/database/task-queries.ts +++ b/src/main/database/task-queries.ts @@ -25,7 +25,7 @@ export function buildTaskQueryMethods( ): TaskRecord[] { const placeholders = statuses.map(() => "?").join(","); return readAll( - `select id, company_id as companyId, project_id as projectId, goal_id as goalId, parent_id as parentId, title, description, assignee_agent_id as assigneeAgentId, workspace_id as workspaceId, status, priority, active_run_id as activeRunId, session_json as sessionJson, session_display_id as sessionDisplayId, cost_usd as costUsd, task_type as taskType, created_at as createdAt, updated_at as updatedAt + `select id, company_id as companyId, project_id as projectId, goal_id as goalId, parent_id as parentId, title, description, assignee_agent_id as assigneeAgentId, workspace_id as workspaceId, status, priority, active_run_id as activeRunId, cost_usd as costUsd, task_type as taskType, created_at as createdAt, updated_at as updatedAt from tasks where company_id = ? and assignee_agent_id = ? and status in (${placeholders}) order by @@ -109,7 +109,7 @@ export function buildTaskQueryMethods( ): TaskListByStatusesRow[] { const placeholders = statuses.map(() => "?").join(", "); return readAll( - `select id, company_id as companyId, project_id as projectId, goal_id as goalId, parent_id as parentId, title, description, assignee_agent_id as assigneeAgentId, workspace_id as workspaceId, status, priority, active_run_id as activeRunId, session_json as sessionJson, session_display_id as sessionDisplayId, cost_usd as costUsd, requires_user_review as requiresUserReview, started_at as startedAt, completed_at as completedAt, cancelled_at as cancelledAt, created_at as createdAt, updated_at as updatedAt from tasks where company_id = ? and status in (${placeholders}) order by created_at desc`, + `select id, company_id as companyId, project_id as projectId, goal_id as goalId, parent_id as parentId, title, description, assignee_agent_id as assigneeAgentId, workspace_id as workspaceId, status, priority, active_run_id as activeRunId, cost_usd as costUsd, requires_user_review as requiresUserReview, started_at as startedAt, completed_at as completedAt, cancelled_at as cancelledAt, created_at as createdAt, updated_at as updatedAt from tasks where company_id = ? and status in (${placeholders}) order by created_at desc`, companyId, ...statuses, ).map((t) => ({ diff --git a/src/main/database/task-sessions-crud.ts b/src/main/database/task-sessions-crud.ts index b744c883..24ce530a 100644 --- a/src/main/database/task-sessions-crud.ts +++ b/src/main/database/task-sessions-crud.ts @@ -4,16 +4,12 @@ import type { TaskSessionRecord } from "@shared/types"; import { type ReadAll, type Transaction, nowIso } from "./helpers"; /** - * Task-session lifecycle helpers extracted from tasks-crud.ts. - * - * Covers the agent-scoped session lifecycle: - * - updateTaskSession / getTaskSession / getActiveTaskSession - * - incrementSessionUsage / archiveSession - * - ensureAgentSession / setAgentSessionId / updateTasksActiveSessionForAgent - * - createNewSession - * - * Every method is spread into the final `buildTaskMethods` result, so - * call-sites on AppDatabase remain unchanged. + * Agent-scoped session lifecycle. Sessions belong to agents, not + * tasks — `agents.session_id` is the canonical pointer, and + * `task_sessions` rows are looked up by their own primary key only. + * The pre-migration `tasks.active_session_id` / `task_sessions.task_id` + * columns were dropped by v41; any read that still needs a session + * given a task id resolves it through the task's assignee agent. */ export function buildTaskSessionMethods( getDb: () => DatabaseSync, @@ -21,118 +17,37 @@ export function buildTaskSessionMethods( transaction: Transaction, ) { return { - updateTaskSession( - taskId: string, - sessionJson: Record | null, + /** + * Stamp pi-ai's transient per-turn state (session resume params, + * display id) onto the given agent session row. Called by the + * worker's run-finished handler with `event.taskSessionId`. + */ + updateSessionParams( + sessionId: string, + sessionParams: 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(); + const sessionParamsJson = sessionParams ? JSON.stringify(sessionParams) : null; getDb() .prepare( - `insert into task_sessions (id, company_id, task_id, agent_id, adapter_type, session_params_json, session_display_id, created_at, updated_at) - values (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + "update task_sessions set session_params_json = ?, session_display_id = ?, updated_at = ? where id = ?", ) - .run( - 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_params_json as sessionParamsJson, session_display_id as sessionDisplayId from task_sessions where id = ?", - ) - .get(taskRow.activeSessionId) as - | { sessionParamsJson: string | null; sessionDisplayId: string | null } - | undefined; - let sessionParams: Record | null = null; - if (row?.sessionParamsJson) { - sessionParams = JSON.parse(row.sessionParamsJson) as Record; - } - return { - sessionParams, - sessionDisplayId: row?.sessionDisplayId ?? null, - }; - }, - - getActiveTaskSession(taskId: string): TaskSessionRecord | null { - const task = getDb() - .prepare("SELECT active_session_id FROM tasks WHERE id = ?") - .get(taskId) as { active_session_id: string | null } | undefined; - if (!task?.active_session_id) return null; - return ( - (getDb() - .prepare( - `SELECT id, company_id as companyId, task_id as taskId, agent_id as agentId, - adapter_type as adapterType, session_params_json as sessionParamsJson, - session_display_id as sessionDisplayId, last_run_id as lastRunId, - last_error as lastError, run_count as runCount, - total_input_tokens as totalInputTokens, total_output_tokens as totalOutputTokens, - rotated_at as rotatedAt, rotation_reason as rotationReason, - handoff_markdown as handoffMarkdown, created_at as createdAt, updated_at as updatedAt - FROM task_sessions WHERE id = ?`, - ) - .get(task.active_session_id) as TaskSessionRecord | undefined) ?? null - ); + .run(sessionParamsJson, sessionDisplayId, ts, sessionId); }, /** - * Fetch a session by its primary key. Sessions are agent-scoped - * (post-v34 migration): a heartbeat run has no task to dereference, - * so the session lookup must take the session id directly. Use with - * `agents.session_id` as the entry point. + * Fetch a session by its primary key. This is the single lookup + * path — the rotation evaluator, the worker's finalizer, and the + * turn-context builder all know which session id they want because + * they either receive it from the agent record or from the worker + * event envelope. */ getSessionById(sessionId: string): TaskSessionRecord | null { return ( (getDb() .prepare( - `SELECT id, company_id as companyId, task_id as taskId, agent_id as agentId, + `SELECT id, company_id as companyId, agent_id as agentId, adapter_type as adapterType, session_params_json as sessionParamsJson, session_display_id as sessionDisplayId, last_run_id as lastRunId, last_error as lastError, run_count as runCount, @@ -175,17 +90,10 @@ export function buildTaskSessionMethods( }, /** - * Returns the agent's active session id, creating one lazily on first - * call. Session is scoped to the agent (not to any particular task); - * switching tasks emits a user turn on the ongoing transcript rather - * than spawning a new session. - * - * Existing agents with pre-migration per-task sessions start fresh - * after this upgrade: their first run creates a new agent-scoped - * session. The old task_sessions + session_entries rows remain in the - * DB as historical records but are not loaded for new runs. This is a - * one-time transcript reset, accepted over a risky merge on a live - * production DB. + * Return the agent's active session id, creating one lazily on + * first call. One session per agent — switching tasks emits a + * "now work on X" user turn on the ongoing transcript rather than + * spawning a new session. */ ensureAgentSession(input: { agentId: string; @@ -200,15 +108,12 @@ export function buildTaskSessionMethods( const sessionId = randomUUID(); const ts = nowIso(); transaction(() => { - // task_id is left as empty string — the session is agent-scoped; - // the column persists for backwards-compatible schema but no - // longer keys the session to a particular task. getDb() .prepare( - `INSERT INTO task_sessions (id, company_id, task_id, agent_id, adapter_type, + `INSERT INTO task_sessions (id, company_id, agent_id, adapter_type, session_params_json, session_display_id, run_count, total_input_tokens, total_output_tokens, created_at, updated_at) - VALUES (?, ?, '', ?, ?, NULL, NULL, 0, 0, 0, ?, ?)`, + VALUES (?, ?, ?, ?, NULL, NULL, 0, 0, 0, ?, ?)`, ) .run(sessionId, input.companyId, input.agentId, input.adapterType, ts, ts); getDb() @@ -226,21 +131,11 @@ export function buildTaskSessionMethods( }, /** - * Point every task currently assigned to an agent at the agent's new - * active session. Called after session rotation so UI paths reading - * per-task active_session_id still see the agent's current session. + * Create a replacement session row after rotation. Inherits the + * agent / company / adapter type from the caller; session params + * and display id carry forward so the next pi-ai turn can resume. */ - updateTasksActiveSessionForAgent(agentId: string, sessionId: string): void { - const ts = nowIso(); - getDb() - .prepare( - "UPDATE tasks SET active_session_id = ?, updated_at = ? WHERE assignee_agent_id = ?", - ) - .run(sessionId, ts, agentId); - }, - createNewSession(input: { - taskId: string; companyId: string; agentId: string; adapterType: string; @@ -251,14 +146,13 @@ export function buildTaskSessionMethods( const ts = nowIso(); getDb() .prepare( - `INSERT INTO task_sessions (id, company_id, task_id, agent_id, adapter_type, + `INSERT INTO task_sessions (id, company_id, agent_id, adapter_type, session_params_json, session_display_id, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, ) .run( sessionId, input.companyId, - input.taskId, input.agentId, input.adapterType, input.sessionParamsJson, @@ -266,9 +160,6 @@ export function buildTaskSessionMethods( ts, ts, ); - getDb() - .prepare("UPDATE tasks SET active_session_id = ?, updated_at = ? WHERE id = ?") - .run(sessionId, ts, input.taskId); return sessionId; }, }; diff --git a/src/main/database/tasks-crud.ts b/src/main/database/tasks-crud.ts index 1bffddb4..e3b6cb4f 100644 --- a/src/main/database/tasks-crud.ts +++ b/src/main/database/tasks-crud.ts @@ -26,8 +26,6 @@ export type TaskListByStatusesRow = { status: string; priority: string; activeRunId: string | null; - sessionJson: string | null; - sessionDisplayId: string | null; costUsd: number | null; requiresUserReview: boolean; startedAt: string | null; diff --git a/src/main/database/tasks-status.ts b/src/main/database/tasks-status.ts index d3970f19..fe262ff9 100644 --- a/src/main/database/tasks-status.ts +++ b/src/main/database/tasks-status.ts @@ -107,7 +107,6 @@ export function buildTaskStatusMethods( t.parent_id as parentId, t.title, t.description, t.assignee_agent_id as assigneeAgentId, t.workspace_id as workspaceId, t.status, t.priority, t.active_run_id as activeRunId, - t.session_json as sessionJson, t.session_display_id as sessionDisplayId, t.cost_usd as costUsd, t.created_at as createdAt, t.updated_at as updatedAt from tasks t diff --git a/src/main/orchestration/turn-context.ts b/src/main/orchestration/turn-context.ts index 098291ff..74884067 100644 --- a/src/main/orchestration/turn-context.ts +++ b/src/main/orchestration/turn-context.ts @@ -154,9 +154,6 @@ function resolveSession( companyId, adapterType: connectorId, }); - // Mirror the agent's active session onto the task pointer so UI paths - // that read per-task active_session_id keep working. - db.updateTasksActiveSessionForAgent(agent.id, agentSessionId); // Evaluate rotation against the agent-scoped session unconditionally. // Heartbeat runs (no task) must rotate too, or their transcript grows @@ -175,18 +172,19 @@ function resolveSession( } // `sessionParams` / `sessionDisplayId` are transient per-run state pi-ai - // stamps onto the task row after a turn (e.g., Gemini's resume id). They - // are only meaningful when a task's cached pointer still matches the - // current (unrotated) session; after rotation the new session has none. + // stamps onto the session row after a turn (e.g., Gemini's resume id). + // They live on the agent session; read directly by session id. let sessionParams: Record | null = null; let sessionDisplayId: string | null = null; - if (task) { - const active = db.getActiveTaskSession(task.id); - if (active && active.id === currentSessionId && active.sessionParamsJson) { - sessionParams = JSON.parse(active.sessionParamsJson) as Record; - sessionDisplayId = active.sessionDisplayId; - } + const currentSession = db.getSessionById(currentSessionId); + if (currentSession?.sessionParamsJson) { + sessionParams = JSON.parse(currentSession.sessionParamsJson) as Record; + sessionDisplayId = currentSession.sessionDisplayId; } + // Suppress the unused-parameter warning while keeping the signature + // stable — task identity is reported through other fields on the + // payload, not through the session row anymore. + void task; return { taskSession: { id: currentSessionId, sessionParams, sessionDisplayId }, diff --git a/src/main/session-management-service.ts b/src/main/session-management-service.ts index bf33fab1..407ea368 100644 --- a/src/main/session-management-service.ts +++ b/src/main/session-management-service.ts @@ -148,10 +148,8 @@ export function createSessionManagementService({ db, logger }: SessionManagement db.archiveSession(session.id, reason, handoff); // 2. Create new session row with fresh counters, carry forward - // session_params. task_id is empty because the session is scoped - // to the agent, not to any particular task. + // session_params so pi-ai can resume. const newSessionId = db.createNewSession({ - taskId: "", companyId: session.companyId, agentId: session.agentId, adapterType: session.adapterType, @@ -159,10 +157,8 @@ export function createSessionManagementService({ db, logger }: SessionManagement sessionDisplayId: session.sessionDisplayId, }); - // 3. Point the agent and any tasks still holding the old session id - // at the freshly-minted session. + // 3. Point the agent at the freshly-minted session. db.setAgentSessionId(session.agentId, newSessionId); - db.updateTasksActiveSessionForAgent(session.agentId, newSessionId); logger.info(`[session] New session ${newSessionId} created for agent ${session.agentId}`); return newSessionId; diff --git a/src/main/worker/handlers/run-finished-helpers.ts b/src/main/worker/handlers/run-finished-helpers.ts index ebae4ac2..fa4f5f86 100644 --- a/src/main/worker/handlers/run-finished-helpers.ts +++ b/src/main/worker/handlers/run-finished-helpers.ts @@ -66,11 +66,19 @@ export function finalizeRun( } } - if (task) { - // Pi-mono sessions are persisted through SqliteSessionStore via RuntimeBridge; - // worker-reported sessionParams is an opaque JSON snapshot of whatever the - // orchestrator wants to stamp onto the task_sessions row. - db.updateTaskSession(task.id, event.sessionParams, event.sessionDisplayId ?? null); + // Stamp pi-ai's transient per-turn state (session resume params, + // display id) onto the agent's current session row. Sessions are + // agent-scoped, so the session id comes from agents.session_id — + // not from a task pointer (that indirection was dropped in v41). + try { + const agent = db.getAgent(run.agentId); + if (agent.sessionId) { + db.updateSessionParams(agent.sessionId, event.sessionParams, event.sessionDisplayId ?? null); + } + } catch (error) { + logger.warn( + `[finalizeRun] updateSessionParams failed for run ${event.runId}: ${error instanceof Error ? error.message : String(error)}`, + ); } leaseRenewalMap.delete(event.runId); diff --git a/src/shared/types/records-core.ts b/src/shared/types/records-core.ts index 94413208..d4c197d6 100644 --- a/src/shared/types/records-core.ts +++ b/src/shared/types/records-core.ts @@ -154,8 +154,6 @@ export interface TaskRecord { status: TaskStatus; priority: "critical" | "high" | "medium" | "low"; activeRunId: string | null; - sessionJson: string | null; - sessionDisplayId: string | null; costUsd: number; taskType: string | null; requiresUserReview: boolean; @@ -276,7 +274,6 @@ export interface BudgetStatusSummary { export interface TaskSessionRecord { id: string; companyId: string; - taskId: string; agentId: string; adapterType: string; sessionParamsJson: string | null; diff --git a/tests/integration/cross-subsystem.spec.ts b/tests/integration/cross-subsystem.spec.ts index f51ecdba..86dcf047 100644 --- a/tests/integration/cross-subsystem.spec.ts +++ b/tests/integration/cross-subsystem.spec.ts @@ -224,7 +224,6 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { // 5. Session management: create session and track usage const sessionId = db.createNewSession({ - taskId, companyId: s.companyId, agentId: s.agentId, adapterType: "gemini-cli", @@ -240,7 +239,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { const task = db.getTask(taskId); expect(task.costUsd).toBeGreaterThan(0); - const session = db.getActiveTaskSession(taskId); + const session = db.getSessionById(sessionId); expect(session).not.toBeNull(); expect(session!.totalInputTokens).toBe(5000); expect(session!.totalOutputTokens).toBe(2000); @@ -382,7 +381,6 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { // Create session const sessionId = db.createNewSession({ - taskId, companyId: s.companyId, agentId: s.agentId, adapterType: "gemini-cli", @@ -411,7 +409,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { sessionService.accumulateTokenUsage(sessionId, 3000, 1000, runId2); // Verify accumulated session state - const session = db.getActiveTaskSession(taskId); + const session = db.getSessionById(sessionId); expect(session).not.toBeNull(); expect(session!.runCount).toBe(2); expect(session!.totalInputTokens).toBe(8000); diff --git a/tests/integration/process-recovery.spec.ts b/tests/integration/process-recovery.spec.ts index 141dd33e..efb1c6dc 100644 --- a/tests/integration/process-recovery.spec.ts +++ b/tests/integration/process-recovery.spec.ts @@ -442,7 +442,6 @@ describe("Process Recovery -- Context Preservation (D-02)", () => { // Create a task session (simulating what happens during normal execution) const sessionId = db.createNewSession({ - taskId, companyId: s.companyId, agentId: s.agentId, adapterType: "gemini-cli", @@ -461,7 +460,7 @@ describe("Process Recovery -- Context Preservation (D-02)", () => { // Get the session and evaluate rotation with maxRuns:1 override // (so it triggers rotation since we did 1 run) - const session = db.getActiveTaskSession(taskId); + const session = db.getSessionById(sessionId); expect(session).not.toBeNull(); const rotationResult = sessionService.evaluateRotation( diff --git a/tests/unit/edge-case-session.test.ts b/tests/unit/edge-case-session.test.ts index ed54749c..79d2829d 100644 --- a/tests/unit/edge-case-session.test.ts +++ b/tests/unit/edge-case-session.test.ts @@ -116,7 +116,6 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { // Create a session const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -130,7 +129,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { } // Get the updated session from DB - const session = db.getActiveTaskSession(taskId); + const session = db.getSessionById(sessionId); expect(session).not.toBeNull(); expect(session!.runCount).toBe(DEFAULT_THRESHOLDS.maxRuns); @@ -145,7 +144,6 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const sessionService = createSessionManagementService({ db, logger: mockLogger }); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -157,7 +155,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { // DEFAULT_THRESHOLDS.maxTokens = 500_000 (total input + output) db.incrementSessionUsage(sessionId, 300000, 250000, "big-run"); - const session = db.getActiveTaskSession(taskId); + const session = db.getSessionById(sessionId); expect(session).not.toBeNull(); const totalTokens = session!.totalInputTokens + session!.totalOutputTokens; expect(totalTokens).toBe(550000); @@ -172,7 +170,6 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const sessionService = createSessionManagementService({ db, logger: mockLogger }); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -205,7 +202,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { unattributedCost: false, }); - const session = db.getActiveTaskSession(taskId)!; + const session = db.getSessionById(sessionId)!; // generateHandoff is agent-scoped: it walks the agent's runs across // all tasks + taskless heartbeats, not a single task. const handoff = sessionService.generateHandoff(session); @@ -230,7 +227,6 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { // Create initial session const oldSessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -246,7 +242,6 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { // Create new session const newSessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -255,7 +250,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { }); // Get the new active session - const newSession = db.getActiveTaskSession(taskId); + const newSession = db.getSessionById(newSessionId); expect(newSession).not.toBeNull(); expect(newSession!.id).toBe(newSessionId); expect(newSession!.id).not.toBe(oldSessionId); @@ -271,7 +266,6 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const sessionService = createSessionManagementService({ db, logger: mockLogger }); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "claude-pro", @@ -284,7 +278,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { db.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`); } - const session = db.getActiveTaskSession(taskId)!; + const session = db.getSessionById(sessionId)!; // Pi-mono delegates compaction to pi-agent-core; AgentCompany still applies rotation uniformly. const result = sessionService.evaluateRotation(session, "claude-pro"); expect(result.shouldRotate).toBe(true); @@ -296,7 +290,6 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const sessionService = createSessionManagementService({ db, logger: mockLogger }); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "openai-codex", @@ -309,7 +302,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { db.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`); } - const session = db.getActiveTaskSession(taskId)!; + const session = db.getSessionById(sessionId)!; // Pi-mono delegates compaction to pi-agent-core; AgentCompany still applies rotation uniformly. const result = sessionService.evaluateRotation(session, "openai-codex"); expect(result.shouldRotate).toBe(true); @@ -322,7 +315,6 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { // Create session and add usage const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -352,7 +344,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { }); // Rotate the session (archives old, creates new) - const oldSession = db.getActiveTaskSession(taskId)!; + const oldSession = db.getSessionById(sessionId)!; sessionService.rotateSession(oldSession); // After rotation, the handoff should be retrievable for the next run diff --git a/tests/unit/session-management.test.ts b/tests/unit/session-management.test.ts index 30b533a8..2423e7ad 100644 --- a/tests/unit/session-management.test.ts +++ b/tests/unit/session-management.test.ts @@ -95,16 +95,14 @@ describe("task_sessions CRUD", () => { await rm(tempDir, { recursive: true, force: true }); }); - it("getActiveTaskSession returns null when active_session_id is null", () => { - const { taskId } = scaffold(db); - const session = db.getActiveTaskSession(taskId); + it("getSessionById returns null for an unknown id", () => { + const session = db.getSessionById("nonexistent"); expect(session).toBeNull(); }); - it("getActiveTaskSession returns TaskSessionRecord when valid session exists", () => { - const { taskId, companyId, agentId } = scaffold(db); + it("getSessionById returns TaskSessionRecord when valid session exists", () => { + const { companyId, agentId } = scaffold(db); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "claude-pro", @@ -112,10 +110,9 @@ describe("task_sessions CRUD", () => { sessionDisplayId: "claude-abc123", }); - const session = db.getActiveTaskSession(taskId); + const session = db.getSessionById(sessionId); expect(session).not.toBeNull(); expect(session!.id).toBe(sessionId); - expect(session!.taskId).toBe(taskId); expect(session!.companyId).toBe(companyId); expect(session!.agentId).toBe(agentId); expect(session!.adapterType).toBe("claude-pro"); @@ -130,9 +127,8 @@ describe("task_sessions CRUD", () => { }); it("incrementSessionUsage atomically updates counters", () => { - const { taskId, companyId, agentId } = scaffold(db); + const { companyId, agentId } = scaffold(db); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "claude-pro", @@ -142,7 +138,7 @@ describe("task_sessions CRUD", () => { db.incrementSessionUsage(sessionId, 1000, 500, "run-001"); - const session = db.getActiveTaskSession(taskId); + const session = db.getSessionById(sessionId); expect(session!.runCount).toBe(1); expect(session!.totalInputTokens).toBe(1000); expect(session!.totalOutputTokens).toBe(500); @@ -150,7 +146,7 @@ describe("task_sessions CRUD", () => { // Second increment accumulates db.incrementSessionUsage(sessionId, 2000, 800, "run-002"); - const session2 = db.getActiveTaskSession(taskId); + const session2 = db.getSessionById(sessionId); expect(session2!.runCount).toBe(2); expect(session2!.totalInputTokens).toBe(3000); expect(session2!.totalOutputTokens).toBe(1300); @@ -158,9 +154,8 @@ describe("task_sessions CRUD", () => { }); it("archiveSession sets rotated_at, rotation_reason, handoff_markdown", () => { - const { taskId, companyId, agentId } = scaffold(db); + const { companyId, agentId } = scaffold(db); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "claude-pro", @@ -170,17 +165,16 @@ describe("task_sessions CRUD", () => { db.archiveSession(sessionId, "max_runs", "# Handoff\nSession reached 20 runs."); - const session = db.getActiveTaskSession(taskId); + const session = db.getSessionById(sessionId); expect(session!.rotatedAt).toBeTruthy(); expect(session!.rotationReason).toBe("max_runs"); expect(session!.handoffMarkdown).toBe("# Handoff\nSession reached 20 runs."); }); - it("createNewSession creates session and updates tasks.active_session_id", () => { - const { taskId, companyId, agentId } = scaffold(db); + it("createNewSession returns an id; adapter type persists", () => { + const { companyId, agentId } = scaffold(db); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -191,8 +185,7 @@ describe("task_sessions CRUD", () => { expect(typeof sessionId).toBe("string"); expect(sessionId.length).toBeGreaterThan(0); - // active_session_id should point to new session - const session = db.getActiveTaskSession(taskId); + const session = db.getSessionById(sessionId); expect(session).not.toBeNull(); expect(session!.id).toBe(sessionId); expect(session!.adapterType).toBe("gemini-cli"); @@ -297,7 +290,6 @@ describe("session management service", () => { const session: TaskSessionRecord = { id: "sess-1", companyId: "co-1", - taskId: "task-1", agentId: "agent-1", adapterType: "gemini-cli", sessionParamsJson: null, @@ -323,7 +315,6 @@ describe("session management service", () => { const session: TaskSessionRecord = { id: "sess-1", companyId: "co-1", - taskId: "task-1", agentId: "agent-1", adapterType: "gemini-cli", sessionParamsJson: null, @@ -349,7 +340,6 @@ describe("session management service", () => { const session: TaskSessionRecord = { id: "sess-1", companyId: "co-1", - taskId: "task-1", agentId: "agent-1", adapterType: "gemini-cli", sessionParamsJson: null, @@ -376,7 +366,6 @@ describe("session management service", () => { const session: TaskSessionRecord = { id: "sess-1", companyId: "co-1", - taskId: "task-1", agentId: "agent-1", adapterType: "gemini-cli", sessionParamsJson: null, @@ -407,7 +396,6 @@ describe("session management service", () => { const session: TaskSessionRecord = { id: "sess-1", companyId: "co-1", - taskId: "task-1", agentId: "agent-1", adapterType: "claude-pro", sessionParamsJson: null, @@ -438,7 +426,6 @@ describe("session management service", () => { const session: TaskSessionRecord = { id: "sess-1", companyId: "co-1", - taskId: "task-1", agentId: "agent-1", adapterType: "gemini-cli", sessionParamsJson: null, @@ -464,7 +451,6 @@ describe("session management service", () => { const session: TaskSessionRecord = { id: "sess-1", companyId: "co-1", - taskId: "task-1", agentId: "agent-1", adapterType: "gemini-cli", sessionParamsJson: null, @@ -490,7 +476,6 @@ describe("session management service", () => { const session: TaskSessionRecord = { id: "sess-1", companyId: "co-1", - taskId: "task-1", agentId: "agent-1", adapterType: "gemini-cli", sessionParamsJson: null, @@ -524,7 +509,6 @@ describe("session management service", () => { it("accumulateTokenUsage calls db.incrementSessionUsage with correct args", () => { const { taskId, companyId, agentId } = scaffold(db); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "claude-pro", @@ -535,7 +519,7 @@ describe("session management service", () => { svc.accumulateTokenUsage(sessionId, 5000, 2000, "run-abc"); - const session = db.getActiveTaskSession(taskId); + const session = db.getSessionById(sessionId); expect(session!.runCount).toBe(1); expect(session!.totalInputTokens).toBe(5000); expect(session!.totalOutputTokens).toBe(2000); @@ -550,7 +534,6 @@ describe("session management service", () => { it("generateHandoff produces markdown with task title and run history", () => { const { taskId, companyId, agentId, workspaceId } = scaffold(db); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -575,7 +558,7 @@ describe("session management service", () => { // Increment session usage to have stats db.incrementSessionUsage(sessionId, 10000, 5000, run.id); - const session = db.getActiveTaskSession(taskId)!; + const session = db.getSessionById(sessionId)!; const svc = createSessionManagementService({ db, logger: mockLogger }); // Handoff is now agent-scoped: it walks the agent's recent runs // (across tasks + heartbeats) rather than a single task. The task @@ -598,7 +581,6 @@ describe("session management service", () => { it("rotateSession archives old session, creates new, copies session_params", () => { const { taskId, companyId, agentId } = scaffold(db); const oldSessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -609,7 +591,7 @@ describe("session management service", () => { // Add some usage db.incrementSessionUsage(oldSessionId, 300000, 200000, "run-10"); - const oldSession = db.getActiveTaskSession(taskId)!; + const oldSession = db.getSessionById(oldSessionId)!; const svc = createSessionManagementService({ db, logger: mockLogger }); const newSessionId = svc.rotateSession(oldSession); @@ -624,8 +606,8 @@ describe("session management service", () => { expect(archivedRows[0].rotationReason).toBe("threshold_exceeded"); expect(archivedRows[0].handoffMarkdown).toContain("# Session Handoff"); - // New session should be active, with fresh counters and copied params - const newSession = db.getActiveTaskSession(taskId)!; + // New session is a distinct row, with fresh counters and copied params + const newSession = db.getSessionById(newSessionId)!; expect(newSession.id).toBe(newSessionId); expect(newSession.runCount).toBe(0); expect(newSession.totalInputTokens).toBe(0); @@ -645,7 +627,6 @@ describe("session management service", () => { // Create and rotate a session const session1Id = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -653,7 +634,7 @@ describe("session management service", () => { sessionDisplayId: null, }); db.incrementSessionUsage(session1Id, 100000, 50000, "run-1"); - const session1 = db.getActiveTaskSession(taskId)!; + const session1 = db.getSessionById(session1Id)!; svc.rotateSession(session1); const handoff = svc.getHandoffContext(taskId); @@ -664,7 +645,6 @@ describe("session management service", () => { it("getHandoffContext returns null when no rotated sessions exist", () => { const { taskId, companyId, agentId } = scaffold(db); db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -685,7 +665,6 @@ describe("session management service", () => { it("getUsageDelta returns token difference between snapshot and current totals", () => { const { taskId, companyId, agentId } = scaffold(db); const sessionId = db.createNewSession({ - taskId, companyId, agentId, adapterType: "gemini-cli", @@ -696,7 +675,7 @@ describe("session management service", () => { // Pre-populate some usage db.incrementSessionUsage(sessionId, 1000, 500, "run-1"); - const session = db.getActiveTaskSession(taskId)!; + const session = db.getSessionById(sessionId)!; const svc = createSessionManagementService({ db, logger: mockLogger }); // Snapshot current state