Runtime configuration support / MDC support #4
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: Issue updated → dispatch to coding agent | |
| on: | |
| issues: | |
| types: | |
| - opened | |
| - edited | |
| - reopened | |
| - assigned | |
| - unassigned | |
| permissions: | |
| contents: read | |
| issues: read | |
| # Optional: set in Settings → Variables → Repository variables | |
| # CODING_AGENT_USER = <agent-gh-handle> (leave empty to disable the assignee filter) | |
| env: | |
| CODING_AGENT_USER: ${{ vars.CODING_AGENT_USER }} | |
| concurrency: | |
| group: issue-${{ github.event.issue.number }}-dispatch | |
| cancel-in-progress: true | |
| jobs: | |
| dispatch: | |
| # Only members/maintainers/collaborators can trigger runs from a public repo | |
| if: ${{ contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.actor_association) }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Determine issue type and assignment (Issue Types only) | |
| id: meta | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # gh uses GH_TOKEN | |
| CODING_AGENT_USER: ${{ env.CODING_AGENT_USER }} | |
| run: | | |
| set -euo pipefail | |
| OWNER="${GITHUB_REPOSITORY%/*}" | |
| REPO="${GITHUB_REPOSITORY#*/}" | |
| NUMBER="${{ github.event.issue.number }}" | |
| # Query: issue type + assignees (no labels) | |
| RESP="$(gh api graphql \ | |
| -H 'GraphQL-Features: issue_types' \ | |
| -f owner="$OWNER" \ | |
| -f repo="$REPO" \ | |
| -F number="$NUMBER" \ | |
| -f query=' | |
| query($owner:String!, $repo:String!, $number:Int!) { | |
| repository(owner:$owner, name:$repo) { | |
| issue(number:$number) { | |
| number | |
| title | |
| issueType { name } | |
| assignees(first: 100) { nodes { login } } | |
| } | |
| } | |
| }' | |
| )" | |
| TYPE="$(jq -r '.data.repository.issue.issueType.name // empty' <<< "$RESP")" | |
| # Optional assignee filter | |
| RAW_AGENT="${CODING_AGENT_USER:-}" | |
| AGENT_CLEAN="$(tr -d '\n' <<< "${RAW_AGENT#@}" | tr '[:upper:]' '[:lower:]')" | |
| if [[ -z "$AGENT_CLEAN" ]]; then | |
| ASSIGNED_OK="true" | |
| else | |
| ASSIGNED_OK="$( | |
| jq -r --arg agent "$AGENT_CLEAN" ' | |
| [.data.repository.issue.assignees.nodes[].login // empty | |
| | ascii_downcase] | index($agent) | if .==null then "false" else "true" end | |
| ' <<< "$RESP" | |
| )" | |
| fi | |
| # Type filter: only "Task" or "Bug" | |
| case "$(tr '[:upper:]' '[:lower:]' <<< "${TYPE}")" in | |
| bug|task) TYPE_OK="true" ;; | |
| *) TYPE_OK="false" ;; | |
| esac | |
| SHOULD=$([[ "$TYPE_OK" == "true" && "$ASSIGNED_OK" == "true" ]] && echo true || echo false) | |
| { | |
| echo "issue_type=${TYPE}" | |
| echo "assigned_ok=${ASSIGNED_OK}" | |
| echo "type_ok=${TYPE_OK}" | |
| echo "should_dispatch=${SHOULD}" | |
| echo "agent_user=${AGENT_CLEAN}" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Log & skip if not eligible | |
| if: ${{ steps.meta.outputs.should_dispatch != 'true' }} | |
| run: | | |
| echo "Issue #${{ github.event.issue.number }} not eligible for dispatch." | |
| echo " issue_type='${{ steps.meta.outputs.issue_type }}' (type_ok=${{ steps.meta.outputs.type_ok }})" | |
| echo " assigned_ok=${{ steps.meta.outputs.assigned_ok }} agent='${{ steps.meta.outputs.agent_user }}'" | |
| echo " Note: This workflow requires GitHub Issue Types ('Task' or 'Bug')." | |
| - name: Build payload | |
| if: ${{ steps.meta.outputs.should_dispatch == 'true' }} | |
| id: payload | |
| env: | |
| ISSUE_TYPE: ${{ steps.meta.outputs.issue_type }} | |
| AGENT_USER: ${{ steps.meta.outputs.agent_user }} | |
| ASSIGNED_OK: ${{ steps.meta.outputs.assigned_ok }} | |
| run: | | |
| jq -n \ | |
| --arg repo "${{ github.repository }}" \ | |
| --argjson issue ${{ github.event.issue.number }} \ | |
| --arg url "${{ github.event.issue.html_url }}" \ | |
| --arg title "${{ github.event.issue.title }}" \ | |
| --arg actor "${{ github.actor }}" \ | |
| --arg action "${{ github.event.action }}" \ | |
| --arg issue_type "${ISSUE_TYPE:-}" \ | |
| --arg agent_user "${AGENT_USER:-}" \ | |
| --arg assigned_ok "${ASSIGNED_OK:-}" \ | |
| '{event_type:"coding_agent_dispatch", | |
| client_payload:{ | |
| repo:$repo, | |
| issue:$issue, | |
| issue_html_url:$url, | |
| issue_title:$title, | |
| issue_actor:$actor, | |
| issue_action:$action, | |
| issue_type:$issue_type, | |
| agent_user:$agent_user, | |
| assigned_ok:$assigned_ok | |
| }}' \ | |
| > payload.json | |
| - name: Preflight (token & target repo access) | |
| if: ${{ steps.meta.outputs.should_dispatch == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.AIBUDDY_DISPATCH_PAT }} | |
| run: | | |
| set -euo pipefail | |
| TARGET="hyperifyio/aibuddy" | |
| if [[ -z "${GH_TOKEN:-}" ]]; then | |
| echo "::error title=Missing secret::AIBUDDY_DISPATCH_PAT is not set." | |
| exit 1 | |
| fi | |
| # Verify token can access the private repo | |
| if ! gh api "repos/$TARGET" >/dev/null 2>err.txt; then | |
| echo "::error title=Token cannot access target repo::$TARGET not accessible with provided token." | |
| cat err.txt || true | |
| exit 1 | |
| fi | |
| # Validate payload JSON | |
| jq -e . payload.json >/dev/null || { | |
| echo "::error title=Invalid payload::payload.json is not valid JSON" | |
| cat payload.json | |
| exit 1 | |
| } | |
| - name: Send repository_dispatch to aibuddy (gh api) | |
| if: ${{ steps.meta.outputs.should_dispatch == 'true' }} | |
| env: | |
| GH_TOKEN: ${{ secrets.AIBUDDY_DISPATCH_PAT }} | |
| run: | | |
| set -euo pipefail | |
| gh api repos/hyperifyio/aibuddy/dispatches \ | |
| --method POST \ | |
| -H "Accept: application/vnd.github+json" \ | |
| --input payload.json | |
| echo "repository_dispatch sent for issue #${{ github.event.issue.number }} (type=${{ steps.meta.outputs.issue_type }}, agent='${{ steps.meta.outputs.agent_user }}')" | |