diff --git a/extensions/qqbot/src/engine/tools/remind-logic.test.ts b/extensions/qqbot/src/engine/tools/remind-logic.test.ts index b734c236b374..c2119cdf9fa6 100644 --- a/extensions/qqbot/src/engine/tools/remind-logic.test.ts +++ b/extensions/qqbot/src/engine/tools/remind-logic.test.ts @@ -59,6 +59,54 @@ describe("engine/tools/remind-logic", () => { }); }); + it.each([ + { + name: "uses the Gateway timezone when omitted", + timezone: undefined, + expectedSchedule: { kind: "cron", expr: "0 9 * * *" }, + expectedSummary: '⏰ Recurring reminder: "test reminder" (0 9 * * *, tz=gateway local)', + }, + { + name: "preserves an explicit IANA timezone", + timezone: " America/New_York ", + expectedSchedule: { + kind: "cron", + expr: "0 9 * * *", + tz: "America/New_York", + }, + expectedSummary: '⏰ Recurring reminder: "test reminder" (0 9 * * *, tz=America/New_York)', + }, + ])("$name for recurring reminders", async ({ timezone, expectedSchedule, expectedSummary }) => { + const calls: RemindCronAction[] = []; + const result = await executeScheduledRemind( + { + action: "add", + content: "test reminder", + to: "qqbot:c2c:123", + time: "0 9 * * *", + ...(timezone ? { timezone } : {}), + }, + {}, + async (params) => { + calls.push(params); + return { id: "job-cron" }; + }, + ); + + const call = calls[0]; + expect(call?.action).toBe("add"); + if (call?.action !== "add") { + throw new Error("expected add cron action"); + } + expect(call.job.schedule).toEqual(expectedSchedule); + expect(result.details).toEqual({ + ok: true, + action: "add", + summary: expectedSummary, + cronResult: { id: "job-cron" }, + }); + }); + it("runs cron list and remove through the scheduler", async () => { const calls: unknown[] = []; await executeScheduledRemind({ action: "list" }, {}, async (params) => { diff --git a/extensions/qqbot/src/engine/tools/remind-logic.ts b/extensions/qqbot/src/engine/tools/remind-logic.ts index 6db64039591f..827696164c76 100644 --- a/extensions/qqbot/src/engine/tools/remind-logic.ts +++ b/extensions/qqbot/src/engine/tools/remind-logic.ts @@ -27,8 +27,6 @@ export interface RemindParams { jobId?: string; } -const QQBOT_DEFAULT_REMINDER_TIMEZONE = "Asia/Shanghai"; - /** * Context supplied by the bridge layer so the engine can remain free of * framework / AsyncLocalStorage dependencies. `fallbackTo` and @@ -99,7 +97,7 @@ export const RemindSchema = { timezone: { type: "string", description: - "Optional IANA timezone used for cron reminders. Include it when the user provides or confirms a timezone; if omitted, QQBot preserves its existing default timezone.", + "Optional IANA timezone used for cron reminders. Include it when the user provides or confirms a timezone; if omitted, Gateway cron uses the host timezone.", }, name: { type: "string", @@ -226,12 +224,16 @@ function buildOnceJob(params: RemindParams, atMs: number, to: string, accountId: function buildCronJob(params: RemindParams, to: string, accountId: string) { const content = params.content!; const name = params.name || generateJobName(content); - const tz = params.timezone || QQBOT_DEFAULT_REMINDER_TIMEZONE; + const timezone = params.timezone?.trim(); return { action: "add" as const, job: { name, - schedule: { kind: "cron" as const, expr: params.time!.trim(), tz }, + schedule: { + kind: "cron" as const, + expr: params.time!.trim(), + ...(timezone ? { tz: timezone } : {}), + }, sessionTarget: "isolated" as const, wakeMode: "now" as const, payload: { @@ -309,11 +311,12 @@ function prepareRemindCronAction( const resolvedAccountId = ctx.fallbackAccountId || "default"; if (isCronExpression(params.time)) { + const timezone = params.timezone?.trim(); return { ok: true, action: "add", cronAction: buildCronJob(params, resolvedTo, resolvedAccountId), - summary: `⏰ Recurring reminder: "${params.content}" (${params.time}, tz=${params.timezone || QQBOT_DEFAULT_REMINDER_TIMEZONE})`, + summary: `⏰ Recurring reminder: "${params.content}" (${params.time}, tz=${timezone || "gateway local"})`, }; }