




















@@ -94,10 +94,195 @@ const REMINDER_CONTEXT_PER_MESSAGE_MAX = 220;
9494const REMINDER_CONTEXT_TOTAL_MAX = 700;
9595const REMINDER_CONTEXT_MARKER = "\n\nRecent context:\n";
969697+function isCronScheduleKind(value: unknown): value is (typeof CRON_SCHEDULE_KINDS)[number] {
98+return value === "at" || value === "every" || value === "cron";
99+}
100+101+function isCronPayloadKind(value: unknown): value is (typeof CRON_PAYLOAD_KINDS)[number] {
102+return value === "systemEvent" || value === "agentTurn";
103+}
104+97105function isMissingOrEmptyObject(value: unknown): boolean {
98106return !value || (isRecord(value) && Object.keys(value).length === 0);
99107}
100108109+function isNonEmptyString(value: unknown): value is string {
110+return typeof value === "string" && value.trim().length > 0;
111+}
112+113+function isStringArrayOrNull(value: unknown): boolean {
114+return (
115+value === null || (Array.isArray(value) && value.every((entry) => typeof entry === "string"))
116+);
117+}
118+119+function moveDefinedField(params: {
120+source: Record<string, unknown>;
121+target: Record<string, unknown>;
122+from: string;
123+to?: string;
124+}): boolean {
125+if (params.source[params.from] === undefined) {
126+return false;
127+}
128+params.target[params.to ?? params.from] = params.source[params.from];
129+delete params.source[params.from];
130+return true;
131+}
132+133+function setScheduleAtMs(schedule: Record<string, unknown>, value: unknown): void {
134+const atMs = typeof value === "number" ? value : Number(value);
135+schedule.at = Number.isFinite(atMs) ? new Date(Math.floor(atMs)).toISOString() : value;
136+}
137+138+function canonicalizeCronToolSchedule(value: Record<string, unknown>): void {
139+const schedule = isRecord(value.schedule) ? { ...value.schedule } : {};
140+let hasSchedule = isRecord(value.schedule);
141+142+if (schedule.atMs !== undefined) {
143+setScheduleAtMs(schedule, schedule.atMs);
144+delete schedule.atMs;
145+if (!isCronScheduleKind(schedule.kind)) {
146+schedule.kind = "at";
147+}
148+}
149+if (schedule.everyMs === undefined && schedule.every !== undefined) {
150+schedule.everyMs = schedule.every;
151+delete schedule.every;
152+}
153+if (schedule.expr === undefined && schedule.cron !== undefined) {
154+schedule.expr = schedule.cron;
155+delete schedule.cron;
156+}
157+if (schedule.staggerMs === undefined && schedule.stagger !== undefined) {
158+schedule.staggerMs = schedule.stagger;
159+delete schedule.stagger;
160+}
161+if (schedule.exact === true && schedule.staggerMs === undefined) {
162+schedule.staggerMs = 0;
163+}
164+delete schedule.exact;
165+166+if (isCronScheduleKind(value.kind) && !isCronScheduleKind(schedule.kind)) {
167+schedule.kind = value.kind;
168+delete value.kind;
169+hasSchedule = true;
170+}
171+172+const movedAt = moveDefinedField({ source: value, target: schedule, from: "at" });
173+if (movedAt && !isCronScheduleKind(schedule.kind)) {
174+schedule.kind = "at";
175+}
176+177+if (value.atMs !== undefined) {
178+setScheduleAtMs(schedule, value.atMs);
179+delete value.atMs;
180+if (!isCronScheduleKind(schedule.kind)) {
181+schedule.kind = "at";
182+}
183+hasSchedule = true;
184+}
185+186+const movedEveryMs =
187+moveDefinedField({ source: value, target: schedule, from: "everyMs" }) ||
188+moveDefinedField({ source: value, target: schedule, from: "every", to: "everyMs" });
189+if (movedEveryMs && !isCronScheduleKind(schedule.kind)) {
190+schedule.kind = "every";
191+}
192+193+const movedCron =
194+moveDefinedField({ source: value, target: schedule, from: "cron", to: "expr" }) ||
195+moveDefinedField({ source: value, target: schedule, from: "expr" });
196+if (movedCron && !isCronScheduleKind(schedule.kind)) {
197+schedule.kind = "cron";
198+}
199+200+for (const key of ["anchorMs", "tz", "staggerMs"] as const) {
201+hasSchedule = moveDefinedField({ source: value, target: schedule, from: key }) || hasSchedule;
202+}
203+hasSchedule =
204+moveDefinedField({ source: value, target: schedule, from: "stagger", to: "staggerMs" }) ||
205+hasSchedule;
206+207+if (value.exact === true && schedule.staggerMs === undefined) {
208+schedule.staggerMs = 0;
209+hasSchedule = true;
210+}
211+delete value.exact;
212+213+if (!isCronScheduleKind(schedule.kind)) {
214+if (schedule.at !== undefined) {
215+schedule.kind = "at";
216+} else if (schedule.everyMs !== undefined) {
217+schedule.kind = "every";
218+} else if (schedule.expr !== undefined) {
219+schedule.kind = "cron";
220+}
221+}
222+223+if (hasSchedule || Object.keys(schedule).length > 0) {
224+value.schedule = schedule;
225+}
226+}
227+228+function canonicalizeCronToolPayload(value: Record<string, unknown>): void {
229+const payload = isRecord(value.payload) ? { ...value.payload } : {};
230+let hasPayload = isRecord(value.payload);
231+232+for (const key of CRON_FLAT_PAYLOAD_KEYS) {
233+hasPayload = moveDefinedField({ source: value, target: payload, from: key }) || hasPayload;
234+}
235+236+if (isCronPayloadKind(value.kind) && !isCronPayloadKind(payload.kind)) {
237+payload.kind = value.kind;
238+delete value.kind;
239+hasPayload = true;
240+}
241+242+if (!isCronPayloadKind(payload.kind)) {
243+const hasAgentTurnSignal =
244+isNonEmptyString(payload.message) ||
245+isNonEmptyString(payload.model) ||
246+isNonEmptyString(payload.thinking) ||
247+typeof payload.timeoutSeconds === "number" ||
248+typeof payload.lightContext === "boolean" ||
249+typeof payload.allowUnsafeExternalContent === "boolean" ||
250+(payload.fallbacks !== undefined && isStringArrayOrNull(payload.fallbacks)) ||
251+(payload.toolsAllow !== undefined && isStringArrayOrNull(payload.toolsAllow));
252+if (hasAgentTurnSignal) {
253+payload.kind = "agentTurn";
254+} else if (isNonEmptyString(payload.text)) {
255+payload.kind = "systemEvent";
256+}
257+}
258+259+if (hasPayload || Object.keys(payload).length > 0) {
260+value.payload = payload;
261+}
262+}
263+264+function canonicalizeCronToolObject(value: Record<string, unknown>): Record<string, unknown> {
265+const unwrapped = isRecord(value.data) ? value.data : isRecord(value.job) ? value.job : value;
266+const next = { ...unwrapped };
267+canonicalizeCronToolSchedule(next);
268+canonicalizeCronToolPayload(next);
269+return next;
270+}
271+272+function isEmptyRecoveredCronPatch(value: unknown): boolean {
273+if (!isRecord(value)) {
274+return true;
275+}
276+const keys = Object.keys(value);
277+return (
278+keys.length === 0 ||
279+(keys.length === 1 &&
280+keys[0] === "payload" &&
281+isRecord(value.payload) &&
282+Object.keys(value.payload).length === 0)
283+);
284+}
285+101286function recoverCronObjectFromFlatParams(params: Record<string, unknown>): {
102287found: boolean;
103288value: Record<string, unknown>;
@@ -110,19 +295,7 @@ function recoverCronObjectFromFlatParams(params: Record<string, unknown>): {
110295found = true;
111296}
112297}
113-if (value.everyMs === undefined && value.every !== undefined) {
114-value.everyMs = value.every;
115-}
116-if (value.staggerMs === undefined && value.stagger !== undefined) {
117-value.staggerMs = value.stagger;
118-}
119-if (value.exact === true && value.staggerMs === undefined) {
120-value.staggerMs = 0;
121-}
122-delete value.every;
123-delete value.stagger;
124-delete value.exact;
125-return { found, value };
298+return { found, value: canonicalizeCronToolObject(value) };
126299}
127300128301function hasCronCreateSignal(value: Record<string, unknown>): boolean {
@@ -662,10 +835,11 @@ Use jobId canonical; id accepted compat. contextMessages (0-10) adds previous me
662835if (!params.job || typeof params.job !== "object") {
663836throw new Error("job required");
664837}
838+const canonicalJob = canonicalizeCronToolObject(params.job as Record<string, unknown>);
665839const job =
666-normalizeCronJobCreate(params.job, {
840+normalizeCronJobCreate(canonicalJob, {
667841sessionContext: { sessionKey: opts?.agentSessionKey },
668-}) ?? params.job;
842+}) ?? canonicalJob;
669843const cfg = getRuntimeConfig();
670844if (job && typeof job === "object") {
671845const { mainKey, alias } = resolveMainSessionAlias(cfg);
@@ -775,13 +949,11 @@ Use jobId canonical; id accepted compat. contextMessages (0-10) adds previous me
775949if (!params.patch || typeof params.patch !== "object") {
776950throw new Error("patch required");
777951}
778-const patch = normalizeCronJobPatch(params.patch) ?? params.patch;
779-if (
780-recoveredFlatPatch &&
781-typeof patch === "object" &&
782-patch !== null &&
783-Object.keys(patch as Record<string, unknown>).length === 0
784-) {
952+const canonicalPatch = canonicalizeCronToolObject(
953+params.patch as Record<string, unknown>,
954+);
955+const patch = normalizeCronJobPatch(canonicalPatch) ?? canonicalPatch;
956+if (recoveredFlatPatch && isEmptyRecoveredCronPatch(patch)) {
785957throw new Error("patch required");
786958}
787959return jsonResult(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。