mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 09:07:46 +02:00
## Thinking Path > - Paperclip orchestrates AI agents through issue checkout, heartbeat runs, routines, and auditable control-plane state > - The runtime path has to recover from lost local processes, transient adapter failures, blocked dependencies, and routine coalescing without stranding work > - The existing branch carried several reliability fixes across heartbeat scheduling, issue runtime controls, routine dispatch, and operator-facing run state > - These changes belong together because they share backend contracts, migrations, and runtime status semantics > - This pull request groups the control-plane/runtime slice so it can merge independently from board UI polish and adapter sandbox work > - The benefit is safer heartbeat recovery, clearer runtime controls, and more predictable recurring execution behavior ## What Changed - Adds bounded heartbeat retry scheduling, scheduled retry state, and Codex transient failure recovery handling. - Tightens heartbeat process recovery, blocker wake behavior, issue comment wake handling, routine dispatch coalescing, and activity/dashboard bounds. - Adds runtime-control MCP tools and Paperclip skill docs for issue workspace runtime management. - Adds migrations `0061_lively_thor_girl.sql` and `0062_routine_run_dispatch_fingerprint.sql`. - Surfaces retry state in run ledger/agent UI and keeps related shared types synchronized. ## Verification - `pnpm exec vitest run server/src/__tests__/heartbeat-retry-scheduling.test.ts server/src/__tests__/heartbeat-process-recovery.test.ts server/src/__tests__/routines-service.test.ts` - `pnpm exec vitest run src/tools.test.ts` from `packages/mcp-server` ## Risks - Medium risk: this touches heartbeat recovery and routine dispatch, which are central execution paths. - Migration order matters if split branches land out of order: merge this PR before branches that assume the new runtime/routine fields. - Runtime retry behavior should be watched in CI and in local operator smoke tests because it changes how transient failures are resumed. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent runtime, shell/git tool use enabled. Exact hosted model build and context window are not exposed in this Paperclip heartbeat environment. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge
52 lines
1.8 KiB
TypeScript
52 lines
1.8 KiB
TypeScript
import type { ActivityEvent, RunLivenessState } from "@paperclipai/shared";
|
|
import { api } from "./client";
|
|
|
|
export type { RunLivenessState } from "@paperclipai/shared";
|
|
|
|
export interface RunForIssue {
|
|
runId: string;
|
|
status: string;
|
|
agentId: string;
|
|
adapterType: string;
|
|
startedAt: string | null;
|
|
finishedAt: string | null;
|
|
createdAt: string;
|
|
invocationSource: string;
|
|
usageJson: Record<string, unknown> | null;
|
|
resultJson: Record<string, unknown> | null;
|
|
logBytes?: number | null;
|
|
retryOfRunId?: string | null;
|
|
scheduledRetryAt?: string | null;
|
|
scheduledRetryAttempt?: number;
|
|
scheduledRetryReason?: string | null;
|
|
retryExhaustedReason?: string | null;
|
|
livenessState?: RunLivenessState | null;
|
|
livenessReason?: string | null;
|
|
continuationAttempt?: number;
|
|
lastUsefulActionAt?: string | null;
|
|
nextAction?: string | null;
|
|
}
|
|
|
|
export interface IssueForRun {
|
|
issueId: string;
|
|
identifier: string | null;
|
|
title: string;
|
|
status: string;
|
|
priority: string;
|
|
}
|
|
|
|
export const activityApi = {
|
|
list: (companyId: string, filters?: { entityType?: string; entityId?: string; agentId?: string; limit?: number }) => {
|
|
const params = new URLSearchParams();
|
|
if (filters?.entityType) params.set("entityType", filters.entityType);
|
|
if (filters?.entityId) params.set("entityId", filters.entityId);
|
|
if (filters?.agentId) params.set("agentId", filters.agentId);
|
|
if (filters?.limit) params.set("limit", String(filters.limit));
|
|
const qs = params.toString();
|
|
return api.get<ActivityEvent[]>(`/companies/${companyId}/activity${qs ? `?${qs}` : ""}`);
|
|
},
|
|
forIssue: (issueId: string) => api.get<ActivityEvent[]>(`/issues/${issueId}/activity`),
|
|
runsForIssue: (issueId: string) => api.get<RunForIssue[]>(`/issues/${issueId}/runs`),
|
|
issuesForRun: (runId: string) => api.get<IssueForRun[]>(`/heartbeat-runs/${runId}/issues`),
|
|
};
|