mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 21:59:38 +01:00
fix(recovery): continue Retry after completed file preservation (#18845)
Reuse completed file preservation for task Retry and show bounded admission explanations, including newer saved messages and identifier routes. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
519b345d69
commit
a80eec1164
13 files changed
+276
-18
No files matched your search
@@ -70,6 +70,17 @@ describe("ordinary task interruption", () => {
|
||||
await render({ ...failed, workspaceFailure, state: "working" });
|
||||
expect(container.textContent).toBe("");
|
||||
});
|
||||
it("shows why an accepted Retry is waiting and keeps another attempt available", async () => {
|
||||
const retryWait = { reason: "remote_cleanup", message: "Retry is waiting for the previous environment to stop. Your message is saved." };
|
||||
await render({ ...failed, retryWait });
|
||||
expect(container.textContent).toContain(retryWait.message);
|
||||
expect(container.textContent).not.toContain("Couldn’t finish this attempt.");
|
||||
await act(async () => container.querySelector<HTMLButtonElement>("button")!.click());
|
||||
expect(retry).toHaveBeenCalledOnce();
|
||||
await render({ ...failed, retryWait }, { canRetry: false });
|
||||
expect(container.textContent).toContain(retryWait.message);
|
||||
expect(container.querySelector("button")).toBeNull();
|
||||
});
|
||||
it("shows an unknown provider's original explanation as text and preserves read-only access", async () => {
|
||||
const providerFailure = { schema: "paperclip.provider-failure.v1" as const, provider: "new-provider", phase: "startup" as const,
|
||||
category: "unknown" as const, retryability: "unknown" as const, httpStatus: 429,
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ExecutionBlockerNotice({ presentation, canRetry, onRetry, optimi
|
||||
canRetry={canRetry && presentation.canRetry} onRetry={onRetry} error={error} className={className} />;
|
||||
if (error && !optimistic && !takingLonger) return <div role="alert" className={cn("flex items-center justify-between gap-2 text-sm text-destructive", className)}><span>{error.message}</span>{canRetry && <Button variant="outline" size="sm" onClick={onRetry}>Retry</Button>}</div>;
|
||||
if (takingLonger) return <div role="status" className={cn("flex items-center justify-between gap-2 text-sm text-muted-foreground", className)}><span>Taking longer than usual…</span>{canRetry && <Button variant="outline" size="sm" onClick={onRetry}>Retry</Button>}</div>;
|
||||
if (!optimistic && presentation?.providerFailure && ["failed", "retry_scheduled", "waiting_for_user"].includes(presentation.state)) {
|
||||
if (!optimistic && !presentation?.retryWait && presentation?.providerFailure && ["failed", "retry_scheduled", "waiting_for_user"].includes(presentation.state)) {
|
||||
return <ProviderFailureNotice failure={presentation.providerFailure} className={className}
|
||||
actions={canRetry && presentation.canRetry ? <Button variant="outline" size="sm" onClick={onRetry}>Retry</Button> : undefined}>
|
||||
{presentation.state === "retry_scheduled" && <p>Paperclip will retry automatically.</p>}
|
||||
@@ -43,6 +43,7 @@ export function ExecutionBlockerNotice({ presentation, canRetry, onRetry, optimi
|
||||
{presentation.state === "failed" && <AlertCircle className="size-5 shrink-0 text-destructive" aria-hidden="true" />}
|
||||
<span className="min-w-0 flex-1">{presentation.state === "retry_scheduled"
|
||||
? `Trying again in ${seconds > 60 ? `${Math.ceil(seconds / 60)}m` : `${seconds}s`}…`
|
||||
: presentation.retryWait ? presentation.retryWait.message
|
||||
: presentation.workspaceFailure
|
||||
? <>Couldn’t prepare the task folders. {presentation.workspaceFailure.message}</>
|
||||
: "Couldn’t finish this attempt. Your message is saved."}</span>
|
||||
|
||||
Reference in new issue
Block a user