From e29c09bd93cb569e58777033d63ad5ec77a5315f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:41:07 +0000 Subject: [PATCH 1/2] feat(ai): Implement Cloudflare Agents SDK PR Manager - Refactored `PrManagerAgent.ts` to use Agents SDK best practices: `getAgentDb`, `migrateAgentDb`, and `blockConcurrencyWhile` within `onStart()`. - Migrated hardcoded SQL strings to Drizzle ORM queries (`prManagerJobs`). - Corrected schema definitions in `src/backend/src/db/schemas/agents/events.ts` and `stateful.ts` to fully embrace DO embedded SQLite best practices. Co-authored-by: jmbish04 <26469722+jmbish04@users.noreply.github.com> --- skills-lock.json | 20 ++++++++++- .../ai/agents/pr-manager/PrManagerAgent.ts | 34 ++++++++----------- src/backend/src/db/schemas/agents/events.ts | 20 +++++++++++ src/backend/src/db/schemas/agents/stateful.ts | 11 ++++++ 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/skills-lock.json b/skills-lock.json index 179b9fb4..61752ac8 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -37,17 +37,35 @@ "skillPath": "skills/durable-objects/SKILL.md", "computedHash": "72efa2277fea08f43b393c74abaacc9fa575e20bc63bdc9d46829b8a39352852" }, + "sandbox-migrate-to-next": { + "source": "cloudflare/skills", + "sourceType": "github", + "skillPath": "skills/sandbox-migrate-to-next/SKILL.md", + "computedHash": "0e2925a737ddb1e2f61c2992bf6173e9761592bbe16d7422d44202fb27f10f24" + }, + "sandbox-next": { + "source": "cloudflare/skills", + "sourceType": "github", + "skillPath": "skills/sandbox-next/SKILL.md", + "computedHash": "ab94d96f3bbcab3ef5ec0ee80b1a253174ce149cff3b98043b3b83a24dfa927f" + }, "sandbox-sdk": { "source": "cloudflare/skills", "sourceType": "github", "skillPath": "skills/sandbox-sdk/SKILL.md", "computedHash": "e44cceebe7e47d815034027c303315139d35c1d92a3ee287dc6d3e9a02f0667b" }, + "sandbox-stable": { + "source": "cloudflare/skills", + "sourceType": "github", + "skillPath": "skills/sandbox-stable/SKILL.md", + "computedHash": "34bee002919f65a346b115ce9bca8a0dbf7e7c728274092f5e869947b8ba8b68" + }, "turnstile-spin": { "source": "cloudflare/skills", "sourceType": "github", "skillPath": "skills/turnstile-spin/SKILL.md", - "computedHash": "ec3868fef71e9ca926c177c4e2f8a5e234ae501bba4258746507ed095613bbb2" + "computedHash": "1b1d7b58ff1a7805b7d86347cc842dcda4cffa4228cfd958225755c2d2a9e4b7" }, "web-perf": { "source": "cloudflare/skills", diff --git a/src/backend/src/ai/agents/pr-manager/PrManagerAgent.ts b/src/backend/src/ai/agents/pr-manager/PrManagerAgent.ts index f60da992..18ae543f 100644 --- a/src/backend/src/ai/agents/pr-manager/PrManagerAgent.ts +++ b/src/backend/src/ai/agents/pr-manager/PrManagerAgent.ts @@ -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(); @@ -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' } }); @@ -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'; @@ -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. } diff --git a/src/backend/src/db/schemas/agents/events.ts b/src/backend/src/db/schemas/agents/events.ts index 03feabf3..aec4b5d4 100644 --- a/src/backend/src/db/schemas/agents/events.ts +++ b/src/backend/src/db/schemas/agents/events.ts @@ -7,6 +7,7 @@ import { sqliteTable, text, + integer, sqliteTableCreator, check, index, @@ -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( diff --git a/src/backend/src/db/schemas/agents/stateful.ts b/src/backend/src/db/schemas/agents/stateful.ts index ae91a138..1f645290 100644 --- a/src/backend/src/db/schemas/agents/stateful.ts +++ b/src/backend/src/db/schemas/agents/stateful.ts @@ -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); `); } From 772033043120fbbf3f91c9ba5c2dda650f9fec34 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:50:09 +0000 Subject: [PATCH 2/2] feat(ai): Implement Cloudflare Agents SDK PR Manager - Refactored `PrManagerAgent.ts` to use Agents SDK best practices: `getAgentDb`, `migrateAgentDb`, and `blockConcurrencyWhile` within `onStart()`. - Migrated hardcoded SQL strings to Drizzle ORM queries (`prManagerJobs`). - Corrected schema definitions in `src/backend/src/db/schemas/agents/events.ts` and `stateful.ts` to fully embrace DO embedded SQLite best practices. Co-authored-by: jmbish04 <26469722+jmbish04@users.noreply.github.com> --- skills-lock.json | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/skills-lock.json b/skills-lock.json index 61752ac8..179b9fb4 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -37,35 +37,17 @@ "skillPath": "skills/durable-objects/SKILL.md", "computedHash": "72efa2277fea08f43b393c74abaacc9fa575e20bc63bdc9d46829b8a39352852" }, - "sandbox-migrate-to-next": { - "source": "cloudflare/skills", - "sourceType": "github", - "skillPath": "skills/sandbox-migrate-to-next/SKILL.md", - "computedHash": "0e2925a737ddb1e2f61c2992bf6173e9761592bbe16d7422d44202fb27f10f24" - }, - "sandbox-next": { - "source": "cloudflare/skills", - "sourceType": "github", - "skillPath": "skills/sandbox-next/SKILL.md", - "computedHash": "ab94d96f3bbcab3ef5ec0ee80b1a253174ce149cff3b98043b3b83a24dfa927f" - }, "sandbox-sdk": { "source": "cloudflare/skills", "sourceType": "github", "skillPath": "skills/sandbox-sdk/SKILL.md", "computedHash": "e44cceebe7e47d815034027c303315139d35c1d92a3ee287dc6d3e9a02f0667b" }, - "sandbox-stable": { - "source": "cloudflare/skills", - "sourceType": "github", - "skillPath": "skills/sandbox-stable/SKILL.md", - "computedHash": "34bee002919f65a346b115ce9bca8a0dbf7e7c728274092f5e869947b8ba8b68" - }, "turnstile-spin": { "source": "cloudflare/skills", "sourceType": "github", "skillPath": "skills/turnstile-spin/SKILL.md", - "computedHash": "1b1d7b58ff1a7805b7d86347cc842dcda4cffa4228cfd958225755c2d2a9e4b7" + "computedHash": "ec3868fef71e9ca926c177c4e2f8a5e234ae501bba4258746507ed095613bbb2" }, "web-perf": { "source": "cloudflare/skills",