@clinebot/core 0.0.36 → 0.0.37

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.
Files changed (228) hide show
  1. package/dist/ClineCore.d.ts +312 -3
  2. package/dist/ClineCore.d.ts.map +1 -1
  3. package/dist/account/cline-account-service.d.ts.map +1 -1
  4. package/dist/cron/cron-event-ingress.d.ts +38 -0
  5. package/dist/cron/cron-event-ingress.d.ts.map +1 -0
  6. package/dist/cron/cron-materializer.d.ts +36 -0
  7. package/dist/cron/cron-materializer.d.ts.map +1 -0
  8. package/dist/cron/cron-reconciler.d.ts +62 -0
  9. package/dist/cron/cron-reconciler.d.ts.map +1 -0
  10. package/dist/cron/cron-report-writer.d.ts +41 -0
  11. package/dist/cron/cron-report-writer.d.ts.map +1 -0
  12. package/dist/cron/cron-runner.d.ts +43 -0
  13. package/dist/cron/cron-runner.d.ts.map +1 -0
  14. package/dist/cron/cron-schema.d.ts +3 -0
  15. package/dist/cron/cron-schema.d.ts.map +1 -0
  16. package/dist/cron/cron-service.d.ts +57 -0
  17. package/dist/cron/cron-service.d.ts.map +1 -0
  18. package/dist/cron/cron-spec-parser.d.ts +27 -0
  19. package/dist/cron/cron-spec-parser.d.ts.map +1 -0
  20. package/dist/cron/cron-watcher.d.ts +23 -0
  21. package/dist/cron/cron-watcher.d.ts.map +1 -0
  22. package/dist/cron/scheduler.d.ts +3 -1
  23. package/dist/cron/scheduler.d.ts.map +1 -1
  24. package/dist/cron/sqlite-cron-store.d.ts +230 -0
  25. package/dist/cron/sqlite-cron-store.d.ts.map +1 -0
  26. package/dist/extensions/plugin/plugin-config-loader.d.ts +7 -1
  27. package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
  28. package/dist/extensions/plugin/plugin-loader.d.ts +10 -6
  29. package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
  30. package/dist/extensions/plugin/plugin-sandbox.d.ts +7 -1
  31. package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
  32. package/dist/extensions/plugin-sandbox-bootstrap.js +236 -275
  33. package/dist/extensions/tools/constants.d.ts +1 -0
  34. package/dist/extensions/tools/constants.d.ts.map +1 -1
  35. package/dist/extensions/tools/definitions.d.ts +2 -3
  36. package/dist/extensions/tools/definitions.d.ts.map +1 -1
  37. package/dist/extensions/tools/executors/editor.d.ts.map +1 -1
  38. package/dist/extensions/tools/helpers.d.ts +1 -0
  39. package/dist/extensions/tools/helpers.d.ts.map +1 -1
  40. package/dist/extensions/tools/index.d.ts +1 -2
  41. package/dist/extensions/tools/index.d.ts.map +1 -1
  42. package/dist/extensions/tools/presets.d.ts +1 -1
  43. package/dist/extensions/tools/schemas.d.ts +25 -3
  44. package/dist/extensions/tools/schemas.d.ts.map +1 -1
  45. package/dist/extensions/tools/team/delegated-agent.d.ts +2 -2
  46. package/dist/extensions/tools/team/delegated-agent.d.ts.map +1 -1
  47. package/dist/extensions/tools/team/multi-agent.d.ts +7 -3
  48. package/dist/extensions/tools/team/multi-agent.d.ts.map +1 -1
  49. package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
  50. package/dist/extensions/tools/types.d.ts +0 -5
  51. package/dist/extensions/tools/types.d.ts.map +1 -1
  52. package/dist/hooks/hook-bridge.d.ts +118 -0
  53. package/dist/hooks/hook-bridge.d.ts.map +1 -0
  54. package/dist/hooks/hook-file-hooks.d.ts +2 -1
  55. package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
  56. package/dist/hooks/hook-registry.d.ts +16 -0
  57. package/dist/hooks/hook-registry.d.ts.map +1 -0
  58. package/dist/hub/browser-websocket.d.ts.map +1 -1
  59. package/dist/hub/client.d.ts +7 -1
  60. package/dist/hub/client.d.ts.map +1 -1
  61. package/dist/hub/daemon-entry.js +721 -461
  62. package/dist/hub/daemon.d.ts.map +1 -1
  63. package/dist/hub/defaults.d.ts +8 -4
  64. package/dist/hub/defaults.d.ts.map +1 -1
  65. package/dist/hub/index.js +665 -415
  66. package/dist/hub/runtime-handlers.d.ts.map +1 -1
  67. package/dist/hub/server.d.ts +18 -0
  68. package/dist/hub/server.d.ts.map +1 -1
  69. package/dist/hub/session-client.d.ts +3 -0
  70. package/dist/hub/session-client.d.ts.map +1 -1
  71. package/dist/hub/start-shared-server.d.ts.map +1 -1
  72. package/dist/hub/ui-client.d.ts +1 -0
  73. package/dist/hub/ui-client.d.ts.map +1 -1
  74. package/dist/index.d.ts +9 -7
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +756 -467
  77. package/dist/llms/cline-recommended-models.d.ts +20 -0
  78. package/dist/llms/cline-recommended-models.d.ts.map +1 -0
  79. package/dist/llms/handler-factory.d.ts +16 -0
  80. package/dist/llms/handler-factory.d.ts.map +1 -0
  81. package/dist/llms/provider-defaults.d.ts.map +1 -1
  82. package/dist/llms/provider-settings.d.ts +45 -2
  83. package/dist/llms/provider-settings.d.ts.map +1 -1
  84. package/dist/llms/runtime-registry.d.ts.map +1 -1
  85. package/dist/runtime/agent-config-adapter.d.ts +148 -0
  86. package/dist/runtime/agent-config-adapter.d.ts.map +1 -0
  87. package/dist/runtime/agent-runtime-config-builder.d.ts +96 -0
  88. package/dist/runtime/agent-runtime-config-builder.d.ts.map +1 -0
  89. package/dist/runtime/history.d.ts +6 -0
  90. package/dist/runtime/history.d.ts.map +1 -1
  91. package/dist/runtime/host.d.ts.map +1 -1
  92. package/dist/runtime/loop-detection.d.ts +59 -0
  93. package/dist/runtime/loop-detection.d.ts.map +1 -0
  94. package/dist/runtime/mistake-tracker.d.ts +69 -0
  95. package/dist/runtime/mistake-tracker.d.ts.map +1 -0
  96. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  97. package/dist/runtime/runtime-event-adapter.d.ts +102 -0
  98. package/dist/runtime/runtime-event-adapter.d.ts.map +1 -0
  99. package/dist/runtime/runtime-host.d.ts +28 -3
  100. package/dist/runtime/runtime-host.d.ts.map +1 -1
  101. package/dist/runtime/session-runtime-orchestrator.d.ts +261 -0
  102. package/dist/runtime/session-runtime-orchestrator.d.ts.map +1 -0
  103. package/dist/runtime/session-runtime.d.ts +16 -3
  104. package/dist/runtime/session-runtime.d.ts.map +1 -1
  105. package/dist/runtime/user-input-builder.d.ts +24 -0
  106. package/dist/runtime/user-input-builder.d.ts.map +1 -0
  107. package/dist/services/index.js +28 -0
  108. package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
  109. package/dist/services/plugin-tools.d.ts.map +1 -1
  110. package/dist/services/providers/local-provider-registry.d.ts +197 -21
  111. package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
  112. package/dist/services/providers/local-provider-service.d.ts +3 -1
  113. package/dist/services/providers/local-provider-service.d.ts.map +1 -1
  114. package/dist/services/session-data.d.ts.map +1 -1
  115. package/dist/services/session-telemetry.d.ts +7 -2
  116. package/dist/services/session-telemetry.d.ts.map +1 -1
  117. package/dist/services/storage/file-team-store.d.ts.map +1 -1
  118. package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
  119. package/dist/services/storage/provider-settings-manager.d.ts +1 -0
  120. package/dist/services/storage/provider-settings-manager.d.ts.map +1 -1
  121. package/dist/services/storage/sqlite-team-store.d.ts.map +1 -1
  122. package/dist/session/conversation-store.d.ts +30 -0
  123. package/dist/session/conversation-store.d.ts.map +1 -0
  124. package/dist/session/message-builder.d.ts +65 -0
  125. package/dist/session/message-builder.d.ts.map +1 -0
  126. package/dist/session/session-manifest.d.ts +1 -1
  127. package/dist/transports/hub.d.ts +14 -3
  128. package/dist/transports/hub.d.ts.map +1 -1
  129. package/dist/transports/local.d.ts +14 -4
  130. package/dist/transports/local.d.ts.map +1 -1
  131. package/dist/transports/remote.d.ts.map +1 -1
  132. package/dist/types/chat-schema.d.ts +5 -5
  133. package/dist/types/config.d.ts +9 -0
  134. package/dist/types/config.d.ts.map +1 -1
  135. package/dist/types/events.d.ts +7 -6
  136. package/dist/types/events.d.ts.map +1 -1
  137. package/dist/types/provider-settings.d.ts +2 -2
  138. package/dist/types/provider-settings.d.ts.map +1 -1
  139. package/dist/types/session.d.ts +5 -2
  140. package/dist/types/session.d.ts.map +1 -1
  141. package/dist/types.d.ts +4 -4
  142. package/dist/types.d.ts.map +1 -1
  143. package/package.json +4 -4
  144. package/src/ClineCore.ts +691 -6
  145. package/src/account/cline-account-service.ts +44 -6
  146. package/src/cron/cron-event-ingress.ts +357 -0
  147. package/src/cron/cron-materializer.ts +97 -0
  148. package/src/cron/cron-reconciler.ts +241 -0
  149. package/src/cron/cron-report-writer.ts +153 -0
  150. package/src/cron/cron-runner.ts +495 -0
  151. package/src/cron/cron-schema.ts +127 -0
  152. package/src/cron/cron-service.ts +163 -0
  153. package/src/cron/cron-spec-parser.ts +489 -0
  154. package/src/cron/cron-watcher.ts +102 -0
  155. package/src/cron/index.ts +10 -0
  156. package/src/cron/scheduler.ts +141 -6
  157. package/src/cron/sqlite-cron-store.ts +1286 -0
  158. package/src/extensions/plugin/plugin-config-loader.ts +21 -1
  159. package/src/extensions/plugin/plugin-loader.ts +25 -9
  160. package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +151 -1
  161. package/src/extensions/plugin/plugin-sandbox.ts +131 -7
  162. package/src/extensions/tools/constants.ts +2 -0
  163. package/src/extensions/tools/definitions.ts +31 -22
  164. package/src/extensions/tools/executors/editor.ts +4 -3
  165. package/src/extensions/tools/helpers.ts +24 -0
  166. package/src/extensions/tools/index.ts +1 -2
  167. package/src/extensions/tools/presets.ts +1 -1
  168. package/src/extensions/tools/schemas.ts +13 -18
  169. package/src/extensions/tools/team/delegated-agent.ts +8 -3
  170. package/src/extensions/tools/team/multi-agent.ts +135 -19
  171. package/src/extensions/tools/team/team-tools.ts +151 -91
  172. package/src/extensions/tools/types.ts +0 -6
  173. package/src/hooks/hook-bridge.ts +489 -0
  174. package/src/hooks/hook-file-hooks.ts +58 -3
  175. package/src/hooks/hook-registry.ts +257 -0
  176. package/src/hub/browser-websocket.ts +26 -4
  177. package/src/hub/client.ts +72 -13
  178. package/src/hub/daemon-entry.ts +35 -0
  179. package/src/hub/daemon.ts +117 -14
  180. package/src/hub/defaults.ts +39 -12
  181. package/src/hub/runtime-handlers.ts +4 -3
  182. package/src/hub/server.ts +506 -77
  183. package/src/hub/session-client.ts +43 -1
  184. package/src/hub/start-shared-server.ts +3 -0
  185. package/src/hub/ui-client.ts +4 -0
  186. package/src/index.ts +46 -1
  187. package/src/llms/cline-recommended-models.ts +167 -0
  188. package/src/llms/handler-factory.ts +56 -0
  189. package/src/llms/provider-defaults.ts +17 -1
  190. package/src/llms/provider-settings.ts +48 -1
  191. package/src/llms/runtime-registry.ts +1 -0
  192. package/src/runtime/agent-config-adapter.ts +636 -0
  193. package/src/runtime/agent-runtime-config-builder.ts +205 -0
  194. package/src/runtime/error-feedback.ts +142 -0
  195. package/src/runtime/history.ts +137 -0
  196. package/src/runtime/host.ts +22 -0
  197. package/src/runtime/loop-detection.ts +162 -0
  198. package/src/runtime/mistake-tracker.ts +221 -0
  199. package/src/runtime/runtime-builder.ts +61 -5
  200. package/src/runtime/runtime-event-adapter.ts +412 -0
  201. package/src/runtime/runtime-host.ts +45 -1
  202. package/src/runtime/session-runtime-orchestrator.ts +1253 -0
  203. package/src/runtime/session-runtime.ts +16 -2
  204. package/src/runtime/user-input-builder.ts +167 -0
  205. package/src/services/local-runtime-bootstrap.ts +128 -22
  206. package/src/services/plugin-tools.ts +1 -0
  207. package/src/services/providers/local-provider-registry.ts +273 -57
  208. package/src/services/providers/local-provider-service.ts +67 -7
  209. package/src/services/session-data.ts +16 -14
  210. package/src/services/session-telemetry.ts +6 -15
  211. package/src/services/storage/file-team-store.ts +1 -5
  212. package/src/services/storage/provider-settings-legacy-migration.ts +8 -47
  213. package/src/services/storage/provider-settings-manager.ts +16 -1
  214. package/src/services/storage/sqlite-team-store.ts +1 -5
  215. package/src/session/conversation-store.ts +77 -0
  216. package/src/session/message-builder.ts +941 -0
  217. package/src/transports/hub.ts +458 -33
  218. package/src/transports/local.ts +296 -65
  219. package/src/transports/remote.ts +1 -0
  220. package/src/types/config.ts +9 -0
  221. package/src/types/events.ts +8 -6
  222. package/src/types/index.ts +3 -0
  223. package/src/types/provider-settings.ts +8 -1
  224. package/src/types/session.ts +5 -2
  225. package/src/types.ts +15 -1
  226. package/dist/cron/index.d.ts +0 -6
  227. package/dist/cron/index.d.ts.map +0 -1
  228. package/dist/services/telemetry/index.js +0 -28
