Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions packages/core/src/runner/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)[] = [];
Expand All @@ -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<RunRow>(
`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.
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/services/conversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConversationRow>(
`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);
Expand Down
3 changes: 3 additions & 0 deletions packages/core/test/conversations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
24 changes: 24 additions & 0 deletions packages/core/test/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
findRunLog,
getRun,
listActiveRuns,
listRuns,
recoverInterruptedRuns,
waitForRun,
} from "../src/runner/runner";
Expand Down Expand Up @@ -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_%'");
});
});
Loading