preview auto-refresh after chat tool calls, gmail sync label scoping, queue notify option, workspace-scoped chat sessions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 01:49:52 +00:00
co-authored by Claude Opus 4.6
parent 968d502eaa
commit 269b1026e5
11 changed files with 80 additions and 28 deletions
+7 -2
View File
@@ -29,9 +29,14 @@ queueRouter.get('/jobs/:id', async (ctx) => {
queueRouter.post('/jobs', async (ctx) => {
const user = ctx.get('user');
const body = ctx.get('body');
const { lane, type, meta } = body as { lane: string; type: string; meta?: Record<string, unknown> };
const { lane, type, meta, notify } = body as {
lane: string;
type: string;
meta?: Record<string, unknown>;
notify?: boolean;
};
const job = await enqueue({ lane, type, userId: user.email, meta });
const job = await enqueue({ lane, type, userId: user.email, meta, notify });
return ctx.json(job, 201);
});
+3 -2
View File
@@ -21,6 +21,7 @@ export async function enqueue(params: EnqueueParams): Promise<Job> {
currentStep: 0,
createdAt: Date.now(),
meta: params.meta,
notify: params.notify,
};
await writeJob(job);
@@ -175,7 +176,7 @@ async function runJob(job: Job) {
fresh.completedAt = Date.now();
await writeJob(fresh);
console.error(`[queue] Job ${fresh.id} failed at step "${step.name}":`, errorMessage);
await notifyFailure(fresh);
if (fresh.notify !== false) await notifyFailure(fresh);
return;
}
}
@@ -186,7 +187,7 @@ async function runJob(job: Job) {
final.completedAt = Date.now();
await writeJob(final);
console.log(`[queue] Job ${final.id} completed`);
await notifyCompletion(final);
if (final.notify !== false) await notifyCompletion(final);
}
}
+8 -5
View File
@@ -383,7 +383,8 @@ const gmailSyncHandler: JobHandler = {
if (year) {
// Year-scoped sync: count total emails first, then sync month by month
const yearQuery = `after:${year}/1/1 before:${year + 1}/1/1`;
const labelScope = '(in:inbox OR in:sent OR in:trash OR in:spam)';
const yearQuery = `${labelScope} after:${year}/1/1 before:${year + 1}/1/1`;
await ctx.updateProgress({ current: 0, total: 0, label: 'Counting emails...' });
const totalEmails = await countMessages(token, yearQuery);
console.log(`[gmail-sync] Pre-flight: ${totalEmails} emails for ${year}`);
@@ -392,7 +393,7 @@ const gmailSyncHandler: JobHandler = {
for (let i = 0; i < months.length; i++) {
const month = months[i]!;
await ctx.updateProgress({ current: totalSaved + totalSkipped, total: totalEmails, label: month.label });
const query = `after:${month.after} before:${month.before}`;
const query = `${labelScope} after:${month.after} before:${month.before}`;
const result = await syncInbox(token, db, ctx.job.userId, query, (p) => {
const current = totalSaved + p.saved + p.skipped + p.errors;
const label = `${month.label} — Saved ${(totalSaved + p.saved).toLocaleString()} of ${totalEmails.toLocaleString()}`;
@@ -410,12 +411,14 @@ const gmailSyncHandler: JobHandler = {
}
await ctx.updateProgress({ current: totalEmails, total: totalEmails, label: 'Done' });
} else {
// If we have a last_sync_date (e.g. from migration), scope the sync to only newer emails
// Scope to inbox + sent to avoid syncing trash/spam/drafts
const lastSyncDate = getSyncMeta(db, 'last_sync_date');
let syncQuery: string | undefined;
const labelScope = 'in:inbox OR in:sent OR in:trash OR in:spam';
let syncQuery: string = labelScope;
if (lastSyncDate) {
const d = new Date(lastSyncDate);
syncQuery = `after:${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
const dateScope = `after:${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`;
syncQuery = `(${labelScope}) ${dateScope}`;
console.log(`[gmail-sync] Scoping full sync with query: ${syncQuery}`);
}
+2
View File
@@ -30,6 +30,7 @@ export type Job = {
startedAt?: number;
completedAt?: number;
meta?: Record<string, unknown>;
notify?: boolean;
};
export type StepContext = {
@@ -54,4 +55,5 @@ export type EnqueueParams = {
type: string;
userId: string;
meta?: Record<string, unknown>;
notify?: boolean;
};