@@ -0,0 +1,102 @@
1
+ import type { FSWatcher } from "node:fs";
2
+ import { existsSync, mkdirSync, watch } from "node:fs";
3
+ import { relative, resolve } from "node:path";
4
+ import type { CronReconciler } from "./cron-reconciler";
5
+
6
+ /**
7
+ * Filesystem watcher for the configured cron specs directory.
8
+ *
9
+ * Uses `node:fs` `watch` with `{ recursive: true }`. Emits a re-reconcile
10
+ * request for each change, debounced per path, so rapid save bursts from
11
+ * editors don't cause redundant DB churn.
12
+ */
13
+
14
+ const DEFAULT_DEBOUNCE_MS = 250;
15
+
16
+ export interface CronWatcherOptions {
17
+ reconciler: CronReconciler;
18
+ debounceMs?: number;
19
+ onError?: (error: unknown) => void;
20
+ onReconciled?: () => void | Promise<void>;
21
+ }
22
+
23
+ export class CronWatcher {
24
+ private readonly reconciler: CronReconciler;
25
+ private readonly debounceMs: number;
26
+ private readonly onError: (error: unknown) => void;
27
+ private readonly onReconciled: () => void | Promise<void>;
28
+ private watcher?: FSWatcher;
29
+ private readonly pending = new Map<string, ReturnType<typeof setTimeout>>();
30
+ private disposed = false;
31
+
32
+ constructor(options: CronWatcherOptions) {
33
+ this.reconciler = options.reconciler;
34
+ this.debounceMs = Math.max(0, options.debounceMs ?? DEFAULT_DEBOUNCE_MS);
35
+ this.onError = options.onError ?? (() => {});
36
+ this.onReconciled = options.onReconciled ?? (() => {});
37
+ }
38
+
39
+ public start(): void {
40
+ if (this.disposed) throw new Error("CronWatcher disposed");
41
+ if (this.watcher) return;
42
+ const dir = this.reconciler.getCronDir();
43
+ try {
44
+ mkdirSync(dir, { recursive: true });
45
+ this.watcher = watch(dir, { recursive: true }, (_eventType, filename) => {
46
+ if (!filename) return;
47
+ const rel = String(filename).replace(/\\/g, "/");
48
+ if (!rel.endsWith(".md")) return;
49
+ if (rel.startsWith("reports/")) return;
50
+ this.scheduleReconcile(rel);
51
+ });
52
+ this.watcher.on("error", this.onError);
53
+ } catch (err) {
54
+ this.onError(err);
55
+ }
56
+ }
57
+
58
+ public stop(): void {
59
+ for (const timer of this.pending.values()) clearTimeout(timer);
60
+ this.pending.clear();
61
+ this.watcher?.close();
62
+ this.watcher = undefined;
63
+ }
64
+
65
+ public dispose(): void {
66
+ this.disposed = true;
67
+ this.stop();
68
+ }
69
+
70
+ private scheduleReconcile(relativePath: string): void {
71
+ const existing = this.pending.get(relativePath);
72
+ if (existing) clearTimeout(existing);
73
+ const timer = setTimeout(() => {
74
+ this.pending.delete(relativePath);
75
+ void this.reconcileNow(relativePath);
76
+ }, this.debounceMs);
77
+ this.pending.set(relativePath, timer);
78
+ }
79
+
80
+ private async reconcileNow(relativePath: string): Promise<void> {
81
+ try {
82
+ const abs = resolve(this.reconciler.getCronDir(), relativePath);
83
+ if (!existsSync(abs)) {
84
+ // File was deleted — force a full reconcile to catch the
85
+ // missing source and mark the spec removed.
86
+ await this.reconciler.reconcileAll();
87
+ await this.onReconciled();
88
+ return;
89
+ }
90
+ // Normalize relative path through the reconciler dir to defend
91
+ // against a watcher emitting an unexpected format.
92
+ const normalizedRel = relative(this.reconciler.getCronDir(), abs).replace(
93
+ /\\/g,
94
+ "/",
95
+ );
96
+ await this.reconciler.reconcileFile(normalizedRel, abs);
97
+ await this.onReconciled();
98
+ } catch (err) {
99
+ this.onError(err);
100
+ }
101
+ }
102
+ }
package/src/cron/index.ts CHANGED
@@ -1,5 +1,15 @@
1
+ export * from "./cron-event-ingress";
2
+ export * from "./cron-materializer";
3
+ export * from "./cron-reconciler";
4
+ export * from "./cron-report-writer";
5
+ export * from "./cron-runner";
6
+ export * from "./cron-schema";
7
+ export * from "./cron-service";
8
+ export * from "./cron-spec-parser";
9
+ export * from "./cron-watcher";
1
10
  export * from "./resource-limiter";
