Use environment variables instead of direct GitHub expression interpolation in bash script. This prevents the script from breaking when comment bodies contain quotes or special characters.
Variables like COMMENT_BODY, COMMENT_AUTHOR, COMMENT_ID_VAL are now passed via env: block instead of being interpolated directly into bash commands.
🤖 Generated with assistance of OhMyOpenCode
386 lines
14 KiB
YAML
386 lines
14 KiB
YAML
name: Sisyphus Agent
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
prompt:
|
|
description: "Custom prompt"
|
|
required: false
|
|
issue_comment:
|
|
types: [created]
|
|
pull_request_review:
|
|
types: [submitted]
|
|
pull_request_review_comment:
|
|
types: [created]
|
|
|
|
jobs:
|
|
agent:
|
|
runs-on: ubuntu-latest
|
|
# @sisyphus-dev-ai mention only (maintainers, exclude self)
|
|
if: |
|
|
github.event_name == 'workflow_dispatch' ||
|
|
(contains(github.event.comment.body || github.event.review.body, '@sisyphus-dev-ai') &&
|
|
(github.event.comment.user.login || github.event.review.user.login) != 'sisyphus-dev-ai' &&
|
|
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association || github.event.review.author_association))
|
|
|
|
# Minimal default GITHUB_TOKEN permissions
|
|
permissions:
|
|
contents: read
|
|
|
|
steps:
|
|
# Checkout with sisyphus-dev-ai's PAT
|
|
- uses: actions/checkout@v5
|
|
with:
|
|
token: ${{ secrets.GH_PAT }}
|
|
fetch-depth: 0
|
|
|
|
# Git config - commits as sisyphus-dev-ai
|
|
- name: Configure Git as sisyphus-dev-ai
|
|
run: |
|
|
git config user.name "sisyphus-dev-ai"
|
|
git config user.email "sisyphus-dev-ai@users.noreply.github.com"
|
|
|
|
# gh CLI auth as sisyphus-dev-ai
|
|
- name: Authenticate gh CLI as sisyphus-dev-ai
|
|
run: |
|
|
echo "${{ secrets.GH_PAT }}" | gh auth login --with-token
|
|
gh auth status
|
|
|
|
- name: Ensure tmux is available (Linux)
|
|
if: runner.os == 'Linux'
|
|
run: |
|
|
set -euo pipefail
|
|
if ! command -v tmux >/dev/null 2>&1; then
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends tmux
|
|
fi
|
|
tmux -V
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: latest
|
|
|
|
- name: Cache Bun dependencies
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.bun/install/cache
|
|
node_modules
|
|
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-bun-
|
|
|
|
# Build local oh-my-opencode
|
|
- name: Build oh-my-opencode
|
|
run: |
|
|
bun install
|
|
bun run build
|
|
|
|
# Install OpenCode + configure local plugin + auth in single step
|
|
- name: Setup OpenCode with oh-my-opencode
|
|
env:
|
|
OPENCODE_AUTH_JSON: ${{ secrets.OPENCODE_AUTH_JSON }}
|
|
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
run: |
|
|
export PATH="$HOME/.opencode/bin:$PATH"
|
|
|
|
# Install OpenCode (skip if cached)
|
|
if ! command -v opencode &>/dev/null; then
|
|
for i in 1 2 3; do
|
|
echo "Attempt $i: Installing OpenCode..."
|
|
curl -fsSL https://opencode.ai/install -o /tmp/opencode-install.sh
|
|
if file /tmp/opencode-install.sh | grep -q "shell script\|text"; then
|
|
bash /tmp/opencode-install.sh && break
|
|
fi
|
|
echo "Download corrupted, retrying in 5s..."
|
|
done
|
|
fi
|
|
opencode --version
|
|
|
|
# Run local oh-my-opencode install (uses built dist)
|
|
bun run dist/cli/index.js install --no-tui --claude=max20 --chatgpt=no --gemini=no
|
|
|
|
# Override plugin to use local file reference
|
|
OPENCODE_JSON=~/.config/opencode/opencode.json
|
|
REPO_PATH=$(pwd)
|
|
jq --arg path "file://$REPO_PATH/src/index.ts" '
|
|
.plugin = [.plugin[] | select(. != "oh-my-opencode")] + [$path]
|
|
' "$OPENCODE_JSON" > /tmp/oc.json && mv /tmp/oc.json "$OPENCODE_JSON"
|
|
|
|
OPENCODE_JSON=~/.config/opencode/opencode.json
|
|
jq --arg baseURL "$ANTHROPIC_BASE_URL" --arg apiKey "$ANTHROPIC_API_KEY" '
|
|
.provider.anthropic = {
|
|
"name": "Anthropic",
|
|
"npm": "@ai-sdk/anthropic",
|
|
"options": {
|
|
"baseURL": $baseURL,
|
|
"apiKey": $apiKey
|
|
},
|
|
"models": {
|
|
"claude-opus-4-5": {
|
|
"id": "claude-opus-4-5-20251101",
|
|
"name": "Opus 4.5",
|
|
"limit": { "context": 190000, "output": 64000 },
|
|
"options": { "effort": "high" }
|
|
},
|
|
"claude-opus-4-5-high": {
|
|
"id": "claude-opus-4-5-20251101",
|
|
"name": "Opus 4.5 High",
|
|
"limit": { "context": 190000, "output": 128000 },
|
|
"options": { "effort": "high", "thinking": { "type": "enabled", "budgetTokens": 64000 } }
|
|
},
|
|
"claude-sonnet-4-5": {
|
|
"id": "claude-sonnet-4-5-20250929",
|
|
"name": "Sonnet 4.5",
|
|
"limit": { "context": 200000, "output": 64000 }
|
|
},
|
|
"claude-sonnet-4-5-high": {
|
|
"id": "claude-sonnet-4-5-20250929",
|
|
"name": "Sonnet 4.5 High",
|
|
"limit": { "context": 200000, "output": 128000 },
|
|
"options": { "thinking": { "type": "enabled", "budgetTokens": 64000 } }
|
|
},
|
|
"claude-haiku-4-5": {
|
|
"id": "claude-haiku-4-5-20251001",
|
|
"name": "Haiku 4.5",
|
|
"limit": { "context": 200000, "output": 64000 }
|
|
}
|
|
}
|
|
}
|
|
' "$OPENCODE_JSON" > /tmp/oc.json && mv /tmp/oc.json "$OPENCODE_JSON"
|
|
|
|
OMO_JSON=~/.config/opencode/oh-my-opencode.json
|
|
PROMPT_APPEND=$(cat << 'PROMPT_EOF'
|
|
|
|
## GitHub Actions Environment
|
|
|
|
You are `sisyphus-dev-ai` in GitHub Actions.
|
|
|
|
### CRITICAL: GitHub Comments = Your ONLY Output
|
|
|
|
User CANNOT see console. Post everything via `gh issue comment` or `gh pr comment`.
|
|
|
|
### Comment Formatting (CRITICAL)
|
|
|
|
**ALWAYS use heredoc syntax for comments containing code references, backticks, or multiline content:**
|
|
|
|
```bash
|
|
gh issue comment <number> --body "$(cat <<'EOF'
|
|
Your comment with `backticks` and code references preserved here.
|
|
Multiple lines work perfectly.
|
|
EOF
|
|
)"
|
|
```
|
|
|
|
**NEVER use direct quotes with backticks** (shell will interpret them as command substitution):
|
|
```bash
|
|
# WRONG - backticks disappear:
|
|
gh issue comment 123 --body "text with `code`"
|
|
|
|
# CORRECT - backticks preserved:
|
|
gh issue comment 123 --body "$(cat <<'EOF'
|
|
text with `code`
|
|
EOF
|
|
)"
|
|
```
|
|
|
|
### GitHub Markdown Rules (MUST FOLLOW)
|
|
|
|
**Code blocks MUST have EXACTLY 3 backticks and language identifier:**
|
|
- CORRECT: ` ```bash ` ... ` ``` `
|
|
- WRONG: ` ``` ` (no language), ` ```` ` (4 backticks), ` `` ` (2 backticks)
|
|
|
|
**Every opening ` ``` ` MUST have a closing ` ``` ` on its own line:**
|
|
```
|
|
```bash
|
|
code here
|
|
```
|
|
```
|
|
|
|
**NO trailing backticks or spaces after closing ` ``` `**
|
|
|
|
**For inline code, use SINGLE backticks:** `code` not ```code```
|
|
|
|
**Lists inside code blocks break rendering - avoid them or use plain text**
|
|
|
|
### Rules
|
|
- EVERY response = GitHub comment (use heredoc for proper escaping)
|
|
- Code changes = PR (never push main/master)
|
|
- Setup: bun install first
|
|
- Acknowledge immediately, report when done
|
|
|
|
### Git Config
|
|
- user.name: sisyphus-dev-ai
|
|
- user.email: sisyphus-dev-ai@users.noreply.github.com
|
|
PROMPT_EOF
|
|
)
|
|
jq --arg append "$PROMPT_APPEND" '.agents.Sisyphus.prompt_append = $append' "$OMO_JSON" > /tmp/omo.json && mv /tmp/omo.json "$OMO_JSON"
|
|
|
|
mkdir -p ~/.local/share/opencode
|
|
echo "$OPENCODE_AUTH_JSON" > ~/.local/share/opencode/auth.json
|
|
chmod 600 ~/.local/share/opencode/auth.json
|
|
|
|
cat "$OPENCODE_JSON"
|
|
|
|
# Collect context
|
|
- name: Collect Context
|
|
id: context
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
COMMENT_BODY: ${{ github.event.comment.body || github.event.review.body }}
|
|
COMMENT_AUTHOR: ${{ github.event.comment.user.login || github.event.review.user.login }}
|
|
COMMENT_ID_VAL: ${{ github.event.comment.id }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
if [[ "$EVENT_NAME" == "issue_comment" ]]; then
|
|
ISSUE_NUM="$ISSUE_NUMBER"
|
|
AUTHOR="$COMMENT_AUTHOR"
|
|
COMMENT_ID="$COMMENT_ID_VAL"
|
|
|
|
# Check if PR or Issue
|
|
if gh api "repos/$REPO/issues/${ISSUE_NUM}" | jq -e '.pull_request' > /dev/null; then
|
|
echo "type=pr" >> $GITHUB_OUTPUT
|
|
echo "number=${ISSUE_NUM}" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "type=issue" >> $GITHUB_OUTPUT
|
|
echo "number=${ISSUE_NUM}" >> $GITHUB_OUTPUT
|
|
fi
|
|
elif [[ "$EVENT_NAME" == "pull_request_review_comment" ]]; then
|
|
echo "type=pr" >> $GITHUB_OUTPUT
|
|
echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
|
AUTHOR="$COMMENT_AUTHOR"
|
|
COMMENT_ID="$COMMENT_ID_VAL"
|
|
elif [[ "$EVENT_NAME" == "pull_request_review" ]]; then
|
|
echo "type=pr" >> $GITHUB_OUTPUT
|
|
echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
|
AUTHOR="$COMMENT_AUTHOR"
|
|
COMMENT_ID=""
|
|
fi
|
|
|
|
echo "comment<<EOF" >> $GITHUB_OUTPUT
|
|
echo "$COMMENT_BODY" >> $GITHUB_OUTPUT
|
|
echo "EOF" >> $GITHUB_OUTPUT
|
|
echo "author=$AUTHOR" >> $GITHUB_OUTPUT
|
|
echo "comment_id=$COMMENT_ID" >> $GITHUB_OUTPUT
|
|
|
|
# Add :eyes: reaction (as sisyphus-dev-ai)
|
|
- name: Add eyes reaction
|
|
if: steps.context.outputs.comment_id != ''
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
|
run: |
|
|
gh api "/repos/${{ github.repository }}/issues/comments/${{ steps.context.outputs.comment_id }}/reactions" \
|
|
-X POST -f content="eyes" || true
|
|
|
|
- name: Add working label
|
|
if: steps.context.outputs.number != ''
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
|
run: |
|
|
gh label create "sisyphus: working" \
|
|
--repo "${{ github.repository }}" \
|
|
--color "fcf2e1" \
|
|
--description "Sisyphus is currently working on this" \
|
|
--force || true
|
|
|
|
if [[ "${{ steps.context.outputs.type }}" == "pr" ]]; then
|
|
gh pr edit "${{ steps.context.outputs.number }}" \
|
|
--repo "${{ github.repository }}" \
|
|
--add-label "sisyphus: working" || true
|
|
else
|
|
gh issue edit "${{ steps.context.outputs.number }}" \
|
|
--repo "${{ github.repository }}" \
|
|
--add-label "sisyphus: working" || true
|
|
fi
|
|
|
|
- name: Run oh-my-opencode
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
|
USER_COMMENT: ${{ steps.context.outputs.comment }}
|
|
COMMENT_AUTHOR: ${{ steps.context.outputs.author }}
|
|
CONTEXT_TYPE: ${{ steps.context.outputs.type }}
|
|
CONTEXT_NUMBER: ${{ steps.context.outputs.number }}
|
|
REPO_NAME: ${{ github.repository }}
|
|
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
run: |
|
|
export PATH="$HOME/.opencode/bin:$PATH"
|
|
|
|
PROMPT=$(cat <<'PROMPT_EOF'
|
|
Your username is @sisyphus-dev-ai, mentioned by @AUTHOR_PLACEHOLDER in REPO_PLACEHOLDER.
|
|
|
|
## Context
|
|
- Type: TYPE_PLACEHOLDER
|
|
- Number: #NUMBER_PLACEHOLDER
|
|
- Repository: REPO_PLACEHOLDER
|
|
- Default Branch: BRANCH_PLACEHOLDER
|
|
|
|
## User's Request
|
|
COMMENT_PLACEHOLDER
|
|
|
|
---
|
|
|
|
First, acknowledge with `gh issue comment NUMBER_PLACEHOLDER --body "👋 Hey @AUTHOR_PLACEHOLDER! I'm on it..."`
|
|
|
|
Then write everything using the todo tools.
|
|
Then investigate and satisfy the request. Only if user requested to you to work explicitely, then use plan agent to plan, todo obsessivley then create a PR to `BRANCH_PLACEHOLDER` branch.
|
|
PROMPT_EOF
|
|
)
|
|
|
|
PROMPT="${PROMPT//AUTHOR_PLACEHOLDER/$COMMENT_AUTHOR}"
|
|
PROMPT="${PROMPT//REPO_PLACEHOLDER/$REPO_NAME}"
|
|
PROMPT="${PROMPT//TYPE_PLACEHOLDER/$CONTEXT_TYPE}"
|
|
PROMPT="${PROMPT//NUMBER_PLACEHOLDER/$CONTEXT_NUMBER}"
|
|
PROMPT="${PROMPT//BRANCH_PLACEHOLDER/$DEFAULT_BRANCH}"
|
|
PROMPT="${PROMPT//COMMENT_PLACEHOLDER/$USER_COMMENT}"
|
|
|
|
bun run dist/cli/index.js run "$PROMPT"
|
|
|
|
# Push changes (as sisyphus-dev-ai)
|
|
- name: Push changes
|
|
if: always()
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
|
run: |
|
|
if [[ -n "$(git status --porcelain)" ]]; then
|
|
git add -A
|
|
git commit -m "chore: changes by sisyphus-dev-ai" || true
|
|
fi
|
|
|
|
BRANCH=$(git branch --show-current)
|
|
if [[ "$BRANCH" != "main" && "$BRANCH" != "master" ]]; then
|
|
git push origin "$BRANCH" || true
|
|
fi
|
|
|
|
- name: Update reaction and remove label
|
|
if: always()
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
|
run: |
|
|
if [[ -n "${{ steps.context.outputs.comment_id }}" ]]; then
|
|
REACTION_ID=$(gh api "/repos/${{ github.repository }}/issues/comments/${{ steps.context.outputs.comment_id }}/reactions" \
|
|
--jq '.[] | select(.content == "eyes" and .user.login == "sisyphus-dev-ai") | .id' | head -1)
|
|
if [[ -n "$REACTION_ID" ]]; then
|
|
gh api -X DELETE "/repos/${{ github.repository }}/reactions/${REACTION_ID}" || true
|
|
fi
|
|
|
|
gh api "/repos/${{ github.repository }}/issues/comments/${{ steps.context.outputs.comment_id }}/reactions" \
|
|
-X POST -f content="+1" || true
|
|
fi
|
|
|
|
if [[ -n "${{ steps.context.outputs.number }}" ]]; then
|
|
if [[ "${{ steps.context.outputs.type }}" == "pr" ]]; then
|
|
gh pr edit "${{ steps.context.outputs.number }}" \
|
|
--repo "${{ github.repository }}" \
|
|
--remove-label "sisyphus: working" || true
|
|
else
|
|
gh issue edit "${{ steps.context.outputs.number }}" \
|
|
--repo "${{ github.repository }}" \
|
|
--remove-label "sisyphus: working" || true
|
|
fi
|
|
fi
|