-
Notifications
You must be signed in to change notification settings - Fork 1
Backend ai #220
base: master
Are you sure you want to change the base?
Backend ai #220
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "version": 1, | ||
| "lastRunAtMs": 0, | ||
| "turnsSinceLastRun": 1, | ||
| "lastTranscriptMtimeMs": null, | ||
| "lastProcessedGenerationId": "02f2727d-db77-45c6-bb2d-4163883b764f", | ||
| "trialStartedAtMs": null | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -484,6 +484,24 @@ export const createForUser = mutation({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const setHasBackendForUser = mutation({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: This mutation is only called from Inngest but is exposed as a public Note: the existing Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userId: v.string(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Authorization check is bypassable — Prompt for AI agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| projectId: v.id("projects"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hasBackend: v.boolean(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handler: async (ctx, args) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const project = await ctx.db.get(args.projectId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!project || project.userId !== args.userId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Unauthorized"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await ctx.db.patch(args.projectId, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hasBackend: args.hasBackend, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedAt: Date.now(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+487
to
+502
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Authorization is bypassable because identity is caller-controlled. Line 477 takes Suggested fix (bind auth to session, not args) export const setHasBackendForUser = mutation({
args: {
- userId: v.string(),
projectId: v.id("projects"),
hasBackend: v.boolean(),
},
handler: async (ctx, args) => {
+ const userId = await requireAuth(ctx);
const project = await ctx.db.get(args.projectId);
- if (!project || project.userId !== args.userId) {
+ if (!project || project.userId !== userId) {
throw new Error("Unauthorized");
}
await ctx.db.patch(args.projectId, {
hasBackend: args.hasBackend,
updatedAt: Date.now(),
});
},
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Internal: Create a project for a specific user (for use from actions/background jobs) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import { mutation } from "./_generated/server"; | ||
| import { v } from "convex/values"; | ||
| import { schemaProposalStatusEnum } from "./schema"; | ||
|
|
||
| export const createForUser = mutation({ | ||
| args: { | ||
| userId: v.string(), | ||
| projectId: v.id("projects"), | ||
| messageId: v.id("messages"), | ||
| proposal: v.string(), | ||
| parsedTables: v.optional( | ||
| v.array( | ||
| v.object({ | ||
| name: v.string(), | ||
| purpose: v.string(), | ||
| fields: v.array(v.string()), | ||
| indexes: v.array(v.string()), | ||
| }) | ||
| ) | ||
| ), | ||
| parsedRelationships: v.optional(v.array(v.string())), | ||
| status: schemaProposalStatusEnum, | ||
| }, | ||
| handler: async (ctx, args) => { | ||
| const project = await ctx.db.get(args.projectId); | ||
| if (!project || project.userId !== args.userId) { | ||
| throw new Error("Unauthorized"); | ||
| } | ||
| const message = await ctx.db.get(args.messageId); | ||
| if (!message || message.projectId !== args.projectId) { | ||
| throw new Error("Message not found"); | ||
| } | ||
| const now = Date.now(); | ||
| return await ctx.db.insert("schemaProposals", { | ||
| projectId: args.projectId, | ||
| messageId: args.messageId, | ||
| userId: args.userId, | ||
| proposal: args.proposal, | ||
| status: args.status, | ||
| parsedTables: args.parsedTables, | ||
| parsedRelationships: args.parsedRelationships, | ||
| createdAt: now, | ||
| updatedAt: now, | ||
| }); | ||
| }, | ||
| }); | ||
|
|
||
| export const markImplementedForUser = mutation({ | ||
| args: { | ||
| userId: v.string(), | ||
| schemaProposalId: v.id("schemaProposals"), | ||
| }, | ||
| handler: async (ctx, args) => { | ||
| const row = await ctx.db.get(args.schemaProposalId); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Missing status guard allows a Prompt for AI agents |
||
| if (!row || row.userId !== args.userId) { | ||
| throw new Error("Unauthorized"); | ||
| } | ||
| const now = Date.now(); | ||
| await ctx.db.patch(args.schemaProposalId, { | ||
| status: "IMPLEMENTED", | ||
| approvedAt: row.approvedAt ?? now, | ||
| implementedAt: now, | ||
| updatedAt: now, | ||
| }); | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import { generateText } from "ai"; | ||
| import { openrouter } from "./client"; | ||
| import { CONVEX_BACKEND_PROMPT } from "@/prompts/backend/convex-backend"; | ||
|
|
||
| const BACKEND_MODEL = "moonshotai/kimi-k2.5:nitro"; | ||
|
|
||
| export interface BackendAgentResult { | ||
| files: Record<string, string>; | ||
| success: boolean; | ||
| error?: string; | ||
| summary?: string; | ||
| } | ||
|
|
||
| export async function runBackendImplementerAgent( | ||
| userPrompt: string, | ||
| schemaProposal: string, | ||
| plan?: string | ||
| ): Promise<BackendAgentResult> { | ||
| console.log("[BACKEND] Starting implementation..."); | ||
|
|
||
| try { | ||
| const augmentedPrompt = [ | ||
| "## User Request", | ||
| userPrompt, | ||
| "", | ||
| "## Approved Schema Design", | ||
| schemaProposal, | ||
| "", | ||
| plan ? `## Implementation Plan\n${plan}\n` : "", | ||
| "## Your Task", | ||
| "Generate the complete Convex backend implementation based on the approved schema.", | ||
| "Create all necessary files with their full content.", | ||
| "", | ||
| "Output format:", | ||
| '1. First, output all file contents using <zapdev_file path="convex/schema.ts"> tags', | ||
| "2. Each file should contain the complete, production-ready code", | ||
| "3. End with a <task_summary> describing what was created", | ||
| "", | ||
| "Example:", | ||
| '<zapdev_file path="convex/schema.ts">', | ||
| "// schema content here", | ||
| "</zapdev_file>", | ||
| '<zapdev_file path="convex/tasks/queries.ts">', | ||
| "// queries content here", | ||
| "</zapdev_file>", | ||
| "", | ||
| "<task_summary>", | ||
| "Created Convex backend with schema and CRUD operations for tasks", | ||
| "</task_summary>", | ||
| ].join("\n"); | ||
|
|
||
| const { text } = await generateText({ | ||
| model: openrouter(BACKEND_MODEL), | ||
| system: CONVEX_BACKEND_PROMPT, | ||
| prompt: augmentedPrompt, | ||
| temperature: 0.2, | ||
| maxOutputTokens: 8192, | ||
| }); | ||
|
|
||
| const files = parseGeneratedFiles(text); | ||
|
|
||
| const summary = text.includes("<task_summary>") | ||
| ? text.match(/<task_summary>([\s\S]*?)<\/task_summary>/)?.[1]?.trim() | ||
| : "Generated Convex backend files"; | ||
|
|
||
| if (Object.keys(files).length === 0) { | ||
| return { | ||
| files, | ||
| success: false, | ||
| error: "No files were generated", | ||
| summary, | ||
| }; | ||
| } | ||
|
|
||
| console.log("[BACKEND] Completed successfully"); | ||
|
|
||
| return { | ||
| files, | ||
| success: true, | ||
| summary, | ||
| }; | ||
| } catch (error) { | ||
| const errorMessage = error instanceof Error ? error.message : String(error); | ||
| console.error("[BACKEND] Error:", errorMessage); | ||
|
|
||
| return { | ||
| files: {}, | ||
| success: false, | ||
| error: errorMessage, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| function parseGeneratedFiles(text: string): Record<string, string> { | ||
| const files: Record<string, string> = {}; | ||
|
|
||
| const fileRegex = /<zapdev_file path="([^"]+)">([\s\S]*?)<\/zapdev_file>/g; | ||
| let match; | ||
|
|
||
| while ((match = fileRegex.exec(text)) !== null) { | ||
| const path = match[1]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: File paths parsed from AI output are not validated or sanitized. Since the user prompt influences the AI response, a crafted prompt could induce the model to emit paths containing Prompt for AI agents |
||
| const content = match[2].trim(); | ||
| files[path] = content; | ||
| } | ||
|
Comment on lines
+100
to
+104
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate generated file paths before accepting them.
🤖 Prompt for AI Agents |
||
|
|
||
| return files; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: Zapdev-labs/zapdev
Length of output: 110
🏁 Script executed:
Repository: Zapdev-labs/zapdev
Length of output: 321
Add
.cursor/hooks/state/to.gitignoreto prevent committing IDE state files.The continual-learning.json file has been removed from the PR. However, to prevent similar auto-generated IDE state files from being accidentally committed in the future, add the following to
.gitignore:Note: The other
.cursor/files (rules and configuration) appear to be intentional and should remain in the repository. Only the state files in.cursor/hooks/state/should be excluded.🤖 Prompt for AI Agents