Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 14 additions & 20 deletions src/backend/src/ai/agents/pr-manager/PrManagerAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { Agent, run } from '@openai/agents';
import { setupOpenAIAgentClient, getJulesClient } from '../../providers';
import { Octokit } from '@octokit/rest';
import { getAgentByName } from 'agents';
import { getAgentDb, migrateAgentDb } from '@/db/schemas/agents/stateful';
import { prManagerJobs } from '@/db/schemas/agents/events';
import { desc } from 'drizzle-orm';

function safeParseJson(output: string) {
let clean = output.trim();
Expand Down Expand Up @@ -46,8 +49,8 @@ export class PrManagerAgent extends runtime.Agent {
return new Response('OK');
}
if (url.pathname === '/api/jobs') {
await this.onStart();
const results = this.sql.prepare("SELECT * FROM pr_manager_jobs ORDER BY created_at DESC LIMIT 50").all();
const db = getAgentDb(this.ctx.storage);
const results = await db.select().from(prManagerJobs).orderBy(desc(prManagerJobs.createdAt)).limit(50);
return new Response(JSON.stringify(results), {
headers: { 'Content-Type': 'application/json' }
});
Expand All @@ -56,24 +59,15 @@ export class PrManagerAgent extends runtime.Agent {
}

async onStart() {
// DO SQLite state management init
this.sql.prepare(`
CREATE TABLE IF NOT EXISTS pr_manager_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
owner TEXT NOT NULL,
repo TEXT NOT NULL,
pull_number INTEGER NOT NULL,
status TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`).run();
await this.ctx.blockConcurrencyWhile(async () => {
migrateAgentDb(this.ctx.storage);
});
}

async scheduled() {
console.log('[PrManagerAgent] Running scheduled PR scan...');
// Ensure agent state is initialized
await this.onStart();
const db = getAgentDb(this.ctx.storage);
// Use env defaults if not provided explicitly in Wrangler
const owner = this.env.TEST_REPO_OWNER || 'cloudflare';
const repo = this.env.TEST_REPO_NAME || 'core-github-api';

Expand Down Expand Up @@ -191,24 +185,24 @@ Output a JSON object with:
if (!allResolved) {
console.log(`[PrManagerAgent] Low confidence in resolving PR #${pr.number}. Adding comment.`);
await octokit.rest.issues.createComment({ owner, repo, issue_number: pr.number, body: "I am unable to confidently resolve these conflicts automatically. Manual intervention is required." });
this.sql.prepare('INSERT INTO pr_manager_jobs (owner, repo, pull_number, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)').bind(owner, repo, pr.number, 'conflict_commented', Date.now(), Date.now()).run();
await db.insert(prManagerJobs).values({ owner, repo, pullNumber: pr.number, status: 'conflict_commented', createdAt: Date.now(), updatedAt: Date.now() });
await execInSandbox('git merge --abort');
} else {
console.log(`[PrManagerAgent] High confidence in resolving PR #${pr.number}. Pushing merge commit...`);
await execInSandbox('git add .');
await execInSandbox('git commit -m "Auto-resolved merge conflicts"');
await execInSandbox(`git push origin ${headRef}`);
this.sql.prepare('INSERT INTO pr_manager_jobs (owner, repo, pull_number, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)').bind(owner, repo, pr.number, 'conflict_resolved', Date.now(), Date.now()).run();
await db.insert(prManagerJobs).values({ owner, repo, pullNumber: pr.number, status: 'conflict_resolved', createdAt: Date.now(), updatedAt: Date.now() });
}
} else {
// Merge succeeded cleanly? Should not happen if mergeable_state === 'dirty', but handle just in case
console.log(`[PrManagerAgent] Merge surprisingly succeeded without conflicts for PR #${pr.number}`);
await execInSandbox(`git push origin ${headRef}`);
this.sql.prepare('INSERT INTO pr_manager_jobs (owner, repo, pull_number, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)').bind(owner, repo, pr.number, 'conflict_resolved', Date.now(), Date.now()).run();
await db.insert(prManagerJobs).values({ owner, repo, pullNumber: pr.number, status: 'conflict_resolved', createdAt: Date.now(), updatedAt: Date.now() });
}
} catch (err) {
console.error(`[PrManagerAgent] Failed to merge PR #${pr.number}:`, err);
this.sql.prepare('INSERT INTO pr_manager_jobs (owner, repo, pull_number, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)').bind(owner, repo, pr.number, 'conflict_failed', Date.now(), Date.now()).run();
await db.insert(prManagerJobs).values({ owner, repo, pullNumber: pr.number, status: 'conflict_failed', createdAt: Date.now(), updatedAt: Date.now() });
} finally {
// Let Sandbox terminate normally, no explicit cleanup needed here unless requested.
}
Expand Down
20 changes: 20 additions & 0 deletions src/backend/src/db/schemas/agents/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import {
sqliteTable,
text,
integer,
sqliteTableCreator,
check,
index,
Expand Down Expand Up @@ -56,6 +57,25 @@ export const automationRuns = sqliteTable(
})
);

// env.DB
// ── pr_manager_jobs ─────────────────────────────────────────────
export const prManagerJobs = sqliteTable(
"pr_manager_jobs",
{
id: integer("id").primaryKey({ autoIncrement: true }),
owner: text("owner").notNull(),
repo: text("repo").notNull(),
pullNumber: integer("pull_number").notNull(),
status: text("status").notNull(),
createdAt: integer("created_at").notNull(),
updatedAt: integer("updated_at").notNull(),
},
(table) => ({
createdAtIdx: index("idx_pr_manager_jobs_created_at").on(table.createdAt),
})
);


// Agent activities timeline table
// ── agent_activities ───────────────────────────
export const agentActivities = sqliteTable(
Expand Down
11 changes: 11 additions & 0 deletions src/backend/src/db/schemas/agents/stateful.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ export function migrateAgentDb(storage: DurableObjectStorage): void {
CONSTRAINT status_check CHECK(status IN ('pending','active','completed','failed'))
);
CREATE INDEX IF NOT EXISTS idx_agent_activities_op ON agent_activities (operation_id);

CREATE TABLE IF NOT EXISTS pr_manager_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
owner TEXT NOT NULL,
repo TEXT NOT NULL,
pull_number INTEGER NOT NULL,
status TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_pr_manager_jobs_created_at ON pr_manager_jobs (created_at);
`);
}

Expand Down
Loading