mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 02:20:38 +09:00
[codex] Add runtime lifecycle recovery and live issue visibility (#4419)
This commit is contained in:
parent
9a8d219949
commit
5a0c1979cf
121 changed files with 9625 additions and 2044 deletions
30
ui/src/api/heartbeats.test.ts
Normal file
30
ui/src/api/heartbeats.test.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mockApi = vi.hoisted(() => ({
|
||||
get: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./client", () => ({
|
||||
api: mockApi,
|
||||
}));
|
||||
|
||||
import { heartbeatsApi } from "./heartbeats";
|
||||
|
||||
describe("heartbeatsApi.liveRunsForCompany", () => {
|
||||
beforeEach(() => {
|
||||
mockApi.get.mockReset();
|
||||
mockApi.get.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
it("keeps the legacy numeric minCount signature", async () => {
|
||||
await heartbeatsApi.liveRunsForCompany("company-1", 4);
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith("/companies/company-1/live-runs?minCount=4");
|
||||
});
|
||||
|
||||
it("passes minCount and limit options to the company live-runs endpoint", async () => {
|
||||
await heartbeatsApi.liveRunsForCompany("company-1", { minCount: 50, limit: 50 });
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith("/companies/company-1/live-runs?minCount=50&limit=50");
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
import type { HeartbeatRun, HeartbeatRunEvent, InstanceSchedulerHeartbeatAgent, WorkspaceOperation } from "@paperclipai/shared";
|
||||
import type {
|
||||
HeartbeatRun,
|
||||
HeartbeatRunEvent,
|
||||
InstanceSchedulerHeartbeatAgent,
|
||||
WorkspaceOperation,
|
||||
} from "@paperclipai/shared";
|
||||
import { api } from "./client";
|
||||
|
||||
export interface RunLivenessFields {
|
||||
|
|
@ -20,12 +25,15 @@ export interface ActiveRunForIssue {
|
|||
agentId: string;
|
||||
agentName: string;
|
||||
adapterType: string;
|
||||
logBytes?: number | null;
|
||||
lastOutputBytes?: number | null;
|
||||
issueId?: string | null;
|
||||
livenessState?: RunLivenessFields["livenessState"];
|
||||
livenessReason?: string | null;
|
||||
continuationAttempt?: number;
|
||||
lastUsefulActionAt?: string | Date | null;
|
||||
nextAction?: string | null;
|
||||
outputSilence?: HeartbeatRun["outputSilence"];
|
||||
}
|
||||
|
||||
export interface LiveRunForIssue {
|
||||
|
|
@ -39,12 +47,23 @@ export interface LiveRunForIssue {
|
|||
agentId: string;
|
||||
agentName: string;
|
||||
adapterType: string;
|
||||
logBytes?: number | null;
|
||||
lastOutputBytes?: number | null;
|
||||
issueId?: string | null;
|
||||
livenessState?: RunLivenessFields["livenessState"];
|
||||
livenessReason?: string | null;
|
||||
continuationAttempt?: number;
|
||||
lastUsefulActionAt?: string | null;
|
||||
nextAction?: string | null;
|
||||
outputSilence?: HeartbeatRun["outputSilence"];
|
||||
}
|
||||
|
||||
export interface WatchdogDecisionInput {
|
||||
runId: string;
|
||||
decision: "snooze" | "continue" | "dismissed_false_positive";
|
||||
evaluationIssueId?: string | null;
|
||||
reason?: string | null;
|
||||
snoozedUntil?: string | null;
|
||||
}
|
||||
|
||||
export const heartbeatsApi = {
|
||||
|
|
@ -71,12 +90,31 @@ export const heartbeatsApi = {
|
|||
`/workspace-operations/${operationId}/log?offset=${encodeURIComponent(String(offset))}&limitBytes=${encodeURIComponent(String(limitBytes))}`,
|
||||
),
|
||||
cancel: (runId: string) => api.post<void>(`/heartbeat-runs/${runId}/cancel`, {}),
|
||||
recordWatchdogDecision: (input: WatchdogDecisionInput) =>
|
||||
api.post(`/heartbeat-runs/${input.runId}/watchdog-decisions`, {
|
||||
decision: input.decision,
|
||||
evaluationIssueId: input.evaluationIssueId ?? null,
|
||||
reason: input.reason ?? null,
|
||||
snoozedUntil: input.snoozedUntil ?? null,
|
||||
}),
|
||||
liveRunsForIssue: (issueId: string) =>
|
||||
api.get<LiveRunForIssue[]>(`/issues/${issueId}/live-runs`),
|
||||
activeRunForIssue: (issueId: string) =>
|
||||
api.get<ActiveRunForIssue | null>(`/issues/${issueId}/active-run`),
|
||||
liveRunsForCompany: (companyId: string, minCount?: number) =>
|
||||
api.get<LiveRunForIssue[]>(`/companies/${companyId}/live-runs${minCount ? `?minCount=${minCount}` : ""}`),
|
||||
liveRunsForCompany: (
|
||||
companyId: string,
|
||||
options?: number | { minCount?: number; limit?: number },
|
||||
) => {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (typeof options === "number") {
|
||||
searchParams.set("minCount", String(options));
|
||||
} else if (options) {
|
||||
if (options.minCount) searchParams.set("minCount", String(options.minCount));
|
||||
if (options.limit) searchParams.set("limit", String(options.limit));
|
||||
}
|
||||
const qs = searchParams.toString();
|
||||
return api.get<LiveRunForIssue[]>(`/companies/${companyId}/live-runs${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
listInstanceSchedulerAgents: () =>
|
||||
api.get<InstanceSchedulerHeartbeatAgent[]>("/instance/scheduler-heartbeats"),
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue