From 29c1dff972a8ab0898bd7b918928e1efcecf2bec Mon Sep 17 00:00:00 2001 From: Daniel Ehrhardt Date: Thu, 29 Oct 2026 03:34:24 +0100 Subject: [PATCH] Look up chat previews only for the listed page and find unfinished runs by status The chat list computed the preview, pause and handover columns for every chat before sorting and cutting the page, so its cost grew with all chats instead of the 100 shown. The runs list with only unfinished statuses walked every run by date because the planner preferred the created_at index for the ORDER BY. --- packages/core/src/runner/runner.ts | 16 ++++++++------ packages/core/src/services/conversations.ts | 6 ++++-- packages/core/test/conversations.test.ts | 3 +++ packages/core/test/runner.test.ts | 24 +++++++++++++++++++++ 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/core/src/runner/runner.ts b/packages/core/src/runner/runner.ts index eca9e27b..6a17db4e 100644 --- a/packages/core/src/runner/runner.ts +++ b/packages/core/src/runner/runner.ts @@ -259,6 +259,8 @@ export function getRun(id: string): Run { return toRun(row); } +const UNFINISHED = new Set(["queued", "running", "paused"]); + export function listRuns(opts: { agentId?: string; status?: string; conversationId?: string; parentRunId?: string; limit?: number } = {}): Run[] { const where: string[] = []; const params: (string | number)[] = []; @@ -274,17 +276,17 @@ export function listRuns(opts: { agentId?: string; status?: string; conversation where.push("conversation_id = ?"); params.push(opts.conversationId); } - if (opts.status) { - const statuses = opts.status.split(",").map((s) => s.trim()).filter(Boolean); - if (statuses.length) { - where.push(`status IN (${statuses.map(() => "?").join(", ")})`); - params.push(...statuses); - } + const statuses = (opts.status ?? "").split(",").map((s) => s.trim()).filter(Boolean); + if (statuses.length) { + where.push(`status IN (${statuses.map(() => "?").join(", ")})`); + params.push(...statuses); } + // Unfinished runs are a handful: find them by status instead of walking every run newest first. + const order = statuses.length && statuses.every((s) => UNFINISHED.has(s)) ? "+created_at" : "created_at"; const limit = Math.min(Math.max(1, Math.floor(opts.limit ?? 50)), 500); params.push(limit); const runs = all( - `SELECT * FROM runs ${where.length ? `WHERE ${where.join(" AND ")}` : ""} ORDER BY created_at DESC, rowid DESC LIMIT ?`, + `SELECT * FROM runs ${where.length ? `WHERE ${where.join(" AND ")}` : ""} ORDER BY ${order} DESC, rowid DESC LIMIT ?`, ...params, ).map(toRun); // Why a paused run stands still: paused by the human, waiting for Claude's limit, or for the human's answer. diff --git a/packages/core/src/services/conversations.ts b/packages/core/src/services/conversations.ts index a3fe06a5..e7e604b1 100644 --- a/packages/core/src/services/conversations.ts +++ b/packages/core/src/services/conversations.ts @@ -512,9 +512,11 @@ export function listConversations( } const limit = Math.min(Math.max(1, Math.floor(opts.limit ?? 100)), 500); params.push(limit); + const order = `${opts.archived ? "" : "c.pinned DESC, "}COALESCE(c.last_message_at, c.created_at) DESC`; + // Pick the page first: the preview and the derived columns are then looked up for those chats only, not for every one. const rows = all( - `SELECT c.*, ${PREVIEW_SQL}, ${FOLLOWUP_SQL}, ${PAUSE_SQL}, ${DELEGATED_SQL} FROM ${FROM_SQL} WHERE ${where.join(" AND ")} - ORDER BY ${opts.archived ? "" : "c.pinned DESC, "}COALESCE(c.last_message_at, c.created_at) DESC LIMIT ?`, + `SELECT c.*, ${PREVIEW_SQL}, ${FOLLOWUP_SQL}, ${PAUSE_SQL}, ${DELEGATED_SQL} FROM ${FROM_SQL} + WHERE c.id IN (SELECT c.id FROM ${FROM_SQL} WHERE ${where.join(" AND ")} ORDER BY ${order} LIMIT ?) ORDER BY ${order}`, ...params, ); return rows.map(toConversation); diff --git a/packages/core/test/conversations.test.ts b/packages/core/test/conversations.test.ts index e05f63a1..fac0172a 100644 --- a/packages/core/test/conversations.test.ts +++ b/packages/core/test/conversations.test.ts @@ -68,6 +68,9 @@ describe("conversation list", () => { expect(list.slice(0, 3).map((x) => x.id)).toEqual([c.id, b.id, a.id]); expect(list.find((x) => x.id === a.id)!.preview).toBe("hello from alpha"); expect(list.every((x) => x.running === false)).toBe(true); + const page = listConversations({ agentId: agent.id, limit: 2 }); + expect(page.map((x) => x.id)).toEqual([c.id, b.id]); + expect(page[1]!.preview).toBe("find the needle here"); expect(listConversations({ search: "needle" }).map((x) => x.id)).toEqual([b.id]); expect(listConversations({ search: "alph" }).map((x) => x.id)).toEqual([a.id]); diff --git a/packages/core/test/runner.test.ts b/packages/core/test/runner.test.ts index b8af10d6..8d50b039 100644 --- a/packages/core/test/runner.test.ts +++ b/packages/core/test/runner.test.ts @@ -30,6 +30,7 @@ import { findRunLog, getRun, listActiveRuns, + listRuns, recoverInterruptedRuns, waitForRun, } from "../src/runner/runner"; @@ -592,3 +593,26 @@ describe("recoverInterruptedRuns", () => { expect(all("SELECT 1 FROM message_images WHERE message_id = ?", messageId)).toEqual([]); }); }); + +describe("listRuns", () => { + test("filters by status newest first, unfinished and finished alike", () => { + const conv = createConversation({ agentId: agent.id }); + const at = (m: number) => `2031-01-01T00:0${m}:00.000Z`; + const rows = [ + ["run_list_test_000001", "succeeded", at(1)], + ["run_list_test_000002", "running", at(2)], + ["run_list_test_000003", "queued", at(3)], + ["run_list_test_000004", "succeeded", at(4)], + ["run_list_test_000005", "running", at(4)], + ] as const; + for (const [id, status, created] of rows) insert("runs", { id, agent_id: agent.id, conversation_id: conv.id, trigger: "chat", status, prompt: "x", created_at: created }); + expect(listRuns({ conversationId: conv.id, status: "queued,running,paused" }).map((r) => r.id)).toEqual([ + "run_list_test_000005", + "run_list_test_000003", + "run_list_test_000002", + ]); + expect(listRuns({ conversationId: conv.id, status: "running", limit: 1 }).map((r) => r.id)).toEqual(["run_list_test_000005"]); + expect(listRuns({ conversationId: conv.id, status: "succeeded" }).map((r) => r.id)).toEqual(["run_list_test_000004", "run_list_test_000001"]); + sql("DELETE FROM runs WHERE id LIKE 'run_list_test_%'"); + }); +});