Repository navigation
[BUG] Linux: in-app self-update always fails for .deb installs in /opt/unigetui (swap helper runs without elevation) #110
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Agent - Implement Ticket | |
| # An AI agent (GitHub Copilot CLI) implements a ticket in UniGetUI, builds and runs its change | |
| # on a live Windows desktop (see .github/actions/README.md), and checks it with screenshots. | |
| # | |
| # resolve turns the trigger into a request; acknowledges it on the issue or PR | |
| # spec (mode spec) an agent writes a spec; spec-publish posts it on the issue for | |
| # a maintainer to approve with "/agent implement" | |
| # implement the agent implements the ticket and the approved spec; the workflow then | |
| # checks the change itself (build, tests, formatting, translations) and | |
| # outlines what changed between the agent's before/after screenshots; a | |
| # second agent, on a different model, reviews it; and a single fix round, on | |
| # the same desktop, fixes the failed checks and the implementable findings | |
| # report reports checks, review and metrics; opens a draft pull request, or pushes | |
| # to the agent pull request it iterates on, and comments on the issue | |
| # | |
| # Triggers: run it by hand; label an issue agent:spec or agent:implement; comment | |
| # "/agent spec" or "/agent implement [guidance]" on an issue; comment "/agent <feedback>" on | |
| # an agent pull request. Labels and comments only start runs for users with write access, | |
| # and only from the default branch's copy of this workflow. agent-eval.yml calls it too. | |
| # | |
| # This is a plain workflow rather than a gh-aw one because gh-aw's agent sandbox needs Linux, | |
| # and the desktop only exists on the Windows runner. It keeps gh-aw's split, though: agent | |
| # jobs have read-only tokens, and only jobs that run no agent can write. Tickets, comments | |
| # and pull request text are untrusted input; everything the agent makes is for human review. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| mode: | |
| description: implement the ticket, or only write a spec for it | |
| type: choice | |
| options: [implement, spec] | |
| default: implement | |
| issue: | |
| description: Number of the issue to implement (or leave empty and describe the ticket below) | |
| required: false | |
| default: '' | |
| ticket: | |
| description: Ticket text, used when no issue is given, or as extra guidance for the issue | |
| required: false | |
| default: '' | |
| spec_run_id: | |
| description: Run ID of an approved spec run to implement | |
| required: false | |
| default: '' | |
| pr: | |
| description: Number of an agent pull request to iterate on, with the feedback below | |
| required: false | |
| default: '' | |
| continue_run_id: | |
| description: Run ID of an earlier implement run to continue from, with the feedback below (no pull request) | |
| required: false | |
| default: '' | |
| feedback: | |
| description: Feedback on the earlier change, with pr or continue_run_id | |
| required: false | |
| default: '' | |
| create_pr: | |
| description: Open a draft pull request (otherwise the change is only uploaded as an artifact) | |
| type: boolean | |
| default: true | |
| model: | |
| description: Copilot model (empty for the repository default) | |
| required: false | |
| default: '' | |
| review_model: | |
| description: Copilot model(s) for the review, comma-separated, first available that differs from the implementer's | |
| required: false | |
| default: '' | |
| base_sha: | |
| description: Commit to start from (empty for the branch the workflow runs on) | |
| required: false | |
| default: '' | |
| artifact_prefix: | |
| description: Prefix for the artifact names | |
| required: false | |
| default: '' | |
| workflow_call: | |
| inputs: | |
| ticket: | |
| type: string | |
| required: true | |
| base_sha: | |
| type: string | |
| default: '' | |
| model: | |
| type: string | |
| default: '' | |
| review_model: | |
| type: string | |
| default: '' | |
| artifact_prefix: | |
| type: string | |
| required: true | |
| create_pr: | |
| type: boolean | |
| default: false | |
| outputs: | |
| has-changes: | |
| value: ${{ jobs.implement.outputs.has-changes }} | |
| checks-passed: | |
| value: ${{ jobs.report.outputs.checks-passed }} | |
| review-verdict: | |
| value: ${{ jobs.report.outputs.review-verdict }} | |
| issues: | |
| types: [labeled] | |
| issue_comment: | |
| types: [created] | |
| permissions: | |
| contents: read | |
| env: | |
| REVIEW_MODELS: claude-sonnet-5.5,claude-opus-5.5,gpt-6-luna,auto | |
| jobs: | |
| resolve: | |
| name: Resolve the request | |
| # A called workflow sees its caller's event; agent-eval.yml always sets artifact_prefix. | |
| if: >- | |
| github.event_name == 'workflow_dispatch' || inputs.artifact_prefix != '' || | |
| (github.event_name == 'issues' && startsWith(github.event.label.name, 'agent:')) || | |
| (github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '/agent')) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| actions: read | |
| issues: write | |
| pull-requests: write | |
| outputs: | |
| proceed: ${{ steps.resolve.outputs.proceed }} | |
| mode: ${{ steps.resolve.outputs.mode }} | |
| issue: ${{ steps.resolve.outputs.issue }} | |
| pr: ${{ steps.resolve.outputs.pr }} | |
| base-sha: ${{ steps.resolve.outputs.base-sha }} | |
| head-branch: ${{ steps.resolve.outputs.head-branch }} | |
| create-pr: ${{ steps.resolve.outputs.create-pr }} | |
| steps: | |
| - name: Checkout the workflow's scripts | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ github.workflow_sha }} | |
| persist-credentials: false | |
| sparse-checkout: .github/agent | |
| - name: Resolve the request | |
| id: resolve | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| IN_MODE: ${{ inputs.mode }} | |
| IN_ISSUE: ${{ inputs.issue }} | |
| IN_PR: ${{ inputs.pr }} | |
| IN_TICKET: ${{ inputs.ticket }} | |
| IN_FEEDBACK: ${{ inputs.feedback }} | |
| IN_CREATE_PR: ${{ inputs.create_pr }} | |
| IN_SPEC_RUN_ID: ${{ inputs.spec_run_id }} | |
| IN_CONTINUE_RUN_ID: ${{ inputs.continue_run_id }} | |
| IN_BASE_SHA: ${{ inputs.base_sha }} | |
| AGENT_EVENT: ${{ inputs.artifact_prefix != '' && 'workflow_call' || github.event_name }} | |
| run: | | |
| ./.github/agent/implement-ticket/Resolve-AgentRequest.ps1 -OutputDir (Join-Path $env:RUNNER_TEMP 'request') -EventName $env:AGENT_EVENT | |
| - name: Upload the request | |
| if: steps.resolve.outputs.proceed == 'true' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ inputs.artifact_prefix }}agent-request | |
| path: ${{ runner.temp }}/request | |
| spec: | |
| name: Write a spec | |
| needs: resolve | |
| if: needs.resolve.outputs.proceed == 'true' && needs.resolve.outputs.mode == 'spec' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| concurrency: | |
| group: agent-${{ needs.resolve.outputs.issue || github.run_id }}-${{ inputs.artifact_prefix }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| copilot-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ needs.resolve.outputs.base-sha }} | |
| persist-credentials: false | |
| # Older bases get this workflow's tooling (.github/agent and .github/actions). | |
| - name: Use this workflow's tooling | |
| shell: pwsh | |
| env: | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: | | |
| git fetch --quiet --depth 1 origin $env:WORKFLOW_SHA | |
| if ($LASTEXITCODE -ne 0) { throw "Could not fetch $env:WORKFLOW_SHA" } | |
| git checkout $env:WORKFLOW_SHA -- .github/agent .github/actions | |
| if ($LASTEXITCODE -ne 0) { throw 'Could not check out the workflow tooling' } | |
| - name: Download the request | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: ${{ inputs.artifact_prefix }}agent-request | |
| path: ${{ runner.temp }}/request | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '24' | |
| - name: Install GitHub Copilot CLI | |
| shell: pwsh | |
| run: | | |
| npm install --global --no-fund --no-audit @github/copilot@1.0.96 | |
| if ($LASTEXITCODE -ne 0) { throw "npm install failed with exit code $LASTEXITCODE" } | |
| - name: Run the spec agent | |
| shell: pwsh | |
| env: | |
| COPILOT_GITHUB_TOKEN: ${{ github.token }} | |
| COPILOT_MODEL: ${{ inputs.model || vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} | |
| run: | | |
| $out = Join-Path $env:RUNNER_TEMP 'spec' | |
| New-Item -Path $out -ItemType Directory -Force | Out-Null | |
| $env:AGENT_TICKET = Join-Path $env:RUNNER_TEMP 'request/ticket.md' | |
| $env:AGENT_SPEC = Join-Path $out 'spec.md' | |
| ./.github/agent/Invoke-CopilotAgent.ps1 ` | |
| -Prompt "Read .github/agent/implement-ticket/SPEC_PROMPT.md and follow it. The ticket is in the file at $env:AGENT_TICKET." ` | |
| -OutputDir $out ` | |
| -Models $env:COPILOT_MODEL ` | |
| -McpConfig (Join-Path $env:GITHUB_WORKSPACE '.github/agent/implement-ticket/mcp-config.json') | |
| if (-not (Test-Path -LiteralPath $env:AGENT_SPEC)) { throw 'The spec agent did not write a spec' } | |
| Get-Content -LiteralPath $env:AGENT_SPEC -Raw | Add-Content -Path $env:GITHUB_STEP_SUMMARY -Encoding utf8 | |
| - name: Upload the spec | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ inputs.artifact_prefix }}agent-spec | |
| path: ${{ runner.temp }}/spec | |
| if-no-files-found: warn | |
| spec-publish: | |
| name: Post the spec for approval | |
| needs: [resolve, spec] | |
| if: needs.spec.result == 'success' && needs.resolve.outputs.issue != '' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| issues: write | |
| steps: | |
| - name: Download the spec | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: ${{ inputs.artifact_prefix }}agent-spec | |
| path: ${{ runner.temp }}/spec | |
| - name: Post the spec on the issue | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| ISSUE: ${{ needs.resolve.outputs.issue }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| $spec = Get-Content -LiteralPath (Join-Path $env:RUNNER_TEMP 'spec/spec.md') -Raw | |
| @( | |
| "Proposed spec, written by the [agent workflow]($env:RUN_URL). Comment ``/agent implement`` to implement it, optionally followed by more guidance, or edit the issue and comment ``/agent spec`` for a new one." | |
| '' | |
| '<!-- agent-spec-begin -->' | |
| $spec.Trim() | |
| '<!-- agent-spec-end -->' | |
| ) -join "`n" | gh issue comment $env:ISSUE --repo $env:GITHUB_REPOSITORY --body-file - | |
| if ($LASTEXITCODE -ne 0) { throw "Could not comment on issue $env:ISSUE" } | |
| implement: | |
| name: Implement on a live desktop | |
| needs: resolve | |
| if: needs.resolve.outputs.proceed == 'true' && needs.resolve.outputs.mode == 'implement' | |
| runs-on: windows-latest | |
| timeout-minutes: 180 | |
| concurrency: | |
| group: agent-${{ needs.resolve.outputs.pr || needs.resolve.outputs.issue || github.run_id }}-${{ inputs.artifact_prefix }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| copilot-requests: write | |
| outputs: | |
| has-changes: ${{ steps.export.outputs.has-changes }} | |
| checks-passed: ${{ steps.checks-after-fix.outputs.checks-passed || steps.checks.outputs.checks-passed }} | |
| runner-name: ${{ runner.name }} | |
| steps: | |
| # The agent works in this checkout, so it must not hold a git credential. | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ needs.resolve.outputs.base-sha }} | |
| persist-credentials: false | |
| - name: Download the request | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: ${{ inputs.artifact_prefix }}agent-request | |
| path: ${{ runner.temp }}/agent/request | |
| # Older bases get this workflow's tooling (.github/agent and .github/actions), and a | |
| # continued run gets its earlier change. Both are committed locally, so the change the | |
| # agent makes is diffed against them. | |
| - name: Prepare the ticket | |
| shell: pwsh | |
| env: | |
| WORKFLOW_SHA: ${{ github.workflow_sha }} | |
| run: | | |
| $agentDir = Join-Path $env:RUNNER_TEMP 'agent' | |
| $request = Join-Path $agentDir 'request' | |
| $screenshots = Join-Path $agentDir 'screenshots' | |
| New-Item -Path $screenshots -ItemType Directory -Force | Out-Null | |
| git fetch --quiet --depth 1 origin $env:WORKFLOW_SHA | |
| if ($LASTEXITCODE -ne 0) { throw "Could not fetch $env:WORKFLOW_SHA" } | |
| git checkout $env:WORKFLOW_SHA -- .github/agent .github/actions | |
| if ($LASTEXITCODE -ne 0) { throw 'Could not check out the workflow tooling' } | |
| git diff --cached --quiet | |
| if ($LASTEXITCODE -ne 0) { | |
| git -c user.name=agent-workflow -c user.email=agent-workflow@localhost commit --quiet --message "Agent workflow tooling at $env:WORKFLOW_SHA" | |
| } | |
| foreach ($previous in Get-ChildItem -LiteralPath $request -Filter 'previous-*.patch' -File | Sort-Object Name) { | |
| git apply --index $previous.FullName | |
| if ($LASTEXITCODE -ne 0) { throw "Could not apply the earlier change $($previous.Name)" } | |
| git -c user.name=agent-workflow -c user.email=agent-workflow@localhost commit --quiet --message "Earlier agent change ($($previous.Name))" | |
| } | |
| Get-Content -Path (Join-Path $request 'ticket.md') | Write-Host | |
| # NuGet's package and HTTP caches default to C:, the runner's slow OS disk; restores | |
| # are much faster on D:, where RUNNER_TEMP and the checkout are. Nothing is cached | |
| # between runs: every run restores from scratch. | |
| $nugetPackages = Join-Path $env:RUNNER_TEMP 'nuget\packages' | |
| $nugetHttpCache = Join-Path $env:RUNNER_TEMP 'nuget\http-cache' | |
| New-Item -Path $nugetPackages, $nugetHttpCache -ItemType Directory -Force | Out-Null | |
| @( | |
| "NUGET_PACKAGES=$nugetPackages" | |
| "NUGET_HTTP_CACHE_PATH=$nugetHttpCache" | |
| "AGENT_DIR=$agentDir" | |
| "AGENT_TICKET=$(Join-Path $request 'ticket.md')" | |
| "AGENT_SPEC=$(Join-Path $request 'spec.md')" | |
| "AGENT_FEEDBACK=$(Join-Path $request 'feedback.md')" | |
| "AGENT_SCREENSHOTS=$screenshots" | |
| "AGENT_PR_BODY=$(Join-Path $agentDir 'pr.md')" | |
| "AGENT_BASE_SHA=$(git rev-parse HEAD)" | |
| ) | Add-Content -Path $env:GITHUB_ENV -Encoding utf8 | |
| # Keep workflow scaffolding in the workspace out of the agent's patch. | |
| @('/artifacts/', '/unigetui_bin/', '/output/', 'copilot-session-*.md') | | |
| Add-Content -Path '.git/info/exclude' -Encoding utf8 | |
| - name: Install .NET | |
| uses: actions/setup-dotnet@v6 | |
| with: | |
| global-json-file: global.json | |
| - name: Start desktop session | |
| uses: ./.github/actions/desktop-session | |
| with: | |
| now-channel: 'true' | |
| # Restores packages and does the first build, so the agent's builds are incremental and | |
| # a broken base build shows up here rather than as an agent failure. | |
| - name: Build UniGetUI | |
| shell: pwsh | |
| run: .\.github\agent\implement-ticket\Start-UniGetUIDev.ps1 -NoLaunch | |
| - name: Install Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '24' | |
| - name: Install GitHub Copilot CLI | |
| shell: pwsh | |
| run: | | |
| npm install --global --no-fund --no-audit @github/copilot@1.0.96 | |
| if ($LASTEXITCODE -ne 0) { throw "npm install failed with exit code $LASTEXITCODE" } | |
| # The token only allows Copilot requests and reading this public repository, and it is | |
| # hidden from the agent's shell. The agent has no GitHub tools and no git credential. | |
| # Its only MCP server is Avalonia's Build MCP (mcp-config.json), a public, read-only | |
| # documentation service that gets no credential. | |
| - name: Run the agent | |
| id: agent | |
| shell: pwsh | |
| timeout-minutes: 80 | |
| env: | |
| COPILOT_GITHUB_TOKEN: ${{ github.token }} | |
| COPILOT_MODEL: ${{ inputs.model || vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} | |
| run: | | |
| .\.github\agent\Invoke-CopilotAgent.ps1 ` | |
| -Prompt "Read .github/agent/implement-ticket/PROMPT.md and follow it. The ticket is in the file at $env:AGENT_TICKET. Save screenshots in $env:AGENT_SCREENSHOTS, and write the pull request description to the file at $env:AGENT_PR_BODY." ` | |
| -OutputDir (Join-Path $env:AGENT_DIR 'out') ` | |
| -Models $env:COPILOT_MODEL ` | |
| -McpConfig (Join-Path $env:GITHUB_WORKSPACE '.github\agent\implement-ticket\mcp-config.json') | |
| - name: Check the change | |
| id: checks | |
| if: always() && steps.agent.outcome != 'skipped' | |
| shell: pwsh | |
| run: | | |
| .\.github\agent\implement-ticket\Test-AgentChange.ps1 -BaseSha $env:AGENT_BASE_SHA -OutputDir (Join-Path $env:AGENT_DIR 'out') | |
| - name: Prepare the screenshots | |
| if: always() && steps.agent.outcome != 'skipped' | |
| shell: pwsh | |
| run: | | |
| .\.github\agent\implement-ticket\New-ScreenshotEvidence.ps1 ` | |
| -ScreenshotsDir $env:AGENT_SCREENSHOTS ` | |
| -OutputDir (Join-Path $env:AGENT_DIR 'out') ` | |
| -PullRequestBody $env:AGENT_PR_BODY | |
| # A second agent, on a different model, reviews the change where it lies, so the fix | |
| # round can run on the same desktop. It must not edit the change; if it does, the | |
| # implementer's change is put back. | |
| - name: Review the change | |
| id: review | |
| if: always() && steps.agent.outcome == 'success' | |
| continue-on-error: true | |
| timeout-minutes: 20 | |
| shell: pwsh | |
| env: | |
| COPILOT_GITHUB_TOKEN: ${{ github.token }} | |
| REVIEW_MODEL_LIST: ${{ inputs.review_model || env.REVIEW_MODELS }} | |
| run: | | |
| .\.github\agent\implement-ticket\Invoke-AgentReview.ps1 ` | |
| -BaseSha $env:AGENT_BASE_SHA ` | |
| -ChangeDir (Join-Path $env:AGENT_DIR 'out') ` | |
| -OutputDir (Join-Path $env:AGENT_DIR 'out\review') ` | |
| -Models ($env:REVIEW_MODEL_LIST -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) | |
| # One fix round and no more: the failed checks and the review findings marked | |
| # implementable (blocker, major, minor). Everything else goes to the human reviewer. | |
| - name: Choose what to fix | |
| id: select | |
| if: always() && steps.agent.outcome == 'success' | |
| shell: pwsh | |
| run: | | |
| $out = Join-Path $env:AGENT_DIR 'out' | |
| .\.github\agent\implement-ticket\Select-AgentFixes.ps1 ` | |
| -ChecksPath (Join-Path $out 'checks.json') ` | |
| -ReviewPath (Join-Path $out 'review\review.json') ` | |
| -OutputDir (Join-Path $out 'fix') | |
| - name: Fix the problems found | |
| id: fix | |
| if: always() && steps.select.outputs.needs-fix == 'true' | |
| continue-on-error: true | |
| timeout-minutes: 40 | |
| shell: pwsh | |
| env: | |
| COPILOT_GITHUB_TOKEN: ${{ github.token }} | |
| COPILOT_MODEL: ${{ inputs.model || vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} | |
| run: | | |
| $fixDir = Join-Path $env:AGENT_DIR 'out\fix' | |
| $env:AGENT_FIX_REQUEST = Join-Path $fixDir 'fix-request.md' | |
| $env:AGENT_FIX_REPORT = Join-Path $fixDir 'fix-report.json' | |
| .\.github\agent\Invoke-CopilotAgent.ps1 ` | |
| -Prompt "Read .github/agent/implement-ticket/FIX_PROMPT.md and follow it. The problems to fix are in the file at $env:AGENT_FIX_REQUEST. Save screenshots in $env:AGENT_SCREENSHOTS, update the pull request description at $env:AGENT_PR_BODY, and write your report to the file at $env:AGENT_FIX_REPORT." ` | |
| -OutputDir $fixDir ` | |
| -Models $env:COPILOT_MODEL ` | |
| -McpConfig (Join-Path $env:GITHUB_WORKSPACE '.github\agent\implement-ticket\mcp-config.json') | |
| # The report shows both rounds of checks: before-fix holds the first. | |
| - name: Check the change after the fixes | |
| id: checks-after-fix | |
| if: always() && steps.fix.outcome != 'skipped' | |
| shell: pwsh | |
| run: | | |
| $out = Join-Path $env:AGENT_DIR 'out' | |
| $before = Join-Path $out 'before-fix' | |
| New-Item -Path $before -ItemType Directory -Force | Out-Null | |
| foreach ($name in @('checks.json', 'checks.md', 'check-logs', 'evidence.json', 'evidence.md')) { | |
| $path = Join-Path $out $name | |
| if (Test-Path -LiteralPath $path) { Move-Item -LiteralPath $path -Destination $before } | |
| } | |
| .\.github\agent\implement-ticket\Test-AgentChange.ps1 -BaseSha $env:AGENT_BASE_SHA -OutputDir $out | |
| .\.github\agent\implement-ticket\New-ScreenshotEvidence.ps1 ` | |
| -ScreenshotsDir $env:AGENT_SCREENSHOTS ` | |
| -OutputDir $out ` | |
| -PullRequestBody $env:AGENT_PR_BODY | |
| - name: Collect the change | |
| id: export | |
| if: always() && steps.agent.outcome != 'skipped' | |
| shell: pwsh | |
| run: | | |
| .\.github\agent\implement-ticket\Export-AgentChange.ps1 ` | |
| -BaseSha $env:AGENT_BASE_SHA ` | |
| -PullRequestBody $env:AGENT_PR_BODY ` | |
| -ScreenshotsDir $env:AGENT_SCREENSHOTS ` | |
| -OutputDir (Join-Path $env:AGENT_DIR 'out') | |
| Copy-Item -Path $env:AGENT_TICKET -Destination (Join-Path $env:AGENT_DIR 'out') | |
| - name: Upload the change | |
| if: always() && steps.export.outcome != 'skipped' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ inputs.artifact_prefix }}agent-change | |
| path: ${{ runner.temp }}/agent/out | |
| if-no-files-found: warn | |
| - name: Clean up desktop session | |
| if: always() | |
| uses: ./.github/actions/desktop-session-cleanup | |
| with: | |
| artifact-name: ${{ inputs.artifact_prefix }}agent-desktop-session | |
| report: | |
| name: Report and publish | |
| needs: [resolve, implement] | |
| # Not after a cancelled implement job; after a failed one, it reports what there is. | |
| if: >- | |
| always() && needs.resolve.outputs.proceed == 'true' && needs.resolve.outputs.mode == 'implement' && | |
| (needs.implement.result == 'success' || needs.implement.result == 'failure') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| actions: read | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| outputs: | |
| checks-passed: ${{ steps.report.outputs.checks-passed }} | |
| review-verdict: ${{ steps.report.outputs.review-verdict }} | |
| steps: | |
| - name: Checkout the base or the pull request branch | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ needs.resolve.outputs.head-branch || needs.resolve.outputs.base-sha }} | |
| token: ${{ secrets.GH_AW_GITHUB_TOKEN || github.token }} | |
| - name: Checkout the workflow's scripts | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ github.workflow_sha }} | |
| path: agent-tools | |
| persist-credentials: false | |
| sparse-checkout: .github/agent | |
| # Missing when the implement job failed before collecting the change. | |
| - name: Download the change | |
| continue-on-error: true | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: ${{ inputs.artifact_prefix }}agent-change | |
| path: ${{ runner.temp }}/agent-change | |
| - name: Write the report | |
| id: report | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| IMPLEMENT_RUNNER: ${{ needs.implement.outputs.runner-name }} | |
| run: | | |
| Add-Content -Path '.git/info/exclude' -Value '/agent-tools/' -Encoding utf8 | |
| ./agent-tools/.github/agent/implement-ticket/New-AgentReport.ps1 ` | |
| -ChangeDir (Join-Path $env:RUNNER_TEMP 'agent-change') ` | |
| -OutputDir (Join-Path $env:RUNNER_TEMP 'report') ` | |
| -ImplementRunner $env:IMPLEMENT_RUNNER | |
| - name: Upload the report | |
| if: always() && steps.report.outcome != 'skipped' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ inputs.artifact_prefix }}agent-report | |
| path: ${{ runner.temp }}/report | |
| if-no-files-found: warn | |
| - name: Open or update the pull request | |
| id: publish | |
| if: needs.implement.outputs.has-changes == 'true' && needs.resolve.outputs.create-pr == 'true' | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || github.token }} | |
| ISSUE: ${{ needs.resolve.outputs.issue }} | |
| PR: ${{ needs.resolve.outputs.pr }} | |
| HEAD_BRANCH: ${{ needs.resolve.outputs.head-branch }} | |
| BASE_BRANCH: ${{ github.event.repository.default_branch }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| $branch = if ($env:HEAD_BRANCH) { $env:HEAD_BRANCH } elseif ($env:ISSUE) { "agent/issue-$($env:ISSUE)-$($env:GITHUB_RUN_ID)" } else { "agent/ticket-$($env:GITHUB_RUN_ID)" } | |
| $base = if ($env:GITHUB_EVENT_NAME -eq 'workflow_dispatch') { $env:GITHUB_REF_NAME } else { $env:BASE_BRANCH } | |
| ./agent-tools/.github/agent/implement-ticket/Publish-AgentPullRequest.ps1 ` | |
| -ChangeDir (Join-Path $env:RUNNER_TEMP 'agent-change') ` | |
| -BodyPath (Join-Path $env:RUNNER_TEMP 'report/body.md') ` | |
| -Repository $env:GITHUB_REPOSITORY ` | |
| -BaseBranch $base ` | |
| -BranchName $branch ` | |
| -RunUrl $env:RUN_URL ` | |
| -IssueNumber $env:ISSUE ` | |
| -PullRequestNumber $env:PR | |
| - name: Report back on the issue | |
| if: always() && needs.resolve.outputs.issue != '' && needs.resolve.outputs.pr == '' | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| ISSUE: ${{ needs.resolve.outputs.issue }} | |
| PR_URL: ${{ steps.publish.outputs.pr-url }} | |
| COMPARE_URL: ${{ steps.publish.outputs.compare-url }} | |
| HAS_CHANGES: ${{ needs.implement.outputs.has-changes }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| $outcome = if ($env:PR_URL) { | |
| "Opened $env:PR_URL." | |
| } elseif ($env:COMPARE_URL) { | |
| "The change is ready to open as a pull request: $env:COMPARE_URL." | |
| } elseif ($env:HAS_CHANGES -eq 'true') { | |
| 'The change is in the run''s artifacts.' | |
| } else { | |
| 'The agent made no change; its explanation is in the run.' | |
| } | |
| $report = Get-Content -LiteralPath (Join-Path $env:RUNNER_TEMP 'report/report.md') -Raw -ErrorAction SilentlyContinue | |
| @("The [agent run]($env:RUN_URL) finished. $outcome", '', '<details><summary>Checks, review and metrics</summary>', '', $report, '', '</details>') -join "`n" | | |
| gh issue comment $env:ISSUE --repo $env:GITHUB_REPOSITORY --body-file - |