Repository navigation
Expand file tree
/
Copy pathtypes.ts
More file actions
100 lines (95 loc) · 3.64 KB
/
Copy pathtypes.ts
File metadata and controls
100 lines (95 loc) · 3.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
export type Reminder = {
id: string
sessionID: string
projectID: string
type: "one-time" | "recurring"
interval: number
originalPrompt: string
userDescription: string
agent?: string
execution?: "action" | "monitor"
pauseReason?: string
/**
* Historical failure count retained when reading older snapshots.
*/
consecutiveFailures?: number
time: {
created: number
nextExecution: number
lastExecution?: number
}
status: "active" | "paused" | "cancelled"
}
export type State = {
reminders: Map<string, Reminder>
timers: Map<string, NodeJS.Timeout>
projectID: string
generation?: string
}
export type PluginConfig = {
enabled: boolean
max_reminders_per_project: number
min_interval_seconds: number
}
type ParseResult = { success: true; data: Reminder } | { success: false; error: string }
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null
const isString = (value: unknown): value is string => typeof value === "string"
const isNumber = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value)
const optionalString = (value: unknown) => value === undefined || isString(value)
const optionalCount = (value: unknown) =>
value === undefined || (typeof value === "number" && Number.isSafeInteger(value) && value >= 0)
const oneOf = <T extends string>(value: unknown, options: readonly T[]): value is T =>
isString(value) && (options as readonly string[]).includes(value)
// Hand-written validation replaces Zod so the plugin has no runtime dependencies.
export const ReminderSchema = {
safeParse(value: unknown): ParseResult {
if (!isRecord(value)) return { success: false, error: "reminder must be an object" }
const time = value.time
if (
!isString(value.id) ||
!isString(value.sessionID) ||
!isString(value.projectID) ||
!oneOf(value.type, ["one-time", "recurring"]) ||
!isNumber(value.interval) ||
!isString(value.originalPrompt) ||
!isString(value.userDescription) ||
!optionalString(value.agent) ||
!(value.execution === undefined || oneOf(value.execution, ["action", "monitor"])) ||
!optionalString(value.pauseReason) ||
!optionalCount(value.consecutiveFailures) ||
!oneOf(value.status, ["active", "paused", "cancelled"]) ||
!isRecord(time) ||
!isNumber(time.created) ||
!isNumber(time.nextExecution) ||
!(time.lastExecution === undefined || isNumber(time.lastExecution))
)
return { success: false, error: "reminder has an invalid shape" }
const data: Reminder = {
id: value.id,
sessionID: value.sessionID,
projectID: value.projectID,
type: value.type,
interval: value.interval,
originalPrompt: value.originalPrompt,
userDescription: value.userDescription,
...(value.agent === undefined ? {} : { agent: value.agent }),
...(value.execution === undefined ? {} : { execution: value.execution }),
...(value.pauseReason === undefined ? {} : { pauseReason: value.pauseReason }),
...(value.consecutiveFailures === undefined || value.consecutiveFailures === 0
? {}
: { consecutiveFailures: value.consecutiveFailures as number }),
time: {
created: time.created,
nextExecution: time.nextExecution,
...(time.lastExecution === undefined ? {} : { lastExecution: time.lastExecution }),
},
status: value.status,
}
return { success: true, data }
},
parse(value: unknown): Reminder {
const result = ReminderSchema.safeParse(value)
if (!result.success) throw new Error(result.error)
return result.data
},
}