diff --git a/.github/workflows/amber-issue-handler.yml b/.github/workflows/amber-issue-handler.yml index c373896f3..8de1f63aa 100644 --- a/.github/workflows/amber-issue-handler.yml +++ b/.github/workflows/amber-issue-handler.yml @@ -28,6 +28,7 @@ permissions: contents: read issues: write pull-requests: write + checks: write jobs: # -- Issue: labeled ambient-code:auto-fix → fresh session prompt -- @@ -96,6 +97,8 @@ jobs: model: claude-opus-4-6 wait: 'true' timeout: '0' + environment-variables: >- + {"SLACK_WEBHOOK_URL": "${{ secrets.SLACK_WEBHOOK_URL }}", "PLATFORM_HOST": "${{ secrets.PLATFORM_HOST }}"} - name: Post-session labels and comment if: steps.existing.outputs.skip != 'true' @@ -206,13 +209,33 @@ jobs: - Are there merge conflicts? Resolve them. - Is CI failing? Read the logs and fix the failures. - Are there review comments (human or bot like CodeRabbit)? Address each comment. - 2. Push fixes. - 3. Ensure the PR body contains this frontmatter as the first line + 2. For each issue you fix, call `log_correction` to record what went wrong and how you fixed it. + 3. Push fixes. + 4. Ensure the PR body contains this frontmatter as the first line (read your session ID from the AGENTIC_SESSION_NAME environment variable): - - 4. Add the `ambient-code:managed` label. - 5. Do not merge. Do not close. Do not force-push. - 6. If fundamentally broken beyond repair, add a comment explaining and stop. + + Increment retry_count from whatever it was before. If retry_count reaches 3 or more, + stop working, add `ambient-code:needs-human` label, remove `ambient-code:managed` label, + comment "AI was unable to resolve after 3 attempts. Needs human attention.", + and send a Slack notification (see below). + 5. Add the `ambient-code:managed` label. + 6. Do not merge. Do not close. Do not force-push. + 7. If fundamentally broken beyond repair, add a comment explaining and stop. + + ## Slack Notifications + + When you need human attention — whether you hit the circuit breaker (3 retries), + you're stuck and can't proceed, or you use the AskUserQuestion tool — send a + Slack notification: + + ```bash + curl -X POST -H 'Content-type: application/json' \ + --data '{"text":"I need human attention\n*PR*: ${{ steps.context.outputs.url }}\n*Session*: '"$PLATFORM_HOST/projects/$AGENTIC_SESSION_NAMESPACE/sessions/$AGENTIC_SESSION_NAME"'\n*Reason*: "}' \ + "$SLACK_WEBHOOK_URL" + ``` + + The environment variables SLACK_WEBHOOK_URL, PLATFORM_HOST, AGENTIC_SESSION_NAMESPACE, + and AGENTIC_SESSION_NAME are available in your environment. Only send if SLACK_WEBHOOK_URL is set. # Fix prompt on an issue: @ambient-code alone — investigate and create PR (same as fresh prompt) - name: Run fix prompt (issue) @@ -250,6 +273,8 @@ jobs: model: claude-opus-4-6 wait: 'true' timeout: '0' + environment-variables: >- + {"SLACK_WEBHOOK_URL": "${{ secrets.SLACK_WEBHOOK_URL }}", "PLATFORM_HOST": "${{ secrets.PLATFORM_HOST }}"} # Custom prompt: @ambient-code — pass user's text - name: Run custom prompt @@ -273,11 +298,56 @@ jobs: model: claude-opus-4-6 wait: 'true' timeout: '0' + environment-variables: >- + {"SLACK_WEBHOOK_URL": "${{ secrets.SLACK_WEBHOOK_URL }}", "PLATFORM_HOST": "${{ secrets.PLATFORM_HOST }}"} + + - name: Post check run on PR + if: >- + always() + && steps.context.outputs.is_fork != 'true' + && steps.context.outputs.type == 'pr' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + SESSION_NAME="${{ steps.fix-session.outputs.session-name || steps.fix-issue-session.outputs.session-name || steps.custom-session.outputs.session-name }}" + SESSION_URL="${{ steps.fix-session.outputs.session-url || steps.fix-issue-session.outputs.session-url || steps.custom-session.outputs.session-url }}" + SESSION_PHASE="${{ steps.fix-session.outputs.session-phase || steps.fix-issue-session.outputs.session-phase || steps.custom-session.outputs.session-phase }}" + + if [ -z "$SESSION_NAME" ]; then + exit 0 + fi + + # Get PR head SHA + HEAD_SHA=$(gh pr view ${{ steps.context.outputs.number }} --repo "${{ github.repository }}" --json headRefOid --jq '.headRefOid') + + # Map session phase to check status/conclusion + CHECK_ARGS=( + -X POST + -f "name=Amber Session" + -f "head_sha=$HEAD_SHA" + -f "details_url=$SESSION_URL" + -f "output[title]=Amber — ${{ steps.context.outputs.prompt_type }} prompt" + -f "output[summary]=Session \`$SESSION_NAME\` (phase: $SESSION_PHASE)" + ) + + case "$SESSION_PHASE" in + Running) + CHECK_ARGS+=(-f "status=in_progress") ;; + Completed) + CHECK_ARGS+=(-f "status=completed" -f "conclusion=success") ;; + Error|Failed) + CHECK_ARGS+=(-f "status=completed" -f "conclusion=failure") ;; + *) + CHECK_ARGS+=(-f "status=completed" -f "conclusion=neutral") ;; + esac + + if ! CHECK_OUTPUT=$(gh api "repos/${{ github.repository }}/check-runs" "${CHECK_ARGS[@]}" 2>&1); then + echo "::warning::Failed to create check run: $CHECK_OUTPUT" + fi - name: Session summary if: always() && steps.context.outputs.is_fork != 'true' run: | - # Get session name from whichever step ran (only one will have output) SESSION_NAME="${{ steps.fix-session.outputs.session-name || steps.fix-issue-session.outputs.session-name || steps.custom-session.outputs.session-name }}" SESSION_PHASE="${{ steps.fix-session.outputs.session-phase || steps.fix-issue-session.outputs.session-phase || steps.custom-session.outputs.session-phase }}" @@ -306,6 +376,8 @@ jobs: AMBIENT_API_URL: ${{ secrets.AMBIENT_API_URL }} AMBIENT_API_TOKEN: ${{ secrets.AMBIENT_BOT_TOKEN }} AMBIENT_PROJECT: ${{ secrets.AMBIENT_PROJECT }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + PLATFORM_HOST: ${{ secrets.PLATFORM_HOST }} run: | pip install --quiet 'requests>=2.31.0' @@ -369,6 +441,26 @@ jobs: print(f" Failed to start session: {e}") return False + def post_check_run(pr_number, session_name): + """Post an in_progress check run on the PR linking to the Amber session.""" + head_sha = gh("pr", "view", str(pr_number), "--repo", REPO, "--json", "headRefOid", "--jq", ".headRefOid") + if not head_sha: + return + host = os.environ.get("PLATFORM_HOST", "").rstrip("/") + session_url = f"{host}/projects/{PROJECT}/sessions/{session_name}" if host else "" + args = ["api", f"repos/{REPO}/check-runs", + "-X", "POST", + "-f", "name=Amber Session", + "-f", f"head_sha={head_sha}", + "-f", "status=in_progress", + "-f", "output[title]=Amber — batch fix", + "-f", f"output[summary]=Session `{session_name}` triggered for PR #{pr_number}"] + if session_url: + args.extend(["-f", f"details_url={session_url}"]) + proc = subprocess.run(["gh"] + list(args), capture_output=True, text=True) + if proc.returncode != 0: + print(f" Warning: failed to create check run for PR #{pr_number}: {proc.stderr or proc.stdout}") + def create_session_api(prompt, session_name="", model="claude-opus-4-6"): """Create a new session or send message to existing one.""" @@ -403,9 +495,17 @@ jobs: # Create new session url = f"{API_URL.rstrip('/')}/projects/{PROJECT}/agentic-sessions" + slack_url = os.environ.get("SLACK_WEBHOOK_URL", "") + platform_host = os.environ.get("PLATFORM_HOST", "").rstrip("/") + env_vars = {} + if slack_url: + env_vars["SLACK_WEBHOOK_URL"] = slack_url + if platform_host: + env_vars["PLATFORM_HOST"] = platform_host body = {"initialPrompt": prompt, "llmSettings": {"model": model}, - "repos": [{"url": f"https://github.com/{REPO}", "branch": "main"}]} + "repos": [{"url": f"https://github.com/{REPO}", "branch": "main"}], + **({"environmentVariables": env_vars} if env_vars else {})} try: resp = requests.post(url, headers={"Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json"}, json=body, timeout=30) @@ -441,13 +541,6 @@ jobs: session_id = fm["session_id"] source = fm["source"] - # Circuit breaker - if fm["retry_count"] >= 3: - print(f"PR #{number}: circuit breaker (retry_count={fm['retry_count']}), adding ambient-code:needs-human") - gh("pr", "edit", str(number), "--repo", REPO, "--add-label", "ambient-code:needs-human", "--remove-label", "ambient-code:managed") - gh("pr", "comment", str(number), "--repo", REPO, "--body", "AI was unable to resolve issues after 3 attempts. Needs human attention.") - continue - # Check for changes using updatedAt from gh pr list (no extra API call) updated_at = pr.get("updatedAt", "") if updated_at and updated_at <= fm["last_action"]: @@ -458,6 +551,7 @@ jobs: # Trigger fix — reuse session if exists, create new if not print(f"PR #{number}: triggering fix (session_id={session_id or 'new'})") + current_retry = fm["retry_count"] if fm else 0 prompt = f"""You are maintaining a pull request. URL: https://github.com/{REPO}/pull/{number} @@ -468,26 +562,31 @@ jobs: - Are there merge conflicts? Resolve them. - Is CI failing? Read the logs and fix the failures. - Are there review comments (human or bot like CodeRabbit)? Address each comment. - 2. Push fixes. - 3. Ensure the PR body contains this frontmatter as the first line + 2. For each issue you fix, call `log_correction` to record what went wrong and how you fixed it. + 3. Push fixes. + 4. Ensure the PR body contains this frontmatter as the first line (read your session ID from the AGENTIC_SESSION_NAME environment variable): - - 4. Add the `ambient-code:managed` label. - 5. Do not merge. Do not close. Do not force-push. - 6. If fundamentally broken beyond repair, add a comment explaining and stop.""" - - create_session_api(prompt, session_name=session_id) - - # Increment retry_count in frontmatter so circuit breaker advances - if fm: - new_count = fm["retry_count"] + 1 - now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") - old_fm = f'acp:session_id={fm["session_id"]} source={fm["source"]} last_action={fm["last_action"]} retry_count={fm["retry_count"]}' - new_fm = f'acp:session_id={fm["session_id"]} source={fm["source"]} last_action={now} retry_count={new_count}' - new_body = body.replace(old_fm, new_fm) - if new_body != body: - gh("pr", "edit", str(number), "--repo", REPO, "--body", new_body) - print(f" Updated frontmatter: retry_count={new_count}, last_action={now}") + + The current retry_count is {current_retry}. Increment it by 1. + If retry_count reaches 3 or more, stop working, add `ambient-code:needs-human` label, + remove `ambient-code:managed` label, comment on the PR, and send a Slack notification. + 5. Add the `ambient-code:managed` label. + 6. Do not merge. Do not close. Do not force-push. + 7. If fundamentally broken beyond repair, add a comment explaining and stop. + + ## Slack Notifications + + When you need human attention — circuit breaker, stuck, or using AskUserQuestion — send: + + curl -X POST -H 'Content-type: application/json' \\ + --data '{{"text":"I need human attention\\n*PR*: https://github.com/{REPO}/pull/{number}\\n*Session*: '"$PLATFORM_HOST/projects/$AGENTIC_SESSION_NAMESPACE/sessions/$AGENTIC_SESSION_NAME"'\\n*Reason*: "}}' \\ + "$SLACK_WEBHOOK_URL" + + Only send if SLACK_WEBHOOK_URL is set.""" + + result_name = create_session_api(prompt, session_name=session_id) + if result_name: + post_check_run(number, result_name) processed += 1