mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
Accept lifecycle-ready running workers in catalog readiness
This commit is contained in:
1 parent
8cd9e8bc6b
commit
81987af644
3 files changed
+40
-2
No files matched your search
@@ -3,6 +3,12 @@ import type { CredentialName } from "./types.js";
|
||||
import type { ChatExternalInputRequirement } from "./chat-private-inputs.js";
|
||||
import type { ChatScenario } from "./chat-feature-cases.js";
|
||||
|
||||
/** Lifecycle readiness permits an already-busy worker; this is not run completion proof. */
|
||||
export function chatCatalogAgentIsReady(value: { lifecycleState?: string | null; status?: string | null; lifecycleError?: unknown }): boolean {
|
||||
return value.lifecycleState === "ready" && !value.lifecycleError
|
||||
&& ["idle", "active", "running"].includes(value.status ?? "");
|
||||
}
|
||||
|
||||
export interface ChatCatalogCheck { id: string; passed: boolean; detail: string }
|
||||
export interface ChatCatalogGrade { passed: boolean; checks: ChatCatalogCheck[] }
|
||||
export interface ChatCatalogContext {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { chatCatalogNativePhaseIsTimely, chatCatalogNativePhaseRemainingBudget } from "./chat-catalog-contract.js";
|
||||
import { chatCatalogAgentIsReady, chatCatalogNativePhaseIsTimely, chatCatalogNativePhaseRemainingBudget } from "./chat-catalog-contract.js";
|
||||
import { chatNativeTimeoutEvidence } from "./chat-native-timeout-policy.js";
|
||||
import { completeNativeSlackFileConditional, dispatchNativeSlackIngress, prepareNativeSlackOtherThread, slackOtherThreadBoundaryMatches, slackNativeRunCheck } from "./chat-slack-ingress.js";
|
||||
import { runScopedCompanionSurface } from "./chat-companion-surface.js";
|
||||
@@ -93,7 +93,7 @@ export async function runChatCatalogFlow(context: ChatCatalogContext) {
|
||||
if (implementation.setupPolicy?.readiness !== "preserve-scenario") for (const agent of agents) await pollUntil({ label: `catalog agent readiness ${agent.id}`,
|
||||
deadlineAt: Math.min(deadlineAt, Date.now() + 45_000), intervalMs: 500,
|
||||
load: () => input.api.get<{ lifecycleState: string; status: string; lifecycleError?: string }>(`/api/agents/${agent.id}`),
|
||||
accept: value => value.lifecycleState === "ready" && ["idle", "active"].includes(value.status),
|
||||
accept: chatCatalogAgentIsReady,
|
||||
reject: value => value.lifecycleError ? "Agent setup failed; diagnostic details withheld" : undefined,
|
||||
});
|
||||
const scopes = [{ scopeType: "company", scopeId: input.fixtures.company.id },
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { chatCatalogAgentIsReady } from "./chat-catalog-contract.js";
|
||||
import { chatCatalogRunStatusAllowed } from "./chat-catalog-run-policy.js";
|
||||
import { pollUntil } from "./api.js";
|
||||
|
||||
describe("catalog agent lifecycle readiness", () => {
|
||||
it.each(["idle", "active", "running"])("accepts a lifecycle-ready %s agent", status => {
|
||||
expect(chatCatalogAgentIsReady({ lifecycleState: "ready", status, lifecycleError: null })).toBe(true);
|
||||
});
|
||||
it.each(["paused", "error", "terminated", "pending_approval", "verifying", "failed", ""])("rejects non-ready operational status %s", status => {
|
||||
expect(chatCatalogAgentIsReady({ lifecycleState: "ready", status })).toBe(false);
|
||||
});
|
||||
it.each(["verifying", "pending", "failed", "paused", undefined, null])("rejects unready lifecycle %s even while running", lifecycleState => {
|
||||
expect(chatCatalogAgentIsReady({ lifecycleState, status: "running" })).toBe(false);
|
||||
});
|
||||
it("rejects lifecycle errors even when the status says ready and running", () => {
|
||||
expect(chatCatalogAgentIsReady({ lifecycleState: "ready", status: "running", lifecycleError: "failure" })).toBe(false);
|
||||
expect(chatCatalogAgentIsReady({ lifecycleState: "ready" })).toBe(false);
|
||||
});
|
||||
it("the actual polling helper accepts a ready busy worker without another readiness request", async () => {
|
||||
let reads = 0;
|
||||
const actual = { id: "held-worker", lifecycleState: "ready", status: "running", lifecycleError: null };
|
||||
expect(await pollUntil({ label: "held worker readiness", deadlineAt: Date.now() + 45_000,
|
||||
load: async () => { reads++; return actual; }, accept: chatCatalogAgentIsReady })).toBe(actual);
|
||||
expect(reads).toBe(1);
|
||||
});
|
||||
it("accepting a busy agent does not accept a running canonical native as a completed run", () => {
|
||||
expect(chatCatalogAgentIsReady({ lifecycleState: "ready", status: "running" })).toBe(true);
|
||||
expect(chatCatalogRunStatusAllowed({ implementation: {}, run: { status: "running" } as never,
|
||||
companyId: "company", leadId: "lead" })).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user