2
11
  export * from "./schedule-command-service";
3
12
  export * from "./schedule-service";
4
13
  export * from "./scheduler";
14
+ export * from "./sqlite-cron-store";
5
15
  export * from "./sqlite-schedule-store";
@@ -60,6 +60,9 @@ function parseCronField(
60
60
  from = resolveValue(rangePart);
61
61
  }
62
62
  }
63
+ if (from > to) {
64
+ throw new Error(`Invalid cron range "${rangePart}"`);
65
+ }
63
66
  for (let value = from; value <= to; value += step) {
64
67
  results.add(value);
65
68
  }
@@ -70,6 +73,9 @@ function parseCronField(
70
73
  if (dashIndex !== -1) {
71
74
  const from = resolveValue(part.slice(0, dashIndex));
72
75
  const to = resolveValue(part.slice(dashIndex + 1));
76
+ if (from > to) {
77
+ throw new Error(`Invalid cron range "${part}"`);
78
+ }
73
79
  for (let value = from; value <= to; value += 1) {
74
80
  results.add(value);
75
81
  }
@@ -107,6 +113,14 @@ export interface ParsedCron {
107
113
  daysOfWeek: number[];
108
114
  }
109
115
 
116
+ interface CronDateParts {
117
+ month: number;
118
+ dayOfMonth: number;
119
+ dayOfWeek: number;
120
+ hour: number;
121
+ minute: number;
122
+ }
123
+
110
124
  function getRequiredField(
111
125
  fields: readonly string[],
112
126
  index: number,
@@ -159,7 +173,131 @@ export function validateCronPattern(pattern: string): void {
159
173
  parseCron(pattern);
160
174
  }
161
175
 
162
- export function getNextCronTime(pattern: string, after: number): number {
176
+ export function validateCronSchedule(
177
+ pattern: string,
178
+ timezone?: string,
179
+ after: number = Date.now(),
180
+ ): void {
181
+ getNextCronTime(pattern, after, timezone);
182
+ }
183
+
184
+ const TIMEZONE_FORMATTERS = new Map<string, Intl.DateTimeFormat>();
185
+ const DOW_BY_SHORT_NAME = new Map(
186
+ DOW_NAMES.map((name, index) => [name, index] as const),
187
+ );
188
+
189
+ function normalizeTimezone(timezone: string | undefined): string | undefined {
190
+ const trimmed = timezone?.trim();
191
+ return trimmed ? trimmed : undefined;
192
+ }
193
+
194
+ function getTimezoneFormatter(timezone: string): Intl.DateTimeFormat {
195
+ const existing = TIMEZONE_FORMATTERS.get(timezone);
196
+ if (existing) return existing;
197
+ const formatter = new Intl.DateTimeFormat("en-US", {
198
+ timeZone: timezone,
199
+ calendar: "gregory",
200
+ numberingSystem: "latn",
201
+ year: "numeric",
202
+ month: "2-digit",
203
+ day: "2-digit",
204
+ weekday: "short",
205
+ hour: "2-digit",
206
+ minute: "2-digit",
207
+ hourCycle: "h23",
208
+ });
209
+ TIMEZONE_FORMATTERS.set(timezone, formatter);
210
+ return formatter;
211
+ }
212
+
213
+ export function validateTimezone(timezone: string | undefined): void {
214
+ const normalized = normalizeTimezone(timezone);
215
+ if (!normalized) return;
216
+ getTimezoneFormatter(normalized).format(new Date());
217
+ }
218
+
219
+ function getZonedCronDateParts(
220
+ timestampMs: number,
221
+ timezone: string,
222
+ ): CronDateParts {
223
+ const parts = getTimezoneFormatter(timezone).formatToParts(
224
+ new Date(timestampMs),
225
+ );
226
+ const byType = new Map(parts.map((part) => [part.type, part.value]));
227
+ const weekdayRaw = byType.get("weekday")?.toLowerCase().slice(0, 3) ?? "";
228
+ const dayOfWeek = DOW_BY_SHORT_NAME.get(
229
+ weekdayRaw as (typeof DOW_NAMES)[number],
230
+ );
231
+ if (dayOfWeek === undefined) {
232
+ throw new Error(`Unable to resolve weekday for timezone "${timezone}"`);
233
+ }
234
+ return {
235
+ month: Number(byType.get("month")),
236
+ dayOfMonth: Number(byType.get("day")),
237
+ dayOfWeek,
238
+ hour: Number(byType.get("hour")),
239
+ minute: Number(byType.get("minute")),
240
+ };
241
+ }
242
+
243
+ function getLocalCronDateParts(timestampMs: number): CronDateParts {
244
+ const date = new Date(timestampMs);
245
+ return {
246
+ month: date.getMonth() + 1,
247
+ dayOfMonth: date.getDate(),
248
+ dayOfWeek: date.getDay(),
249
+ hour: date.getHours(),
250
+ minute: date.getMinutes(),
251
+ };
252
+ }
253
+
254
+ function cronMatchesParts(cron: ParsedCron, parts: CronDateParts): boolean {
255
+ return (
256
+ cron.months.includes(parts.month) &&
257
+ cron.daysOfMonth.includes(parts.dayOfMonth) &&
258
+ cron.daysOfWeek.includes(parts.dayOfWeek) &&
259
+ cron.hours.includes(parts.hour) &&
260
+ cron.minutes.includes(parts.minute)
261
+ );
262
+ }
263
+
264
+ function getNextCronTimeByMinuteScan(
265
+ pattern: string,
266
+ after: number,
267
+ timezone: string,
268
+ ): number {
269
+ const cron = parseCron(pattern);
270
+ const next = new Date(after);
271
+ next.setSeconds(0, 0);
272
+ let nextMs = next.getTime() + 60_000;
273
+
274
+ const limit = new Date(after);
275
+ limit.setFullYear(limit.getFullYear() + 4);
276
+ const limitMs = limit.getTime();
277
+
278
+ while (nextMs <= limitMs) {
279
+ if (cronMatchesParts(cron, getZonedCronDateParts(nextMs, timezone))) {
280
+ return nextMs;
281
+ }
282
+ nextMs += 60_000;
283
+ }
284
+
285
+ throw new Error(
286
+ `No cron occurrence found within 4 years for pattern "${pattern}" in timezone "${timezone}"`,
287
+ );
288
+ }
289
+
290
+ export function getNextCronTime(
291
+ pattern: string,
292
+ after: number,
293
+ timezone?: string,
294
+ ): number {
295
+ const normalizedTimezone = normalizeTimezone(timezone);
296
+ if (normalizedTimezone) {
297
+ validateTimezone(normalizedTimezone);
298
+ return getNextCronTimeByMinuteScan(pattern, after, normalizedTimezone);
299
+ }
300
+
163
301
  const cron = parseCron(pattern);
164
302
  let next = new Date(after);
165
303
  next.setSeconds(0, 0);
@@ -169,11 +307,8 @@ export function getNextCronTime(pattern: string, after: number): number {
169
307
  limit.setFullYear(limit.getFullYear() + 4);
170
308
 
171
309
  while (next <= limit) {
172
- const month = next.getMonth() + 1;
173
- const dayOfMonth = next.getDate();
174
- const dayOfWeek = next.getDay();
175
- const hour = next.getHours();
176
- const minute = next.getMinutes();
310
+ const { month, dayOfMonth, dayOfWeek, hour, minute } =
311
+ getLocalCronDateParts(next.getTime());
177
312
 
178
313
  if (!cron.months.includes(month)) {
179
314
  const targetMonth =