Accept lifecycle-ready running workers in catalog readiness

This commit is contained in:
Dotta committed 2026-10-28 08:15:26 -05:00
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 {
+2 -2
View File
@@ -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);
});
});