openmausbot 0.1.68 → 0.1.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{index-HqooKFTS.js → index-BmHupgVJ.js} +1 -1
- package/dist/assets/index-COp5lMnv.js +279 -0
- package/dist/assets/index-Jk7TIeyW.css +1 -0
- package/dist/index.html +2 -2
- package/dist-server/container-mcp.js +38 -2
- package/dist-server/drivers/agents-proxy.js +85 -5
- package/dist-server/index.js +1465 -611
- package/dist-server/openmausbot.js +554 -191
- package/dist-server/pair-cli.js +554 -191
- package/dist-server/server/avatar-image.js +86 -34
- package/dist-server/server/bot-package.js +60 -14
- package/dist-server/server/config.js +23 -2
- package/dist-server/server/drivers/acp/qwen.js +142 -5
- package/dist-server/server/drivers/agents-proxy.js +95 -4
- package/dist-server/server/drivers/codex-device-auth.js +102 -3
- package/dist-server/server/drivers/codex-identity.js +115 -0
- package/dist-server/server/drivers/codex.js +6 -0
- package/dist-server/server/harness/registry.js +2 -0
- package/dist-server/server/index.js +26 -5
- package/dist-server/server/package-export.js +7 -0
- package/dist-server/server/provider-auth-sessions.js +24 -0
- package/dist-server/server/routine-requests.js +270 -25
- package/dist-server/server/routines.js +185 -18
- package/dist-server/server/steer-queue.js +28 -0
- package/dist-server/shared/image-generation.js +28 -0
- package/dist-server/vps-container-mcp.js +38 -2
- package/package.json +1 -1
- package/dist/assets/index-CbaejjQA.js +0 -278
- package/dist/assets/index-aJ5EWZOQ.css +0 -1
|
@@ -28,6 +28,10 @@ const ACTION_COPY = {
|
|
|
28
28
|
};
|
|
29
29
|
const ROUTINE_REQUEST_FINGERPRINT_VERSION = 1;
|
|
30
30
|
const jsonObjectSchema = z.record(z.string(), z.custom());
|
|
31
|
+
const toolIntervalWindowSchema = z.object({
|
|
32
|
+
start: z.string().max(5),
|
|
33
|
+
end: z.string().max(5),
|
|
34
|
+
}).strict();
|
|
31
35
|
const routineToolScheduleSchema = z.discriminatedUnion("type", [
|
|
32
36
|
z.object({ type: z.literal("once"), at: z.string().max(64) }).strict(),
|
|
33
37
|
z.object({
|
|
@@ -39,6 +43,9 @@ const routineToolScheduleSchema = z.discriminatedUnion("type", [
|
|
|
39
43
|
type: z.literal("interval"),
|
|
40
44
|
everyMinutes: z.number(),
|
|
41
45
|
anchorAt: z.string().max(64).optional(),
|
|
46
|
+
weekdays: z.array(z.string().max(9)).min(1).max(7).nullable().optional(),
|
|
47
|
+
window: toolIntervalWindowSchema.nullable().optional(),
|
|
48
|
+
endsAt: z.string().max(64).nullable().optional(),
|
|
42
49
|
}).strict(),
|
|
43
50
|
]);
|
|
44
51
|
const routineToolDefinitionSchema = z.object({
|
|
@@ -71,6 +78,10 @@ const routineProposalSchema = z.discriminatedUnion("action", [
|
|
|
71
78
|
z.object({ action: z.literal("delete"), routineId: z.string().max(128) }).strict(),
|
|
72
79
|
]);
|
|
73
80
|
const storedWeekdaysSchema = z.array(z.number().int().min(0).max(6)).min(1).max(7).refine((weekdays) => new Set(weekdays).size === weekdays.length, "Stored routine weekdays must be unique");
|
|
81
|
+
const storedIntervalWindowSchema = z.object({
|
|
82
|
+
start: z.string().regex(TIME),
|
|
83
|
+
end: z.string().regex(TIME),
|
|
84
|
+
}).strict().refine(({ start, end }) => start < end, "Stored interval window must end later on the same day");
|
|
74
85
|
const storedScheduleSchema = z.discriminatedUnion("type", [
|
|
75
86
|
z.object({ type: z.literal("once"), at: z.number().int().nonnegative() }).strict(),
|
|
76
87
|
z.object({
|
|
@@ -82,8 +93,64 @@ const storedScheduleSchema = z.discriminatedUnion("type", [
|
|
|
82
93
|
type: z.literal("interval"),
|
|
83
94
|
everyMinutes: z.number().int().min(5).max(1_440),
|
|
84
95
|
anchorAt: z.number().int().nonnegative().max(MAX_DATE_MS).optional(),
|
|
96
|
+
weekdays: storedWeekdaysSchema.optional(),
|
|
97
|
+
window: storedIntervalWindowSchema.optional(),
|
|
98
|
+
endsAt: z.number().int().nonnegative().max(MAX_DATE_MS).optional(),
|
|
85
99
|
}).strict(),
|
|
86
|
-
])
|
|
100
|
+
]).superRefine((schedule, context) => {
|
|
101
|
+
if (schedule.type !== "interval")
|
|
102
|
+
return;
|
|
103
|
+
if (schedule.window && intervalWindowMinutes(schedule.window) < schedule.everyMinutes) {
|
|
104
|
+
context.addIssue({
|
|
105
|
+
code: "custom",
|
|
106
|
+
message: "Stored interval window must be at least as long as the cadence",
|
|
107
|
+
path: ["window"],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (schedule.endsAt !== undefined && schedule.anchorAt !== undefined && schedule.endsAt < schedule.anchorAt) {
|
|
111
|
+
context.addIssue({
|
|
112
|
+
code: "custom",
|
|
113
|
+
message: "Stored interval end must not be before its anchor",
|
|
114
|
+
path: ["endsAt"],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const storedScheduleChangesSchema = z.discriminatedUnion("type", [
|
|
119
|
+
z.object({ type: z.literal("once"), at: z.number().int().nonnegative() }).strict(),
|
|
120
|
+
z.object({
|
|
121
|
+
type: z.literal("daily"),
|
|
122
|
+
time: z.string().regex(TIME),
|
|
123
|
+
weekdays: storedWeekdaysSchema,
|
|
124
|
+
}).strict(),
|
|
125
|
+
z.object({
|
|
126
|
+
type: z.literal("interval"),
|
|
127
|
+
everyMinutes: z.number().int().min(5).max(1_440),
|
|
128
|
+
anchorAt: z.number().int().nonnegative().max(MAX_DATE_MS).optional(),
|
|
129
|
+
weekdays: storedWeekdaysSchema.nullable().optional(),
|
|
130
|
+
window: storedIntervalWindowSchema.nullable().optional(),
|
|
131
|
+
endsAt: z.number().int().nonnegative().max(MAX_DATE_MS).nullable().optional(),
|
|
132
|
+
}).strict(),
|
|
133
|
+
]).superRefine((schedule, context) => {
|
|
134
|
+
if (schedule.type !== "interval")
|
|
135
|
+
return;
|
|
136
|
+
if (schedule.window && intervalWindowMinutes(schedule.window) < schedule.everyMinutes) {
|
|
137
|
+
context.addIssue({
|
|
138
|
+
code: "custom",
|
|
139
|
+
message: "Stored interval window must be at least as long as the cadence",
|
|
140
|
+
path: ["window"],
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (schedule.endsAt !== undefined &&
|
|
144
|
+
schedule.endsAt !== null &&
|
|
145
|
+
schedule.anchorAt !== undefined &&
|
|
146
|
+
schedule.endsAt < schedule.anchorAt) {
|
|
147
|
+
context.addIssue({
|
|
148
|
+
code: "custom",
|
|
149
|
+
message: "Stored interval end must not be before its anchor",
|
|
150
|
+
path: ["endsAt"],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
87
154
|
const storedDefinitionSchema = z.object({
|
|
88
155
|
name: z.string().trim().min(1).max(80),
|
|
89
156
|
instructions: z.string().trim().min(1).max(20_000),
|
|
@@ -94,9 +161,12 @@ const storedDefinitionSchema = z.object({
|
|
|
94
161
|
continuity: z.boolean().optional(),
|
|
95
162
|
}).strict();
|
|
96
163
|
const storedChangesSchema = storedDefinitionSchema
|
|
97
|
-
.omit({ timeoutMinutes: true })
|
|
164
|
+
.omit({ schedule: true, timeoutMinutes: true })
|
|
98
165
|
.partial()
|
|
99
|
-
.extend({
|
|
166
|
+
.extend({
|
|
167
|
+
schedule: storedScheduleChangesSchema.optional(),
|
|
168
|
+
timeoutMinutes: z.number().int().min(5).max(240).nullable().optional(),
|
|
169
|
+
})
|
|
100
170
|
.strict()
|
|
101
171
|
.refine((changes) => Object.values(changes).some((value) => value !== undefined), "Stored routine update must change at least one field");
|
|
102
172
|
const storedManageBase = {
|
|
@@ -161,6 +231,32 @@ function timeout(value) {
|
|
|
161
231
|
}
|
|
162
232
|
return value;
|
|
163
233
|
}
|
|
234
|
+
function intervalWindowMinutes(window) {
|
|
235
|
+
const [startHour, startMinute] = window.start.split(":").map(Number);
|
|
236
|
+
const [endHour, endMinute] = window.end.split(":").map(Number);
|
|
237
|
+
return endHour * 60 + endMinute - (startHour * 60 + startMinute);
|
|
238
|
+
}
|
|
239
|
+
function intervalWeekdays(values) {
|
|
240
|
+
const weekdays = values.map((day) => {
|
|
241
|
+
const number = WEEKDAY_NUMBER[day.toLowerCase()];
|
|
242
|
+
if (number === undefined)
|
|
243
|
+
throw new RoutineRequestError(`Unsupported weekday: ${day}`);
|
|
244
|
+
return number;
|
|
245
|
+
});
|
|
246
|
+
return [...new Set(weekdays)].sort();
|
|
247
|
+
}
|
|
248
|
+
function intervalWindow(value, everyMinutes) {
|
|
249
|
+
if (!TIME.test(value.start) || !TIME.test(value.end)) {
|
|
250
|
+
throw new RoutineRequestError("Interval windows must use 24-hour HH:MM");
|
|
251
|
+
}
|
|
252
|
+
if (value.start >= value.end) {
|
|
253
|
+
throw new RoutineRequestError("Interval windows must end later on the same day");
|
|
254
|
+
}
|
|
255
|
+
if (intervalWindowMinutes(value) < everyMinutes) {
|
|
256
|
+
throw new RoutineRequestError("The interval window must be at least as long as everyMinutes");
|
|
257
|
+
}
|
|
258
|
+
return { start: value.start, end: value.end };
|
|
259
|
+
}
|
|
164
260
|
function rfc3339Instant(value, offsetMessage) {
|
|
165
261
|
const parts = RFC3339_WITH_OFFSET.exec(value);
|
|
166
262
|
if (!parts)
|
|
@@ -201,14 +297,31 @@ function normalizeSchedule(schedule, now) {
|
|
|
201
297
|
if (!Number.isInteger(schedule.everyMinutes) || schedule.everyMinutes < 5 || schedule.everyMinutes > 1_440) {
|
|
202
298
|
throw new RoutineRequestError("everyMinutes must be a whole number from 5 to 1440");
|
|
203
299
|
}
|
|
204
|
-
|
|
205
|
-
|
|
300
|
+
let anchorAt;
|
|
301
|
+
if (schedule.anchorAt !== undefined) {
|
|
302
|
+
anchorAt = rfc3339Instant(schedule.anchorAt, "Interval starts need an RFC3339 date-time with an explicit timezone offset");
|
|
303
|
+
if (!Number.isSafeInteger(anchorAt) || anchorAt < 0 || anchorAt > MAX_DATE_MS) {
|
|
304
|
+
throw new RoutineRequestError("Choose a valid interval start time");
|
|
305
|
+
}
|
|
206
306
|
}
|
|
207
|
-
|
|
208
|
-
if (
|
|
209
|
-
|
|
307
|
+
let endsAt;
|
|
308
|
+
if (schedule.endsAt !== undefined && schedule.endsAt !== null) {
|
|
309
|
+
endsAt = rfc3339Instant(schedule.endsAt, "Interval ends need an RFC3339 date-time with an explicit timezone offset");
|
|
310
|
+
if (!Number.isSafeInteger(endsAt) || endsAt < 0 || endsAt > MAX_DATE_MS) {
|
|
311
|
+
throw new RoutineRequestError("Choose a valid interval end time");
|
|
312
|
+
}
|
|
313
|
+
if (endsAt < (anchorAt ?? now)) {
|
|
314
|
+
throw new RoutineRequestError("The interval end must not be before its start");
|
|
315
|
+
}
|
|
210
316
|
}
|
|
211
|
-
return {
|
|
317
|
+
return {
|
|
318
|
+
type: "interval",
|
|
319
|
+
everyMinutes: schedule.everyMinutes,
|
|
320
|
+
...(anchorAt === undefined ? {} : { anchorAt }),
|
|
321
|
+
...(schedule.weekdays == null ? {} : { weekdays: intervalWeekdays(schedule.weekdays) }),
|
|
322
|
+
...(schedule.window == null ? {} : { window: intervalWindow(schedule.window, schedule.everyMinutes) }),
|
|
323
|
+
...(endsAt === undefined ? {} : { endsAt }),
|
|
324
|
+
};
|
|
212
325
|
}
|
|
213
326
|
if (!TIME.test(schedule.time)) {
|
|
214
327
|
throw new RoutineRequestError("Weekly schedule time must use 24-hour HH:MM");
|
|
@@ -225,6 +338,17 @@ function normalizeSchedule(schedule, now) {
|
|
|
225
338
|
});
|
|
226
339
|
return { type: "daily", time: schedule.time, weekdays: [...new Set(weekdays)].sort() };
|
|
227
340
|
}
|
|
341
|
+
function normalizeScheduleChanges(schedule, now) {
|
|
342
|
+
const normalized = normalizeSchedule(schedule, now);
|
|
343
|
+
if (schedule.type !== "interval" || normalized.type !== "interval")
|
|
344
|
+
return normalized;
|
|
345
|
+
return {
|
|
346
|
+
...normalized,
|
|
347
|
+
...(schedule.weekdays === null ? { weekdays: null } : {}),
|
|
348
|
+
...(schedule.window === null ? { window: null } : {}),
|
|
349
|
+
...(schedule.endsAt === null ? { endsAt: null } : {}),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
228
352
|
function normalizeDefinition(input, now) {
|
|
229
353
|
const timeoutMinutes = timeout(input.timeoutMinutes);
|
|
230
354
|
return {
|
|
@@ -244,7 +368,7 @@ function normalizeChanges(input, now) {
|
|
|
244
368
|
if (input.instructions !== undefined)
|
|
245
369
|
changes.instructions = text(input.instructions, "instructions", 20_000);
|
|
246
370
|
if (input.schedule !== undefined)
|
|
247
|
-
changes.schedule =
|
|
371
|
+
changes.schedule = normalizeScheduleChanges(input.schedule, now);
|
|
248
372
|
if (input.runOn !== undefined)
|
|
249
373
|
changes.runOn = runOn(input.runOn);
|
|
250
374
|
if (input.durationMinutes !== undefined)
|
|
@@ -263,6 +387,15 @@ function routineId(value) {
|
|
|
263
387
|
function ownedRoutine(manager, id, botId) {
|
|
264
388
|
return manager.listRoutines().find((routine) => routine.id === id && routine.botId === botId) ?? null;
|
|
265
389
|
}
|
|
390
|
+
function noFutureResumeMessage(schedule) {
|
|
391
|
+
if (schedule.type === "interval") {
|
|
392
|
+
return "That interval routine has no future runs. Choose a later end time or remove the end restriction before resuming.";
|
|
393
|
+
}
|
|
394
|
+
if (schedule.type === "once") {
|
|
395
|
+
return "That one-time routine's scheduled time has passed. Update it to a new future time before resuming.";
|
|
396
|
+
}
|
|
397
|
+
return "That routine has no future runs. Update its schedule before resuming.";
|
|
398
|
+
}
|
|
266
399
|
function normalizedOperation(manager, botId, validated, now) {
|
|
267
400
|
if (validated.action === "create") {
|
|
268
401
|
const operation = {
|
|
@@ -286,7 +419,7 @@ function normalizedOperation(manager, botId, validated, now) {
|
|
|
286
419
|
};
|
|
287
420
|
}
|
|
288
421
|
if (validated.action === "resume" && nextOccurrence(current.schedule, now) === null) {
|
|
289
|
-
throw new RoutineRequestError(
|
|
422
|
+
throw new RoutineRequestError(noFutureResumeMessage(current.schedule), 409);
|
|
290
423
|
}
|
|
291
424
|
return { action: validated.action, routineId: id, expectedUpdatedAt: current.updatedAt };
|
|
292
425
|
}
|
|
@@ -294,10 +427,60 @@ function asSchedule(schedule, now) {
|
|
|
294
427
|
if (schedule.type === "once")
|
|
295
428
|
return { type: "once", at: schedule.at };
|
|
296
429
|
if (schedule.type === "interval") {
|
|
297
|
-
return {
|
|
430
|
+
return {
|
|
431
|
+
type: "interval",
|
|
432
|
+
everyMinutes: schedule.everyMinutes,
|
|
433
|
+
anchorAt: schedule.anchorAt ?? now,
|
|
434
|
+
...(schedule.weekdays === undefined ? {} : { weekdays: [...schedule.weekdays] }),
|
|
435
|
+
...(schedule.window === undefined ? {} : { window: { ...schedule.window } }),
|
|
436
|
+
...(schedule.endsAt === undefined ? {} : { endsAt: schedule.endsAt }),
|
|
437
|
+
};
|
|
298
438
|
}
|
|
299
439
|
return { type: "daily", time: schedule.time, weekdays: [...schedule.weekdays] };
|
|
300
440
|
}
|
|
441
|
+
function schedulePatch(schedule, now, current) {
|
|
442
|
+
if (schedule.type !== "interval")
|
|
443
|
+
return asSchedule(schedule, now);
|
|
444
|
+
return {
|
|
445
|
+
type: "interval",
|
|
446
|
+
everyMinutes: schedule.everyMinutes,
|
|
447
|
+
anchorAt: schedule.anchorAt ?? (current.type === "interval" ? current.anchorAt : now),
|
|
448
|
+
...(Object.hasOwn(schedule, "weekdays")
|
|
449
|
+
? { weekdays: schedule.weekdays === null ? null : [...schedule.weekdays] }
|
|
450
|
+
: {}),
|
|
451
|
+
...(Object.hasOwn(schedule, "window")
|
|
452
|
+
? { window: schedule.window === null ? null : { ...schedule.window } }
|
|
453
|
+
: {}),
|
|
454
|
+
...(Object.hasOwn(schedule, "endsAt") ? { endsAt: schedule.endsAt } : {}),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function effectiveSchedule(current, incoming) {
|
|
458
|
+
if (incoming.type !== "interval") {
|
|
459
|
+
return incoming.type === "once"
|
|
460
|
+
? { type: "once", at: incoming.at }
|
|
461
|
+
: { type: "daily", time: incoming.time, weekdays: [...incoming.weekdays] };
|
|
462
|
+
}
|
|
463
|
+
const previous = current.type === "interval" ? current : undefined;
|
|
464
|
+
const merged = {
|
|
465
|
+
type: "interval",
|
|
466
|
+
everyMinutes: incoming.everyMinutes,
|
|
467
|
+
...(incoming.anchorAt !== undefined
|
|
468
|
+
? { anchorAt: incoming.anchorAt }
|
|
469
|
+
: previous?.anchorAt !== undefined
|
|
470
|
+
? { anchorAt: previous.anchorAt }
|
|
471
|
+
: {}),
|
|
472
|
+
};
|
|
473
|
+
const weekdays = incoming.weekdays === undefined ? previous?.weekdays : incoming.weekdays;
|
|
474
|
+
if (weekdays !== undefined && weekdays !== null)
|
|
475
|
+
merged.weekdays = [...weekdays];
|
|
476
|
+
const window = incoming.window === undefined ? previous?.window : incoming.window;
|
|
477
|
+
if (window !== undefined && window !== null)
|
|
478
|
+
merged.window = { ...window };
|
|
479
|
+
const endsAt = incoming.endsAt === undefined ? previous?.endsAt : incoming.endsAt;
|
|
480
|
+
if (endsAt !== undefined && endsAt !== null)
|
|
481
|
+
merged.endsAt = endsAt;
|
|
482
|
+
return merged;
|
|
483
|
+
}
|
|
301
484
|
function nextForOperation(operation, manager, now) {
|
|
302
485
|
if (operation.action === "create") {
|
|
303
486
|
if (operation.routine.schedule.type === "interval" && operation.routine.schedule.anchorAt === undefined)
|
|
@@ -317,9 +500,12 @@ function nextForOperation(operation, manager, now) {
|
|
|
317
500
|
return null;
|
|
318
501
|
if (!current.enabled)
|
|
319
502
|
return null;
|
|
320
|
-
|
|
503
|
+
const definition = effectiveDefinition(operation, manager);
|
|
504
|
+
if (!definition)
|
|
505
|
+
return null;
|
|
506
|
+
if (definition.schedule.type === "interval" && definition.schedule.anchorAt === undefined)
|
|
321
507
|
return null;
|
|
322
|
-
const schedule =
|
|
508
|
+
const schedule = asSchedule(definition.schedule, now);
|
|
323
509
|
return nextOccurrence(schedule, now);
|
|
324
510
|
}
|
|
325
511
|
function formatInstant(at, timeZone) {
|
|
@@ -334,13 +520,27 @@ function formatInstant(at, timeZone) {
|
|
|
334
520
|
return new Date(at).toISOString();
|
|
335
521
|
}
|
|
336
522
|
}
|
|
523
|
+
function intervalHasRestrictions(schedule) {
|
|
524
|
+
return schedule.weekdays !== undefined || schedule.window !== undefined || schedule.endsAt !== undefined;
|
|
525
|
+
}
|
|
337
526
|
export function scheduleText(schedule, timeZone) {
|
|
338
527
|
if (schedule.type === "once")
|
|
339
528
|
return `${formatInstant(schedule.at, timeZone)} (${timeZone})`;
|
|
340
529
|
if (schedule.type === "interval") {
|
|
341
|
-
|
|
342
|
-
|
|
530
|
+
const restricted = intervalHasRestrictions(schedule);
|
|
531
|
+
const cadence = schedule.anchorAt === undefined
|
|
532
|
+
? restricted
|
|
533
|
+
? `Every ${schedule.everyMinutes} minutes, cadence starting at confirmation; first run is the next allowed time`
|
|
534
|
+
: `Every ${schedule.everyMinutes} minutes, starting one interval after confirmation`
|
|
343
535
|
: `Every ${schedule.everyMinutes} minutes, anchored at ${formatInstant(schedule.anchorAt, timeZone)} (${timeZone})`;
|
|
536
|
+
const restrictions = [
|
|
537
|
+
schedule.weekdays?.length
|
|
538
|
+
? `on ${schedule.weekdays.map((day) => WEEKDAY_LABEL[day]).join(", ")} (${timeZone})`
|
|
539
|
+
: null,
|
|
540
|
+
schedule.window ? `during ${schedule.window.start}–${schedule.window.end} (${timeZone})` : null,
|
|
541
|
+
schedule.endsAt !== undefined ? `until ${formatInstant(schedule.endsAt, timeZone)} (${timeZone})` : null,
|
|
542
|
+
].filter((part) => part !== null);
|
|
543
|
+
return restrictions.length ? `${cadence} · ${restrictions.join(" · ")}` : cadence;
|
|
344
544
|
}
|
|
345
545
|
const days = schedule.weekdays.map((day) => WEEKDAY_LABEL[day]).join(", ");
|
|
346
546
|
return `${days} at ${schedule.time} (${timeZone})`;
|
|
@@ -355,6 +555,9 @@ export function consequenceLine(schedule, continuity = false) {
|
|
|
355
555
|
if (schedule.type === "once")
|
|
356
556
|
return "Will run once; that run starts a fresh session.";
|
|
357
557
|
if (schedule.type === "interval") {
|
|
558
|
+
if (intervalHasRestrictions(schedule)) {
|
|
559
|
+
return `Will run every ${schedule.everyMinutes} minutes when its day, time, and end restrictions allow; ${session}.`;
|
|
560
|
+
}
|
|
358
561
|
const runsPerDay = Math.round(1440 / schedule.everyMinutes);
|
|
359
562
|
const cadence = runsPerDay <= 1 ? "about once a day" : `about ${runsPerDay} times a day`;
|
|
360
563
|
return `Will run ${cadence}; ${session}.`;
|
|
@@ -372,7 +575,15 @@ function effectiveDefinition(operation, manager) {
|
|
|
372
575
|
const base = {
|
|
373
576
|
name: existing.name,
|
|
374
577
|
instructions: existing.prompt,
|
|
375
|
-
schedule:
|
|
578
|
+
schedule: existing.schedule.type === "interval"
|
|
579
|
+
? {
|
|
580
|
+
...existing.schedule,
|
|
581
|
+
...(existing.schedule.weekdays ? { weekdays: [...existing.schedule.weekdays] } : {}),
|
|
582
|
+
...(existing.schedule.window ? { window: { ...existing.schedule.window } } : {}),
|
|
583
|
+
}
|
|
584
|
+
: existing.schedule.type === "daily"
|
|
585
|
+
? { ...existing.schedule, weekdays: [...existing.schedule.weekdays] }
|
|
586
|
+
: { ...existing.schedule },
|
|
376
587
|
runOn: existing.runOn,
|
|
377
588
|
durationMinutes: existing.durationMinutes,
|
|
378
589
|
...(existing.timeoutMinutes === undefined ? {} : { timeoutMinutes: existing.timeoutMinutes }),
|
|
@@ -380,8 +591,12 @@ function effectiveDefinition(operation, manager) {
|
|
|
380
591
|
};
|
|
381
592
|
if (operation.action !== "update")
|
|
382
593
|
return base;
|
|
383
|
-
const { timeoutMinutes, ...changes } = operation.changes;
|
|
384
|
-
const merged = {
|
|
594
|
+
const { schedule, timeoutMinutes, ...changes } = operation.changes;
|
|
595
|
+
const merged = {
|
|
596
|
+
...base,
|
|
597
|
+
...changes,
|
|
598
|
+
...(schedule === undefined ? {} : { schedule: effectiveSchedule(base.schedule, schedule) }),
|
|
599
|
+
};
|
|
385
600
|
if (timeoutMinutes === null)
|
|
386
601
|
delete merged.timeoutMinutes;
|
|
387
602
|
else if (timeoutMinutes !== undefined)
|
|
@@ -413,10 +628,15 @@ function cardCopy(operation, manager, timeZone, now) {
|
|
|
413
628
|
: manager.listRoutines().find((routine) => routine.id === operation.routineId) ?? null;
|
|
414
629
|
const remainsPaused = operation.action === "update" && current?.enabled === false;
|
|
415
630
|
const deferredInterval = definition.schedule.type === "interval" && definition.schedule.anchorAt === undefined;
|
|
631
|
+
const deferredRestrictedInterval = deferredInterval &&
|
|
632
|
+
definition.schedule.type === "interval" &&
|
|
633
|
+
intervalHasRestrictions(definition.schedule);
|
|
416
634
|
const nextDescription = remainsPaused
|
|
417
635
|
? "None — this routine remains paused"
|
|
418
636
|
: deferredInterval
|
|
419
|
-
?
|
|
637
|
+
? deferredRestrictedInterval
|
|
638
|
+
? `Next allowed time after confirmation (${timeZone})`
|
|
639
|
+
: "One interval after confirmation"
|
|
420
640
|
: nextRunAt !== null
|
|
421
641
|
? formatInstant(nextRunAt, timeZone)
|
|
422
642
|
: operation.action === "pause"
|
|
@@ -470,14 +690,14 @@ function inputFromDefinition(definition, botId, now) {
|
|
|
470
690
|
...(definition.continuity ? { continuity: true } : {}),
|
|
471
691
|
};
|
|
472
692
|
}
|
|
473
|
-
function updateFromChanges(changes, now) {
|
|
693
|
+
function updateFromChanges(changes, now, currentSchedule) {
|
|
474
694
|
const patch = {};
|
|
475
695
|
if (changes.name !== undefined)
|
|
476
696
|
patch.name = changes.name;
|
|
477
697
|
if (changes.instructions !== undefined)
|
|
478
698
|
patch.prompt = changes.instructions;
|
|
479
699
|
if (changes.schedule !== undefined)
|
|
480
|
-
patch.schedule =
|
|
700
|
+
patch.schedule = schedulePatch(changes.schedule, now, currentSchedule);
|
|
481
701
|
if (changes.runOn !== undefined)
|
|
482
702
|
patch.runOn = changes.runOn;
|
|
483
703
|
if (changes.durationMinutes !== undefined)
|
|
@@ -549,11 +769,36 @@ function revalidateOperation(operation, manager, botId, now) {
|
|
|
549
769
|
if (schedule?.type === "once" && schedule.at <= now) {
|
|
550
770
|
throw new RoutineRequestError("That one-time schedule is now in the past. Ask the bot to propose a new time.", 409);
|
|
551
771
|
}
|
|
772
|
+
if (schedule?.type === "interval") {
|
|
773
|
+
const base = operation.action === "create"
|
|
774
|
+
? operation.routine.schedule
|
|
775
|
+
: current
|
|
776
|
+
? effectiveSchedule(current.schedule.type === "interval"
|
|
777
|
+
? {
|
|
778
|
+
...current.schedule,
|
|
779
|
+
...(current.schedule.weekdays ? { weekdays: [...current.schedule.weekdays] } : {}),
|
|
780
|
+
...(current.schedule.window ? { window: { ...current.schedule.window } } : {}),
|
|
781
|
+
}
|
|
782
|
+
: current.schedule.type === "daily"
|
|
783
|
+
? { ...current.schedule, weekdays: [...current.schedule.weekdays] }
|
|
784
|
+
: { ...current.schedule }, schedule)
|
|
785
|
+
: null;
|
|
786
|
+
const concrete = base ? asSchedule(base, now) : null;
|
|
787
|
+
if (concrete) {
|
|
788
|
+
const valid = storedScheduleSchema.safeParse(concrete);
|
|
789
|
+
if (!valid.success) {
|
|
790
|
+
throw new RoutineRequestError(schemaIssue(valid.error, "Invalid interval schedule"));
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (concrete && nextOccurrence(concrete, now) === null) {
|
|
794
|
+
throw new RoutineRequestError("That interval no longer has a future run. Choose a later end time or remove the end restriction.", 409);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
552
797
|
if (operation.action === "resume") {
|
|
553
798
|
if (!current)
|
|
554
799
|
throw new RoutineRequestError("That routine no longer exists", 404);
|
|
555
800
|
if (nextOccurrence(current.schedule, now) === null) {
|
|
556
|
-
throw new RoutineRequestError(
|
|
801
|
+
throw new RoutineRequestError(noFutureResumeMessage(current.schedule), 409);
|
|
557
802
|
}
|
|
558
803
|
}
|
|
559
804
|
}
|
|
@@ -838,8 +1083,8 @@ export class RoutineRequestService {
|
|
|
838
1083
|
fingerprint,
|
|
839
1084
|
}).id;
|
|
840
1085
|
case "update": {
|
|
841
|
-
verifyManageSnapshot(operation, this.routines, payload.botId);
|
|
842
|
-
const updated = this.routines.update(operation.routineId, updateFromChanges(operation.changes, confirmationAt), {
|
|
1086
|
+
const current = verifyManageSnapshot(operation, this.routines, payload.botId);
|
|
1087
|
+
const updated = this.routines.update(operation.routineId, updateFromChanges(operation.changes, confirmationAt, current.schedule), {
|
|
843
1088
|
requestId: payload.requestId,
|
|
844
1089
|
messageId,
|
|
845
1090
|
botId: payload.botId,
|