diff --git a/.github/scripts/keepalive_gate.js b/.github/scripts/keepalive_gate.js index 52d62c437..ca6bdef90 100644 --- a/.github/scripts/keepalive_gate.js +++ b/.github/scripts/keepalive_gate.js @@ -7,6 +7,7 @@ const AGENT_LABEL_PREFIX = 'agent:'; const MAX_RUNS_PREFIX = 'agents:max-runs:'; const SYNC_REQUIRED_LABEL = 'agents:sync-required'; const ACTIVATED_LABEL = 'agents:activated'; +const PAUSE_LABEL = 'agents:pause'; const DEFAULT_RUN_CAP = 1; const MIN_RUN_CAP = 1; const MAX_RUN_CAP = 5; @@ -901,6 +902,7 @@ async function evaluateKeepaliveGate({ core, github, context, options = {} }) { headRef: '', hasSyncRequiredLabel: false, hasActivatedLabel: false, + hasPauseLabel: false, requireHumanActivation: false, activationComment: null, gateStatus: { found: false, success: false, status: '', conclusion: '' }, @@ -934,6 +936,7 @@ async function evaluateKeepaliveGate({ core, github, context, options = {} }) { headRef: '', hasSyncRequiredLabel: false, hasActivatedLabel: false, + hasPauseLabel: false, requireHumanActivation: false, activationComment: null, gateStatus: { found: false, success: false, status: '', conclusion: '' }, @@ -948,6 +951,7 @@ async function evaluateKeepaliveGate({ core, github, context, options = {} }) { const labels = Array.isArray(pr?.labels) ? pr.labels : []; const labelNames = extractLabelNames(labels); const hasKeepaliveLabel = labelNames.includes(KEEPALIVE_LABEL); + const hasPauseLabel = labelNames.includes(PAUSE_LABEL); const hasActivatedLabel = labelNames.includes(ACTIVATED_LABEL); const hasSyncRequiredLabel = labelNames.includes(SYNC_REQUIRED_LABEL); const agentAliases = extractAgentAliases(labels); @@ -1018,7 +1022,10 @@ async function evaluateKeepaliveGate({ core, github, context, options = {} }) { let reason = 'ok'; let pendingGate = false; - if (hasSyncRequiredLabel) { + if (hasPauseLabel) { + ok = false; + reason = 'keepalive-paused'; + } else if (hasSyncRequiredLabel) { ok = false; reason = 'sync-required'; } else if (!hasKeepaliveLabel) { @@ -1085,6 +1092,7 @@ async function evaluateKeepaliveGate({ core, github, context, options = {} }) { primaryAgent, headSha, headRef, + hasPauseLabel, lastGreenSha: gateSucceeded ? headSha : '', hasSyncRequiredLabel, hasActivatedLabel, diff --git a/.github/scripts/keepalive_orchestrator_gate_runner.js b/.github/scripts/keepalive_orchestrator_gate_runner.js index 18e33fcf4..e57efa40c 100644 --- a/.github/scripts/keepalive_orchestrator_gate_runner.js +++ b/.github/scripts/keepalive_orchestrator_gate_runner.js @@ -163,6 +163,17 @@ async function runKeepaliveGate({ core, github, context, env }) { } }; + if (!preGate.ok) { + addReason(preGate.reason || 'pre-gate-failed'); + summary + .addRaw( + `Pre-gate check failed: reason=${preGate.reason || 'unknown'} ok=${preGate.ok ? 'true' : 'false'}` + ) + .addEOL(); + } else if (preGate.pendingGate) { + summary.addRaw('Gate pending; keepalive will retry once gate concludes.').addEOL(); + } + let headSha = ''; if (!pr) { addReason('missing-pr'); @@ -183,30 +194,17 @@ async function runKeepaliveGate({ core, github, context, env }) { .filter(Boolean) ); + if (currentLabels.has('agents:pause')) { + addReason('keepalive-paused'); + summary.addRaw('Keepalive paused by agents:pause label.').addEOL(); + } + const requiredLabels = ['agents:keepalive']; if (agentAlias) { requiredLabels.push(`agent:${agentAlias}`); } const missingLabels = requiredLabels.filter((label) => !currentLabels.has(label)); - if (missingLabels.length) { - try { - await github.rest.issues.addLabels({ - owner, - repo, - issue_number: prNumber, - labels: missingLabels, - }); - summary.addRaw(`Applied keepalive labels to PR #${prNumber}: ${missingLabels.join(', ')}`).addEOL(); - for (const label of missingLabels) { - currentLabels.add(label); - } - } catch (labelError) { - const message = labelError instanceof Error ? labelError.message : String(labelError); - summary.addRaw(`Failed to apply keepalive labels to PR #${prNumber}: ${message}`).addEOL(); - } - } - const unresolvedLabels = requiredLabels.filter((label) => !currentLabels.has(label)); if (unresolvedLabels.length) { unresolvedLabels.forEach((label) => addReason(`missing-label:${label}`)); @@ -304,50 +302,14 @@ async function runKeepaliveGate({ core, github, context, env }) { } - const currentAssignees = (pr.assignees || []).map((assignee) => assignee?.login).filter(Boolean); const humanAssignees = (pr.assignees || []) .filter((assignee) => isAssignable(assignee)) .map((assignee) => assignee.login) .filter(Boolean); if (!humanAssignees.length) { - const candidateLogins = []; - const author = pr.user; - if (isAssignable(author)) { - candidateLogins.push(author.login); - } - for (const reviewer of pr.requested_reviewers || []) { - if (isAssignable(reviewer)) { - candidateLogins.push(reviewer.login); - } - } - - const uniqueCandidates = []; - const seen = new Set(); - for (const login of candidateLogins) { - const normalised = login.toLowerCase(); - if (!seen.has(normalised)) { - seen.add(normalised); - uniqueCandidates.push(login); - } - } - - if (uniqueCandidates.length) { - try { - await github.rest.issues.addAssignees({ - owner, - repo, - issue_number: prNumber, - assignees: uniqueCandidates, - }); - summary.addRaw(`Assigned human owners to PR #${prNumber}: ${uniqueCandidates.join(', ')}`).addEOL(); - } catch (assignmentError) { - const message = assignmentError instanceof Error ? assignmentError.message : String(assignmentError); - summary.addRaw(`Failed to assign humans to PR #${prNumber}: ${message}`).addEOL(); - } - } else { - summary.addRaw(`No human assignees available for PR #${prNumber}; continuing without assignment.`).addEOL(); - } + addReason('no-human-assignee'); + summary.addRaw(`No human assignees available for PR #${prNumber}; skipping keepalive.`).addEOL(); } }