Merge ebb41dd853 into c9e3bb7ca4
This commit is contained in:
commit
919a60ebd0
|
|
@ -698,6 +698,36 @@ describeEmbeddedPostgres("heartbeat resolved dependency wake reconciliation", ()
|
|||
expect(wakes.some((wake) => ["queued", "claimed", "completed"].includes(wake.status))).toBe(true);
|
||||
});
|
||||
|
||||
it.each(["paused", "terminated", "pending_approval"] as const)(
|
||||
"skips resolved-dependency backstop candidates whose assignee is %s",
|
||||
async (assigneeStatus) => {
|
||||
await enableAutoRecovery();
|
||||
const { companyId, agentId, blockedIssueId } =
|
||||
await seedResolvedDependencyBackstopFixture({ workspaceState: "none" });
|
||||
// Flip the assignee into a directly non-invokable state. The backstop
|
||||
// must drop the candidate at the SQL layer — otherwise it enqueues a
|
||||
// wake that always fails with 409 and loops on every heartbeat.
|
||||
await db.update(agents).set({ status: assigneeStatus }).where(eq(agents.id, agentId));
|
||||
|
||||
const result = await heartbeatService(db).reconcileIssueGraphLiveness();
|
||||
|
||||
expect(result.dependencyWakeBackstopChecked).toBe(0);
|
||||
expect(result.dependencyWakesHealed).toBe(0);
|
||||
expect(result.dependencyWakeIssueIds).not.toContain(blockedIssueId);
|
||||
|
||||
const wakes = await db
|
||||
.select({ id: agentWakeupRequests.id })
|
||||
.from(agentWakeupRequests)
|
||||
.where(
|
||||
and(
|
||||
eq(agentWakeupRequests.companyId, companyId),
|
||||
eq(agentWakeupRequests.reason, "issue_blockers_resolved"),
|
||||
),
|
||||
);
|
||||
expect(wakes).toHaveLength(0);
|
||||
},
|
||||
);
|
||||
|
||||
it("waits for workspace finalize before healing a resolved blocked dependent", async () => {
|
||||
const { companyId, agentId, blockedIssueId, blockerIssueId, executionWorkspaceId } =
|
||||
await seedResolvedDependencyBackstopFixture({ workspaceState: "not_finalized" });
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ import {
|
|||
buildIssueBlockersResolvedWakeStateKey,
|
||||
findExistingIssueBlockersResolvedWakeForReadyState,
|
||||
} from "../issue-dependency-wakeups.js";
|
||||
import { evaluateAgentInvokabilityFromDb } from "../agent-invokability.js";
|
||||
import { DIRECT_NON_INVOKABLE_STATUSES, evaluateAgentInvokabilityFromDb } from "../agent-invokability.js";
|
||||
import { isHeartbeatWakeOnDemandEnabled } from "../heartbeat-policy.js";
|
||||
import {
|
||||
DEFAULT_MAX_SUCCESSFUL_RUN_HANDOFF_ATTEMPTS,
|
||||
|
|
@ -5219,6 +5219,16 @@ export function recoveryService(
|
|||
isNull(issues.conversationAgentId),
|
||||
visibleIssueCondition(),
|
||||
sql`${issues.assigneeAgentId} is not null`,
|
||||
// Skip candidates whose assignee is directly non-invokable (paused,
|
||||
// terminated, pending_approval). Without this filter the backstop
|
||||
// repeatedly enqueues wakes that always fail with 409, producing a
|
||||
// steady stream of `wake_target_not_invokable` warnings for the same
|
||||
// issue every heartbeat until an operator intervenes. Matches the
|
||||
// atomic guard in the heartbeat's `agents.status -> running` update.
|
||||
// Org-chain invalidity is not covered here for cost reasons; the
|
||||
// enqueue path still filters those cases via
|
||||
// `evaluateAgentInvokabilityFromDb`.
|
||||
notInArray(agents.status, [...DIRECT_NON_INVOKABLE_STATUSES]),
|
||||
];
|
||||
if (opts?.companyId) filters.push(eq(issues.companyId, opts.companyId));
|
||||
if (afterIssueId) filters.push(gt(issues.id, afterIssueId));
|
||||
|
|
@ -5241,6 +5251,7 @@ export function recoveryService(
|
|||
})
|
||||
.from(issueRelations)
|
||||
.innerJoin(issues, eq(issueRelations.relatedIssueId, issues.id))
|
||||
.innerJoin(agents, eq(agents.id, issues.assigneeAgentId))
|
||||
.where(and(...filters))
|
||||
.orderBy(asc(issues.id))
|
||||
.limit(RESOLVED_DEPENDENCY_WAKE_BACKSTOP_CANDIDATE_LIMIT);
|
||||
|
|
@ -5256,6 +5267,7 @@ export function recoveryService(
|
|||
totalCount: sql<number>`count(*) over()::int`,
|
||||
})
|
||||
.from(issues)
|
||||
.innerJoin(agents, eq(agents.id, issues.assigneeAgentId))
|
||||
.where(and(...filters))
|
||||
.orderBy(asc(issues.id))
|
||||
.limit(RESOLVED_DEPENDENCY_WAKE_BACKSTOP_CANDIDATE_LIMIT);
|
||||
|
|
|
|||
Loading…
Reference in New Issue