Add agent workflows & stuff
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import { v } from 'convex/values';
|
||||
|
||||
import type { Doc } from './_generated/dataModel';
|
||||
import {
|
||||
internalMutation,
|
||||
internalQuery,
|
||||
mutation,
|
||||
query,
|
||||
} from './_generated/server';
|
||||
import { getOwnedSpoon, getRequiredUserId } from './model';
|
||||
|
||||
const defaultSettings = {
|
||||
autoRefreshEnabled: true,
|
||||
autoReviewEnabled: true,
|
||||
autoSyncEnabled: false,
|
||||
requireAiLowRiskForSync: true,
|
||||
requireCleanCompareForSync: true,
|
||||
ignoredFilePatterns: [] as string[],
|
||||
importantFilePatterns: [] as string[],
|
||||
};
|
||||
|
||||
export const getForSpoon = query({
|
||||
args: { spoonId: v.id('spoons') },
|
||||
handler: async (ctx, { spoonId }) => {
|
||||
const ownerId = await getRequiredUserId(ctx);
|
||||
await getOwnedSpoon(ctx, spoonId, ownerId);
|
||||
return (
|
||||
(await ctx.db
|
||||
.query('spoonSettings')
|
||||
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
|
||||
.first()) ?? { spoonId, ownerId, ...defaultSettings }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const ensureForSpoon = internalMutation({
|
||||
args: { spoonId: v.id('spoons'), ownerId: v.id('users') },
|
||||
handler: async (ctx, { spoonId, ownerId }) => {
|
||||
const existing = await ctx.db
|
||||
.query('spoonSettings')
|
||||
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
|
||||
.first();
|
||||
if (existing) return existing._id;
|
||||
const now = Date.now();
|
||||
return await ctx.db.insert('spoonSettings', {
|
||||
spoonId,
|
||||
ownerId,
|
||||
...defaultSettings,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const getInternal = internalQuery({
|
||||
args: { spoonId: v.id('spoons'), ownerId: v.id('users') },
|
||||
handler: async (ctx, { spoonId, ownerId }) => {
|
||||
const settings = await ctx.db
|
||||
.query('spoonSettings')
|
||||
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
|
||||
.first();
|
||||
if (settings && settings.ownerId !== ownerId) {
|
||||
throw new Error('Spoon settings ownership mismatch.');
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
});
|
||||
|
||||
export const listRefreshDue = internalQuery({
|
||||
args: { limit: v.number() },
|
||||
handler: async (ctx, { limit }) => {
|
||||
const now = Date.now();
|
||||
const settings = await ctx.db.query('spoonSettings').collect();
|
||||
const due = [];
|
||||
for (const item of settings) {
|
||||
if (!item.autoRefreshEnabled) continue;
|
||||
const spoon = await ctx.db.get(item.spoonId);
|
||||
if (
|
||||
!spoon ||
|
||||
spoon.status === 'archived' ||
|
||||
spoon.provider !== 'github'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (spoon.syncCadence === 'manual') continue;
|
||||
const cadenceMs =
|
||||
spoon.syncCadence === 'weekly'
|
||||
? 7 * 24 * 60 * 60 * 1000
|
||||
: 24 * 60 * 60 * 1000;
|
||||
const last = spoon.lastGithubRefreshAt ?? spoon.lastCheckedAt ?? 0;
|
||||
if (now - last >= cadenceMs) {
|
||||
due.push({ spoonId: item.spoonId, ownerId: item.ownerId });
|
||||
}
|
||||
if (due.length >= limit) break;
|
||||
}
|
||||
return due;
|
||||
},
|
||||
});
|
||||
|
||||
export const update = mutation({
|
||||
args: {
|
||||
spoonId: v.id('spoons'),
|
||||
autoRefreshEnabled: v.optional(v.boolean()),
|
||||
autoReviewEnabled: v.optional(v.boolean()),
|
||||
autoSyncEnabled: v.optional(v.boolean()),
|
||||
requireAiLowRiskForSync: v.optional(v.boolean()),
|
||||
requireCleanCompareForSync: v.optional(v.boolean()),
|
||||
ignoredFilePatterns: v.optional(v.array(v.string())),
|
||||
importantFilePatterns: v.optional(v.array(v.string())),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const ownerId = await getRequiredUserId(ctx);
|
||||
await getOwnedSpoon(ctx, args.spoonId, ownerId);
|
||||
let settings = await ctx.db
|
||||
.query('spoonSettings')
|
||||
.withIndex('by_spoon', (q) => q.eq('spoonId', args.spoonId))
|
||||
.first();
|
||||
if (!settings) {
|
||||
const id = await ctx.db.insert('spoonSettings', {
|
||||
spoonId: args.spoonId,
|
||||
ownerId,
|
||||
...defaultSettings,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
settings = await ctx.db.get(id);
|
||||
}
|
||||
const patch: Partial<Doc<'spoonSettings'>> = { updatedAt: Date.now() };
|
||||
if (args.autoRefreshEnabled !== undefined) {
|
||||
patch.autoRefreshEnabled = args.autoRefreshEnabled;
|
||||
}
|
||||
if (args.autoReviewEnabled !== undefined) {
|
||||
patch.autoReviewEnabled = args.autoReviewEnabled;
|
||||
}
|
||||
if (args.autoSyncEnabled !== undefined) {
|
||||
patch.autoSyncEnabled = args.autoSyncEnabled;
|
||||
}
|
||||
if (args.requireAiLowRiskForSync !== undefined) {
|
||||
patch.requireAiLowRiskForSync = args.requireAiLowRiskForSync;
|
||||
}
|
||||
if (args.requireCleanCompareForSync !== undefined) {
|
||||
patch.requireCleanCompareForSync = args.requireCleanCompareForSync;
|
||||
}
|
||||
if (args.ignoredFilePatterns !== undefined) {
|
||||
patch.ignoredFilePatterns = args.ignoredFilePatterns;
|
||||
}
|
||||
if (args.importantFilePatterns !== undefined) {
|
||||
patch.importantFilePatterns = args.importantFilePatterns;
|
||||
}
|
||||
if (!settings) throw new Error('Spoon settings not found.');
|
||||
await ctx.db.patch(settings._id, patch);
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user