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:
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>,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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"),
|
||||
|
||||
Reference in new issue
Block a user