feat(notifications): emit at maintenance/turn/sync/reauth events
This commit is contained in:
@@ -4,6 +4,7 @@ import type { Doc, Id } from './_generated/dataModel';
|
||||
import type { MutationCtx } from './_generated/server';
|
||||
import { internalMutation, mutation, query } from './_generated/server';
|
||||
import { getOwnedSpoon, getRequiredUserId, optionalText } from './model';
|
||||
import { emitNotification } from './notifications';
|
||||
import type { AgentRuntimeName } from './runtimeSupport';
|
||||
import { runtimesForProfile } from './runtimeSupport';
|
||||
import { assertWorkerToken } from './workerAuth';
|
||||
@@ -1344,6 +1345,21 @@ export const updateStatus = mutation({
|
||||
}
|
||||
await ctx.db.patch(job.threadId, threadPatch);
|
||||
}
|
||||
if (
|
||||
(args.status === 'changes_ready' ||
|
||||
args.status === 'draft_pr_opened') &&
|
||||
job.status !== args.status
|
||||
) {
|
||||
await emitNotification(ctx, {
|
||||
userId: job.ownerId,
|
||||
kind: 'agent_turn_finished',
|
||||
title: 'Agent turn finished',
|
||||
body: args.summary ?? job.summary ?? '',
|
||||
link: `/threads/${job.threadId}`,
|
||||
threadId: job.threadId,
|
||||
spoonId: job.spoonId,
|
||||
});
|
||||
}
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
@@ -1687,6 +1703,17 @@ export const applyMaintenanceDecision = mutation({
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
if (status === 'waiting_for_user') {
|
||||
await emitNotification(ctx, {
|
||||
userId: job.ownerId,
|
||||
kind: 'agent_needs_input',
|
||||
title: 'Agent needs your input',
|
||||
body: args.summary,
|
||||
link: `/threads/${job.threadId}`,
|
||||
threadId: job.threadId,
|
||||
spoonId: job.spoonId,
|
||||
});
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
query,
|
||||
} from './_generated/server';
|
||||
import { getRequiredUserId } from './model';
|
||||
import { emitNotification } from './notifications';
|
||||
|
||||
export const getInstallUrl = query({
|
||||
args: {},
|
||||
@@ -158,6 +159,15 @@ export const setConnectionStatusByInstallation = internalMutation({
|
||||
const now = Date.now();
|
||||
for (const connection of connections) {
|
||||
await ctx.db.patch(connection._id, { status, updatedAt: now });
|
||||
if (status !== 'active') {
|
||||
await emitNotification(ctx, {
|
||||
userId: connection.userId,
|
||||
kind: 'connection_needs_reauth',
|
||||
title: 'GitHub connection needs re-authorization',
|
||||
body: 'Reconnect your GitHub account to keep syncing.',
|
||||
link: '/settings/integrations',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { updated: connections.length };
|
||||
|
||||
@@ -378,6 +378,16 @@ const refreshOwnedSpoon = async (
|
||||
error: message,
|
||||
}),
|
||||
]);
|
||||
if (!rateLimited) {
|
||||
await ctx.runMutation(internal.notifications.emit, {
|
||||
userId: ownerId,
|
||||
kind: 'sync_failed',
|
||||
title: `Sync failed for ${spoon.name}`,
|
||||
body: message,
|
||||
link: `/spoons/${spoonId}`,
|
||||
spoonId,
|
||||
});
|
||||
}
|
||||
throw new ConvexError(message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
optionalText,
|
||||
requireText,
|
||||
} from './model';
|
||||
import { emitNotification } from './notifications';
|
||||
|
||||
const threadSource = v.union(
|
||||
v.literal('user_request'),
|
||||
@@ -500,6 +501,15 @@ export const createMaintenanceThread = internalMutation({
|
||||
jobType: args.jobType,
|
||||
},
|
||||
);
|
||||
await emitNotification(ctx, {
|
||||
userId: args.ownerId,
|
||||
kind: 'maintenance_thread',
|
||||
title: args.title,
|
||||
body: args.summary,
|
||||
link: `/threads/${threadId}`,
|
||||
spoonId: args.spoonId,
|
||||
threadId,
|
||||
});
|
||||
return threadId;
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user