Q158: run history and run output come from the database, not the snapshot window

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-09 02:31:43 -04:00
1 parent a075f931d5
commit 9d166e3fc9
18 files changed
+222 -35

No files matched your search

@@ -48,6 +48,10 @@ export const organizationContracts = {
setTaskBlocked: z.strictObject({ orgId: id, taskId: id, reason: z.string().nullable() }) satisfies z.ZodType<C.SetTaskBlockedInput>,
taskDeleteImpact: z.strictObject({ orgId: id, taskId: id }),
cancelQueuedRun: z.strictObject({ orgId: id, runId: id }),
runHistory: z.union([
z.strictObject({ orgId: id, beforeRunId: id.nullable(), limit: z.number().int().min(1).max(500) }),
z.strictObject({ orgId: id, beforeRunId: id.nullable(), from: z.strictObject({ createdAt: z.string(), seq: z.number().int() }) }),
]),
updateTaskStatus: z.strictObject({ orgId: id, taskId: id, status: z.enum(TASK_STATUS) }) satisfies z.ZodType<C.UpdateTaskStatusInput>,
runTask: z.strictObject({ orgId: id, taskId: id }) satisfies z.ZodType<C.RunTaskInput>,
createGoal: z.strictObject({ orgId: id, title: z.string(), description: z.string(), ownerAgentId: nullableId }) satisfies z.ZodType<C.CreateGoalInput>,
+2
View File
@@ -38,6 +38,7 @@ import { normalizeExternalBrowserUrl } from "./external-url";
import { resumeGoal } from "../services/goal-review-service";
import { stopAllRuns, stopRun } from "../services/run-control";
import { cancelQueuedRun } from "../services/run-cancel";
import { runHistory } from "../services/run-history";
export interface IpcDeps {
connection: DatabaseConnection;
@@ -168,6 +169,7 @@ export function registerIpcHandlers(deps: IpcDeps): void {
handle("setTaskBlocked", (input) => mutate(() => organization.setTaskBlocked(input)));
handle("taskDeleteImpact", (input) => ok(organization.taskDeleteImpact(input.orgId, input.taskId)));
handle("cancelQueuedRun", (input) => mutate(() => cancelQueuedRun(deps.connection.db, input.orgId, input.runId)));
handle("runHistory", (input) => ok(runHistory(deps.connection.db, input.orgId, input.beforeRunId, "limit" in input ? { limit: input.limit } : { from: input.from })));
handle("deleteTask", (input) => mutate(() => organization.deleteTask(input)));
handle("updateTaskStatus", (input) => mutate(() => organization.updateTaskStatus(input.orgId, input.taskId, input.status)));
handle("runTask", async (input) => {
+2 -1
View File
@@ -40,7 +40,8 @@ export type RequestPurpose = "agent_turn" | "goal_audit" | "model_check" | "manu
export function requestsForRun(db: DatabaseSync, orgId: string, runId: string): RunRequestRecord[] {
return db.prepare(`
select id, purpose, model, state, error_message as errorMessage, input_tokens as inputTokens, output_tokens as outputTokens,
cost_cents as costCents, created_at as createdAt, completed_at as completedAt
cost_cents as costCents, created_at as createdAt, completed_at as completedAt,
input_char_count as inputCharCount, output_char_count as outputCharCount, response_text as responseText
from provider_requests where org_id = ? and task_run_id = ? order by rowid
`).all(orgId, runId) as unknown as RunRequestRecord[];
}
+35
View File
@@ -0,0 +1,35 @@
import type { DatabaseSync } from "node:sqlite";
import type { RunCursor, RunHistoryPage, TaskRunRecord } from "../../shared/domain";
import { camel } from "./snapshot-service";
/** Which older runs to return: the next page before the cursor, or every run from a position (which survives its run's deletion) up to it. */
export type RunHistoryScope = { limit: number } | { from: RunCursor };
/** Runs before a loaded one (or the newest when none is loaded), oldest first, in time then insertion order, and whether older ones exist. */
export function runHistory(db: DatabaseSync, orgId: string, beforeRunId: string | null, scope: RunHistoryScope): RunHistoryPage {
const conditions = ["org_id = ?"];
const params: Array<string | number> = [orgId];
let boundary: RunCursor | null = null;
if (beforeRunId !== null) {
const before = db.prepare("select created_at, rowid from task_runs where id = ? and org_id = ?").get(beforeRunId, orgId) as { created_at: string; rowid: number } | undefined;
if (!before) throw new Error("The oldest loaded run was not found.");
conditions.push("(created_at, rowid) < (?, ?)");
params.push(before.created_at, before.rowid);
boundary = { createdAt: before.created_at, seq: before.rowid };
}
if ("from" in scope) {
conditions.push("(created_at, rowid) >= (?, ?)");
params.push(scope.from.createdAt, scope.from.seq);
}
const limit = "limit" in scope ? scope.limit : -1;
const rows = db.prepare(`select *, rowid as seq from task_runs where ${conditions.join(" and ")} order by created_at desc, rowid desc limit ?`)
.all(...params, limit === -1 ? -1 : limit + 1) as Array<Record<string, unknown>>;
const page = (limit === -1 ? rows : rows.slice(0, limit)).reverse();
const first = page[0];
const oldest = first ? { createdAt: first.created_at as string, seq: first.seq as number } : null;
// A range is measured from its pinned position, which holds even when the runs at it were deleted.
const hasMore = "limit" in scope ? rows.length > limit : db.prepare(`
select 1 from task_runs where org_id = ? and (created_at, rowid) < (?, ?) limit 1
`).get(orgId, scope.from.createdAt, scope.from.seq) !== undefined;
return { runs: page.map(({ seq: _seq, ...row }) => camel(row) as unknown as TaskRunRecord), hasMore, oldest, boundary };
}
+1 -1
View File
@@ -95,7 +95,7 @@ export class SnapshotService {
workspaces,
goals: list(this.db, "goals", "created_at asc"),
projects: list(this.db, "projects", "created_at asc"),
taskRuns: recentList<AppSnapshot["taskRuns"][number]>(this.db, "task_runs", "created_at desc", 200).reverse(),
taskRuns: recentList<AppSnapshot["taskRuns"][number]>(this.db, "task_runs", "created_at desc, rowid desc", 200).reverse(),
approvals: list(this.db, "approvals", "created_at desc"),
chatSpaces: list(this.db, "chat_spaces", "created_at asc"),
chatMemberships: list(this.db, "chat_memberships", "space_id asc, principal_type asc, principal_id asc"),
+1
View File
@@ -93,6 +93,7 @@ const api: DesktopApi = {
setTaskBlocked: (input) => invoke(IPC_CHANNELS.setTaskBlocked, input),
taskDeleteImpact: (input) => invoke(IPC_CHANNELS.taskDeleteImpact, input),
cancelQueuedRun: (input) => invoke(IPC_CHANNELS.cancelQueuedRun, input),
runHistory: (input) => invoke(IPC_CHANNELS.runHistory, input),
revokeConnector: (input) => invoke(IPC_CHANNELS.revokeConnector, input),
refreshComposioAccounts: (input) => invoke(IPC_CHANNELS.refreshComposioAccounts, input),
search: (input) => invoke(IPC_CHANNELS.search, input),
+1 -1
View File
@@ -96,7 +96,7 @@ export function App() {
office: <OfficePage {...pageProps} />,
settings: <SettingsPage {...pageProps} />,
}[route];
const pageKey = focus ? `${route}:${focus.kind}:${focus.id}` : route;
const pageKey = `${snapshot.activeOrgId}:${route}${focus ? `:${focus.kind}:${focus.id}` : ""}`;
return (
<AvatarDirectoryContext.Provider value={avatarDirectory}>
+2
View File
@@ -52,6 +52,8 @@ export const zh: Record<string, string> = {
"Showing {shown} of {total}": "显示 {total} 项中的 {shown} 项",
"Show earlier decisions": "显示更早的决定",
"Cancel run": "取消运行",
"Show older runs": "显示更早的运行",
"These are all the runs.": "已显示全部运行。",
"Running…": "运行中…",
"A cancelled session can start again on the next heartbeat; block the task to keep it stopped.": "取消的会话可能在下一次心跳时再次开始;如需保持停止,请阻塞该任务。",
"A new session on the task picks up from what earlier sessions did and where they stopped.": "任务的新会话会接着之前会话做过的工作和停下的位置继续。",
+58 -10
View File
@@ -1,7 +1,8 @@
import { RunRequestList } from "./runs/RunRequestList";
import { useMemo, useState } from "react";
import { useRunRequests } from "./runs/use-run-requests";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Avatar, Button, Pill, Segmented } from "../shared/ui";
import type { AgentRecord, ProviderRecord, TaskRecord, TaskRunRecord } from "../../../shared/domain";
import type { AgentRecord, ProviderRecord, RunCursor, TaskRecord, TaskRunRecord } from "../../../shared/domain";
import type { PageProps } from "./types";
import { activeOrgId, formatRelative } from "./types";
import { t } from "../i18n/i18n";
@@ -17,11 +18,48 @@ const FILTERS: Array<{ id: StatusFilter; label: string }> = [
export function RunsPage({ snapshot, actions, navigate, focus }: PageProps) {
const orgId = activeOrgId(snapshot);
const taskRuns = useMemo(() => snapshot.taskRuns.filter((r) => r.orgId === orgId), [snapshot.taskRuns, orgId]);
const recentRuns = useMemo(() => snapshot.taskRuns.filter((r) => r.orgId === orgId), [snapshot.taskRuns, orgId]);
// Older runs page in from the database; their range, from a pinned position, refreshes with each reload.
const [pinned, setPinned] = useState<RunCursor | null>(null);
const [olderRuns, setOlderRuns] = useState<TaskRunRecord[]>([]);
const [olderDone, setOlderDone] = useState(false);
const [historyError, setHistoryError] = useState<string | null>(null);
const anchorId = recentRuns.length > 0 ? recentRuns[0].id : null;
useEffect(() => {
if (!pinned) return;
let cancelled = false;
window.agentCompany.runHistory({ orgId, beforeRunId: anchorId, from: pinned }).then((result) => {
if (cancelled) return;
if (!result.ok) throw new Error(result.error.message);
setOlderRuns(result.data.runs);
setOlderDone(!result.data.hasMore);
setHistoryError(null);
}).catch((error: unknown) => {
if (!cancelled) setHistoryError(error instanceof Error ? error.message : String(error));
});
return () => {
cancelled = true;
};
}, [orgId, anchorId, pinned, recentRuns]);
const taskRuns = useMemo(() => [...olderRuns, ...recentRuns], [olderRuns, recentRuns]);
const oldestShownId = taskRuns.length > 0 ? taskRuns[0].id : null;
const loadOlder = useCallback(() => {
window.agentCompany.runHistory({ orgId, beforeRunId: oldestShownId, limit: 100 }).then((result) => {
if (!result.ok) throw new Error(result.error.message);
setHistoryError(null);
// With nothing older, the oldest shown run's position is pinned so later reloads keep the range.
const position = result.data.oldest ?? result.data.boundary;
if (position) setPinned(position);
setOlderDone(!result.data.hasMore);
}).catch((error: unknown) => setHistoryError(error instanceof Error ? error.message : String(error)));
}, [orgId, oldestShownId]);
// While the snapshot holds none of this company's runs and nothing is pinned, each reload asks the database for the newest page.
useEffect(() => {
if (recentRuns.length === 0 && pinned === null) loadOlder();
}, [recentRuns, pinned, loadOlder]);
const tasks = useMemo(() => snapshot.tasks.filter((t) => t.orgId === orgId), [snapshot.tasks, orgId]);
const agents = useMemo(() => snapshot.agents.filter((a) => a.orgId === orgId), [snapshot.agents, orgId]);
const providers = useMemo(() => snapshot.providers.filter((p) => p.orgId === orgId), [snapshot.providers, orgId]);
const providerRequests = useMemo(() => snapshot.providerRequests.filter((r) => r.orgId === orgId), [snapshot.providerRequests, orgId]);
const [filter, setFilter] = useState<StatusFilter>("all");
const [selectedId, setSelectedId] = useState<string | null>(focus?.kind === "run" ? focus.id : null);
const [search, setSearch] = useState("");
@@ -62,7 +100,8 @@ export function RunsPage({ snapshot, actions, navigate, focus }: PageProps) {
<Segmented value={filter} onChange={setFilter} options={FILTERS} />
</div>
<div className="pane-scroll">
{filtered.map((run) => {
{/* Newest first, so "show older runs" continues below. */}
{[...filtered].reverse().map((run) => {
const task = tasks.find((t) => t.id === run.taskId);
const agent = agents.find((a) => a.id === run.agentId);
const active = run.id === selectedId;
@@ -86,11 +125,17 @@ export function RunsPage({ snapshot, actions, navigate, focus }: PageProps) {
);
})}
{filtered.length === 0 && <div style={{ padding: 16, fontSize: 14, color: "var(--text-3)" }}>{t("No runs")}</div>}
{historyError && <div className="form-error" role="alert" style={{ padding: "0 16px" }}>{historyError}</div>}
<div style={{ padding: 12, display: "flex", justifyContent: "center" }}>
{olderDone
? <span style={{ fontSize: 12, color: "var(--text-3)" }}>{t("These are all the runs.")}</span>
: <Button size="small" onClick={loadOlder}>{t("Show older runs")}</Button>}
</div>
</div>
</aside>
<div style={{ flex: 1, minWidth: 0, overflow: "auto", padding: 24 }}>
{selected ? (
<RunDetail key={selected.id} run={selected} tasks={tasks} agents={agents} providers={providers} providerRequests={providerRequests} actions={actions} onOpenTask={(taskId) => navigate("work", { kind: "task", id: taskId })} />
<RunDetail key={selected.id} run={selected} tasks={tasks} agents={agents} providers={providers} actions={actions} onOpenTask={(taskId) => navigate("work", { kind: "task", id: taskId })} />
) : (
<div style={{ display: "grid", placeItems: "center", height: "100%", color: "var(--text-3)", fontSize: 14 }}>
{t("Select a run")}
@@ -101,19 +146,22 @@ export function RunsPage({ snapshot, actions, navigate, focus }: PageProps) {
);
}
function RunDetail({ run, tasks, agents, providers, providerRequests, actions, onOpenTask }: {
function RunDetail({ run, tasks, agents, providers, actions, onOpenTask }: {
actions: PageProps["actions"];
onOpenTask: (taskId: string) => void;
run: TaskRunRecord;
tasks: TaskRecord[];
agents: AgentRecord[];
providers: ProviderRecord[];
providerRequests: Array<{ id: string; orgId: string; providerId: string; model: string; state: string; responseText: string | null; inputCharCount: number; outputCharCount: number; createdAt: string; inputTokens: number | null; outputTokens: number | null; costCents: number | null }>;
}) {
const task = tasks.find((t) => t.id === run.taskId);
const agent = agents.find((a) => a.id === run.agentId);
const provider = providers.find((p) => p.id === run.providerId);
const request = run.providerRequestId ? providerRequests.find((r) => r.id === run.providerRequestId) : null;
// The run's calls come from the database, so an old run still shows its output; refetched with each snapshot reload.
const loadedRequests = useRunRequests(run.orgId, run.id, run);
const request = loadedRequests && "requests" in loadedRequests && run.providerRequestId
? loadedRequests.requests.find((item) => item.id === run.providerRequestId) ?? null
: null;
const started = run.startedAt ? new Date(run.startedAt) : null;
const completed = run.completedAt ? new Date(run.completedAt) : null;
@@ -165,7 +213,7 @@ function RunDetail({ run, tasks, agents, providers, providerRequests, actions, o
<MetaLabel>{t("Context")}</MetaLabel><div className="mono" style={{ fontSize: 12, overflow: "hidden", textOverflow: "ellipsis" }}>{run.executionContextId}</div>
</div>
<RunRequestList orgId={run.orgId} runId={run.id} state={run.state} />
<RunRequestList loaded={loadedRequests} />
{run.state === "superseded" && run.errorMessage && (
<div className="card" style={{ padding: 16, marginBottom: 16 }}>
+3 -21
View File
@@ -1,5 +1,4 @@
import { useEffect, useState } from "react";
import type { RunRequestRecord } from "../../../../shared/provider-runtime-domain";
import type { LoadedRequests } from "./use-run-requests";
import { Pill } from "../../shared/ui";
import { t } from "../../i18n/i18n";
@@ -12,25 +11,8 @@ function purposeLabel(purpose: string): string {
}
/** Every model call the run made, in order, so a turn or an audit can be found by its task. */
export function RunRequestList({ orgId, runId, state }: { orgId: string; runId: string; state: string }) {
// Fetched calls are kept with the run they belong to, so switching runs never shows another run's chain.
const [loaded, setLoaded] = useState<{ runId: string; requests: RunRequestRecord[] } | { runId: string; error: string } | null>(null);
useEffect(() => {
let cancelled = false;
window.agentCompany.runRequests({ orgId, runId })
.then((result) => {
if (cancelled) return;
setLoaded(result.ok ? { runId, requests: result.data } : { runId, error: result.error.message });
})
.catch((reason: unknown) => {
if (!cancelled) setLoaded({ runId, error: reason instanceof Error ? reason.message : String(reason) });
});
return () => {
cancelled = true;
};
}, [orgId, runId, state]);
if (!loaded || loaded.runId !== runId) return null;
export function RunRequestList({ loaded }: { loaded: LoadedRequests | null }) {
if (!loaded) return null;
if ("error" in loaded) return <div className="form-error" role="alert">{loaded.error}</div>;
const { requests } = loaded;
return (
@@ -0,0 +1,25 @@
import { useEffect, useState } from "react";
import type { RunRequestRecord } from "../../../../shared/provider-runtime-domain";
export type LoadedRequests = { runId: string; requests: RunRequestRecord[] } | { runId: string; error: string };
/** Every model call of one run, read from the database (not the snapshot's window), refetched whenever `refreshKey` changes. */
export function useRunRequests(orgId: string, runId: string, refreshKey: unknown): LoadedRequests | null {
// Fetched calls are kept with the run they belong to, so switching runs never shows another run's chain.
const [loaded, setLoaded] = useState<LoadedRequests | null>(null);
useEffect(() => {
let cancelled = false;
window.agentCompany.runRequests({ orgId, runId })
.then((result) => {
if (cancelled) return;
setLoaded(result.ok ? { runId, requests: result.data } : { runId, error: result.error.message });
})
.catch((reason: unknown) => {
if (!cancelled) setLoaded({ runId, error: reason instanceof Error ? reason.message : String(reason) });
});
return () => {
cancelled = true;
};
}, [orgId, runId, refreshKey]);
return loaded && loaded.runId === runId ? loaded : null;
}
+3 -1
View File
@@ -1,4 +1,4 @@
import type { TaskDeleteImpact } from "./domain";
import type { RunCursor, RunHistoryPage, TaskDeleteImpact } from "./domain";
import type { SpaceHistoryPage, ThreadHistory } from "./chat-domain";
import type { RunRequestRecord } from "./provider-runtime-domain";
import type { ApprovalDrift } from "./approval-pin";
@@ -207,6 +207,8 @@ export interface DesktopApi {
taskDeleteImpact(input: { orgId: string; taskId: string }): Promise<DesktopResult<TaskDeleteImpact>>;
/** Cancels a run that has not started yet. */
cancelQueuedRun(input: { orgId: string; runId: string }): Promise<DesktopResult<AppSnapshot>>;
/** Runs older than a loaded one, for history beyond the snapshot's recent runs. */
runHistory(input: { orgId: string; beforeRunId: string | null } & ({ limit: number } | { from: RunCursor })): Promise<DesktopResult<RunHistoryPage>>;
chatSpaceHistory(input: { orgId: string; spaceId: string; beforeMessageId: string | null } & ({ limit: number } | { fromMessageId: string })): Promise<DesktopResult<SpaceHistoryPage>>;
revokeConnector(input: RevokeConnectorInput): Promise<DesktopResult<AppSnapshot>>;
search(input: SearchInput): Promise<DesktopResult<SearchResultRecord[]>>;
+14
View File
@@ -105,6 +105,20 @@ export interface TaskDeleteImpact {
pendingApprovals: number;
}
/** A position in run history (time, then insertion), which stays valid after its run is deleted. */
export interface RunCursor {
createdAt: string;
seq: number;
}
/** A page of older runs, oldest first: whether older ones exist, the position of its oldest run, and of the run it was read before. */
export interface RunHistoryPage {
runs: TaskRunRecord[];
hasMore: boolean;
oldest: RunCursor | null;
boundary: RunCursor | null;
}
export interface TaskRecord {
id: string;
orgId: string;
+1
View File
@@ -86,6 +86,7 @@ export const IPC_CHANNELS = {
setTaskBlocked: "agentcompany:set-task-blocked",
taskDeleteImpact: "agentcompany:task-delete-impact",
cancelQueuedRun: "agentcompany:cancel-queued-run",
runHistory: "agentcompany:run-history",
revokeConnector: "agentcompany:revoke-connector",
search: "agentcompany:search",
rendererEvent: "agentcompany:renderer-event",
+3
View File
@@ -56,4 +56,7 @@ export interface RunRequestRecord {
costCents: number | null;
createdAt: string;
completedAt: string | null;
inputCharCount: number;
outputCharCount: number;
responseText: string | null;
}
+14
View File
@@ -290,6 +290,20 @@ test("the pinned CEO conversation is the Messages front door", async () => {
await expect(start).toBeVisible();
await expect(chatLog.getByText("General line 0", { exact: true })).toBeVisible();
// Runs older than the loaded window page in from the database, down to the first one.
await page.getByRole("button", { name: "Runs" }).click();
const runRows = page.locator(".pane-scroll .nav-row");
await expect(runRows).toHaveCount(200);
await page.getByRole("button", { name: "Show older runs" }).click();
await expect.poll(async () => runRows.count()).toBeGreaterThan(200);
for (let click = 0; click < 10 && await page.getByRole("button", { name: "Show older runs" }).isVisible(); click += 1) {
await page.getByRole("button", { name: "Show older runs" }).click();
await page.waitForTimeout(300);
}
await expect(page.getByText("These are all the runs.")).toBeVisible();
await page.getByRole("button", { name: "Messages" }).click();
await page.locator(".convo-item", { hasText: "general" }).click();
// The header lists the real members, how each agent replies, and who can read.
await page.getByRole("button", { name: /^Members \(/ }).click();
const roster = page.getByRole("menu");
+3
View File
@@ -40,6 +40,9 @@ describe("a run's model calls", () => {
});
const runId = claimNextQueuedRun(connection)!.id;
await expect(execution.executeRun(orgId, runId)).rejects.toThrow();
// Each call carries its own output, so the run's detail does not depend on a snapshot window.
const turnCall = requestsForRun(connection.db, orgId, runId).find((request) => request.purpose === "agent_turn" && request.state === "succeeded")!;
expect([typeof turnCall.responseText, typeof turnCall.inputCharCount, typeof turnCall.outputCharCount]).toEqual(["string", "number", "number"]);
expect(requestsForRun(connection.db, orgId, runId).map((request) => [request.purpose, request.state])).toEqual([
["model_check", "succeeded"],
["agent_turn", "succeeded"],
+50
View File
@@ -0,0 +1,50 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { runHistory } from "../../src/main/services/run-history";
import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS } from "../../src/shared/domain";
describe("run history", () => {
test("pages back past the snapshot's recent runs to the very first", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-run-history-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let taskId = "";
let agentId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "Acme", employeeName: "Yang", mission: "m", goal: "g" });
agentId = organization.createAgent({ orgId, name: "Ada", role: "Engineer", department: "Engineering", modelProviderId: null, modelId: null, budgetCents: 1000, managerAgentId: null });
taskId = organization.createTask({
orgId, title: "Work", description: "d", status: TASK_STATUS.BACKLOG, assigneeAgentId: agentId, risk: RISK_LEVEL.LOW,
projectId: null, goalId: null, workspaceId: null, priority: TASK_PRIORITY.NORMAL, requiresFilesystem: false,
});
// Every run shares one instant, so only insertion order can page them correctly.
const insert = connection.db.prepare("insert into task_runs(id, org_id, task_id, agent_id, trigger, state, created_at) values (?, ?, ?, ?, 'manual', 'completed', '2026-04-01T00:00:00.000Z')");
for (let index = 0; index < 230; index += 1) insert.run(`run_${String(index).padStart(3, "0")}`, orgId, taskId, agentId);
});
const recent = new SnapshotService(connection.db).load().taskRuns;
expect(recent).toHaveLength(200);
expect(recent[0].id).toBe("run_030");
const older = runHistory(connection.db, orgId, recent[0].id, { limit: 20 });
expect(older.runs.map((run) => run.id)).toEqual(Array.from({ length: 20 }, (_, index) => `run_${String(index + 10).padStart(3, "0")}`));
expect(older.hasMore).toBe(true);
const oldest = runHistory(connection.db, orgId, older.runs[0].id, { limit: 20 });
expect(oldest.runs.map((run) => run.id)).toEqual(Array.from({ length: 10 }, (_, index) => `run_${String(index).padStart(3, "0")}`));
expect(oldest.hasMore).toBe(false);
expect(() => runHistory(connection.db, orgId, "run_missing", { limit: 20 })).toThrow("The oldest loaded run was not found.");
// The loaded range refreshes in one query from its oldest run up to the cursor.
const range = runHistory(connection.db, orgId, recent[0].id, { from: oldest.oldest! });
expect([range.runs.length, range.runs[0].id, range.runs.at(-1)!.id, range.hasMore]).toEqual([30, "run_000", "run_029", false]);
// The pinned position holds after its run is deleted, and the range still says whether older runs exist.
connection.db.prepare("delete from task_runs where id in ('run_000', 'run_001')").run();
expect(runHistory(connection.db, orgId, recent[0].id, { from: older.oldest! })).toMatchObject({ hasMore: true });
expect(runHistory(connection.db, orgId, recent[0].id, { from: oldest.oldest! }).runs[0].id).toBe("run_002");
// With nothing loaded, the newest runs come first.
expect(runHistory(connection.db, orgId, null, { limit: 2 }).runs.map((run) => run.id)).toEqual(["run_228", "run_229"]);
connection.close();
});
});