logiguard fork v3: full patch set on verified 8c74db0 tree
Includes prior-session patches (carry forward so the app compiles): - crates/gpui/build.rs: cross-compile manifest fix - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token trait method - crates/gpui/src/window.rs: Window::activate_with_token public API - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow::activate_with_token + activate() keyboard-serial fix Plus the focus-serial fix: - serial.rs: SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; latest_serial_of() Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
331
.github/workflows/background_agent_mvp.yml
vendored
Normal file
331
.github/workflows/background_agent_mvp.yml
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
name: background_agent_mvp
|
||||
|
||||
# NOTE: Scheduled runs disabled as of 2026-02-24. The workflow can still be
|
||||
# triggered manually via workflow_dispatch. See Notion doc "Background Agent
|
||||
# for Zed" for current status and contact info to resume this work.
|
||||
on:
|
||||
# schedule:
|
||||
# - cron: "0 16 * * 1-5"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
crash_ids:
|
||||
description: "Optional comma-separated Sentry issue IDs (e.g. ZED-4VS,ZED-123)"
|
||||
required: false
|
||||
type: string
|
||||
reviewers:
|
||||
description: "Optional comma-separated GitHub reviewer handles"
|
||||
required: false
|
||||
type: string
|
||||
top:
|
||||
description: "Top N candidates when crash_ids is empty"
|
||||
required: false
|
||||
type: string
|
||||
default: "3"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
|
||||
DROID_MODEL: claude-opus-4-5-20251101
|
||||
SENTRY_ORG: zed-dev
|
||||
|
||||
jobs:
|
||||
run-mvp:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 180
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Droid CLI
|
||||
run: |
|
||||
curl -fsSL https://app.factory.ai/cli | sh
|
||||
echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
|
||||
echo "DROID_BIN=${HOME}/.local/bin/droid" >> "$GITHUB_ENV"
|
||||
"${HOME}/.local/bin/droid" --version
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Resolve reviewers
|
||||
id: reviewers
|
||||
env:
|
||||
INPUT_REVIEWERS: ${{ inputs.reviewers }}
|
||||
DEFAULT_REVIEWERS: ${{ vars.BACKGROUND_AGENT_REVIEWERS }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "$DEFAULT_REVIEWERS" ]; then
|
||||
DEFAULT_REVIEWERS="eholk,morgankrey,osiewicz,bennetbo"
|
||||
fi
|
||||
REVIEWERS="${INPUT_REVIEWERS:-$DEFAULT_REVIEWERS}"
|
||||
REVIEWERS="$(echo "$REVIEWERS" | tr -d '[:space:]')"
|
||||
echo "reviewers=$REVIEWERS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Select crash candidates
|
||||
id: candidates
|
||||
env:
|
||||
INPUT_CRASH_IDS: ${{ inputs.crash_ids }}
|
||||
INPUT_TOP: ${{ inputs.top }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_BACKGROUND_AGENT_MVP_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
PREFETCH_DIR="/tmp/crash-data"
|
||||
ARGS=(--select-only --prefetch-dir "$PREFETCH_DIR" --org "$SENTRY_ORG")
|
||||
if [ -n "$INPUT_CRASH_IDS" ]; then
|
||||
ARGS+=(--crash-ids "$INPUT_CRASH_IDS")
|
||||
else
|
||||
TARGET_DRAFT_PRS="${INPUT_TOP:-3}"
|
||||
if ! [[ "$TARGET_DRAFT_PRS" =~ ^[0-9]+$ ]] || [ "$TARGET_DRAFT_PRS" -lt 1 ]; then
|
||||
TARGET_DRAFT_PRS="3"
|
||||
fi
|
||||
CANDIDATE_TOP=$((TARGET_DRAFT_PRS * 5))
|
||||
if [ "$CANDIDATE_TOP" -gt 100 ]; then
|
||||
CANDIDATE_TOP=100
|
||||
fi
|
||||
ARGS+=(--top "$CANDIDATE_TOP" --sample-size 100)
|
||||
fi
|
||||
|
||||
IDS="$(python3 script/run-background-agent-mvp-local "${ARGS[@]}")"
|
||||
|
||||
if [ -z "$IDS" ]; then
|
||||
echo "No candidates selected"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using crash IDs: $IDS"
|
||||
echo "ids=$IDS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Run background agent pipeline per crash
|
||||
id: pipeline
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REVIEWERS: ${{ steps.reviewers.outputs.reviewers }}
|
||||
CRASH_IDS: ${{ steps.candidates.outputs.ids }}
|
||||
TARGET_DRAFT_PRS_INPUT: ${{ inputs.top }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git config user.name "factory-droid[bot]"
|
||||
git config user.email "138933559+factory-droid[bot]@users.noreply.github.com"
|
||||
|
||||
# Crash ID format validation regex
|
||||
CRASH_ID_PATTERN='^[A-Za-z0-9]+-[A-Za-z0-9]+$'
|
||||
TARGET_DRAFT_PRS="${TARGET_DRAFT_PRS_INPUT:-3}"
|
||||
if ! [[ "$TARGET_DRAFT_PRS" =~ ^[0-9]+$ ]] || [ "$TARGET_DRAFT_PRS" -lt 1 ]; then
|
||||
TARGET_DRAFT_PRS="3"
|
||||
fi
|
||||
CREATED_DRAFT_PRS=0
|
||||
|
||||
IFS=',' read -r -a CRASH_ID_ARRAY <<< "$CRASH_IDS"
|
||||
|
||||
for CRASH_ID in "${CRASH_ID_ARRAY[@]}"; do
|
||||
if [ "$CREATED_DRAFT_PRS" -ge "$TARGET_DRAFT_PRS" ]; then
|
||||
echo "Reached target draft PR count ($TARGET_DRAFT_PRS), stopping candidate processing"
|
||||
break
|
||||
fi
|
||||
|
||||
CRASH_ID="$(echo "$CRASH_ID" | xargs)"
|
||||
[ -z "$CRASH_ID" ] && continue
|
||||
|
||||
# Validate crash ID format to prevent injection via branch names or prompts
|
||||
if ! [[ "$CRASH_ID" =~ $CRASH_ID_PATTERN ]]; then
|
||||
echo "ERROR: Invalid crash ID format: '$CRASH_ID' — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
BRANCH="background-agent/mvp-${CRASH_ID,,}-$(date +%Y%m%d)"
|
||||
echo "Running crash pipeline for $CRASH_ID on $BRANCH"
|
||||
|
||||
# Deduplication: skip if a draft PR already exists for this crash
|
||||
EXISTING_BRANCH_PR="$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number' || echo "")"
|
||||
if [ -n "$EXISTING_BRANCH_PR" ]; then
|
||||
echo "Draft PR #$EXISTING_BRANCH_PR already exists for $CRASH_ID — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! git fetch origin main; then
|
||||
echo "WARNING: Failed to fetch origin/main for $CRASH_ID — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! git checkout -B "$BRANCH" origin/main; then
|
||||
echo "WARNING: Failed to create checkout branch $BRANCH for $CRASH_ID — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
CRASH_DATA_FILE="/tmp/crash-data/crash-${CRASH_ID}.md"
|
||||
if [ ! -f "$CRASH_DATA_FILE" ]; then
|
||||
echo "WARNING: No pre-fetched crash data for $CRASH_ID at $CRASH_DATA_FILE — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
python3 -c "
|
||||
import sys
|
||||
crash_id, data_file = sys.argv[1], sys.argv[2]
|
||||
prompt = f'''You are running the weekly background crash-fix MVP pipeline for crash {crash_id}.
|
||||
|
||||
The crash report has been pre-fetched and is available at: {data_file}
|
||||
Read this file to get the crash data. Do not call script/sentry-fetch.
|
||||
|
||||
Required workflow:
|
||||
1. Read the crash report from {data_file}
|
||||
2. Read and follow .rules.
|
||||
3. Follow .factory/prompts/crash/investigate.md and write ANALYSIS.md
|
||||
4. Follow .factory/prompts/crash/link-issues.md and write LINKED_ISSUES.md
|
||||
5. Follow .factory/prompts/crash/fix.md to implement a minimal fix with tests
|
||||
6. Run validators required by the fix prompt for the affected code paths
|
||||
7. Write PR_BODY.md with sections:
|
||||
- Crash Summary
|
||||
- Root Cause
|
||||
- Fix
|
||||
- Validation
|
||||
- Potentially Related Issues (High/Medium/Low from LINKED_ISSUES.md)
|
||||
- Reviewer Checklist
|
||||
- Release Notes (final section; format as Release Notes:, then a blank line, then one bullet like - N/A)
|
||||
|
||||
Constraints:
|
||||
- Do not merge or auto-approve.
|
||||
- Keep changes narrowly scoped to this crash.
|
||||
- Do not modify files in .github/, .factory/, or script/ directories.
|
||||
- When investigating git history, limit your search to the last 2 weeks of commits. Do not traverse older history.
|
||||
- If the crash is not solvable with available context, write a clear blocker summary to PR_BODY.md.
|
||||
'''
|
||||
import textwrap
|
||||
with open('/tmp/background-agent-prompt.md', 'w') as f:
|
||||
f.write(textwrap.dedent(prompt))
|
||||
" "$CRASH_ID" "$CRASH_DATA_FILE"
|
||||
|
||||
if ! "$DROID_BIN" exec --auto medium -m "$DROID_MODEL" -f /tmp/background-agent-prompt.md; then
|
||||
echo "Droid execution failed for $CRASH_ID, continuing to next candidate"
|
||||
continue
|
||||
fi
|
||||
|
||||
for REPORT_FILE in ANALYSIS.md LINKED_ISSUES.md PR_BODY.md; do
|
||||
if [ -f "$REPORT_FILE" ]; then
|
||||
echo "::group::${CRASH_ID} ${REPORT_FILE}"
|
||||
cat "$REPORT_FILE"
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
done
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "No code changes produced for $CRASH_ID"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Stage only expected file types — not git add -A
|
||||
git add -- '*.rs' '*.toml' 'Cargo.lock' 'ANALYSIS.md' 'LINKED_ISSUES.md' 'PR_BODY.md'
|
||||
|
||||
# Reject changes to protected paths
|
||||
PROTECTED_CHANGES="$(git diff --cached --name-only | grep -E '^(\.github/|\.factory/|script/)' || true)"
|
||||
if [ -n "$PROTECTED_CHANGES" ]; then
|
||||
echo "ERROR: Agent modified protected paths — aborting commit for $CRASH_ID:"
|
||||
echo "$PROTECTED_CHANGES"
|
||||
git reset HEAD -- .
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "Fix crash ${CRASH_ID}"
|
||||
fi
|
||||
|
||||
git push -u origin "$BRANCH"
|
||||
|
||||
CRATE_PREFIX=""
|
||||
CHANGED_CRATES="$(git diff --cached --name-only | awk -F/ '/^crates\/[^/]+\// {print $2}' | sort -u)"
|
||||
if [ -n "$CHANGED_CRATES" ] && [ "$(printf "%s\n" "$CHANGED_CRATES" | wc -l | tr -d ' ')" -eq 1 ]; then
|
||||
CRATE_PREFIX="${CHANGED_CRATES}: "
|
||||
fi
|
||||
|
||||
TITLE="${CRATE_PREFIX}Fix crash ${CRASH_ID}"
|
||||
BODY_FILE="PR_BODY.md"
|
||||
if [ ! -f "$BODY_FILE" ]; then
|
||||
BODY_FILE="/tmp/pr-body-${CRASH_ID}.md"
|
||||
printf "Automated draft crash-fix pipeline output for %s.\n\nNo PR_BODY.md was generated by the agent; please review commit and linked artifacts manually.\n" "$CRASH_ID" > "$BODY_FILE"
|
||||
fi
|
||||
|
||||
python3 -c '
|
||||
import re
|
||||
import sys
|
||||
|
||||
path = sys.argv[1]
|
||||
body = open(path, encoding="utf-8").read()
|
||||
pattern = re.compile(r"(^|\n)Release Notes:\r?\n(?:\r?\n)*(?P<bullets>(?:\s*-\s+.*(?:\r?\n|$))+)", re.MULTILINE)
|
||||
match = pattern.search(body)
|
||||
|
||||
if match:
|
||||
bullets = [
|
||||
re.sub(r"^\s*", "", bullet)
|
||||
for bullet in re.findall(r"^\s*-\s+.*$", match.group("bullets"), re.MULTILINE)
|
||||
]
|
||||
if not bullets:
|
||||
bullets = ["- N/A"]
|
||||
section = "Release Notes:\n\n" + "\n".join(bullets)
|
||||
body_without_release_notes = (body[: match.start()] + body[match.end() :]).rstrip()
|
||||
if body_without_release_notes:
|
||||
normalized_body = f"{body_without_release_notes}\n\n{section}\n"
|
||||
else:
|
||||
normalized_body = f"{section}\n"
|
||||
else:
|
||||
normalized_body = body.rstrip() + "\n\nRelease Notes:\n\n- N/A\n"
|
||||
|
||||
with open(path, "w", encoding="utf-8") as file:
|
||||
file.write(normalized_body)
|
||||
' "$BODY_FILE"
|
||||
|
||||
EXISTING_PR="$(gh pr list --head "$BRANCH" --json number --jq '.[0].number')"
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
gh pr edit "$EXISTING_PR" --title "$TITLE" --body-file "$BODY_FILE"
|
||||
PR_NUMBER="$EXISTING_PR"
|
||||
else
|
||||
PR_URL="$(gh pr create --draft --base main --head "$BRANCH" --title "$TITLE" --body-file "$BODY_FILE")"
|
||||
PR_NUMBER="$(basename "$PR_URL")"
|
||||
fi
|
||||
|
||||
if [ -n "$REVIEWERS" ]; then
|
||||
IFS=',' read -r -a REVIEWER_ARRAY <<< "$REVIEWERS"
|
||||
for REVIEWER in "${REVIEWER_ARRAY[@]}"; do
|
||||
[ -z "$REVIEWER" ] && continue
|
||||
gh pr edit "$PR_NUMBER" --add-reviewer "$REVIEWER" || true
|
||||
done
|
||||
fi
|
||||
|
||||
CREATED_DRAFT_PRS=$((CREATED_DRAFT_PRS + 1))
|
||||
echo "Created/updated draft PRs this run: $CREATED_DRAFT_PRS/$TARGET_DRAFT_PRS"
|
||||
done
|
||||
|
||||
echo "created_draft_prs=$CREATED_DRAFT_PRS" >> "$GITHUB_OUTPUT"
|
||||
echo "target_draft_prs=$TARGET_DRAFT_PRS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cleanup pre-fetched crash data
|
||||
if: always()
|
||||
run: rm -rf /tmp/crash-data
|
||||
|
||||
- name: Workflow summary
|
||||
if: always()
|
||||
env:
|
||||
SUMMARY_CRASH_IDS: ${{ steps.candidates.outputs.ids }}
|
||||
SUMMARY_REVIEWERS: ${{ steps.reviewers.outputs.reviewers }}
|
||||
SUMMARY_CREATED_DRAFT_PRS: ${{ steps.pipeline.outputs.created_draft_prs }}
|
||||
SUMMARY_TARGET_DRAFT_PRS: ${{ steps.pipeline.outputs.target_draft_prs }}
|
||||
run: |
|
||||
{
|
||||
echo "## Background Agent MVP"
|
||||
echo ""
|
||||
echo "- Crash IDs: ${SUMMARY_CRASH_IDS:-none}"
|
||||
echo "- Reviewer routing: ${SUMMARY_REVIEWERS:-NOT CONFIGURED}"
|
||||
echo "- Draft PRs created: ${SUMMARY_CREATED_DRAFT_PRS:-0}/${SUMMARY_TARGET_DRAFT_PRS:-3}"
|
||||
echo "- Pipeline: investigate -> link-issues -> fix -> draft PR"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
concurrency:
|
||||
group: background-agent-mvp
|
||||
cancel-in-progress: false
|
||||
Reference in New Issue
Block a user