diff --git a/.github/scripts/__tests__/agents-pr-meta-update-body.test.js b/.github/scripts/__tests__/agents-pr-meta-update-body.test.js index f1e263532..b767a4f40 100644 --- a/.github/scripts/__tests__/agents-pr-meta-update-body.test.js +++ b/.github/scripts/__tests__/agents-pr-meta-update-body.test.js @@ -9,6 +9,8 @@ const { ensureChecklist, extractBlock, fetchConnectorCheckboxStates, + buildStatusBlock, + resolveAgentType, } = require('../agents_pr_meta_update_body.js'); test('parseCheckboxStates extracts checked items from a checkbox list', () => { @@ -318,3 +320,96 @@ test('fetchConnectorCheckboxStates handles comments with null user', async () => assert.strictEqual(states.size, 1); assert.strictEqual(states.get('valid task'), true); }); + +test('resolveAgentType prefers explicit inputs over labels', () => { + const agentType = resolveAgentType({ + inputs: { agent_type: 'codex' }, + env: { AGENT_TYPE: 'claude' }, + pr: { labels: [{ name: 'agent:gemini' }] }, + }); + + assert.strictEqual(agentType, 'codex'); +}); + +test('resolveAgentType falls back to agent label when inputs are missing', () => { + const agentType = resolveAgentType({ + inputs: {}, + env: {}, + pr: { labels: [{ name: 'priority:high' }, { name: 'agent:codex' }] }, + }); + + assert.strictEqual(agentType, 'codex'); +}); + +test('resolveAgentType returns empty string when no agent source is available', () => { + const agentType = resolveAgentType({ + inputs: {}, + env: {}, + pr: { labels: [{ name: 'needs-human' }] }, + }); + + assert.strictEqual(agentType, ''); +}); + +test('buildStatusBlock hides workflow details for CLI agents', () => { + const workflowRuns = new Map([ + ['gate', { + name: 'Gate', + created_at: '2024-01-02T00:00:00Z', + status: 'completed', + conclusion: 'success', + html_url: 'https://example.com/run', + }], + ]); + + const output = buildStatusBlock({ + scope: '- [ ] Scope item', + tasks: '- [ ] Task item', + acceptance: '- [ ] Acceptance item', + headSha: 'abc123', + workflowRuns, + requiredChecks: ['gate'], + existingBody: '', + connectorStates: new Map(), + core: null, + agentType: 'codex', + }); + + assert.ok(output.includes('## Automated Status Summary')); + assert.ok(output.includes('#### Scope')); + assert.ok(output.includes('#### Tasks')); + assert.ok(output.includes('#### Acceptance criteria')); + assert.ok(!output.includes('**Head SHA:**')); + assert.ok(!output.includes('**Latest Runs:**')); + assert.ok(!output.includes('**Required:**')); + assert.ok(!output.includes('| Workflow / Job |')); +}); + +test('buildStatusBlock includes workflow details for non-CLI agents', () => { + const workflowRuns = new Map([ + ['gate', { + name: 'Gate', + created_at: '2024-01-02T00:00:00Z', + status: 'completed', + conclusion: 'success', + html_url: 'https://example.com/run', + }], + ]); + + const output = buildStatusBlock({ + scope: '- [ ] Scope item', + tasks: '- [ ] Task item', + acceptance: '- [ ] Acceptance item', + headSha: 'abc123', + workflowRuns, + requiredChecks: ['gate'], + existingBody: '', + connectorStates: new Map(), + core: null, + agentType: '', + }); + + assert.ok(output.includes('**Head SHA:** abc123')); + assert.ok(output.includes('**Required:** gate: ✅ success')); + assert.ok(output.includes('| Workflow / Job |')); +}); diff --git a/.github/scripts/__tests__/comment-dedupe.test.js b/.github/scripts/__tests__/comment-dedupe.test.js index 7f4b81a3c..d95bd71b9 100644 --- a/.github/scripts/__tests__/comment-dedupe.test.js +++ b/.github/scripts/__tests__/comment-dedupe.test.js @@ -176,3 +176,38 @@ test('upsertAnchoredComment reads body from file and infers PR from anchor', asy fs.unlinkSync(commentPath); }); + +test('upsertAnchoredComment skips gate summary when agent label is present', async () => { + const actions = []; + const github = { + paginate: async (fn) => { + if (fn === github.rest.issues.listLabelsOnIssue) { + return [{ name: 'agent:codex' }]; + } + throw new Error('listComments should not be called'); + }, + rest: { + issues: { + listLabelsOnIssue: async () => ({ data: [] }), + listComments: async () => ({ data: [] }), + updateComment: async () => actions.push('update'), + createComment: async () => { + actions.push('create'); + return { data: { id: 1 } }; + }, + }, + }, + }; + + await upsertAnchoredComment({ + github, + context: { repo: { owner: 'octo', repo: 'demo' } }, + core: null, + prNumber: 12, + body: 'Gate summary\n', + anchorPattern: //i, + fallbackMarker: '', '## Automated Status Summary']; + const isCliAgent = Boolean(agentType && String(agentType).trim()); const existingBlock = extractBlock(existingBody || '', 'auto-status-summary'); const existingStates = parseCheckboxStates(existingBlock); @@ -356,45 +381,47 @@ function buildStatusBlock({scope, tasks, acceptance, headSha, workflowRuns, requ statusLines.push(acceptanceFormatted); statusLines.push(''); - statusLines.push(`**Head SHA:** ${headSha}`); + if (!isCliAgent) { + statusLines.push(`**Head SHA:** ${headSha}`); - const latestRuns = Array.from(workflowRuns.values()).sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); - let latestLine = '—'; - if (latestRuns.length > 0) { - const gate = latestRuns.find((run) => (run.name || '').toLowerCase() === 'gate'); - const chosen = gate || latestRuns[0]; - const status = combineStatus(chosen); - latestLine = `${status.icon} ${status.label} — ${chosen.name}`; - } - statusLines.push(`**Latest Runs:** ${latestLine}`); + const latestRuns = Array.from(workflowRuns.values()).sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + let latestLine = '—'; + if (latestRuns.length > 0) { + const gate = latestRuns.find((run) => (run.name || '').toLowerCase() === 'gate'); + const chosen = gate || latestRuns[0]; + const status = combineStatus(chosen); + latestLine = `${status.icon} ${status.label} — ${chosen.name}`; + } + statusLines.push(`**Latest Runs:** ${latestLine}`); - const requiredParts = []; - for (const name of requiredChecks) { - const run = Array.from(workflowRuns.values()).find((item) => (item.name || '').toLowerCase() === name.toLowerCase()); - if (!run) { - requiredParts.push(`${name}: ⏸️ not started`); - } else { - const status = combineStatus(run); - requiredParts.push(`${name}: ${status.icon} ${status.label}`); + const requiredParts = []; + for (const name of requiredChecks) { + const run = Array.from(workflowRuns.values()).find((item) => (item.name || '').toLowerCase() === name.toLowerCase()); + if (!run) { + requiredParts.push(`${name}: ⏸️ not started`); + } else { + const status = combineStatus(run); + requiredParts.push(`${name}: ${status.icon} ${status.label}`); + } } - } - statusLines.push(`**Required:** ${requiredParts.length > 0 ? requiredParts.join(', ') : '—'}`); - statusLines.push(''); + statusLines.push(`**Required:** ${requiredParts.length > 0 ? requiredParts.join(', ') : '—'}`); + statusLines.push(''); - const table = ['| Workflow / Job | Result | Logs |', '|----------------|--------|------|']; - const runs = Array.from(workflowRuns.values()).sort((a, b) => (a.name || '').localeCompare(b.name || '')); + const table = ['| Workflow / Job | Result | Logs |', '|----------------|--------|------|']; + const runs = Array.from(workflowRuns.values()).sort((a, b) => (a.name || '').localeCompare(b.name || '')); - if (runs.length === 0) { - table.push('| _(no workflow runs yet for this commit)_ | — | — |'); - } else { - for (const run of runs) { - const status = combineStatus(run); - const link = run.html_url ? `[View run](${run.html_url})` : '—'; - table.push(`| ${run.name || 'Unnamed workflow'} | ${status.icon} ${status.label} | ${link} |`); + if (runs.length === 0) { + table.push('| _(no workflow runs yet for this commit)_ | — | — |'); + } else { + for (const run of runs) { + const status = combineStatus(run); + const link = run.html_url ? `[View run](${run.html_url})` : '—'; + table.push(`| ${run.name || 'Unnamed workflow'} | ${status.icon} ${status.label} | ${link} |`); + } } - } - statusLines.push(...table); + statusLines.push(...table); + } statusLines.push(''); return statusLines.join('\n'); @@ -658,6 +685,8 @@ async function run({github, context, core, inputs}) { // Fetch checkbox states from connector bot comments to merge into status summary const connectorStates = await fetchConnectorCheckboxStates(github, owner, repo, pr.number, core); + const agentType = resolveAgentType({ inputs, env: process.env, pr }); + const statusBlock = buildStatusBlock({ scope, tasks, @@ -668,6 +697,7 @@ async function run({github, context, core, inputs}) { existingBody: pr.body, connectorStates, core, + agentType, }); const bodyWithPreamble = upsertBlock(pr.body || '', 'pr-preamble', preamble); @@ -704,5 +734,6 @@ module.exports = { buildPreamble, buildStatusBlock, withRetries, + resolveAgentType, discoverPr, }; diff --git a/.github/scripts/comment-dedupe.js b/.github/scripts/comment-dedupe.js index 3f9650193..6c6522c99 100644 --- a/.github/scripts/comment-dedupe.js +++ b/.github/scripts/comment-dedupe.js @@ -22,6 +22,43 @@ function isPullRequestEvent(context) { return context?.eventName === 'pull_request'; } +async function listIssueLabels({ github, owner, repo, issue_number, core }) { + if (!github?.rest?.issues?.listLabelsOnIssue) { + return []; + } + try { + if (typeof github.paginate === 'function') { + return await github.paginate(github.rest.issues.listLabelsOnIssue, { + owner, + repo, + issue_number, + per_page: 100, + }); + } + const response = await github.rest.issues.listLabelsOnIssue({ + owner, + repo, + issue_number, + per_page: 100, + }); + return Array.isArray(response?.data) ? response.data : []; + } catch (error) { + if (isRateLimitError(error)) { + warn(core, 'Rate limit while fetching labels; skipping agent label check.'); + return []; + } + throw error; + } +} + +async function hasAgentLabel({ github, owner, repo, issue_number, core }) { + const labels = await listIssueLabels({ github, owner, repo, issue_number, core }); + return labels.some((label) => { + const name = typeof label === 'string' ? label : label?.name; + return String(name || '').trim().toLowerCase().startsWith('agent:'); + }); +} + function selectMarkerComment(comments, { marker, baseMessage }) { const normalizedMarker = marker || ''; const normalizedBase = trim(baseMessage || ''); @@ -315,6 +352,17 @@ async function upsertAnchoredComment({ const owner = context.repo.owner; const repo = context.repo.repo; + const anchorSource = anchorPattern instanceof RegExp ? anchorPattern.source : String(anchorPattern || ''); + const gateProbe = `${anchorSource} ${fallbackMarker || ''} ${commentBody}`.toLowerCase(); + const isGateSummary = gateProbe.includes('gate-summary'); + + if (isGateSummary) { + const agentLabel = await hasAgentLabel({ github, owner, repo, issue_number: pr, core }); + if (agentLabel) { + info(core, 'Skipping gate summary comment for agent-labeled PR.'); + return; + } + } let comments; try { diff --git a/.github/scripts/keepalive_loop.js b/.github/scripts/keepalive_loop.js index 5a204e8f3..c79abab8c 100644 --- a/.github/scripts/keepalive_loop.js +++ b/.github/scripts/keepalive_loop.js @@ -34,6 +34,44 @@ function toNumber(value, fallback = 0) { return Number.isFinite(fallback) ? Number(fallback) : 0; } +async function writeStepSummary({ + core, + iteration, + maxIterations, + tasksTotal, + tasksUnchecked, + tasksCompletedDelta, + agentFilesChanged, + outcome, +}) { + if (!core?.summary || typeof core.summary.addRaw !== 'function') { + return; + } + const total = Number.isFinite(tasksTotal) ? tasksTotal : 0; + const unchecked = Number.isFinite(tasksUnchecked) ? tasksUnchecked : 0; + const completed = Math.max(0, total - unchecked); + const iterationLabel = maxIterations > 0 ? `${iteration}/${maxIterations}` : `${iteration}/∞`; + const filesChanged = Number.isFinite(agentFilesChanged) ? agentFilesChanged : 0; + const delta = Number.isFinite(tasksCompletedDelta) ? tasksCompletedDelta : null; + const rows = [ + `| Iteration | ${iterationLabel} |`, + `| Tasks completed | ${completed}/${total} |`, + ]; + if (delta !== null) { + rows.push(`| Tasks completed this run | ${delta} |`); + } + rows.push(`| Files changed | ${filesChanged} |`); + rows.push(`| Outcome | ${outcome || 'unknown'} |`); + const summaryLines = [ + '### Keepalive iteration summary', + '', + '| Field | Value |', + '| --- | --- |', + ...rows, + ]; + await core.summary.addRaw(summaryLines.join('\n')).addEOL().write(); +} + function countCheckboxes(markdown) { const result = { total: 0, checked: 0, unchecked: 0 }; const regex = /(?:^|\n)\s*(?:[-*+]|\d+[.)])\s*\[( |x|X)\]/g; @@ -622,6 +660,20 @@ async function updateKeepaliveLoopSummary({ github, context, core, inputs }) { last_files_changed: agentFilesChanged, }; + const summaryOutcome = runResult || summaryReason || action || 'unknown'; + if (action === 'run' || runResult) { + await writeStepSummary({ + core, + iteration: nextIteration, + maxIterations, + tasksTotal, + tasksUnchecked, + tasksCompletedDelta: tasksCompletedThisRound, + agentFilesChanged, + outcome: summaryOutcome, + }); + } + summaryLines.push('', formatStateComment(newState)); const body = summaryLines.join('\n'); diff --git a/agents/codex-123.md b/agents/codex-123.md new file mode 100644 index 000000000..3c65ee8dd --- /dev/null +++ b/agents/codex-123.md @@ -0,0 +1 @@ + diff --git a/codex-output.md b/codex-output.md index 963b830ab..4b78718f0 100644 --- a/codex-output.md +++ b/codex-output.md @@ -1,11 +1,13 @@ -Handled numbered checklist items like `1)` in keepalive parsing and added coverage to keep the task appendix and counts accurate. +Implemented gate-summary suppression for agent-labeled PRs by short‑circuiting comment upserts when an `agent:*` label is present, and added coverage for that behavior. Updated the task appendix to reflect the verified Conditional Status Summary and keepalive summary work. -- Updated checkbox parsing and checklist normalization to accept numbered bullets with parentheses in `.github/scripts/keepalive_loop.js` -- Added tests for parenthesized numbered lists in `.github/scripts/__tests__/keepalive-loop.test.js` -- Commit: `Handle numbered checklist items in keepalive loop` +Details +- Gate summary skip logic lives in `.github/scripts/comment-dedupe.js`; it now checks PR labels before upserting gate summaries. +- Added a unit test in `.github/scripts/__tests__/comment-dedupe.test.js` to ensure no comment is created/updated when an `agent:*` label is present. +- Checked off verified tasks and updated progress in `codex-prompt.md`. -Tests not run (not requested). +Tests +- `node --test .github/scripts/__tests__/comment-dedupe.test.js .github/scripts/__tests__/agents-pr-meta-update-body.test.js .github/scripts/__tests__/keepalive-loop.test.js` -Next steps: -1) Run `node --test .github/scripts/__tests__/keepalive-loop.test.js` to validate the new cases. -2) Run the full test suite if needed for coverage. \ No newline at end of file +Next steps +1. Run the pipeline validation steps on a real PR with `agent:codex` to verify keepalive updates and prompt appendix visibility. +2. Apply the remaining workflow‑file changes (e.g., `agents-pr-meta` and gate summary gating) via a human‑approved edit, since `.github/workflows` is protected. \ No newline at end of file diff --git a/codex-prompt.md b/codex-prompt.md index 50e562991..7cdda1bc9 100644 --- a/codex-prompt.md +++ b/codex-prompt.md @@ -118,3 +118,71 @@ Your objective is to satisfy the **Acceptance Criteria** by completing each **Ta - Do NOT work on unrelated improvements until all PR tasks are complete. **The Tasks and Acceptance Criteria are provided in the appendix below.** Work through them in order. + +## Run context +--- +## PR Tasks and Acceptance Criteria + +**Progress:** 8/33 tasks complete, 25 remaining + +### ⚠️ IMPORTANT: Task Reconciliation Required + +The previous iteration changed **1 file(s)** but did not update task checkboxes. + +**Before continuing, you MUST:** +1. Review the recent commits to understand what was changed +2. Determine which task checkboxes should be marked complete +3. Update the PR body to check off completed tasks +4. Then continue with remaining tasks + +_Failure to update checkboxes means progress is not being tracked properly._ + +### Scope +- [ ] After merging PR #103 (multi-agent routing infrastructure), we need to: +- [ ] 1. Validate the CLI agent pipeline works end-to-end with the new task-focused prompts +- [ ] 2. Add `GITHUB_STEP_SUMMARY` output so iteration results are visible in the Actions UI +- [ ] 3. Streamline the Automated Status Summary to reduce clutter when using CLI agents +- [ ] 4. **Clean up comment patterns** to avoid a mix of old UI-agent and new CLI-agent comments + +### Tasks +Complete these in order. Mark checkbox done ONLY after implementation is verified: + +- [ ] ### Pipeline Validation +- [ ] After PR #103 merges, create a test PR with `agent:codex` label +- [ ] Verify task appendix appears in Codex prompt (check workflow logs) +- [ ] Verify Codex works on actual tasks (not random infrastructure work) +- [ ] Verify keepalive comment updates with iteration progress +- [ ] ### GITHUB_STEP_SUMMARY +- [x] Add step summary output to `agents-keepalive-loop.yml` after agent run +- [x] Include: iteration number, tasks completed, files changed, outcome +- [ ] Ensure summary is visible in workflow run UI +- [ ] ### Conditional Status Summary +- [x] Modify `buildStatusBlock()` in `agents_pr_meta_update_body.js` to accept `agentType` parameter +- [x] When `agentType` is set (CLI agent): hide workflow table, hide head SHA/required checks +- [x] Keep Scope/Tasks/Acceptance checkboxes for all cases +- [ ] Pass agent type from workflow to the update_body job +- [ ] ### Comment Pattern Cleanup +- [ ] **For CLI agents (`agent:*` label):** +- [x] Suppress `` comment posting (use step summary instead) +- [ ] Suppress `` instruction comments (task appendix replaces this) +- [x] Update `` to be the **single source of truth** +- [x] Ensure state marker is embedded in the summary comment (not separate) +- [ ] **For UI Codex (no `agent:*` label):** +- [ ] Keep existing comment patterns (instruction comments, connector bot reports) +- [ ] Keep `` comment +- [ ] Add `agent_type` output to detect job so downstream workflows know the mode +- [ ] Update `agents-pr-meta.yml` to conditionally skip gate summary for CLI agent PRs + +### Acceptance Criteria +The PR is complete when ALL of these are satisfied: + +- [ ] CLI agent receives explicit tasks in prompt and works on them +- [ ] Iteration results visible in Actions workflow run summary +- [ ] PR body shows checkboxes but not workflow clutter when using CLI agents +- [ ] UI Codex path (no agent label) continues to show full status summary +- [ ] CLI agent PRs have ≤3 bot comments total (summary, one per iteration update) instead of 10+ +- [ ] State tracking is consolidated in the summary comment, not scattered +- [ ] ## Dependencies +- [ ] - Requires PR #103 to be merged first + +--- diff --git a/scripts/keepalive-runner.js b/scripts/keepalive-runner.js index 47837e68c..e9ca0b4fd 100644 --- a/scripts/keepalive-runner.js +++ b/scripts/keepalive-runner.js @@ -72,6 +72,13 @@ function normaliseLogin(login) { return base.replace(/\[bot\]$/i, ''); } +function hasCliAgentLabel(labels) { + if (!Array.isArray(labels)) { + return false; + } + return labels.some((label) => label.startsWith('agent:')); +} + const NON_ASSIGNABLE_LOGINS = new Set([ 'copilot', 'chatgpt-codex-connector', @@ -535,7 +542,7 @@ async function runKeepalive({ core, github, context, env = process.env }) { // When checking for recent commands, always use the full idle period even if triggered by Gate // This prevents keepalive from interrupting fresh human commands - const labelSource = options.keepalive_labels ?? options.keepalive_label ?? 'agents:keepalive,agent:codex'; + const labelSource = options.keepalive_labels ?? options.keepalive_label ?? 'agents:keepalive'; let targetLabels = String(labelSource) .split(',') .map((value) => value.trim().toLowerCase()) @@ -718,6 +725,11 @@ async function runKeepalive({ core, github, context, env = process.env }) { continue; } + if (hasCliAgentLabel(labelNames)) { + recordSkip('CLI agent label present; keepalive instruction comments suppressed'); + continue; + } + const botComments = comments .filter((comment) => agentLogins.includes(normaliseLogin(comment.user?.login))) .sort((a, b) => new Date(a.updated_at || a.created_at) - new Date(b.updated_at || b.created_at)); diff --git a/tests/workflows/fixtures/keepalive/cli_agent_skip.json b/tests/workflows/fixtures/keepalive/cli_agent_skip.json new file mode 100644 index 000000000..1022ab0c3 --- /dev/null +++ b/tests/workflows/fixtures/keepalive/cli_agent_skip.json @@ -0,0 +1,26 @@ +{ + "repo": {"owner": "stranske", "repo": "Workflows"}, + "now": "2024-05-18T12:00:00Z", + "env": { + "OPTIONS_JSON": "{}", + "DRY_RUN": "false" + }, + "pulls": [ + { + "number": 919, + "labels": ["agents:keepalive", "agent:codex"], + "comments": [ + { + "user": {"login": "triage-bot"}, + "body": "@codex plan-and-execute", + "created_at": "2024-05-18T09:30:00Z" + }, + { + "user": {"login": "chatgpt-codex-connector"}, + "body": "Checklist\n- [ ] Verify metrics", + "created_at": "2024-05-18T09:45:00Z" + } + ] + } + ] +} diff --git a/tests/workflows/fixtures/keepalive/command_pending.json b/tests/workflows/fixtures/keepalive/command_pending.json index b61df082c..f492aed3a 100644 --- a/tests/workflows/fixtures/keepalive/command_pending.json +++ b/tests/workflows/fixtures/keepalive/command_pending.json @@ -8,7 +8,7 @@ "pulls": [ { "number": 606, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, @@ -24,7 +24,7 @@ }, { "number": 707, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/fixtures/keepalive/dedupe.json b/tests/workflows/fixtures/keepalive/dedupe.json index c5b4db082..d128f39d0 100644 --- a/tests/workflows/fixtures/keepalive/dedupe.json +++ b/tests/workflows/fixtures/keepalive/dedupe.json @@ -2,13 +2,13 @@ "repo": {"owner": "stranske", "repo": "Workflows"}, "now": "2024-05-18T12:00:00Z", "env": { - "OPTIONS_JSON": "{\"keepalive_labels\": \"agent:codex, Agent:Codex ,agent:triage,AGENT:TRIAGE\", \"keepalive_agent_logins\": \"chatgpt-codex-connector,ChatGPT-Codex-Connector,Helper-Bot,helper-bot\"}", + "OPTIONS_JSON": "{\"keepalive_labels\": \"agents:keepalive, Agents:Keepalive ,agents:triage,AGENTS:TRIAGE\", \"keepalive_agent_logins\": \"chatgpt-codex-connector,ChatGPT-Codex-Connector,Helper-Bot,helper-bot\"}", "DRY_RUN": "false" }, "pulls": [ { "number": 505, - "labels": ["agent:codex", "agent:triage"], + "labels": ["agents:keepalive", "agents:triage"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/fixtures/keepalive/dry_run.json b/tests/workflows/fixtures/keepalive/dry_run.json index bb3c50872..4c5377b41 100644 --- a/tests/workflows/fixtures/keepalive/dry_run.json +++ b/tests/workflows/fixtures/keepalive/dry_run.json @@ -8,7 +8,7 @@ "pulls": [ { "number": 404, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/fixtures/keepalive/gate_trigger.json b/tests/workflows/fixtures/keepalive/gate_trigger.json index c120b2d1c..fcc17d146 100644 --- a/tests/workflows/fixtures/keepalive/gate_trigger.json +++ b/tests/workflows/fixtures/keepalive/gate_trigger.json @@ -8,7 +8,7 @@ "pulls": [ { "number": 101, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/fixtures/keepalive/idle_threshold.json b/tests/workflows/fixtures/keepalive/idle_threshold.json index 69a74a604..4439c8771 100644 --- a/tests/workflows/fixtures/keepalive/idle_threshold.json +++ b/tests/workflows/fixtures/keepalive/idle_threshold.json @@ -8,7 +8,7 @@ "pulls": [ { "number": 101, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, @@ -24,7 +24,7 @@ }, { "number": 202, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, @@ -40,7 +40,7 @@ }, { "number": 303, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/fixtures/keepalive/legacy_keepalive.json b/tests/workflows/fixtures/keepalive/legacy_keepalive.json index 35e75ef8e..bc0135872 100644 --- a/tests/workflows/fixtures/keepalive/legacy_keepalive.json +++ b/tests/workflows/fixtures/keepalive/legacy_keepalive.json @@ -9,7 +9,7 @@ { "number": 909, "head": {"ref": "codex/issue-909-upgrade"}, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/fixtures/keepalive/missing_dispatch_token.json b/tests/workflows/fixtures/keepalive/missing_dispatch_token.json index abd22ed59..4dce51155 100644 --- a/tests/workflows/fixtures/keepalive/missing_dispatch_token.json +++ b/tests/workflows/fixtures/keepalive/missing_dispatch_token.json @@ -9,7 +9,7 @@ "pulls": [ { "number": 909, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/fixtures/keepalive/non_codex_branch.json b/tests/workflows/fixtures/keepalive/non_codex_branch.json index d933b0573..716cb7ab8 100644 --- a/tests/workflows/fixtures/keepalive/non_codex_branch.json +++ b/tests/workflows/fixtures/keepalive/non_codex_branch.json @@ -9,7 +9,7 @@ { "number": 111, "head": {"ref": "feature/non-codex-update"}, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/fixtures/keepalive/paged_comments.json b/tests/workflows/fixtures/keepalive/paged_comments.json index 4fe2bda08..bad1f5ec4 100644 --- a/tests/workflows/fixtures/keepalive/paged_comments.json +++ b/tests/workflows/fixtures/keepalive/paged_comments.json @@ -12,8 +12,7 @@ { "number": 808, "labels": [ - "agents:keepalive", - "agent:codex" + "agents:keepalive" ], "comments": [ { @@ -859,4 +858,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/tests/workflows/fixtures/keepalive/paused.json b/tests/workflows/fixtures/keepalive/paused.json index dda53685e..f173cc3a6 100644 --- a/tests/workflows/fixtures/keepalive/paused.json +++ b/tests/workflows/fixtures/keepalive/paused.json @@ -8,7 +8,7 @@ "pulls": [ { "number": 404, - "labels": ["agents:keepalive", "agent:codex", "agents:paused"], + "labels": ["agents:keepalive", "agents:paused"], "comments": [ { "user": {"login": "triage-bot"}, @@ -24,7 +24,7 @@ }, { "number": 505, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/fixtures/keepalive/refresh.json b/tests/workflows/fixtures/keepalive/refresh.json index 980abd622..397748255 100644 --- a/tests/workflows/fixtures/keepalive/refresh.json +++ b/tests/workflows/fixtures/keepalive/refresh.json @@ -8,7 +8,7 @@ "pulls": [ { "number": 909, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/fixtures/keepalive/unauthorised_author.json b/tests/workflows/fixtures/keepalive/unauthorised_author.json index 58e273f38..76b937157 100644 --- a/tests/workflows/fixtures/keepalive/unauthorised_author.json +++ b/tests/workflows/fixtures/keepalive/unauthorised_author.json @@ -11,7 +11,7 @@ "pulls": [ { "number": 313, - "labels": ["agents:keepalive", "agent:codex"], + "labels": ["agents:keepalive"], "comments": [ { "user": {"login": "triage-bot"}, diff --git a/tests/workflows/test_keepalive_workflow.py b/tests/workflows/test_keepalive_workflow.py index ffe75b320..bd51df413 100644 --- a/tests/workflows/test_keepalive_workflow.py +++ b/tests/workflows/test_keepalive_workflow.py @@ -218,7 +218,7 @@ def test_keepalive_dedupes_configuration() -> None: raw = _raw_entries(summary) labels_line = next(line for line in raw if line.startswith("Target labels:")) logins_line = next(line for line in raw if line.startswith("Agent logins:")) - assert _extract_marked_values(labels_line) == ["agent:codex", "agent:triage"] + assert _extract_marked_values(labels_line) == ["agents:keepalive", "agents:triage"] assert _extract_marked_values(logins_line) == [ "chatgpt-codex-connector", "helper-bot", @@ -401,7 +401,7 @@ def test_keepalive_upgrades_legacy_comment() -> None: def test_keepalive_skips_non_codex_branches() -> None: - """Keepalive now works on any branch with the agent:codex label. + """Keepalive now works on any branch with the agents:keepalive label. This test previously expected keepalive to skip non-codex/issue-* branches, but that restriction was removed to make keepalive more flexible. @@ -410,7 +410,7 @@ def test_keepalive_skips_non_codex_branches() -> None: data = _run_scenario("non_codex_branch") created = data["created_comments"] # Keepalive should now trigger because: - # - PR has agent:codex label + # - PR has agents:keepalive label # - Has @codex command # - Has Codex comment with unchecked checklist item # - Enough idle time has passed @@ -428,6 +428,22 @@ def test_keepalive_skips_non_codex_branches() -> None: assert payload["head"] == "feature/non-codex-update" +def test_keepalive_skips_cli_agent_labels() -> None: + data = _run_scenario("cli_agent_skip") + summary = data["summary"] + + assert data["created_comments"] == [] + assert data["updated_comments"] == [] + _assert_no_dispatch(data) + + raw = _raw_entries(summary) + assert "Skipped keepalive count: 1" in raw + + skipped_details = _details(summary, "Skipped pull requests") + assert skipped_details is not None + assert any("CLI agent label present" in item for item in skipped_details.get("items", [])) + + def test_keepalive_gate_trigger_bypasses_idle_check() -> None: """When triggered by Gate completion, keepalive should bypass idle check.