[codex] Add runtime lifecycle recovery and live issue visibility (#4419)

This commit is contained in:
Dotta 2026-04-24 15:50:32 -05:00 committed by GitHub
parent 9a8d219949
commit 5a0c1979cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
121 changed files with 9625 additions and 2044 deletions

View file

@ -1,324 +1,16 @@
export type IssueLivenessSeverity = "warning" | "critical";
export type IssueLivenessState =
| "blocked_by_unassigned_issue"
| "blocked_by_uninvokable_assignee"
| "blocked_by_cancelled_issue"
| "invalid_review_participant";
export interface IssueLivenessIssueInput {
id: string;
companyId: string;
identifier: string | null;
title: string;
status: string;
projectId?: string | null;
goalId?: string | null;
parentId?: string | null;
assigneeAgentId?: string | null;
assigneeUserId?: string | null;
createdByAgentId?: string | null;
createdByUserId?: string | null;
executionState?: Record<string, unknown> | null;
}
export interface IssueLivenessRelationInput {
companyId: string;
blockerIssueId: string;
blockedIssueId: string;
}
export interface IssueLivenessAgentInput {
id: string;
companyId: string;
name: string;
role: string;
title?: string | null;
status: string;
reportsTo?: string | null;
}
export interface IssueLivenessExecutionPathInput {
companyId: string;
issueId: string | null;
agentId?: string | null;
status: string;
}
export interface IssueLivenessDependencyPathEntry {
issueId: string;
identifier: string | null;
title: string;
status: string;
}
export interface IssueLivenessFinding {
issueId: string;
companyId: string;
identifier: string | null;
state: IssueLivenessState;
severity: IssueLivenessSeverity;
reason: string;
dependencyPath: IssueLivenessDependencyPathEntry[];
recommendedOwnerAgentId: string | null;
recommendedOwnerCandidateAgentIds: string[];
recommendedAction: string;
incidentKey: string;
}
export interface IssueGraphLivenessInput {
issues: IssueLivenessIssueInput[];
relations: IssueLivenessRelationInput[];
agents: IssueLivenessAgentInput[];
activeRuns?: IssueLivenessExecutionPathInput[];
queuedWakeRequests?: IssueLivenessExecutionPathInput[];
}
const INVOKABLE_AGENT_STATUSES = new Set(["active", "idle", "running", "error"]);
const BLOCKING_AGENT_STATUSES = new Set(["paused", "terminated", "pending_approval"]);
function issueLabel(issue: IssueLivenessIssueInput) {
return issue.identifier ?? issue.id;
}
function pathEntry(issue: IssueLivenessIssueInput): IssueLivenessDependencyPathEntry {
return {
issueId: issue.id,
identifier: issue.identifier,
title: issue.title,
status: issue.status,
};
}
function isInvokableAgent(agent: IssueLivenessAgentInput | null | undefined) {
return Boolean(agent && INVOKABLE_AGENT_STATUSES.has(agent.status));
}
function hasActiveExecutionPath(
companyId: string,
issueId: string,
activeRuns: IssueLivenessExecutionPathInput[],
queuedWakeRequests: IssueLivenessExecutionPathInput[],
) {
return [...activeRuns, ...queuedWakeRequests].some(
(entry) => entry.companyId === companyId && entry.issueId === issueId,
);
}
function readPrincipalAgentId(principal: unknown): string | null {
if (!principal || typeof principal !== "object") return null;
const value = principal as Record<string, unknown>;
return value.type === "agent" && typeof value.agentId === "string" && value.agentId.length > 0
? value.agentId
: null;
}
function principalIsResolvableUser(principal: unknown): boolean {
if (!principal || typeof principal !== "object") return false;
const value = principal as Record<string, unknown>;
return value.type === "user" && typeof value.userId === "string" && value.userId.length > 0;
}
function agentChainCandidates(
startAgentId: string | null | undefined,
agentsById: Map<string, IssueLivenessAgentInput>,
companyId: string,
) {
const candidates: string[] = [];
const seen = new Set<string>();
let current = startAgentId ? agentsById.get(startAgentId) : null;
while (current?.reportsTo) {
if (seen.has(current.reportsTo)) break;
seen.add(current.reportsTo);
const manager = agentsById.get(current.reportsTo);
if (!manager || manager.companyId !== companyId) break;
if (isInvokableAgent(manager)) candidates.push(manager.id);
current = manager;
}
return candidates;
}
function fallbackExecutiveCandidates(agents: IssueLivenessAgentInput[], companyId: string) {
const active = agents.filter((agent) => agent.companyId === companyId && isInvokableAgent(agent));
const executive = active.filter((agent) => {
const haystack = `${agent.role} ${agent.title ?? ""} ${agent.name}`.toLowerCase();
return /\b(cto|chief technology|ceo|chief executive)\b/.test(haystack);
});
const roots = active.filter((agent) => !agent.reportsTo);
return [...executive, ...roots, ...active].map((agent) => agent.id);
}
function ownerCandidatesForIssue(
issue: IssueLivenessIssueInput,
agents: IssueLivenessAgentInput[],
agentsById: Map<string, IssueLivenessAgentInput>,
) {
const candidates = [
...agentChainCandidates(issue.assigneeAgentId, agentsById, issue.companyId),
...agentChainCandidates(issue.createdByAgentId, agentsById, issue.companyId),
...fallbackExecutiveCandidates(agents, issue.companyId),
];
return [...new Set(candidates)];
}
function incidentKey(input: {
companyId: string;
issueId: string;
state: IssueLivenessState;
blockerIssueId?: string | null;
participantAgentId?: string | null;
}) {
return [
"harness_liveness",
input.companyId,
input.issueId,
input.state,
input.blockerIssueId ?? input.participantAgentId ?? "none",
].join(":");
}
function finding(input: {
issue: IssueLivenessIssueInput;
state: IssueLivenessState;
severity?: IssueLivenessSeverity;
reason: string;
dependencyPath: IssueLivenessIssueInput[];
recommendedOwnerCandidateAgentIds: string[];
recommendedAction: string;
blockerIssueId?: string | null;
participantAgentId?: string | null;
}): IssueLivenessFinding {
return {
issueId: input.issue.id,
companyId: input.issue.companyId,
identifier: input.issue.identifier,
state: input.state,
severity: input.severity ?? "critical",
reason: input.reason,
dependencyPath: input.dependencyPath.map(pathEntry),
recommendedOwnerAgentId: input.recommendedOwnerCandidateAgentIds[0] ?? null,
recommendedOwnerCandidateAgentIds: input.recommendedOwnerCandidateAgentIds,
recommendedAction: input.recommendedAction,
incidentKey: incidentKey({
companyId: input.issue.companyId,
issueId: input.issue.id,
state: input.state,
blockerIssueId: input.blockerIssueId,
participantAgentId: input.participantAgentId,
}),
};
}
export function classifyIssueGraphLiveness(input: IssueGraphLivenessInput): IssueLivenessFinding[] {
const issuesById = new Map(input.issues.map((issue) => [issue.id, issue]));
const agentsById = new Map(input.agents.map((agent) => [agent.id, agent]));
const blockersByBlockedIssueId = new Map<string, IssueLivenessRelationInput[]>();
const findings: IssueLivenessFinding[] = [];
const activeRuns = input.activeRuns ?? [];
const queuedWakeRequests = input.queuedWakeRequests ?? [];
for (const relation of input.relations) {
const list = blockersByBlockedIssueId.get(relation.blockedIssueId) ?? [];
list.push(relation);
blockersByBlockedIssueId.set(relation.blockedIssueId, list);
}
for (const issue of input.issues) {
const ownerCandidates = ownerCandidatesForIssue(issue, input.agents, agentsById);
if (issue.status === "blocked") {
const relations = blockersByBlockedIssueId.get(issue.id) ?? [];
for (const relation of relations) {
if (relation.companyId !== issue.companyId) continue;
const blocker = issuesById.get(relation.blockerIssueId);
if (!blocker || blocker.companyId !== issue.companyId || blocker.status === "done") continue;
if (blocker.status === "cancelled") {
findings.push(finding({
issue,
state: "blocked_by_cancelled_issue",
reason: `${issueLabel(issue)} is still blocked by cancelled issue ${issueLabel(blocker)}.`,
dependencyPath: [issue, blocker],
recommendedOwnerCandidateAgentIds: ownerCandidates,
recommendedAction:
`Inspect ${issueLabel(blocker)} and either remove it from ${issueLabel(issue)}'s blockers or replace it with an actionable unblock issue.`,
blockerIssueId: blocker.id,
}));
continue;
}
if (!blocker.assigneeAgentId && !blocker.assigneeUserId) {
if (hasActiveExecutionPath(issue.companyId, blocker.id, activeRuns, queuedWakeRequests)) continue;
findings.push(finding({
issue,
state: "blocked_by_unassigned_issue",
reason: `${issueLabel(issue)} is blocked by unassigned issue ${issueLabel(blocker)} with no user owner.`,
dependencyPath: [issue, blocker],
recommendedOwnerCandidateAgentIds: ownerCandidates,
recommendedAction:
`Assign ${issueLabel(blocker)} to an owner who can complete it, or remove it from ${issueLabel(issue)}'s blockers if it is no longer required.`,
blockerIssueId: blocker.id,
}));
continue;
}
if (!blocker.assigneeAgentId) continue;
if (hasActiveExecutionPath(issue.companyId, blocker.id, activeRuns, queuedWakeRequests)) continue;
const blockerAgent = agentsById.get(blocker.assigneeAgentId);
if (!blockerAgent || blockerAgent.companyId !== issue.companyId || BLOCKING_AGENT_STATUSES.has(blockerAgent.status)) {
findings.push(finding({
issue,
state: "blocked_by_uninvokable_assignee",
reason: blockerAgent
? `${issueLabel(issue)} is blocked by ${issueLabel(blocker)}, but its assignee is ${blockerAgent.status}.`
: `${issueLabel(issue)} is blocked by ${issueLabel(blocker)}, but its assignee no longer exists.`,
dependencyPath: [issue, blocker],
recommendedOwnerCandidateAgentIds: ownerCandidates,
recommendedAction:
`Review ${issueLabel(blocker)} and assign it to an active owner or replace the blocker with an actionable issue.`,
blockerIssueId: blocker.id,
}));
}
}
}
if (issue.status !== "in_review" || !issue.executionState) continue;
const participant = issue.executionState.currentParticipant;
const participantAgentId = readPrincipalAgentId(participant);
if (participantAgentId) {
const participantAgent = agentsById.get(participantAgentId);
if (!isInvokableAgent(participantAgent) || participantAgent?.companyId !== issue.companyId) {
findings.push(finding({
issue,
state: "invalid_review_participant",
reason: participantAgent
? `${issueLabel(issue)} is in review, but current participant agent is ${participantAgent.status}.`
: `${issueLabel(issue)} is in review, but current participant agent cannot be resolved.`,
dependencyPath: [issue],
recommendedOwnerCandidateAgentIds: ownerCandidates,
recommendedAction:
`Repair ${issueLabel(issue)}'s review participant or return the issue to an active assignee with a clear change request.`,
participantAgentId,
}));
}
continue;
}
if (!principalIsResolvableUser(participant)) {
findings.push(finding({
issue,
state: "invalid_review_participant",
reason: `${issueLabel(issue)} is in review, but its current participant cannot be resolved.`,
dependencyPath: [issue],
recommendedOwnerCandidateAgentIds: ownerCandidates,
recommendedAction:
`Repair ${issueLabel(issue)}'s review participant or return the issue to an active assignee with a clear change request.`,
}));
}
}
return findings;
}
export {
classifyIssueGraphLiveness,
} from "./recovery/issue-graph-liveness.js";
export type {
IssueGraphLivenessInput,
IssueLivenessAgentInput,
IssueLivenessDependencyPathEntry,
IssueLivenessExecutionPathInput,
IssueLivenessFinding,
IssueLivenessIssueInput,
IssueLivenessOwnerCandidate,
IssueLivenessOwnerCandidateReason,
IssueLivenessRelationInput,
IssueLivenessSeverity,
IssueLivenessState,
} from "./recovery/issue-graph-liveness.js";