Add agent workflows & stuff
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { ConvexError, v } from 'convex/values';
|
||||
|
||||
import type { Doc } from './_generated/dataModel';
|
||||
import { mutation, query } from './_generated/server';
|
||||
import {
|
||||
internalMutation,
|
||||
internalQuery,
|
||||
mutation,
|
||||
query,
|
||||
} from './_generated/server';
|
||||
import {
|
||||
getOwnedSpoon,
|
||||
getRequiredUserId,
|
||||
@@ -49,6 +54,17 @@ const spoonStatus = v.union(
|
||||
v.literal('archived'),
|
||||
);
|
||||
|
||||
const spoonSyncStatus = v.union(
|
||||
v.literal('unknown'),
|
||||
v.literal('up_to_date'),
|
||||
v.literal('behind'),
|
||||
v.literal('ahead'),
|
||||
v.literal('diverged'),
|
||||
v.literal('checking'),
|
||||
v.literal('conflict'),
|
||||
v.literal('error'),
|
||||
);
|
||||
|
||||
const hasForkMetadata = (args: {
|
||||
forkOwner?: string;
|
||||
forkRepo?: string;
|
||||
@@ -79,6 +95,48 @@ export const get = query({
|
||||
},
|
||||
});
|
||||
|
||||
export const getDetails = query({
|
||||
args: { spoonId: v.id('spoons') },
|
||||
handler: async (ctx, { spoonId }) => {
|
||||
const ownerId = await getRequiredUserId(ctx);
|
||||
const spoon = await getOwnedSpoon(ctx, spoonId, ownerId);
|
||||
const [state, settings, latestReview, recentRuns, agentRequests] =
|
||||
await Promise.all([
|
||||
ctx.db
|
||||
.query('spoonRepositoryStates')
|
||||
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
|
||||
.first(),
|
||||
ctx.db
|
||||
.query('spoonSettings')
|
||||
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
|
||||
.first(),
|
||||
ctx.db
|
||||
.query('aiReviews')
|
||||
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
|
||||
.order('desc')
|
||||
.first(),
|
||||
ctx.db
|
||||
.query('syncRuns')
|
||||
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
|
||||
.order('desc')
|
||||
.take(10),
|
||||
ctx.db
|
||||
.query('agentRequests')
|
||||
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
|
||||
.order('desc')
|
||||
.take(10),
|
||||
]);
|
||||
return { spoon, state, settings, latestReview, recentRuns, agentRequests };
|
||||
},
|
||||
});
|
||||
|
||||
export const getOwnedForAction = internalQuery({
|
||||
args: { spoonId: v.id('spoons'), ownerId: v.id('users') },
|
||||
handler: async (ctx, { spoonId, ownerId }) => {
|
||||
return await getOwnedSpoon(ctx, spoonId, ownerId);
|
||||
},
|
||||
});
|
||||
|
||||
export const createManual = mutation({
|
||||
args: {
|
||||
name: v.string(),
|
||||
@@ -103,12 +161,13 @@ export const createManual = mutation({
|
||||
const now = Date.now();
|
||||
const forkOwner = optionalText(args.forkOwner);
|
||||
const forkRepo = optionalText(args.forkRepo);
|
||||
const forkDefaultBranch = optionalText(args.forkDefaultBranch);
|
||||
const forkUrl = optionalText(args.forkUrl);
|
||||
const status = hasForkMetadata({ forkOwner, forkRepo, forkUrl })
|
||||
? 'draft'
|
||||
: 'needs_connection';
|
||||
|
||||
return await ctx.db.insert('spoons', {
|
||||
const spoonId = await ctx.db.insert('spoons', {
|
||||
ownerId,
|
||||
name: requireText(args.name, 'Spoon name'),
|
||||
description: optionalText(args.description),
|
||||
@@ -122,7 +181,7 @@ export const createManual = mutation({
|
||||
upstreamUrl: requireText(args.upstreamUrl, 'Upstream URL'),
|
||||
forkOwner,
|
||||
forkRepo,
|
||||
forkDefaultBranch: optionalText(args.forkDefaultBranch),
|
||||
forkDefaultBranch,
|
||||
forkUrl,
|
||||
visibility: args.visibility,
|
||||
maintenanceMode: args.maintenanceMode,
|
||||
@@ -130,9 +189,87 @@ export const createManual = mutation({
|
||||
productionRefStrategy: args.productionRefStrategy,
|
||||
tagPattern: optionalText(args.tagPattern),
|
||||
status,
|
||||
syncStatus: 'unknown',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
await ctx.db.insert('spoonSettings', {
|
||||
spoonId,
|
||||
ownerId,
|
||||
autoRefreshEnabled: true,
|
||||
autoReviewEnabled: true,
|
||||
autoSyncEnabled: false,
|
||||
requireAiLowRiskForSync: true,
|
||||
requireCleanCompareForSync: true,
|
||||
ignoredFilePatterns: [],
|
||||
importantFilePatterns: [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
await ctx.db.insert('spoonAgentSettings', {
|
||||
spoonId,
|
||||
ownerId,
|
||||
enabled: true,
|
||||
defaultBaseBranch: forkDefaultBranch ?? args.upstreamDefaultBranch,
|
||||
branchPrefix: 'spoon/agent',
|
||||
agentModel: 'gpt-5.1-codex',
|
||||
reasoningEffort: 'high',
|
||||
maxJobDurationMs: 1_800_000,
|
||||
maxOutputBytes: 200_000,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
return spoonId;
|
||||
},
|
||||
});
|
||||
|
||||
export const patchSyncFields = internalMutation({
|
||||
args: {
|
||||
spoonId: v.id('spoons'),
|
||||
syncStatus: v.optional(spoonSyncStatus),
|
||||
upstreamAheadBy: v.optional(v.number()),
|
||||
forkAheadBy: v.optional(v.number()),
|
||||
lastMergeBaseCommit: v.optional(v.string()),
|
||||
lastUpstreamCommit: v.optional(v.string()),
|
||||
lastForkCommit: v.optional(v.string()),
|
||||
lastSyncRunId: v.optional(v.id('syncRuns')),
|
||||
lastAiReviewId: v.optional(v.id('aiReviews')),
|
||||
lastGithubRefreshAt: v.optional(v.number()),
|
||||
lastSuccessfulRefreshAt: v.optional(v.number()),
|
||||
lastCheckedAt: v.optional(v.number()),
|
||||
lastError: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const patch: Partial<Doc<'spoons'>> = { updatedAt: Date.now() };
|
||||
if (args.syncStatus !== undefined) patch.syncStatus = args.syncStatus;
|
||||
if (args.upstreamAheadBy !== undefined) {
|
||||
patch.upstreamAheadBy = args.upstreamAheadBy;
|
||||
}
|
||||
if (args.forkAheadBy !== undefined) patch.forkAheadBy = args.forkAheadBy;
|
||||
if (args.lastMergeBaseCommit !== undefined) {
|
||||
patch.lastMergeBaseCommit = args.lastMergeBaseCommit;
|
||||
}
|
||||
if (args.lastUpstreamCommit !== undefined) {
|
||||
patch.lastUpstreamCommit = args.lastUpstreamCommit;
|
||||
}
|
||||
if (args.lastForkCommit !== undefined) {
|
||||
patch.lastForkCommit = args.lastForkCommit;
|
||||
}
|
||||
if (args.lastSyncRunId !== undefined)
|
||||
patch.lastSyncRunId = args.lastSyncRunId;
|
||||
if (args.lastAiReviewId !== undefined)
|
||||
patch.lastAiReviewId = args.lastAiReviewId;
|
||||
if (args.lastGithubRefreshAt !== undefined) {
|
||||
patch.lastGithubRefreshAt = args.lastGithubRefreshAt;
|
||||
}
|
||||
if (args.lastSuccessfulRefreshAt !== undefined) {
|
||||
patch.lastSuccessfulRefreshAt = args.lastSuccessfulRefreshAt;
|
||||
}
|
||||
if (args.lastCheckedAt !== undefined)
|
||||
patch.lastCheckedAt = args.lastCheckedAt;
|
||||
if (args.lastError !== undefined) patch.lastError = args.lastError;
|
||||
await ctx.db.patch(args.spoonId, patch);
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user