Narrow heartbeat log endpoint lookups

(cherry picked from commit 46b224081b2a5aa286da53f8ec0f530d7ccccc90)
This commit is contained in:
Dotta committed 2026-04-15 09:12:52 -05:00
1 parent 5abc8cca8b
commit 10ade0582b
3 files changed
+72 -5

No files matched your search

@@ -9,6 +9,8 @@ const mockAgentService = vi.hoisted(() => ({
const mockHeartbeatService = vi.hoisted(() => ({
getRunIssueSummary: vi.fn(),
getActiveRunIssueSummaryForAgent: vi.fn(),
getRunLogAccess: vi.fn(),
readLog: vi.fn(),
}));
const mockIssueService = vi.hoisted(() => ({
@@ -100,6 +102,19 @@ describe("agent live run routes", () => {
issueId: "issue-1",
});
mockHeartbeatService.getActiveRunIssueSummaryForAgent.mockResolvedValue(null);
mockHeartbeatService.getRunLogAccess.mockResolvedValue({
id: "run-1",
companyId: "company-1",
logStore: "local_file",
logRef: "logs/run-1.ndjson",
});
mockHeartbeatService.readLog.mockResolvedValue({
runId: "run-1",
store: "local_file",
logRef: "logs/run-1.ndjson",
content: "chunk",
nextOffset: 5,
});
});
it("returns a compact active run payload for issue polling", async () => {
@@ -163,4 +178,27 @@ describe("agent live run routes", () => {
adapterType: "codex_local",
});
});
it("uses narrow run log metadata lookups for log polling", async () => {
const res = await request(await createApp()).get("/api/heartbeat-runs/run-1/log?offset=12&limitBytes=64");
expect(res.status, JSON.stringify(res.body)).toBe(200);
expect(mockHeartbeatService.getRunLogAccess).toHaveBeenCalledWith("run-1");
expect(mockHeartbeatService.readLog).toHaveBeenCalledWith({
id: "run-1",
companyId: "company-1",
logStore: "local_file",
logRef: "logs/run-1.ndjson",
}, {
offset: 12,
limitBytes: 64,
});
expect(res.body).toEqual({
runId: "run-1",
store: "local_file",
logRef: "logs/run-1.ndjson",
content: "chunk",
nextOffset: 5,
});
});
});
+2 -2
View File
@@ -2479,7 +2479,7 @@ export function agentRoutes(db: Db) {
router.get("/heartbeat-runs/:runId/log", async (req, res) => {
const runId = req.params.runId as string;
const run = await heartbeat.getRun(runId);
const run = await heartbeat.getRunLogAccess(runId);
if (!run) {
res.status(404).json({ error: "Heartbeat run not found" });
return;
@@ -2488,7 +2488,7 @@ export function agentRoutes(db: Db) {
const offset = Number(req.query.offset ?? 0);
const limitBytes = Number(req.query.limitBytes ?? 256000);
const result = await heartbeat.readLog(runId, {
const result = await heartbeat.readLog(run, {
offset: Number.isFinite(offset) ? offset : 0,
limitBytes: Number.isFinite(limitBytes) ? limitBytes : 256000,
});
+32 -3
View File
@@ -473,6 +473,13 @@ const heartbeatRunSafeColumns = {
resultJson: heartbeatRunSafeResultJsonColumn,
} as const;
const heartbeatRunLogAccessColumns = {
id: heartbeatRuns.id,
companyId: heartbeatRuns.companyId,
logStore: heartbeatRuns.logStore,
logRef: heartbeatRuns.logRef,
} as const;
const heartbeatRunIssueSummaryColumns = {
id: heartbeatRuns.id,
status: heartbeatRuns.status,
@@ -1513,6 +1520,14 @@ export function heartbeatService(db: Db) {
.then((rows) => rows[0] ?? null);
}
async function getRunLogAccess(runId: string) {
return db
.select(heartbeatRunLogAccessColumns)
.from(heartbeatRuns)
.where(eq(heartbeatRuns.id, runId))
.then((rows) => rows[0] ?? null);
}
async function getIssueExecutionContext(companyId: string, issueId: string) {
return db
.select({
@@ -2942,7 +2957,10 @@ export function heartbeatService(db: Db) {
async function escalateStrandedAssignedIssue(input: {
issue: typeof issues.$inferSelect;
previousStatus: "todo" | "in_progress";
latestRun: typeof heartbeatRuns.$inferSelect | null;
latestRun: Pick<
typeof heartbeatRuns.$inferSelect,
"id" | "status" | "error" | "errorCode" | "contextSnapshot"
> | null;
comment: string;
}) {
const updated = await issuesSvc.update(input.issue.id, {
@@ -5175,6 +5193,8 @@ export function heartbeatService(db: Db) {
getRun,
getRunLogAccess,
getRuntimeState: async (agentId: string) => {
const state = await getRuntimeState(agentId);
const agent = await getAgent(agentId);
@@ -5248,8 +5268,17 @@ export function heartbeatService(db: Db) {
.orderBy(asc(heartbeatRunEvents.seq))
.limit(Math.max(1, Math.min(limit, 1000))),
readLog: async (runId: string, opts?: { offset?: number; limitBytes?: number }) => {
const run = await getRun(runId);
readLog: async (
runOrLookup: string | {
id: string;
companyId: string;
logStore: string | null;
logRef: string | null;
},
opts?: { offset?: number; limitBytes?: number },
) => {
const run = typeof runOrLookup === "string" ? await getRunLogAccess(runOrLookup) : runOrLookup;
const runId = typeof runOrLookup === "string" ? runOrLookup : runOrLookup.id;
if (!run) throw notFound("Heartbeat run not found");
if (!run.logStore || !run.logRef) throw notFound("Run log not found");