Skip to content

Commit 2f50f22

Browse files
committed
More progress
1 parent 6ae6029 commit 2f50f22

File tree

9 files changed

+115
-156
lines changed

9 files changed

+115
-156
lines changed

apps/cloud-agents/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"scripts": {
66
"lint": "next lint",
77
"check-types": "tsc --noEmit",
8-
"dev": "next dev --port 3001",
8+
"dev": "concurrently \"next dev --port 3001\" \"ngrok http 3001 --domain cte.ngrok.dev\"",
99
"build": "next build",
1010
"start": "next start --port 3001",
1111
"clean": "rimraf .next .turbo",
@@ -45,6 +45,7 @@
4545
"@types/node": "^22.15.20",
4646
"@types/react": "^18.3.23",
4747
"@types/react-dom": "^18.3.7",
48+
"concurrently": "^9.1.0",
4849
"drizzle-kit": "^0.31.1",
4950
"tsx": "^4.19.3"
5051
}

apps/cloud-agents/src/app/api/jobs/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export async function POST(request: NextRequest) {
2020

2121
switch (values.type) {
2222
case "github.issue.fix":
23-
await enqueue("github.issue.fix", values.payload, job.id)
23+
await enqueue({ jobId: job.id, type: "github.issue.fix", payload: values.payload })
2424
break
2525
default:
2626
throw new Error(`Unknown job type: ${values.type}`)

apps/cloud-agents/src/app/api/webhooks/github/route.ts

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,12 @@ import { NextRequest, NextResponse } from "next/server"
22
import { createHmac } from "crypto"
33
import { z } from "zod"
44

5+
import { type JobType, type JobStatus, type JobPayload, githubWebhookSchema } from "@/types"
56
import { db, cloudJobs } from "@/db"
67
import { enqueue } from "@/lib"
78

8-
const githubWebhookSchema = z.object({
9-
action: z.string(),
10-
issue: z.object({
11-
number: z.number(),
12-
title: z.string(),
13-
body: z.string().nullable(),
14-
labels: z.array(z.object({ name: z.string() })),
15-
}),
16-
repository: z.object({
17-
full_name: z.string(),
18-
}),
19-
})
20-
21-
function verifySignature(payload: string, signature: string, secret: string): boolean {
22-
const expectedSignature = createHmac("sha256", secret).update(payload, "utf8").digest("hex")
9+
function verifySignature(body: string, signature: string, secret: string): boolean {
10+
const expectedSignature = createHmac("sha256", secret).update(body, "utf8").digest("hex")
2311
const receivedSignature = signature.replace("sha256=", "")
2412
return expectedSignature === receivedSignature
2513
}
@@ -33,40 +21,45 @@ export async function POST(request: NextRequest) {
3321
return NextResponse.json({ error: "Missing signature" }, { status: 400 })
3422
}
3523

36-
const payload = await request.text()
24+
const body = await request.text()
3725

38-
if (!verifySignature(payload, signature, process.env.GITHUB_WEBHOOK_SECRET!)) {
26+
if (!verifySignature(body, signature, process.env.GITHUB_WEBHOOK_SECRET!)) {
3927
return NextResponse.json({ error: "Invalid signature" }, { status: 401 })
4028
}
4129

30+
console.log("✅ Signature verified")
31+
console.log("📋 Event ->", event)
32+
4233
if (event !== "issues") {
43-
return NextResponse.json({ message: "Event ignored" })
34+
return NextResponse.json({ message: "event_ignored" })
4435
}
4536

46-
const data = githubWebhookSchema.parse(JSON.parse(payload))
37+
const data = githubWebhookSchema.parse(JSON.parse(body))
38+
39+
console.log("🗄️ Data ->", data)
4740

4841
if (data.action !== "opened") {
49-
return NextResponse.json({ message: "Action ignored" })
42+
return NextResponse.json({ message: "action_ignored" })
5043
}
5144

52-
const jobPayload = {
45+
const type: JobType = "github.issue.fix"
46+
const status: JobStatus = "pending"
47+
48+
const payload: JobPayload<typeof type> = {
5349
repo: data.repository.full_name,
5450
issue: data.issue.number,
5551
title: data.issue.title,
5652
body: data.issue.body || "",
5753
labels: data.issue.labels.map((label) => label.name),
5854
}
5955

60-
const [job] = await db
61-
.insert(cloudJobs)
62-
.values({ type: "github.issue.fix", status: "pending", payload: jobPayload })
63-
.returning()
56+
const [job] = await db.insert(cloudJobs).values({ type, status, payload }).returning()
6457

6558
if (!job) {
6659
throw new Error("Failed to create job")
6760
}
6861

69-
await enqueue("github.issue.fix", jobPayload, job.id)
62+
await enqueue({ jobId: job.id, type, payload })
7063
return NextResponse.json({ message: "Job created successfully", jobId: job.id })
7164
} catch (error) {
7265
console.error("GitHub webhook error:", error)

apps/cloud-agents/src/db/schema.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
import { pgTable, text, timestamp, integer, jsonb } from "drizzle-orm/pg-core"
22
import { relations } from "drizzle-orm"
33

4+
import type { JobType, JobStatus, JobPayload } from "@/types"
5+
46
/**
57
* cloudJobs
68
*/
79

810
export const cloudJobs = pgTable("cloud_jobs", {
911
id: integer().primaryKey().generatedAlwaysAsIdentity(),
10-
type: text().notNull(), // e.g., 'github.issue.fix'
11-
status: text().notNull().default("pending"), // 'pending', 'processing', 'completed', 'failed'
12-
payload: jsonb().notNull(), // job-specific data
13-
result: jsonb(), // job output
14-
error: text(), // error message if failed
15-
createdAt: timestamp("created_at").notNull().defaultNow(),
12+
type: text().notNull().$type<JobType>(),
13+
status: text().notNull().default("pending").$type<JobStatus>(),
14+
payload: jsonb().notNull().$type<JobPayload>(),
15+
result: jsonb(),
16+
error: text(),
1617
startedAt: timestamp("started_at"),
1718
completedAt: timestamp("completed_at"),
19+
createdAt: timestamp("created_at").notNull().defaultNow(),
1820
})
1921

2022
export const cloudJobsRelations = relations(cloudJobs, ({ many }) => ({
2123
tasks: many(cloudTasks),
2224
}))
2325

2426
export type CloudJob = typeof cloudJobs.$inferSelect
27+
2528
export type InsertCloudJob = typeof cloudJobs.$inferInsert
29+
2630
export type UpdateCloudJob = Partial<Omit<CloudJob, "id" | "createdAt">>
2731

2832
/**
@@ -34,8 +38,8 @@ export const cloudTasks = pgTable("cloud_tasks", {
3438
jobId: integer("job_id")
3539
.references(() => cloudJobs.id)
3640
.notNull(),
37-
taskId: integer("task_id"), // references tasks from evals database
38-
containerId: text("container_id"), // Docker container ID
41+
taskId: integer("task_id"),
42+
containerId: text("container_id"),
3943
createdAt: timestamp("created_at").notNull().defaultNow(),
4044
})
4145

@@ -44,7 +48,9 @@ export const cloudTasksRelations = relations(cloudTasks, ({ one }) => ({
4448
}))
4549

4650
export type CloudTask = typeof cloudTasks.$inferSelect
51+
4752
export type InsertCloudTask = typeof cloudTasks.$inferInsert
53+
4854
export type UpdateCloudTask = Partial<Omit<CloudTask, "id" | "createdAt">>
4955

5056
/**

apps/cloud-agents/src/lib/job.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { eq } from "drizzle-orm"
22
import { Job } from "bullmq"
33

4-
import { db, cloudJobs } from "@/db"
5-
import type { JobTypes, CloudJobData } from "@/types"
4+
import { db, cloudJobs, type UpdateCloudJob } from "@/db"
5+
import type { JobTypes, JobType, JobStatus, JobParams } from "@/types"
66

77
import { fixGitHubIssue } from "./jobs/fixGitHubIssue"
88

9-
export async function processJob(job: Job<CloudJobData>) {
10-
const { type, payload, jobId } = job.data
11-
console.log(`Processing job ${jobId} of type ${type}`)
9+
export async function processJob<T extends JobType>({ data: { type, payload, jobId }, ...job }: Job<JobParams<T>>) {
10+
console.log(`[${job.name} | ${job.id}] Processing job ${jobId} of type ${type}`)
1211

1312
try {
1413
await updateJobStatus(jobId, "processing")
@@ -23,36 +22,31 @@ export async function processJob(job: Job<CloudJobData>) {
2322
}
2423

2524
await updateJobStatus(jobId, "completed", result)
26-
console.log(`Job ${jobId} completed successfully`)
25+
console.log(`[${job.name} | ${job.id}] Job ${jobId} completed successfully`)
2726
} catch (error) {
28-
console.error(`Job ${jobId} failed:`, error)
27+
console.error(`[${job.name} | ${job.id}] Job ${jobId} failed:`, error)
2928
const errorMessage = error instanceof Error ? error.message : String(error)
3029
await updateJobStatus(jobId, "failed", undefined, errorMessage)
3130
throw error // Re-throw to mark job as failed in BullMQ.
3231
}
3332
}
3433

35-
async function updateJobStatus(
36-
jobId: number,
37-
status: "processing" | "completed" | "failed",
38-
result?: unknown,
39-
error?: string,
40-
) {
41-
const updates: Record<string, unknown> = { status }
34+
async function updateJobStatus(jobId: number, status: JobStatus, result?: unknown, error?: string) {
35+
const values: UpdateCloudJob = { status }
4236

4337
if (status === "processing") {
44-
updates.startedAt = new Date()
38+
values.startedAt = new Date()
4539
} else if (status === "completed" || status === "failed") {
46-
updates.completedAt = new Date()
40+
values.completedAt = new Date()
4741

4842
if (result) {
49-
updates.result = result
43+
values.result = result
5044
}
5145

5246
if (error) {
53-
updates.error = error
47+
values.error = error
5448
}
5549
}
5650

57-
await db.update(cloudJobs).set(updates).where(eq(cloudJobs.id, jobId))
51+
await db.update(cloudJobs).set(values).where(eq(cloudJobs.id, jobId))
5852
}

apps/cloud-agents/src/lib/queue.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Queue, Job } from "bullmq"
22

3-
import type { JobTypes, CloudJobData } from "@/types"
3+
import type { JobTypes, JobPayload, JobParams } from "@/types"
44

55
import { redis } from "./redis"
66

@@ -14,10 +14,6 @@ const queue = new Queue("cloud-agents", {
1414
},
1515
})
1616

17-
export async function enqueue<T extends keyof JobTypes>(
18-
type: T,
19-
payload: JobTypes[T],
20-
jobId: number,
21-
): Promise<Job<CloudJobData<T>>> {
22-
return queue.add(type, { type, payload, jobId }, { jobId: `${type}-${jobId}` })
17+
export async function enqueue<T extends keyof JobTypes>(params: JobParams<T>): Promise<Job<JobPayload<T>>> {
18+
return queue.add(params.type, params, { jobId: `${params.type}-${params.jobId}` })
2319
}

apps/cloud-agents/src/types/index.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,30 @@ import { z } from "zod"
22

33
export interface JobTypes {
44
"github.issue.fix": {
5-
repo: string // e.g., "RooCodeInc/Roo-Code"
6-
issue: number // Issue number
7-
title: string // Issue title
8-
body: string // Issue description
9-
labels?: string[] // Issue labels
5+
repo: string
6+
issue: number
7+
title: string
8+
body: string
9+
labels?: string[]
1010
}
1111
}
1212

13+
export type JobType = keyof JobTypes
14+
1315
export type JobStatus = "pending" | "processing" | "completed" | "failed"
1416

15-
export type CloudJobData<T extends keyof JobTypes = keyof JobTypes> = {
16-
type: T
17-
payload: JobTypes[T]
17+
export type JobPayload<T extends JobType = JobType> = JobTypes[T]
18+
19+
export type JobParams<T extends JobType> = {
1820
jobId: number
21+
type: T
22+
payload: JobPayload<T>
1923
}
2024

25+
/**
26+
* CreateJob
27+
*/
28+
2129
export const createJobSchema = z.discriminatedUnion("type", [
2230
z.object({
2331
type: z.literal("github.issue.fix"),
@@ -32,3 +40,22 @@ export const createJobSchema = z.discriminatedUnion("type", [
3240
])
3341

3442
export type CreateJob = z.infer<typeof createJobSchema>
43+
44+
/**
45+
* GitHubWebhook
46+
*/
47+
48+
export const githubWebhookSchema = z.object({
49+
action: z.string(),
50+
issue: z.object({
51+
number: z.number(),
52+
title: z.string(),
53+
body: z.string().nullable(),
54+
labels: z.array(z.object({ name: z.string() })),
55+
}),
56+
repository: z.object({
57+
full_name: z.string(),
58+
}),
59+
})
60+
61+
export type GitHubWebhook = z.infer<typeof githubWebhookSchema>

0 commit comments

Comments
 (0)