mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
Narrow heartbeat log endpoint lookups
(cherry picked from commit 46b224081b2a5aa286da53f8ec0f530d7ccccc90)
This commit is contained in:
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
Reference in new issue
Block a user