refactor: consolidate Devin session logic into reusable action (#26843)
* fix: reuse existing Devin sessions in stale PR completion workflow Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor: consolidate Devin session logic into reusable action - Create .github/actions/devin-session composite action for session checking - Update stale-pr-devin-completion.yml to use the new action - Update cubic-devin-review.yml to use the new action - Update devin-conflict-resolver.yml to use the new action - Rename workflows to 'PR Labeled' with descriptive job names The new action checks for existing Devin sessions by: 1. Looking for session URLs in PR body (for PRs created by Devin) 2. Searching PR comments for known Devin session patterns 3. Verifying session is active (working, blocked, or resumed status) This eliminates duplicated session checking logic across all three workflows. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * revert: restore original workflow names Reverted workflow names back to descriptive names: - 'Stale Community PR Devin Completion' (was 'PR Labeled') - 'Devin PR Conflict Resolver' (was 'PR Labeled') Descriptive names are the recommended convention as they appear in GitHub Actions tab, status checks, and notifications. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix: add active status check for PR body sessions Addresses Cubic AI feedback (confidence 9/10): PR body sessions now verify the session is active (working, blocked, or resumed) before reusing, matching the behavior of comment-based session checks. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
1487901f8e
commit
ea3bab57b0
@@ -0,0 +1,124 @@
|
||||
name: 'Devin Session Checker'
|
||||
description: 'Check for existing Devin sessions on a PR'
|
||||
|
||||
inputs:
|
||||
devin-api-key:
|
||||
description: 'Devin API key'
|
||||
required: true
|
||||
github-token:
|
||||
description: 'GitHub token for API calls'
|
||||
required: true
|
||||
pr-number:
|
||||
description: 'PR number to check for existing sessions'
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
has-existing-session:
|
||||
description: 'Whether an existing active session was found (true/false)'
|
||||
value: ${{ steps.check-session.outputs.has-existing-session }}
|
||||
session-id:
|
||||
description: 'ID of the existing session (if found)'
|
||||
value: ${{ steps.check-session.outputs.session-id }}
|
||||
session-url:
|
||||
description: 'URL of the existing session (if found)'
|
||||
value: ${{ steps.check-session.outputs.session-url }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Check for existing Devin session
|
||||
id: check-session
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ inputs.devin-api-key }}
|
||||
PR_NUMBER: ${{ inputs.pr-number }}
|
||||
with:
|
||||
github-token: ${{ inputs.github-token }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = parseInt(process.env.PR_NUMBER, 10);
|
||||
const SESSION_URL_REGEX = /app\.devin\.ai\/sessions\/([a-f0-9-]+)/;
|
||||
const ACTIVE_STATUSES = ['working', 'blocked', 'resumed'];
|
||||
|
||||
async function checkSessionStatus(sessionId) {
|
||||
const response = await fetch(`https://api.devin.ai/v1/sessions/${sessionId}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.DEVIN_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(`Failed to fetch session ${sessionId}: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
const prBody = pr.body || '';
|
||||
const prSessionMatch = prBody.match(SESSION_URL_REGEX);
|
||||
|
||||
if (prSessionMatch) {
|
||||
const sessionId = prSessionMatch[1];
|
||||
console.log(`PR was created by Devin session: ${sessionId}`);
|
||||
const session = await checkSessionStatus(sessionId);
|
||||
if (session) {
|
||||
console.log(`PR creator session status: ${session.status_enum}`);
|
||||
if (ACTIVE_STATUSES.includes(session.status_enum)) {
|
||||
core.setOutput('has-existing-session', 'true');
|
||||
core.setOutput('session-id', sessionId);
|
||||
core.setOutput('session-url', `https://app.devin.ai/sessions/${sessionId}`);
|
||||
return;
|
||||
} else {
|
||||
console.log(`PR creator session ${sessionId} is not active, will check comments`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const devinSessionPatterns = [
|
||||
'Devin AI is completing this stale PR',
|
||||
'Devin AI is addressing Cubic AI',
|
||||
'Devin AI is resolving merge conflicts'
|
||||
];
|
||||
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
for (const comment of comments.data.reverse()) {
|
||||
const hasDevinSession = devinSessionPatterns.some(pattern => comment.body?.includes(pattern));
|
||||
if (hasDevinSession) {
|
||||
const match = comment.body?.match(SESSION_URL_REGEX);
|
||||
if (match) {
|
||||
const sessionId = match[1];
|
||||
console.log(`Found existing Devin session from comment: ${sessionId}`);
|
||||
|
||||
const session = await checkSessionStatus(sessionId);
|
||||
if (session) {
|
||||
if (ACTIVE_STATUSES.includes(session.status_enum)) {
|
||||
console.log(`Session ${sessionId} is active (status: ${session.status_enum})`);
|
||||
core.setOutput('has-existing-session', 'true');
|
||||
core.setOutput('session-id', sessionId);
|
||||
core.setOutput('session-url', `https://app.devin.ai/sessions/${sessionId}`);
|
||||
return;
|
||||
} else {
|
||||
console.log(`Session ${sessionId} is not active (status: ${session.status_enum})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('No existing active Devin session found');
|
||||
core.setOutput('has-existing-session', 'false');
|
||||
core.setOutput('session-id', '');
|
||||
core.setOutput('session-url', '');
|
||||
@@ -46,103 +46,27 @@ jobs:
|
||||
|
||||
core.setOutput('has-prompt', 'true');
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check for existing Devin session
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true'
|
||||
id: check-session
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
|
||||
uses: ./.github/actions/devin-session
|
||||
with:
|
||||
script: |
|
||||
async function checkSessionStatus(sessionId) {
|
||||
const response = await fetch(`https://api.devin.ai/v1/sessions/${sessionId}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.DEVIN_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(`Failed to fetch session ${sessionId}: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
// If PR was created through Devin, always use that session
|
||||
const prBody = context.payload.pull_request.body || '';
|
||||
const prSessionMatch = prBody.match(/app\.devin\.ai\/sessions\/([a-f0-9-]+)/);
|
||||
|
||||
if (prSessionMatch) {
|
||||
const sessionId = prSessionMatch[1];
|
||||
console.log(`PR was created by Devin session: ${sessionId}`);
|
||||
|
||||
const session = await checkSessionStatus(sessionId);
|
||||
if (session) {
|
||||
console.log(`PR creator session status: ${session.status_enum}`);
|
||||
core.exportVariable('EXISTING_SESSION_ID', sessionId);
|
||||
core.exportVariable('SESSION_URL', `https://app.devin.ai/sessions/${sessionId}`);
|
||||
core.setOutput('has-active-session', 'true');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// PR was not created through Devin - check for existing Devin session from PR comments
|
||||
// Look for any Devin session comment (cubic review, conflict resolution, stale PR completion, etc.)
|
||||
const devinSessionPatterns = [
|
||||
'Devin AI is addressing Cubic AI',
|
||||
'Devin AI is resolving merge conflicts',
|
||||
'Devin AI is completing this stale PR'
|
||||
];
|
||||
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number
|
||||
});
|
||||
|
||||
let sessionId = null;
|
||||
for (const comment of comments.data.reverse()) {
|
||||
const hasDevinSession = devinSessionPatterns.some(pattern => comment.body.includes(pattern));
|
||||
if (hasDevinSession) {
|
||||
const match = comment.body.match(/app\.devin\.ai\/sessions\/([a-f0-9-]+)/);
|
||||
if (match) {
|
||||
sessionId = match[1];
|
||||
console.log(`Found existing Devin session from comment: ${sessionId}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sessionId) {
|
||||
console.log('No existing Devin session found in PR comments');
|
||||
core.setOutput('has-active-session', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found session ID from comment: ${sessionId}`);
|
||||
|
||||
const session = await checkSessionStatus(sessionId);
|
||||
if (!session) {
|
||||
core.setOutput('has-active-session', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
const activeStatuses = ['working', 'blocked', 'resumed'];
|
||||
|
||||
if (activeStatuses.includes(session.status_enum)) {
|
||||
console.log(`Session ${sessionId} is active (status: ${session.status_enum})`);
|
||||
core.exportVariable('EXISTING_SESSION_ID', sessionId);
|
||||
core.exportVariable('SESSION_URL', `https://app.devin.ai/sessions/${sessionId}`);
|
||||
core.setOutput('has-active-session', 'true');
|
||||
} else {
|
||||
console.log(`Session ${sessionId} is not active (status: ${session.status_enum})`);
|
||||
core.setOutput('has-active-session', 'false');
|
||||
}
|
||||
devin-api-key: ${{ secrets.DEVIN_API_KEY }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr-number: ${{ github.event.pull_request.number }}
|
||||
|
||||
- name: Set session environment variables
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true' && steps.check-session.outputs.has-existing-session == 'true'
|
||||
run: |
|
||||
echo "EXISTING_SESSION_ID=${{ steps.check-session.outputs.session-id }}" >> $GITHUB_ENV
|
||||
echo "SESSION_URL=${{ steps.check-session.outputs.session-url }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Send message to existing Devin session
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true' && steps.check-session.outputs.has-active-session == 'true'
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true' && steps.check-session.outputs.has-existing-session == 'true'
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
|
||||
run: |
|
||||
@@ -172,7 +96,7 @@ jobs:
|
||||
echo "Message sent to existing session successfully"
|
||||
|
||||
- name: Create new Devin session
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true' && steps.check-session.outputs.has-active-session == 'false'
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true' && steps.check-session.outputs.has-existing-session == 'false'
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
|
||||
run: |
|
||||
|
||||
@@ -216,11 +216,27 @@ jobs:
|
||||
|
||||
console.log(`Posted comment and removed label for PR #${prNumber}`);
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.get-pr.outputs.has-pr == 'true' && steps.get-pr.outputs.needs-maintainer-access != 'true' && steps.get-pr.outputs.has-conflicts == 'true'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check for existing Devin session
|
||||
if: steps.get-pr.outputs.has-pr == 'true' && steps.get-pr.outputs.needs-maintainer-access != 'true' && steps.get-pr.outputs.has-conflicts == 'true'
|
||||
id: check-session
|
||||
uses: ./.github/actions/devin-session
|
||||
with:
|
||||
devin-api-key: ${{ secrets.DEVIN_API_KEY }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr-number: ${{ steps.get-pr.outputs.pr-number }}
|
||||
|
||||
- name: Create Devin session for conflict resolution
|
||||
if: steps.get-pr.outputs.has-pr == 'true' && steps.get-pr.outputs.needs-maintainer-access != 'true' && steps.get-pr.outputs.has-conflicts == 'true'
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
HAS_EXISTING_SESSION: ${{ steps.check-session.outputs.has-existing-session }}
|
||||
EXISTING_SESSION_ID: ${{ steps.check-session.outputs.session-id }}
|
||||
EXISTING_SESSION_URL: ${{ steps.check-session.outputs.session-url }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -229,75 +245,12 @@ jobs:
|
||||
const pr = JSON.parse(fs.readFileSync('/tmp/pr-data.json', 'utf8'));
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
async function checkSessionStatus(sessionId) {
|
||||
const response = await fetch(`https://api.devin.ai/v1/sessions/${sessionId}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.DEVIN_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(`Failed to fetch session ${sessionId}: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async function findExistingSession(prNumber, prBody) {
|
||||
// First check if PR was created through Devin
|
||||
const prSessionMatch = prBody?.match(/app\.devin\.ai\/sessions\/([a-f0-9-]+)/);
|
||||
if (prSessionMatch) {
|
||||
const sessionId = prSessionMatch[1];
|
||||
console.log(`PR #${prNumber} was created by Devin session: ${sessionId}`);
|
||||
const session = await checkSessionStatus(sessionId);
|
||||
if (session) {
|
||||
return { sessionId, sessionUrl: `https://app.devin.ai/sessions/${sessionId}`, isFromPrBody: true };
|
||||
}
|
||||
}
|
||||
|
||||
// Check PR comments for existing Devin session (conflict resolution or Cubic AI review)
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const sessionPatterns = [
|
||||
'Devin AI is resolving merge conflicts',
|
||||
'Devin AI is addressing Cubic AI'
|
||||
];
|
||||
|
||||
for (const comment of comments.data.reverse()) {
|
||||
const hasDevinSession = sessionPatterns.some(pattern => comment.body?.includes(pattern));
|
||||
if (hasDevinSession) {
|
||||
const match = comment.body?.match(/app\.devin\.ai\/sessions\/([a-f0-9-]+)/);
|
||||
if (match) {
|
||||
const sessionId = match[1];
|
||||
console.log(`Found existing session from comment: ${sessionId}`);
|
||||
|
||||
const session = await checkSessionStatus(sessionId);
|
||||
if (session) {
|
||||
const activeStatuses = ['working', 'blocked', 'resumed'];
|
||||
if (activeStatuses.includes(session.status_enum)) {
|
||||
console.log(`Session ${sessionId} is active (status: ${session.status_enum})`);
|
||||
return { sessionId, sessionUrl: `https://app.devin.ai/sessions/${sessionId}`, isFromPrBody: false };
|
||||
} else {
|
||||
console.log(`Session ${sessionId} is not active (status: ${session.status_enum})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
const hasExistingSession = process.env.HAS_EXISTING_SESSION === 'true';
|
||||
const existingSessionId = process.env.EXISTING_SESSION_ID;
|
||||
const existingSessionUrl = process.env.EXISTING_SESSION_URL;
|
||||
|
||||
console.log(`Processing PR #${pr.number}: ${pr.title}${pr.is_fork ? ' (fork)' : ''}`);
|
||||
|
||||
const existingSession = await findExistingSession(pr.number, pr.body);
|
||||
|
||||
const forkInstructions = pr.is_fork ? `
|
||||
IMPORTANT: This PR is from a fork.
|
||||
- Clone the FORK repository: ${pr.head_repo_owner}/${pr.head_repo_name}
|
||||
@@ -386,8 +339,8 @@ jobs:
|
||||
let sessionUrl;
|
||||
let isNewSession = false;
|
||||
|
||||
if (existingSession) {
|
||||
console.log(`Sending message to existing session ${existingSession.sessionId} for PR #${pr.number}`);
|
||||
if (hasExistingSession) {
|
||||
console.log(`Sending message to existing session ${existingSessionId} for PR #${pr.number}`);
|
||||
|
||||
const message = `PR #${pr.number} has merge conflicts that need to be resolved.
|
||||
|
||||
@@ -395,7 +348,7 @@ jobs:
|
||||
|
||||
Continue working on the same PR branch and push your fixes.`;
|
||||
|
||||
const response = await fetch(`https://api.devin.ai/v1/sessions/${existingSession.sessionId}/message`, {
|
||||
const response = await fetch(`https://api.devin.ai/v1/sessions/${existingSessionId}/message`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.DEVIN_API_KEY}`,
|
||||
@@ -405,11 +358,11 @@ jobs:
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to send message to session ${existingSession.sessionId}: ${response.status}`);
|
||||
console.error(`Failed to send message to session ${existingSessionId}: ${response.status}`);
|
||||
throw new Error(`Failed to send message to existing session: ${response.status}`);
|
||||
}
|
||||
|
||||
sessionUrl = existingSession.sessionUrl;
|
||||
sessionUrl = existingSessionUrl;
|
||||
console.log(`Message sent to existing session for PR #${pr.number}`);
|
||||
} else {
|
||||
console.log(`Creating new Devin session for PR #${pr.number}`);
|
||||
|
||||
@@ -140,7 +140,18 @@ jobs:
|
||||
|
||||
core.setFailed(`Cannot complete this fork PR: the fork owner has not enabled "Allow edits from maintainers". A comment has been posted requesting access.`);
|
||||
|
||||
- name: Create Devin session
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check for existing Devin session
|
||||
id: check-session
|
||||
uses: ./.github/actions/devin-session
|
||||
with:
|
||||
devin-api-key: ${{ secrets.DEVIN_API_KEY }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pr-number: ${{ steps.pr.outputs.pr_number }}
|
||||
|
||||
- name: Create or reuse Devin session
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
|
||||
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
|
||||
@@ -150,6 +161,9 @@ jobs:
|
||||
IS_FORK: ${{ steps.pr.outputs.is_fork }}
|
||||
HEAD_REPO_FULL_NAME: ${{ steps.pr.outputs.head_repo_full_name }}
|
||||
REPO_NAME: ${{ github.repository }}
|
||||
HAS_EXISTING_SESSION: ${{ steps.check-session.outputs.has-existing-session }}
|
||||
EXISTING_SESSION_ID: ${{ steps.check-session.outputs.session-id }}
|
||||
EXISTING_SESSION_URL: ${{ steps.check-session.outputs.session-url }}
|
||||
run: |
|
||||
if [ "$IS_FORK" = "true" ]; then
|
||||
CLONE_INSTRUCTIONS="This is a fork PR. Clone from the fork repository: ${HEAD_REPO_FULL_NAME}
|
||||
@@ -200,25 +214,54 @@ jobs:
|
||||
6. Credit the original author in your commit messages where appropriate.
|
||||
7. CRITICAL: If this is a fork PR and you encounter ANY error when pushing (permission denied, authentication failure, etc.) even after using the DEVIN_ACTIONS_PAT, you MUST fail the task immediately. Do NOT attempt to push to a new branch in the main repository as a workaround. Simply report the error and stop."
|
||||
|
||||
RESPONSE=$(curl -s -X POST "https://api.devin.ai/v1/sessions" \
|
||||
-H "Authorization: Bearer ${DEVIN_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n \
|
||||
--arg prompt "$FULL_PROMPT" \
|
||||
--arg title "Complete Stale PR #${PR_NUMBER}: ${PR_TITLE}" \
|
||||
'{
|
||||
prompt: $prompt,
|
||||
title: $title,
|
||||
tags: ["stale-pr-completion", "pr-'${PR_NUMBER}'"]
|
||||
}')")
|
||||
if [ "$HAS_EXISTING_SESSION" = "true" ]; then
|
||||
echo "Sending message to existing session ${EXISTING_SESSION_ID}"
|
||||
|
||||
SESSION_URL=$(echo "$RESPONSE" | jq -r '.url // .session_url // empty')
|
||||
if [ -n "$SESSION_URL" ]; then
|
||||
echo "Devin session created: $SESSION_URL"
|
||||
echo "SESSION_URL=$SESSION_URL" >> $GITHUB_ENV
|
||||
MESSAGE="This PR has been marked as stale and needs to be completed.
|
||||
|
||||
${FULL_PROMPT}
|
||||
|
||||
Continue working on the same PR branch and push your fixes."
|
||||
|
||||
HTTP_CODE=$(curl -s -o /tmp/devin-response.json -w "%{http_code}" -X POST "https://api.devin.ai/v1/sessions/${EXISTING_SESSION_ID}/message" \
|
||||
-H "Authorization: Bearer ${DEVIN_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg message "$MESSAGE" '{message: $message}')")
|
||||
|
||||
if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
|
||||
echo "Failed to send message to Devin session: HTTP $HTTP_CODE"
|
||||
cat /tmp/devin-response.json
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Message sent to existing session successfully"
|
||||
echo "SESSION_URL=$EXISTING_SESSION_URL" >> $GITHUB_ENV
|
||||
echo "IS_NEW_SESSION=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Failed to create Devin session"
|
||||
exit 1
|
||||
echo "Creating new Devin session"
|
||||
|
||||
RESPONSE=$(curl -s -X POST "https://api.devin.ai/v1/sessions" \
|
||||
-H "Authorization: Bearer ${DEVIN_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n \
|
||||
--arg prompt "$FULL_PROMPT" \
|
||||
--arg title "Complete Stale PR #${PR_NUMBER}: ${PR_TITLE}" \
|
||||
'{
|
||||
prompt: $prompt,
|
||||
title: $title,
|
||||
tags: ["stale-pr-completion", "pr-'${PR_NUMBER}'"]
|
||||
}')")
|
||||
|
||||
SESSION_URL=$(echo "$RESPONSE" | jq -r '.url // .session_url // empty')
|
||||
if [ -n "$SESSION_URL" ]; then
|
||||
echo "Devin session created: $SESSION_URL"
|
||||
echo "SESSION_URL=$SESSION_URL" >> $GITHUB_ENV
|
||||
echo "IS_NEW_SESSION=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Failed to create Devin session"
|
||||
echo "$RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Post comment with Devin session link
|
||||
@@ -233,9 +276,15 @@ jobs:
|
||||
const sessionUrl = process.env.SESSION_URL;
|
||||
const prAuthor = process.env.PR_AUTHOR;
|
||||
const prNumber = process.env.PR_NUMBER;
|
||||
const isNewSession = process.env.IS_NEW_SESSION === 'true';
|
||||
|
||||
const message = isNewSession
|
||||
? `### Devin AI is completing this stale PR\n\nThis PR by @${prAuthor} has been marked as stale. A Devin session has been created to complete the remaining work.\n\n[View Devin Session](${sessionUrl})\n\n---\n*Devin will review the PR, address any feedback, and push updates to complete this PR.*`
|
||||
: `### Devin AI is completing this stale PR\n\nThis PR by @${prAuthor} has been marked as stale. The existing Devin session has been notified to complete the remaining work.\n\n[View Devin Session](${sessionUrl})\n\n---\n*Devin will review the PR, address any feedback, and push updates to complete this PR.*`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt(prNumber),
|
||||
body: `### Devin AI is completing this stale PR\n\nThis PR by @${prAuthor} has been marked as stale. A Devin session has been created to complete the remaining work.\n\n[View Devin Session](${sessionUrl})\n\n---\n*Devin will review the PR, address any feedback, and push updates to complete this PR.*`
|
||||
body: message
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user