pockcode 0.0.1

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.
@@ -0,0 +1,32 @@
1
+ import { o as processDueMessageSchedules, s as syncMessageScheduleRunStatuses } from "./message-schedules.service-Be9t4LDg.js";
2
+ //#region app/server/message-schedule-monitor.server.ts
3
+ var pollMs = 3e4;
4
+ var started = false;
5
+ var processing = false;
6
+ var timer = null;
7
+ function startMessageScheduleMonitor() {
8
+ if (started) {
9
+ tick();
10
+ return;
11
+ }
12
+ started = true;
13
+ tick();
14
+ timer = setInterval(() => {
15
+ tick();
16
+ }, pollMs);
17
+ timer.unref?.();
18
+ }
19
+ async function tick() {
20
+ if (processing) return;
21
+ processing = true;
22
+ try {
23
+ await processDueMessageSchedules();
24
+ await syncMessageScheduleRunStatuses();
25
+ } catch (error) {
26
+ console.error("Message schedule monitor failed.", error);
27
+ } finally {
28
+ processing = false;
29
+ }
30
+ }
31
+ //#endregion
32
+ export { startMessageScheduleMonitor };
@@ -0,0 +1,407 @@
1
+ import { i as publishProviderEvent, l as HttpError } from "./socket.server-7dC-9Fnw.js";
2
+ import { H as ensureDatabase, O as requireConnectedAccount, U as prisma, a as executeMessage, r as createChat } from "./chats.service-H6m23Fna.js";
3
+ import { randomUUID } from "node:crypto";
4
+ //#region app/server/message-schedules.service.ts
5
+ var terminalRunStatuses = new Set([
6
+ "COMPLETED",
7
+ "FAILED",
8
+ "CANCELLED"
9
+ ]);
10
+ var schedulePageLimit = 500;
11
+ var dueScheduleLimit = 20;
12
+ async function listMessageSchedules(workingDirectory) {
13
+ await ensureDatabase();
14
+ const path = workingDirectory?.trim();
15
+ return (path ? await prisma.$queryRawUnsafe(`SELECT * FROM "MessageSchedule" WHERE "status" <> 'ARCHIVED' AND "workingDirectory" = ? ORDER BY "nextRunAt" IS NULL, "nextRunAt" ASC, "updatedAt" DESC LIMIT ?`, path, schedulePageLimit) : await prisma.$queryRawUnsafe(`SELECT * FROM "MessageSchedule" WHERE "status" <> 'ARCHIVED' ORDER BY "nextRunAt" IS NULL, "nextRunAt" ASC, "updatedAt" DESC LIMIT ?`, schedulePageLimit)).map(serializeSchedule);
16
+ }
17
+ async function getMessageSchedule(scheduleId) {
18
+ const row = await readScheduleRow(scheduleId);
19
+ if (row.status === "ARCHIVED") throw new HttpError(404, "Schedule not found.");
20
+ return serializeSchedule(row);
21
+ }
22
+ async function createMessageSchedule(dto) {
23
+ await ensureDatabase();
24
+ const message = requiredTrimmed(dto.message, "message", 2e4);
25
+ const title = normalizeTitle(dto.title) ?? titleFromMessage(message);
26
+ const workingDirectory = requiredTrimmed(dto.workingDirectory, "workingDirectory", 2e3);
27
+ const firstRunAt = readDate(dto.firstRunAt, "firstRunAt");
28
+ const account = await requireConnectedAccount(dto.accountId);
29
+ const recurrence = normalizeRecurrence(dto.recurrence, firstRunAt);
30
+ const status = dto.status === "PAUSED" ? "PAUSED" : "ACTIVE";
31
+ const chat = await createChat({
32
+ accountId: account.id,
33
+ collaborationMode: dto.collaborationMode ?? "default",
34
+ model: nullableString(dto.model),
35
+ permissionMode: dto.permissionMode ?? "default",
36
+ providerId: account.providerId,
37
+ reasoningEffort: nullableString(dto.reasoningEffort),
38
+ serviceTier: nullableString(dto.serviceTier),
39
+ title: `Schedule: ${title}`,
40
+ workingDirectory
41
+ });
42
+ const id = randomUUID();
43
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
44
+ await prisma.$executeRawUnsafe(`INSERT INTO "MessageSchedule" (
45
+ "id", "title", "message", "workingDirectory", "chatId", "providerId", "accountId",
46
+ "model", "reasoningEffort", "serviceTier", "collaborationMode", "permissionMode", "goalObjective",
47
+ "status", "recurrence", "nextRunAt", "createdAt", "updatedAt"
48
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, id, title, message, workingDirectory, chat.id, account.providerId, account.id, nullableString(dto.model), nullableString(dto.reasoningEffort), nullableString(dto.serviceTier), dto.collaborationMode ?? "default", dto.permissionMode ?? "default", nullableString(dto.goalObjective), status, JSON.stringify(recurrence), firstRunAt.toISOString(), timestamp, timestamp);
49
+ const schedule = await getMessageSchedule(id);
50
+ publishScheduleUpdated(schedule);
51
+ return schedule;
52
+ }
53
+ async function updateMessageSchedule(scheduleId, dto) {
54
+ const current = await readScheduleRow(scheduleId);
55
+ if (current.status === "ARCHIVED") throw new HttpError(404, "Schedule not found.");
56
+ const account = dto.accountId === void 0 || dto.accountId === current.accountId ? null : dto.accountId ? await requireConnectedAccount(dto.accountId) : null;
57
+ if (account && account.providerId !== current.providerId) throw new HttpError(400, "Switching provider types for an existing schedule is not supported.");
58
+ const firstRunAt = dto.firstRunAt === void 0 ? null : dto.firstRunAt === null ? null : readDate(dto.firstRunAt, "firstRunAt");
59
+ const anchorDate = firstRunAt ?? readNullableDate(current.nextRunAt) ?? readDateValue(current.createdAt);
60
+ const recurrence = dto.recurrence === void 0 ? readRecurrence(current.recurrence, anchorDate) : normalizeRecurrence(dto.recurrence, anchorDate);
61
+ const nextStatus = readScheduleStatus(dto.status) ?? readScheduleStatus(current.status) ?? "ACTIVE";
62
+ const nextRunAt = nextStatus === "ARCHIVED" || nextStatus === "COMPLETED" ? null : firstRunAt === null ? readNullableDate(current.nextRunAt) : firstRunAt;
63
+ await prisma.$executeRawUnsafe(`UPDATE "MessageSchedule" SET
64
+ "title" = ?,
65
+ "message" = ?,
66
+ "accountId" = ?,
67
+ "model" = ?,
68
+ "reasoningEffort" = ?,
69
+ "serviceTier" = ?,
70
+ "collaborationMode" = ?,
71
+ "permissionMode" = ?,
72
+ "goalObjective" = ?,
73
+ "status" = ?,
74
+ "recurrence" = ?,
75
+ "nextRunAt" = ?,
76
+ "updatedAt" = ?
77
+ WHERE "id" = ?`, dto.title === void 0 ? current.title : normalizeTitle(dto.title) ?? current.title, dto.message === void 0 ? current.message : requiredTrimmed(dto.message, "message", 2e4), dto.accountId === void 0 ? current.accountId : account?.id ?? null, dto.model === void 0 ? current.model : nullableString(dto.model), dto.reasoningEffort === void 0 ? current.reasoningEffort : nullableString(dto.reasoningEffort), dto.serviceTier === void 0 ? current.serviceTier : nullableString(dto.serviceTier), dto.collaborationMode === void 0 ? current.collaborationMode : dto.collaborationMode ?? "default", dto.permissionMode === void 0 ? current.permissionMode : dto.permissionMode ?? "default", dto.goalObjective === void 0 ? current.goalObjective : nullableString(dto.goalObjective), nextStatus, JSON.stringify(recurrence), nextRunAt?.toISOString() ?? null, (/* @__PURE__ */ new Date()).toISOString(), scheduleId);
78
+ const schedule = await getMessageSchedule(scheduleId);
79
+ publishScheduleUpdated(schedule);
80
+ return schedule;
81
+ }
82
+ async function archiveMessageSchedule(scheduleId) {
83
+ await readScheduleRow(scheduleId);
84
+ await prisma.$executeRawUnsafe(`UPDATE "MessageSchedule" SET "status" = 'ARCHIVED', "nextRunAt" = NULL, "updatedAt" = ? WHERE "id" = ?`, (/* @__PURE__ */ new Date()).toISOString(), scheduleId);
85
+ const schedule = serializeSchedule(await readScheduleRow(scheduleId));
86
+ publishScheduleUpdated(schedule);
87
+ return schedule;
88
+ }
89
+ async function listMessageScheduleRuns(scheduleId) {
90
+ await readScheduleRow(scheduleId);
91
+ return (await prisma.$queryRawUnsafe(`SELECT * FROM "MessageScheduleRun" WHERE "scheduleId" = ? ORDER BY "scheduledFor" DESC, "createdAt" DESC LIMIT 100`, scheduleId)).map(serializeScheduleRun);
92
+ }
93
+ async function processDueMessageSchedules(now = /* @__PURE__ */ new Date()) {
94
+ await ensureDatabase();
95
+ await syncMessageScheduleRunStatuses();
96
+ const rows = await prisma.$queryRawUnsafe(`SELECT * FROM "MessageSchedule"
97
+ WHERE "status" = 'ACTIVE' AND "nextRunAt" IS NOT NULL AND datetime("nextRunAt") <= datetime(?)
98
+ ORDER BY "nextRunAt" ASC LIMIT ?`, now.toISOString(), dueScheduleLimit);
99
+ for (const row of rows) await executeDueSchedule(row, now);
100
+ await syncMessageScheduleRunStatuses();
101
+ }
102
+ async function syncMessageScheduleRunStatuses() {
103
+ await ensureDatabase();
104
+ const rows = await prisma.$queryRawUnsafe(`SELECT
105
+ "MessageScheduleRun".*,
106
+ "ChatRun"."status" AS "chatRunStatus",
107
+ "ChatRun"."error" AS "chatRunError",
108
+ "ChatRun"."endedAt" AS "chatRunEndedAt"
109
+ FROM "MessageScheduleRun"
110
+ LEFT JOIN "ChatRun" ON "MessageScheduleRun"."chatRunId" = "ChatRun"."id"
111
+ WHERE "MessageScheduleRun"."chatRunId" IS NOT NULL
112
+ AND "MessageScheduleRun"."status" IN ('QUEUED', 'RUNNING')
113
+ LIMIT 100`);
114
+ for (const row of rows) {
115
+ const chatRunStatus = readRunStatus(row.chatRunStatus);
116
+ if (!chatRunStatus || chatRunStatus === row.status) continue;
117
+ const endedAt = terminalRunStatuses.has(chatRunStatus) ? readNullableDate(row.chatRunEndedAt) ?? /* @__PURE__ */ new Date() : null;
118
+ await prisma.$executeRawUnsafe(`UPDATE "MessageScheduleRun" SET "status" = ?, "error" = ?, "endedAt" = COALESCE(?, "endedAt"), "updatedAt" = ? WHERE "id" = ?`, chatRunStatus, chatRunStatus === "FAILED" ? row.chatRunError ?? "Scheduled chat run failed." : row.error, endedAt?.toISOString() ?? null, (/* @__PURE__ */ new Date()).toISOString(), row.id);
119
+ const run = serializeScheduleRun({
120
+ ...row,
121
+ endedAt: endedAt ?? row.endedAt,
122
+ error: row.chatRunError ?? row.error,
123
+ status: chatRunStatus
124
+ });
125
+ await refreshScheduleLastRunStatus(row.scheduleId, run);
126
+ publishScheduleRunUpdated(run);
127
+ }
128
+ }
129
+ async function executeDueSchedule(row, now) {
130
+ const schedule = serializeSchedule(row);
131
+ const nextRunAt = readNullableDate(row.nextRunAt);
132
+ if (!nextRunAt) return;
133
+ const scheduledFor = latestDueOccurrence(schedule.recurrence, nextRunAt, now);
134
+ const runId = randomUUID();
135
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
136
+ await prisma.$executeRawUnsafe(`INSERT INTO "MessageScheduleRun" (
137
+ "id", "scheduleId", "chatId", "scheduledFor", "status", "createdAt", "updatedAt"
138
+ ) VALUES (?, ?, ?, ?, 'QUEUED', ?, ?)`, runId, row.id, row.chatId, scheduledFor.toISOString(), timestamp, timestamp);
139
+ publishScheduleRunUpdated(await getScheduleRun(runId));
140
+ const runCountAfter = await countScheduleRuns(row.id);
141
+ const advance = advanceSchedule(schedule.recurrence, scheduledFor, now, runCountAfter);
142
+ try {
143
+ const chatId = await ensureScheduleChat(row);
144
+ const result = await executeMessage(chatId, {
145
+ accountId: row.accountId ?? void 0,
146
+ collaborationMode: row.collaborationMode,
147
+ content: row.message,
148
+ goalObjective: row.goalObjective,
149
+ metadata: {
150
+ model: row.model,
151
+ reasoningEffort: row.reasoningEffort,
152
+ scheduleId: row.id,
153
+ scheduleRunId: runId,
154
+ serviceTier: row.serviceTier
155
+ },
156
+ permissionMode: row.permissionMode
157
+ });
158
+ const nextStatus = readRunStatus(result.status) ?? "QUEUED";
159
+ await prisma.$executeRawUnsafe(`UPDATE "MessageScheduleRun" SET
160
+ "chatId" = ?,
161
+ "chatRunId" = ?,
162
+ "startedAt" = ?,
163
+ "status" = ?,
164
+ "updatedAt" = ?
165
+ WHERE "id" = ?`, chatId, result.runId ?? null, (/* @__PURE__ */ new Date()).toISOString(), nextStatus, (/* @__PURE__ */ new Date()).toISOString(), runId);
166
+ await updateScheduleAfterAttempt(row.id, scheduledFor, nextStatus, advance);
167
+ } catch (error) {
168
+ const message = error instanceof Error ? error.message : "Scheduled message failed.";
169
+ const failedAt = (/* @__PURE__ */ new Date()).toISOString();
170
+ await prisma.$executeRawUnsafe(`UPDATE "MessageScheduleRun" SET "status" = 'FAILED', "error" = ?, "startedAt" = COALESCE("startedAt", ?), "endedAt" = ?, "updatedAt" = ? WHERE "id" = ?`, message, failedAt, failedAt, failedAt, runId);
171
+ await updateScheduleAfterAttempt(row.id, scheduledFor, "FAILED", advance);
172
+ }
173
+ publishScheduleRunUpdated(await getScheduleRun(runId));
174
+ publishScheduleUpdated(await getMessageSchedule(row.id).catch(() => serializeSchedule({
175
+ ...row,
176
+ status: advance.status,
177
+ nextRunAt: advance.nextRunAt
178
+ })));
179
+ }
180
+ async function ensureScheduleChat(row) {
181
+ if (row.chatId) {
182
+ const existing = await prisma.chat.findUnique({ where: { id: row.chatId } });
183
+ if (existing && existing.status !== "ARCHIVED") return existing.id;
184
+ }
185
+ if (!row.accountId) throw new HttpError(400, "Schedule has no provider account.");
186
+ const account = await requireConnectedAccount(row.accountId);
187
+ if (account.providerId !== row.providerId) throw new HttpError(400, "Schedule account provider no longer matches the schedule.");
188
+ const chat = await createChat({
189
+ accountId: account.id,
190
+ collaborationMode: row.collaborationMode,
191
+ model: row.model,
192
+ permissionMode: row.permissionMode,
193
+ providerId: row.providerId,
194
+ reasoningEffort: row.reasoningEffort,
195
+ serviceTier: row.serviceTier,
196
+ title: `Schedule: ${row.title}`,
197
+ workingDirectory: row.workingDirectory
198
+ });
199
+ await prisma.$executeRawUnsafe(`UPDATE "MessageSchedule" SET "chatId" = ?, "updatedAt" = ? WHERE "id" = ?`, chat.id, (/* @__PURE__ */ new Date()).toISOString(), row.id);
200
+ row.chatId = chat.id;
201
+ return chat.id;
202
+ }
203
+ async function updateScheduleAfterAttempt(scheduleId, scheduledFor, lastRunStatus, advance) {
204
+ await prisma.$executeRawUnsafe(`UPDATE "MessageSchedule" SET
205
+ "lastRunAt" = ?,
206
+ "lastRunStatus" = ?,
207
+ "nextRunAt" = ?,
208
+ "status" = ?,
209
+ "updatedAt" = ?
210
+ WHERE "id" = ?`, scheduledFor.toISOString(), lastRunStatus, advance.nextRunAt?.toISOString() ?? null, advance.status, (/* @__PURE__ */ new Date()).toISOString(), scheduleId);
211
+ }
212
+ async function refreshScheduleLastRunStatus(scheduleId, run) {
213
+ await prisma.$executeRawUnsafe(`UPDATE "MessageSchedule" SET "lastRunStatus" = ?, "updatedAt" = ? WHERE "id" = ? AND "lastRunAt" = ?`, run.status, (/* @__PURE__ */ new Date()).toISOString(), scheduleId, run.scheduledFor);
214
+ const schedule = await getMessageSchedule(scheduleId).catch(() => null);
215
+ if (schedule) publishScheduleUpdated(schedule);
216
+ }
217
+ async function readScheduleRow(scheduleId) {
218
+ await ensureDatabase();
219
+ const row = (await prisma.$queryRawUnsafe(`SELECT * FROM "MessageSchedule" WHERE "id" = ? LIMIT 1`, scheduleId))[0];
220
+ if (!row) throw new HttpError(404, "Schedule not found.");
221
+ return row;
222
+ }
223
+ async function getScheduleRun(runId) {
224
+ const row = (await prisma.$queryRawUnsafe(`SELECT * FROM "MessageScheduleRun" WHERE "id" = ? LIMIT 1`, runId))[0];
225
+ if (!row) throw new HttpError(404, "Schedule run not found.");
226
+ return serializeScheduleRun(row);
227
+ }
228
+ async function countScheduleRuns(scheduleId) {
229
+ const count = (await prisma.$queryRawUnsafe(`SELECT COUNT(*) AS "count" FROM "MessageScheduleRun" WHERE "scheduleId" = ? AND "status" <> 'CANCELLED'`, scheduleId))[0]?.count ?? 0;
230
+ return typeof count === "bigint" ? Number(count) : count;
231
+ }
232
+ function advanceSchedule(recurrence, scheduledFor, now, runCountAfter) {
233
+ if (recurrence.frequency === "none") return {
234
+ nextRunAt: null,
235
+ status: "COMPLETED"
236
+ };
237
+ if (recurrence.maxRuns && runCountAfter >= recurrence.maxRuns) return {
238
+ nextRunAt: null,
239
+ status: "COMPLETED"
240
+ };
241
+ let nextRunAt = addRecurrence(scheduledFor, recurrence);
242
+ let guard = 0;
243
+ while (nextRunAt.getTime() <= now.getTime() && guard < 1e4) {
244
+ nextRunAt = addRecurrence(nextRunAt, recurrence);
245
+ guard += 1;
246
+ }
247
+ const endAt = recurrence.endAt ? readDate(recurrence.endAt, "recurrence.endAt") : null;
248
+ if (endAt && nextRunAt.getTime() > endAt.getTime()) return {
249
+ nextRunAt: null,
250
+ status: "COMPLETED"
251
+ };
252
+ return {
253
+ nextRunAt,
254
+ status: "ACTIVE"
255
+ };
256
+ }
257
+ function latestDueOccurrence(recurrence, firstDue, now) {
258
+ if (recurrence.frequency === "none") return firstDue;
259
+ let latest = firstDue;
260
+ let next = addRecurrence(latest, recurrence);
261
+ let guard = 0;
262
+ while (next.getTime() <= now.getTime() && guard < 1e4) {
263
+ latest = next;
264
+ next = addRecurrence(latest, recurrence);
265
+ guard += 1;
266
+ }
267
+ return latest;
268
+ }
269
+ function addRecurrence(date, recurrence) {
270
+ const interval = Math.max(1, Math.floor(recurrence.interval || 1));
271
+ if (recurrence.frequency === "daily") {
272
+ const next = new Date(date);
273
+ next.setUTCDate(next.getUTCDate() + interval);
274
+ return next;
275
+ }
276
+ if (recurrence.frequency === "weekly") {
277
+ const next = new Date(date);
278
+ next.setUTCDate(next.getUTCDate() + interval * 7);
279
+ return next;
280
+ }
281
+ if (recurrence.frequency === "monthly") return addMonthsClamped(date, interval, recurrence.anchorDay ?? date.getUTCDate());
282
+ return new Date(date);
283
+ }
284
+ function addMonthsClamped(date, months, anchorDay) {
285
+ const targetMonth = date.getUTCMonth() + months;
286
+ const target = new Date(Date.UTC(date.getUTCFullYear(), targetMonth, 1, date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds()));
287
+ const lastDay = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0)).getUTCDate();
288
+ target.setUTCDate(Math.min(anchorDay, lastDay));
289
+ return target;
290
+ }
291
+ function normalizeRecurrence(value, firstRunAt) {
292
+ const frequency = value?.frequency === "daily" || value?.frequency === "weekly" || value?.frequency === "monthly" ? value.frequency : "none";
293
+ const interval = typeof value?.interval === "number" && Number.isFinite(value.interval) ? Math.min(365, Math.max(1, Math.floor(value.interval))) : 1;
294
+ const maxRuns = typeof value?.maxRuns === "number" && Number.isFinite(value.maxRuns) ? Math.min(1e4, Math.max(1, Math.floor(value.maxRuns))) : null;
295
+ const endAt = typeof value?.endAt === "string" && value.endAt.trim() ? readDate(value.endAt, "recurrence.endAt").toISOString() : null;
296
+ const anchorDay = typeof value?.anchorDay === "number" && Number.isFinite(value.anchorDay) ? Math.min(31, Math.max(1, Math.floor(value.anchorDay))) : firstRunAt.getUTCDate();
297
+ return {
298
+ anchorDay: frequency === "monthly" ? anchorDay : null,
299
+ endAt,
300
+ frequency,
301
+ interval,
302
+ maxRuns
303
+ };
304
+ }
305
+ function readRecurrence(value, anchorDate) {
306
+ if (!value) return normalizeRecurrence(void 0, anchorDate);
307
+ const record = typeof value === "string" ? parseJsonObject(value) : value;
308
+ if (!record || typeof record !== "object" || Array.isArray(record)) return normalizeRecurrence(void 0, anchorDate);
309
+ return normalizeRecurrence(record, anchorDate);
310
+ }
311
+ function parseJsonObject(value) {
312
+ try {
313
+ return JSON.parse(value);
314
+ } catch {
315
+ return null;
316
+ }
317
+ }
318
+ function serializeSchedule(row) {
319
+ const anchor = readNullableDate(row.nextRunAt) ?? readDateValue(row.createdAt);
320
+ return {
321
+ accountId: row.accountId,
322
+ chatId: row.chatId,
323
+ collaborationMode: row.collaborationMode,
324
+ createdAt: readDateValue(row.createdAt).toISOString(),
325
+ goalObjective: row.goalObjective,
326
+ id: row.id,
327
+ lastRunAt: readNullableDate(row.lastRunAt)?.toISOString() ?? null,
328
+ lastRunStatus: readRunStatus(row.lastRunStatus),
329
+ message: row.message,
330
+ model: row.model,
331
+ nextRunAt: readNullableDate(row.nextRunAt)?.toISOString() ?? null,
332
+ permissionMode: row.permissionMode,
333
+ providerId: row.providerId,
334
+ reasoningEffort: row.reasoningEffort,
335
+ recurrence: readRecurrence(row.recurrence, anchor),
336
+ serviceTier: row.serviceTier,
337
+ status: readScheduleStatus(row.status) ?? "ACTIVE",
338
+ title: row.title,
339
+ updatedAt: readDateValue(row.updatedAt).toISOString(),
340
+ workingDirectory: row.workingDirectory
341
+ };
342
+ }
343
+ function serializeScheduleRun(row) {
344
+ return {
345
+ chatId: row.chatId,
346
+ chatRunId: row.chatRunId,
347
+ createdAt: readDateValue(row.createdAt).toISOString(),
348
+ endedAt: readNullableDate(row.endedAt)?.toISOString() ?? null,
349
+ error: row.error,
350
+ id: row.id,
351
+ scheduleId: row.scheduleId,
352
+ scheduledFor: readDateValue(row.scheduledFor).toISOString(),
353
+ startedAt: readNullableDate(row.startedAt)?.toISOString() ?? null,
354
+ status: readRunStatus(row.status) ?? "QUEUED",
355
+ updatedAt: readDateValue(row.updatedAt).toISOString()
356
+ };
357
+ }
358
+ function publishScheduleUpdated(schedule) {
359
+ publishProviderEvent({
360
+ type: "schedule.updated",
361
+ payload: schedule
362
+ });
363
+ }
364
+ function publishScheduleRunUpdated(run) {
365
+ publishProviderEvent({
366
+ type: "schedule.run.updated",
367
+ payload: run
368
+ });
369
+ }
370
+ function readDate(value, field) {
371
+ const date = new Date(value);
372
+ if (!Number.isFinite(date.getTime())) throw new HttpError(400, `${field} must be a valid date.`);
373
+ return date;
374
+ }
375
+ function readNullableDate(value) {
376
+ if (value === null) return null;
377
+ return readDateValue(value);
378
+ }
379
+ function readDateValue(value) {
380
+ const date = value instanceof Date ? value : new Date(value);
381
+ return Number.isFinite(date.getTime()) ? date : /* @__PURE__ */ new Date();
382
+ }
383
+ function readScheduleStatus(value) {
384
+ return value === "ACTIVE" || value === "PAUSED" || value === "COMPLETED" || value === "ARCHIVED" ? value : null;
385
+ }
386
+ function readRunStatus(value) {
387
+ return value === "QUEUED" || value === "RUNNING" || value === "COMPLETED" || value === "FAILED" || value === "CANCELLED" ? value : null;
388
+ }
389
+ function nullableString(value) {
390
+ return typeof value === "string" && value.trim() ? value.trim() : null;
391
+ }
392
+ function requiredTrimmed(value, field, maxLength) {
393
+ if (typeof value !== "string" || !value.trim()) throw new HttpError(400, `${field} is required.`);
394
+ const trimmed = value.trim();
395
+ if (trimmed.length > maxLength) throw new HttpError(400, `${field} must be ${maxLength} characters or fewer.`);
396
+ return trimmed;
397
+ }
398
+ function normalizeTitle(value) {
399
+ if (typeof value !== "string") return null;
400
+ const title = value.trim().replace(/\s+/gu, " ");
401
+ return title ? title.slice(0, 160) : null;
402
+ }
403
+ function titleFromMessage(value) {
404
+ return value.trim().replace(/\s+/gu, " ").slice(0, 80) || "Scheduled message";
405
+ }
406
+ //#endregion
407
+ export { listMessageSchedules as a, updateMessageSchedule as c, listMessageScheduleRuns as i, createMessageSchedule as n, processDueMessageSchedules as o, getMessageSchedule as r, syncMessageScheduleRunStatuses as s, archiveMessageSchedule as t };
@@ -0,0 +1,32 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { homedir } from "node:os";
4
+ //#region app/server/runtime-paths.server.ts
5
+ function resolveHomePath(inputPath) {
6
+ if (inputPath === "~") return homedir();
7
+ if (inputPath.startsWith("~/")) return join(homedir(), inputPath.slice(2));
8
+ return resolve(inputPath);
9
+ }
10
+ function resolvePockcodeHome() {
11
+ return resolveHomePath(process.env.POCKCODE_HOME?.trim() || "~/.pockcode");
12
+ }
13
+ function resolvePockcodeDatabasePath() {
14
+ return join(resolvePockcodeHome(), "pockcode.db");
15
+ }
16
+ function resolvePockcodeAuthPath() {
17
+ return join(resolvePockcodeHome(), "auth.json");
18
+ }
19
+ function sqliteDatabaseUrl(databasePath = resolvePockcodeDatabasePath()) {
20
+ return `file:${databasePath}?connection_limit=1&pool_timeout=30`;
21
+ }
22
+ function ensureParentDirectory(filePath) {
23
+ mkdirSync(dirname(filePath), {
24
+ recursive: true,
25
+ mode: 448
26
+ });
27
+ }
28
+ function resolveProviderDataHome(providerId) {
29
+ return join(resolvePockcodeHome(), "providers", providerId);
30
+ }
31
+ //#endregion
32
+ export { resolveProviderDataHome as a, resolvePockcodeDatabasePath as i, resolveHomePath as n, sqliteDatabaseUrl as o, resolvePockcodeAuthPath as r, ensureParentDirectory as t };