@clinebot/core 0.0.35 → 0.0.36

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 (335) hide show
  1. package/README.md +1 -2
  2. package/dist/ClineCore.d.ts +53 -39
  3. package/dist/ClineCore.d.ts.map +1 -1
  4. package/dist/account/index.d.ts +1 -1
  5. package/dist/account/index.d.ts.map +1 -1
  6. package/dist/account/rpc.d.ts +6 -6
  7. package/dist/account/rpc.d.ts.map +1 -1
  8. package/dist/cron/index.d.ts +6 -0
  9. package/dist/cron/index.d.ts.map +1 -0
  10. package/dist/cron/resource-limiter.d.ts +9 -0
  11. package/dist/cron/resource-limiter.d.ts.map +1 -0
  12. package/dist/cron/schedule-command-service.d.ts +10 -0
  13. package/dist/cron/schedule-command-service.d.ts.map +1 -0
  14. package/dist/cron/schedule-service.d.ts +100 -0
  15. package/dist/cron/schedule-service.d.ts.map +1 -0
  16. package/dist/cron/scheduler.d.ts +66 -0
  17. package/dist/cron/scheduler.d.ts.map +1 -0
  18. package/dist/cron/sqlite-schedule-store.d.ts +52 -0
  19. package/dist/cron/sqlite-schedule-store.d.ts.map +1 -0
  20. package/dist/extensions/config/agent-config-loader.d.ts +4 -3
  21. package/dist/extensions/config/agent-config-loader.d.ts.map +1 -1
  22. package/dist/extensions/config/runtime-commands.d.ts +1 -0
  23. package/dist/extensions/config/runtime-commands.d.ts.map +1 -1
  24. package/dist/extensions/config/user-instruction-config-loader.d.ts +1 -0
  25. package/dist/extensions/config/user-instruction-config-loader.d.ts.map +1 -1
  26. package/dist/extensions/context/agentic-compaction.d.ts +2 -2
  27. package/dist/extensions/context/agentic-compaction.d.ts.map +1 -1
  28. package/dist/extensions/context/compaction-shared.d.ts +5 -4
  29. package/dist/extensions/context/compaction-shared.d.ts.map +1 -1
  30. package/dist/extensions/context/compaction.d.ts.map +1 -1
  31. package/dist/extensions/plugin/plugin-config-loader.d.ts +9 -2
  32. package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
  33. package/dist/extensions/plugin/plugin-loader.d.ts +5 -3
  34. package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
  35. package/dist/extensions/plugin/plugin-module-import.d.ts.map +1 -1
  36. package/dist/extensions/plugin/plugin-sandbox.d.ts +15 -2
  37. package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
  38. package/dist/extensions/plugin/plugin-targeting.d.ts +7 -0
  39. package/dist/extensions/plugin/plugin-targeting.d.ts.map +1 -0
  40. package/dist/extensions/plugin-sandbox-bootstrap.js +211 -211
  41. package/dist/extensions/tools/definitions.d.ts +1 -1
  42. package/dist/extensions/tools/definitions.d.ts.map +1 -1
  43. package/dist/extensions/tools/executors/apply-patch.d.ts +3 -1
  44. package/dist/extensions/tools/executors/apply-patch.d.ts.map +1 -1
  45. package/dist/extensions/tools/executors/search.d.ts +1 -1
  46. package/dist/extensions/tools/executors/search.d.ts.map +1 -1
  47. package/dist/extensions/tools/index.d.ts +2 -0
  48. package/dist/extensions/tools/index.d.ts.map +1 -1
  49. package/dist/extensions/tools/presets.d.ts +26 -43
  50. package/dist/extensions/tools/presets.d.ts.map +1 -1
  51. package/dist/extensions/tools/runtime.d.ts +25 -0
  52. package/dist/extensions/tools/runtime.d.ts.map +1 -0
  53. package/dist/extensions/tools/schemas.d.ts.map +1 -1
  54. package/dist/extensions/tools/team/team-tools.d.ts +1 -0
  55. package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
  56. package/dist/hooks/hook-file-hooks.d.ts +4 -1
  57. package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
  58. package/dist/hooks/index.d.ts +0 -1
  59. package/dist/hooks/index.d.ts.map +1 -1
  60. package/dist/hooks/subprocess.d.ts +8 -1
  61. package/dist/hooks/subprocess.d.ts.map +1 -1
  62. package/dist/hub/browser-websocket.d.ts +18 -0
  63. package/dist/hub/browser-websocket.d.ts.map +1 -0
  64. package/dist/hub/client.d.ts +45 -0
  65. package/dist/hub/client.d.ts.map +1 -0
  66. package/dist/hub/connect.d.ts +15 -0
  67. package/dist/hub/connect.d.ts.map +1 -0
  68. package/dist/hub/daemon-entry.d.ts +2 -0
  69. package/dist/hub/daemon-entry.d.ts.map +1 -0
  70. package/dist/hub/daemon-entry.js +1045 -0
  71. package/dist/hub/daemon.d.ts +5 -0
  72. package/dist/hub/daemon.d.ts.map +1 -0
  73. package/dist/hub/defaults.d.ts +13 -0
  74. package/dist/hub/defaults.d.ts.map +1 -0
  75. package/dist/hub/discovery.d.ts +29 -0
  76. package/dist/hub/discovery.d.ts.map +1 -0
  77. package/dist/hub/index.d.ts +15 -0
  78. package/dist/hub/index.d.ts.map +1 -0
  79. package/dist/hub/index.js +1044 -0
  80. package/dist/hub/native-transport.d.ts +17 -0
  81. package/dist/hub/native-transport.d.ts.map +1 -0
  82. package/dist/hub/runtime-handlers.d.ts +11 -0
  83. package/dist/hub/runtime-handlers.d.ts.map +1 -0
  84. package/dist/hub/server.d.ts +86 -0
  85. package/dist/hub/server.d.ts.map +1 -0
  86. package/dist/hub/session-client.d.ts +87 -0
  87. package/dist/hub/session-client.d.ts.map +1 -0
  88. package/dist/hub/start-shared-server.d.ts +19 -0
  89. package/dist/hub/start-shared-server.d.ts.map +1 -0
  90. package/dist/hub/transport.d.ts +8 -0
  91. package/dist/hub/transport.d.ts.map +1 -0
  92. package/dist/hub/ui-client.d.ts +44 -0
  93. package/dist/hub/ui-client.d.ts.map +1 -0
  94. package/dist/hub/workspace.d.ts +4 -0
  95. package/dist/hub/workspace.d.ts.map +1 -0
  96. package/dist/index.d.ts +26 -15
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +498 -476
  99. package/dist/llms/configured-provider-registry.d.ts +28 -0
  100. package/dist/llms/configured-provider-registry.d.ts.map +1 -0
  101. package/dist/llms/provider-defaults.d.ts +27 -0
  102. package/dist/llms/provider-defaults.d.ts.map +1 -0
  103. package/dist/llms/provider-settings.d.ts +202 -0
  104. package/dist/llms/provider-settings.d.ts.map +1 -0
  105. package/dist/llms/runtime-config.d.ts +4 -0
  106. package/dist/llms/runtime-config.d.ts.map +1 -0
  107. package/dist/llms/runtime-registry.d.ts +20 -0
  108. package/dist/llms/runtime-registry.d.ts.map +1 -0
  109. package/dist/llms/runtime-types.d.ts +85 -0
  110. package/dist/llms/runtime-types.d.ts.map +1 -0
  111. package/dist/runtime/host.d.ts +1 -2
  112. package/dist/runtime/host.d.ts.map +1 -1
  113. package/dist/runtime/rules.d.ts +1 -0
  114. package/dist/runtime/rules.d.ts.map +1 -1
  115. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  116. package/dist/runtime/runtime-host.d.ts +22 -24
  117. package/dist/runtime/runtime-host.d.ts.map +1 -1
  118. package/dist/runtime/runtime-oauth-token-manager.d.ts.map +1 -1
  119. package/dist/runtime/session-runtime.d.ts +1 -19
  120. package/dist/runtime/session-runtime.d.ts.map +1 -1
  121. package/dist/services/global-settings.d.ts +12 -0
  122. package/dist/services/global-settings.d.ts.map +1 -0
  123. package/dist/services/local-runtime-bootstrap.d.ts +9 -3
  124. package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
  125. package/dist/services/plugin-tools.d.ts +16 -0
  126. package/dist/services/plugin-tools.d.ts.map +1 -0
  127. package/dist/services/providers/local-provider-registry.d.ts +4 -4
  128. package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
  129. package/dist/services/providers/local-provider-service.d.ts +13 -13
  130. package/dist/services/providers/local-provider-service.d.ts.map +1 -1
  131. package/dist/services/session-data.d.ts +1 -1
  132. package/dist/services/session-data.d.ts.map +1 -1
  133. package/dist/services/storage/provider-settings-legacy-migration.d.ts +1 -1
  134. package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
  135. package/dist/services/telemetry/index.js +28 -15
  136. package/dist/services/workspace-manifest.d.ts +11 -0
  137. package/dist/services/workspace-manifest.d.ts.map +1 -1
  138. package/dist/session/persistence-service.d.ts +11 -23
  139. package/dist/session/persistence-service.d.ts.map +1 -1
  140. package/dist/session/session-manifest-store.d.ts +22 -0
  141. package/dist/session/session-manifest-store.d.ts.map +1 -0
  142. package/dist/session/session-row.d.ts +93 -0
  143. package/dist/session/session-row.d.ts.map +1 -0
  144. package/dist/session/session-service.d.ts +2 -102
  145. package/dist/session/session-service.d.ts.map +1 -1
  146. package/dist/session/subagent-session-manager.d.ts +36 -0
  147. package/dist/session/subagent-session-manager.d.ts.map +1 -0
  148. package/dist/session/team-persistence-store.d.ts +24 -0
  149. package/dist/session/team-persistence-store.d.ts.map +1 -0
  150. package/dist/transports/hub.d.ts +47 -0
  151. package/dist/transports/hub.d.ts.map +1 -0
  152. package/dist/transports/local.d.ts +10 -6
  153. package/dist/transports/local.d.ts.map +1 -1
  154. package/dist/transports/remote.d.ts +10 -0
  155. package/dist/transports/remote.d.ts.map +1 -0
  156. package/dist/transports/runtime-host-support.d.ts +3 -2
  157. package/dist/transports/runtime-host-support.d.ts.map +1 -1
  158. package/dist/types/chat-schema.d.ts +10 -12
  159. package/dist/types/chat-schema.d.ts.map +1 -1
  160. package/dist/types/config.d.ts +8 -7
  161. package/dist/types/config.d.ts.map +1 -1
  162. package/dist/types/provider-settings.d.ts +4 -5
  163. package/dist/types/provider-settings.d.ts.map +1 -1
  164. package/dist/types/session.d.ts +2 -1
  165. package/dist/types/session.d.ts.map +1 -1
  166. package/dist/types.d.ts +8 -1
  167. package/dist/types.d.ts.map +1 -1
  168. package/package.json +20 -6
  169. package/src/ClineCore.ts +68 -40
  170. package/src/account/index.ts +3 -3
  171. package/src/account/rpc.ts +12 -12
  172. package/src/cron/index.ts +5 -0
  173. package/src/cron/resource-limiter.ts +46 -0
  174. package/src/cron/schedule-command-service.ts +193 -0
  175. package/src/cron/schedule-service.ts +703 -0
  176. package/src/cron/scheduler.ts +637 -0
  177. package/src/cron/sqlite-schedule-store.ts +708 -0
  178. package/src/extensions/config/agent-config-loader.ts +17 -7
  179. package/src/extensions/config/runtime-commands.ts +6 -0
  180. package/src/extensions/config/user-instruction-config-loader.ts +1 -0
  181. package/src/extensions/context/agentic-compaction.ts +3 -3
  182. package/src/extensions/context/basic-compaction.ts +2 -2
  183. package/src/extensions/context/compaction-shared.ts +5 -4
  184. package/src/extensions/context/compaction.ts +3 -3
  185. package/src/extensions/plugin/plugin-config-loader.ts +17 -2
  186. package/src/extensions/plugin/plugin-loader.ts +48 -4
  187. package/src/extensions/plugin/plugin-module-import.ts +0 -2
  188. package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +93 -39
  189. package/src/extensions/plugin/plugin-sandbox.ts +47 -27
  190. package/src/extensions/plugin/plugin-targeting.ts +32 -0
  191. package/src/extensions/tools/definitions.ts +30 -49
  192. package/src/extensions/tools/executors/apply-patch.ts +69 -80
  193. package/src/extensions/tools/executors/search.ts +195 -3
  194. package/src/extensions/tools/index.ts +10 -0
  195. package/src/extensions/tools/presets.ts +31 -46
  196. package/src/extensions/tools/runtime.ts +261 -0
  197. package/src/extensions/tools/schemas.ts +4 -2
  198. package/src/extensions/tools/team/team-tools.ts +21 -0
  199. package/src/hooks/hook-file-hooks.ts +8 -2
  200. package/src/hooks/index.ts +0 -7
  201. package/src/hooks/subprocess-runner.ts +1 -1
  202. package/src/hooks/subprocess.ts +9 -0
  203. package/src/hub/browser-websocket.ts +137 -0
  204. package/src/hub/client.ts +574 -0
  205. package/src/hub/connect.ts +156 -0
  206. package/src/hub/daemon-entry.ts +87 -0
  207. package/src/hub/daemon.ts +181 -0
  208. package/src/hub/defaults.ts +43 -0
  209. package/src/hub/discovery.ts +247 -0
  210. package/src/hub/index.ts +14 -0
  211. package/src/hub/native-transport.ts +31 -0
  212. package/src/hub/runtime-handlers.ts +140 -0
  213. package/src/hub/server.ts +1888 -0
  214. package/src/hub/session-client.ts +460 -0
  215. package/src/hub/start-shared-server.ts +58 -0
  216. package/src/hub/transport.ts +14 -0
  217. package/src/hub/ui-client.ts +122 -0
  218. package/src/hub/workspace.ts +19 -0
  219. package/src/index.ts +124 -68
  220. package/src/llms/configured-provider-registry.ts +193 -0
  221. package/src/llms/provider-defaults.ts +637 -0
  222. package/src/llms/provider-settings.ts +263 -0
  223. package/src/llms/runtime-config.ts +43 -0
  224. package/src/llms/runtime-registry.ts +171 -0
  225. package/src/llms/runtime-types.ts +121 -0
  226. package/src/runtime/host.ts +107 -269
  227. package/src/runtime/index.ts +1 -0
  228. package/src/runtime/rules.ts +12 -0
  229. package/src/runtime/runtime-builder.ts +24 -8
  230. package/src/runtime/runtime-host.ts +89 -61
  231. package/src/runtime/runtime-oauth-token-manager.ts +11 -15
  232. package/src/runtime/session-runtime.ts +0 -24
  233. package/src/services/global-settings.ts +122 -0
  234. package/src/services/local-runtime-bootstrap.ts +51 -13
  235. package/src/services/plugin-tools.ts +85 -0
  236. package/src/services/providers/local-provider-registry.ts +6 -6
  237. package/src/services/providers/local-provider-service.ts +42 -37
  238. package/src/services/session-data.ts +15 -9
  239. package/src/services/storage/provider-settings-legacy-migration.ts +6 -4
  240. package/src/services/storage/provider-settings-manager.ts +1 -1
  241. package/src/services/workspace-manifest.ts +18 -0
  242. package/src/session/file-session-service.ts +1 -1
  243. package/src/session/index.ts +6 -27
  244. package/src/session/persistence-service.ts +119 -504
  245. package/src/session/session-manifest-store.ts +158 -0
  246. package/src/session/session-row.ts +199 -0
  247. package/src/session/session-service.ts +17 -376
  248. package/src/session/session-team-coordination.ts +1 -1
  249. package/src/session/subagent-session-manager.ts +397 -0
  250. package/src/session/team-persistence-store.ts +176 -0
  251. package/src/transports/hub.ts +656 -0
  252. package/src/transports/local.ts +135 -40
  253. package/src/transports/remote.ts +26 -0
  254. package/src/transports/runtime-host-support.ts +63 -9
  255. package/src/types/chat-schema.ts +4 -5
  256. package/src/types/config.ts +8 -7
  257. package/src/types/provider-settings.ts +11 -7
  258. package/src/types/session.ts +2 -4
  259. package/src/types.ts +27 -1
  260. package/dist/hooks/persistent.d.ts +0 -64
  261. package/dist/hooks/persistent.d.ts.map +0 -1
  262. package/dist/runtime/rpc-runtime-ensure.d.ts +0 -65
  263. package/dist/runtime/rpc-runtime-ensure.d.ts.map +0 -1
  264. package/dist/runtime/rpc-spawn-lease.d.ts +0 -8
  265. package/dist/runtime/rpc-spawn-lease.d.ts.map +0 -1
  266. package/dist/session/rpc-session-service.d.ts +0 -16
  267. package/dist/session/rpc-session-service.d.ts.map +0 -1
  268. package/dist/session/sqlite-rpc-session-backend.d.ts +0 -31
  269. package/dist/session/sqlite-rpc-session-backend.d.ts.map +0 -1
  270. package/dist/transports/rpc.d.ts +0 -51
  271. package/dist/transports/rpc.d.ts.map +0 -1
  272. package/src/ClineCore.test.ts +0 -226
  273. package/src/account/cline-account-service.test.ts +0 -185
  274. package/src/account/featurebase-token.test.ts +0 -175
  275. package/src/account/rpc.test.ts +0 -63
  276. package/src/auth/bounded-ttl-cache.test.ts +0 -38
  277. package/src/auth/client.test.ts +0 -69
  278. package/src/auth/cline.test.ts +0 -267
  279. package/src/auth/codex.test.ts +0 -170
  280. package/src/auth/oca.test.ts +0 -340
  281. package/src/auth/server.test.ts +0 -287
  282. package/src/auth/utils.test.ts +0 -128
  283. package/src/extensions/config/agent-config-loader.test.ts +0 -236
  284. package/src/extensions/config/hooks-config-loader.test.ts +0 -20
  285. package/src/extensions/config/runtime-commands.test.ts +0 -115
  286. package/src/extensions/config/unified-config-file-watcher.test.ts +0 -196
  287. package/src/extensions/config/user-instruction-config-loader.test.ts +0 -246
  288. package/src/extensions/context/compaction.test.ts +0 -483
  289. package/src/extensions/mcp/config-loader.test.ts +0 -238
  290. package/src/extensions/mcp/manager.test.ts +0 -105
  291. package/src/extensions/plugin/plugin-config-loader.test.ts +0 -184
  292. package/src/extensions/plugin/plugin-loader.test.ts +0 -292
  293. package/src/extensions/plugin/plugin-sandbox.test.ts +0 -423
  294. package/src/extensions/tools/definitions.test.ts +0 -780
  295. package/src/extensions/tools/executors/bash.test.ts +0 -87
  296. package/src/extensions/tools/executors/editor.test.ts +0 -35
  297. package/src/extensions/tools/executors/file-read.test.ts +0 -125
  298. package/src/extensions/tools/model-tool-routing.test.ts +0 -86
  299. package/src/extensions/tools/presets.test.ts +0 -70
  300. package/src/extensions/tools/team/multi-agent.lifecycle.test.ts +0 -455
  301. package/src/extensions/tools/team/spawn-agent-tool.test.ts +0 -381
  302. package/src/extensions/tools/team/team-tools.test.ts +0 -918
  303. package/src/hooks/checkpoint-hooks.test.ts +0 -168
  304. package/src/hooks/hook-file-hooks.test.ts +0 -311
  305. package/src/hooks/persistent.ts +0 -661
  306. package/src/runtime/history.test.ts +0 -114
  307. package/src/runtime/host.test.ts +0 -230
  308. package/src/runtime/rpc-runtime-ensure.test.ts +0 -123
  309. package/src/runtime/rpc-runtime-ensure.ts +0 -659
  310. package/src/runtime/rpc-spawn-lease.test.ts +0 -81
  311. package/src/runtime/rpc-spawn-lease.ts +0 -156
  312. package/src/runtime/runtime-builder.team-persistence.test.ts +0 -245
  313. package/src/runtime/runtime-builder.test.ts +0 -615
  314. package/src/runtime/runtime-oauth-token-manager.test.ts +0 -137
  315. package/src/runtime/runtime-parity.test.ts +0 -143
  316. package/src/services/providers/local-provider-service.test.ts +0 -1062
  317. package/src/services/session-data.test.ts +0 -160
  318. package/src/services/storage/provider-settings-legacy-migration.test.ts +0 -424
  319. package/src/services/storage/provider-settings-manager.test.ts +0 -191
  320. package/src/services/telemetry/OpenTelemetryAdapter.test.ts +0 -157
  321. package/src/services/telemetry/OpenTelemetryProvider.test.ts +0 -326
  322. package/src/services/telemetry/TelemetryLoggerSink.test.ts +0 -42
  323. package/src/services/telemetry/TelemetryService.test.ts +0 -134
  324. package/src/services/telemetry/distinct-id.test.ts +0 -57
  325. package/src/services/workspace/file-indexer.d.ts +0 -11
  326. package/src/services/workspace/file-indexer.test.ts +0 -156
  327. package/src/services/workspace/mention-enricher.test.ts +0 -106
  328. package/src/session/persistence-service.test.ts +0 -300
  329. package/src/session/rpc-session-service.ts +0 -114
  330. package/src/session/session-service.team-persistence.test.ts +0 -48
  331. package/src/session/sqlite-rpc-session-backend.ts +0 -301
  332. package/src/transports/local.e2e.test.ts +0 -380
  333. package/src/transports/local.test.ts +0 -2559
  334. package/src/transports/rpc.test.ts +0 -82
  335. package/src/transports/rpc.ts +0 -665
@@ -0,0 +1,637 @@
1
+ import {
2
+ createSessionId,
3
+ type HubScheduleCreateInput,
4
+ type HubScheduleUpdateInput,
5
+ type ScheduleExecutionRecord,
6
+ type ScheduleExecutionStatus,
7
+ type ScheduleRecord,
8
+ } from "@clinebot/shared";
9
+
10
+ function createHubId(prefix: string): string {
11
+ return createSessionId(`${prefix}_`);
12
+ }
13
+
14
+ function parseCronField(
15
+ token: string,
16
+ min: number,
17
+ max: number,
18
+ names?: readonly string[],
19
+ ): number[] {
20
+ const results = new Set<number>();
21
+
22
+ function resolveValue(raw: string): number {
23
+ const lower = raw.toLowerCase();
24
+ if (names) {
25
+ const index = names.indexOf(lower);
26
+ if (index !== -1) {
27
+ return index + min;
28
+ }
29
+ }
30
+ const value = Number(raw);
31
+ if (!Number.isInteger(value) || value < min || value > max) {
32
+ throw new Error(`Invalid cron value "${raw}" for range [${min}-${max}]`);
33
+ }
34
+ return value;
35
+ }
36
+
37
+ for (const part of token.split(",")) {
38
+ if (part === "*") {
39
+ for (let value = min; value <= max; value += 1) {
40
+ results.add(value);
41
+ }
42
+ continue;
43
+ }
44
+
45
+ const stepSeparator = part.indexOf("/");
46
+ if (stepSeparator !== -1) {
47
+ const rangePart = part.slice(0, stepSeparator);
48
+ const step = Number(part.slice(stepSeparator + 1));
49
+ if (!Number.isInteger(step) || step < 1) {
50
+ throw new Error(`Invalid step "${part.slice(stepSeparator + 1)}"`);
51
+ }
52
+ let from = min;
53
+ let to = max;
54
+ if (rangePart !== "*") {
55
+ const dashIndex = rangePart.indexOf("-");
56
+ if (dashIndex !== -1) {
57
+ from = resolveValue(rangePart.slice(0, dashIndex));
58
+ to = resolveValue(rangePart.slice(dashIndex + 1));
59
+ } else {
60
+ from = resolveValue(rangePart);
61
+ }
62
+ }
63
+ for (let value = from; value <= to; value += step) {
64
+ results.add(value);
65
+ }
66
+ continue;
67
+ }
68
+
69
+ const dashIndex = part.indexOf("-");
70
+ if (dashIndex !== -1) {
71
+ const from = resolveValue(part.slice(0, dashIndex));
72
+ const to = resolveValue(part.slice(dashIndex + 1));
73
+ for (let value = from; value <= to; value += 1) {
74
+ results.add(value);
75
+ }
76
+ continue;
77
+ }
78
+
79
+ results.add(resolveValue(part));
80
+ }
81
+
82
+ return [...results].sort((left, right) => left - right);
83
+ }
84
+
85
+ const MONTH_NAMES = [
86
+ "jan",
87
+ "feb",
88
+ "mar",
89
+ "apr",
90
+ "may",
91
+ "jun",
92
+ "jul",
93
+ "aug",
94
+ "sep",
95
+ "oct",
96
+ "nov",
97
+ "dec",
98
+ ] as const;
99
+
100
+ const DOW_NAMES = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"] as const;
101
+
102
+ export interface ParsedCron {
103
+ minutes: number[];
104
+ hours: number[];
105
+ daysOfMonth: number[];
106
+ months: number[];
107
+ daysOfWeek: number[];
108
+ }
109
+
110
+ function getRequiredField(
111
+ fields: readonly string[],
112
+ index: number,
113
+ pattern: string,
114
+ ): string {
115
+ const value = fields[index];
116
+ if (typeof value !== "string") {
117
+ throw new Error(
118
+ `Invalid cron pattern "${pattern}": missing field ${index + 1}`,
119
+ );
120
+ }
121
+ return value;
122
+ }
123
+
124
+ function getFirstCronValue(values: readonly number[], label: string): number {
125
+ const value = values[0];
126
+ if (typeof value !== "number") {
127
+ throw new Error(`Invalid cron pattern: no values parsed for ${label}`);
128
+ }
129
+ return value;
130
+ }
131
+
132
+ export function parseCron(pattern: string): ParsedCron {
133
+ const fields = pattern.trim().split(/\s+/);
134
+ if (fields.length !== 5) {
135
+ throw new Error(
136
+ `Invalid cron pattern "${pattern}": expected 5 fields, got ${fields.length}`,
137
+ );
138
+ }
139
+ return {
140
+ minutes: parseCronField(getRequiredField(fields, 0, pattern), 0, 59),
141
+ hours: parseCronField(getRequiredField(fields, 1, pattern), 0, 23),
142
+ daysOfMonth: parseCronField(getRequiredField(fields, 2, pattern), 1, 31),
143
+ months: parseCronField(
144
+ getRequiredField(fields, 3, pattern),
145
+ 1,
146
+ 12,
147
+ MONTH_NAMES,
148
+ ),
149
+ daysOfWeek: parseCronField(
150
+ getRequiredField(fields, 4, pattern),
151
+ 0,
152
+ 6,
153
+ DOW_NAMES,
154
+ ),
155
+ };
156
+ }
157
+
158
+ export function validateCronPattern(pattern: string): void {
159
+ parseCron(pattern);
160
+ }
161
+
162
+ export function getNextCronTime(pattern: string, after: number): number {
163
+ const cron = parseCron(pattern);
164
+ let next = new Date(after);
165
+ next.setSeconds(0, 0);
166
+ next = new Date(next.getTime() + 60_000);
167
+
168
+ const limit = new Date(after);
169
+ limit.setFullYear(limit.getFullYear() + 4);
170
+
171
+ 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();
177
+
178
+ if (!cron.months.includes(month)) {
179
+ const targetMonth =
180
+ cron.months.find((value) => value > month) ??
181
+ getFirstCronValue(cron.months, "months");
182
+ const yearDelta = targetMonth <= month ? 1 : 0;
183
+ next = new Date(
184
+ next.getFullYear() + yearDelta,
185
+ targetMonth - 1,
186
+ 1,
187
+ 0,
188
+ 0,
189
+ 0,
190
+ 0,
191
+ );
192
+ continue;
193
+ }
194
+
195
+ if (
196
+ !cron.daysOfMonth.includes(dayOfMonth) ||
197
+ !cron.daysOfWeek.includes(dayOfWeek)
198
+ ) {
199
+ next = new Date(
200
+ next.getFullYear(),
201
+ next.getMonth(),
202
+ dayOfMonth + 1,
203
+ 0,
204
+ 0,
205
+ 0,
206
+ 0,
207
+ );
208
+ continue;
209
+ }
210
+
211
+ if (!cron.hours.includes(hour)) {
212
+ const targetHour =
213
+ cron.hours.find((value) => value > hour) ??
214
+ getFirstCronValue(cron.hours, "hours");
215
+ const dayDelta = targetHour <= hour ? 1 : 0;
216
+ next = new Date(
217
+ next.getFullYear(),
218
+ next.getMonth(),
219
+ next.getDate() + dayDelta,
220
+ targetHour,
221
+ 0,
222
+ 0,
223
+ 0,
224
+ );
225
+ continue;
226
+ }
227
+
228
+ if (!cron.minutes.includes(minute)) {
229
+ const targetMinute =
230
+ cron.minutes.find((value) => value > minute) ??
231
+ getFirstCronValue(cron.minutes, "minutes");
232
+ const hourDelta = targetMinute <= minute ? 1 : 0;
233
+ next = new Date(
234
+ next.getFullYear(),
235
+ next.getMonth(),
236
+ next.getDate(),
237
+ next.getHours() + hourDelta,
238
+ targetMinute,
239
+ 0,
240
+ 0,
241
+ );
242
+ continue;
243
+ }
244
+
245
+ return next.getTime();
246
+ }
247
+
248
+ throw new Error(
249
+ `No cron occurrence found within 4 years for pattern "${pattern}"`,
250
+ );
251
+ }
252
+
253
+ export class ScheduleStore {
254
+ private schedules = new Map<string, ScheduleRecord>();
255
+ private executions = new Map<string, ScheduleExecutionRecord>();
256
+
257
+ load(
258
+ schedules: ScheduleRecord[] | undefined,
259
+ executions: ScheduleExecutionRecord[] | undefined,
260
+ ): void {
261
+ this.schedules.clear();
262
+ this.executions.clear();
263
+ for (const schedule of schedules ?? []) {
264
+ this.schedules.set(schedule.scheduleId, { ...schedule });
265
+ }
266
+ for (const execution of executions ?? []) {
267
+ this.executions.set(execution.executionId, { ...execution });
268
+ }
269
+ }
270
+
271
+ snapshotSchedules(): ScheduleRecord[] {
272
+ return [...this.schedules.values()].map((record) => ({ ...record }));
273
+ }
274
+
275
+ snapshotExecutions(): ScheduleExecutionRecord[] {
276
+ return [...this.executions.values()].map((record) => ({ ...record }));
277
+ }
278
+
279
+ create(input: HubScheduleCreateInput, now: number): ScheduleRecord {
280
+ validateCronPattern(input.cronPattern);
281
+ const nextRunAt = getNextCronTime(input.cronPattern, now);
282
+ const record: ScheduleRecord = {
283
+ scheduleId: createHubId("sched"),
284
+ name: input.name,
285
+ cronPattern: input.cronPattern,
286
+ prompt: input.prompt,
287
+ workspaceRoot: input.workspaceRoot,
288
+ enabled: input.enabled ?? true,
289
+ createdAt: now,
290
+ updatedAt: now,
291
+ nextRunAt,
292
+ runtimeOptions: input.runtimeOptions
293
+ ? { ...input.runtimeOptions }
294
+ : undefined,
295
+ metadata: input.metadata ? { ...input.metadata } : undefined,
296
+ };
297
+ this.schedules.set(record.scheduleId, record);
298
+ return { ...record };
299
+ }
300
+
301
+ get(scheduleId: string): ScheduleRecord | undefined {
302
+ const record = this.schedules.get(scheduleId);
303
+ return record ? { ...record } : undefined;
304
+ }
305
+
306
+ list(): ScheduleRecord[] {
307
+ return [...this.schedules.values()].map((record) => ({ ...record }));
308
+ }
309
+
310
+ update(input: HubScheduleUpdateInput, now: number): ScheduleRecord {
311
+ const existing = this.schedules.get(input.scheduleId);
312
+ if (!existing) {
313
+ throw new Error(`Schedule "${input.scheduleId}" not found`);
314
+ }
315
+ const cronPattern = input.cronPattern ?? existing.cronPattern;
316
+ if (input.cronPattern) {
317
+ validateCronPattern(input.cronPattern);
318
+ }
319
+ const enabled = input.enabled ?? existing.enabled;
320
+ const nextRunAt =
321
+ (input.cronPattern || input.enabled !== undefined) && enabled
322
+ ? getNextCronTime(cronPattern, now)
323
+ : enabled
324
+ ? existing.nextRunAt
325
+ : undefined;
326
+ const updated: ScheduleRecord = {
327
+ ...existing,
328
+ name: input.name ?? existing.name,
329
+ cronPattern,
330
+ prompt: input.prompt ?? existing.prompt,
331
+ enabled,
332
+ updatedAt: now,
333
+ nextRunAt,
334
+ runtimeOptions:
335
+ input.runtimeOptions !== undefined
336
+ ? { ...input.runtimeOptions }
337
+ : existing.runtimeOptions,
338
+ metadata:
339
+ input.metadata !== undefined
340
+ ? { ...input.metadata }
341
+ : existing.metadata,
342
+ };
343
+ this.schedules.set(updated.scheduleId, updated);
344
+ return { ...updated };
345
+ }
346
+
347
+ setEnabled(
348
+ scheduleId: string,
349
+ enabled: boolean,
350
+ now: number,
351
+ ): ScheduleRecord {
352
+ const existing = this.schedules.get(scheduleId);
353
+ if (!existing) {
354
+ throw new Error(`Schedule "${scheduleId}" not found`);
355
+ }
356
+ const updated: ScheduleRecord = {
357
+ ...existing,
358
+ enabled,
359
+ updatedAt: now,
360
+ nextRunAt: enabled
361
+ ? getNextCronTime(existing.cronPattern, now)
362
+ : undefined,
363
+ };
364
+ this.schedules.set(scheduleId, updated);
365
+ return { ...updated };
366
+ }
367
+
368
+ delete(scheduleId: string): void {
369
+ if (!this.schedules.has(scheduleId)) {
370
+ throw new Error(`Schedule "${scheduleId}" not found`);
371
+ }
372
+ this.schedules.delete(scheduleId);
373
+ }
374
+
375
+ recordTriggered(scheduleId: string, now: number): ScheduleRecord {
376
+ const existing = this.schedules.get(scheduleId);
377
+ if (!existing) {
378
+ throw new Error(`Schedule "${scheduleId}" not found`);
379
+ }
380
+ const updated: ScheduleRecord = {
381
+ ...existing,
382
+ lastRunAt: now,
383
+ updatedAt: now,
384
+ nextRunAt: getNextCronTime(existing.cronPattern, now),
385
+ };
386
+ this.schedules.set(scheduleId, updated);
387
+ return { ...updated };
388
+ }
389
+
390
+ getDueSchedules(now: number): ScheduleRecord[] {
391
+ return [...this.schedules.values()]
392
+ .filter(
393
+ (record) =>
394
+ record.enabled &&
395
+ record.nextRunAt !== undefined &&
396
+ record.nextRunAt <= now,
397
+ )
398
+ .map((record) => ({ ...record }));
399
+ }
400
+
401
+ startExecution(
402
+ scheduleId: string,
403
+ sessionId: string,
404
+ now: number,
405
+ ): ScheduleExecutionRecord {
406
+ const record: ScheduleExecutionRecord = {
407
+ executionId: createHubId("sexec"),
408
+ scheduleId,
409
+ sessionId,
410
+ triggeredAt: now,
411
+ startedAt: now,
412
+ status: "running",
413
+ };
414
+ this.executions.set(record.executionId, record);
415
+ return { ...record };
416
+ }
417
+
418
+ completeExecution(
419
+ executionId: string,
420
+ status: Exclude<ScheduleExecutionStatus, "running">,
421
+ now: number,
422
+ errorMessage?: string,
423
+ ): ScheduleExecutionRecord {
424
+ const existing = this.executions.get(executionId);
425
+ if (!existing) {
426
+ throw new Error(`Execution "${executionId}" not found`);
427
+ }
428
+ const updated: ScheduleExecutionRecord = {
429
+ ...existing,
430
+ status,
431
+ endedAt: now,
432
+ ...(errorMessage ? { errorMessage } : {}),
433
+ };
434
+ this.executions.set(executionId, updated);
435
+ return { ...updated };
436
+ }
437
+
438
+ listExecutions(scheduleId?: string): ScheduleExecutionRecord[] {
439
+ const records = [...this.executions.values()];
440
+ return (
441
+ scheduleId
442
+ ? records.filter((record) => record.scheduleId === scheduleId)
443
+ : records
444
+ )
445
+ .map((record) => ({ ...record }))
446
+ .sort((left, right) => right.triggeredAt - left.triggeredAt);
447
+ }
448
+ }
449
+
450
+ export interface SchedulerTriggerContext {
451
+ scheduleId: string;
452
+ sessionId: string;
453
+ executionId: string;
454
+ }
455
+
456
+ export interface SchedulerCallbacks {
457
+ onTrigger(record: ScheduleRecord): Promise<{ sessionId: string }>;
458
+ onExecutionCompleted(
459
+ context: SchedulerTriggerContext,
460
+ execution: ScheduleExecutionRecord,
461
+ ): void;
462
+ onExecutionFailed(
463
+ context: SchedulerTriggerContext,
464
+ execution: ScheduleExecutionRecord,
465
+ error: unknown,
466
+ ): void;
467
+ onScheduleUpdated(record: ScheduleRecord): void;
468
+ onPersist(): Promise<void>;
469
+ }
470
+
471
+ export interface HubSchedulerOptions {
472
+ store: ScheduleStore;
473
+ callbacks: SchedulerCallbacks;
474
+ now?: () => number;
475
+ pollIntervalMs?: number;
476
+ }
477
+
478
+ const DEFAULT_POLL_INTERVAL_MS = 15_000;
479
+
480
+ export class HubScheduler {
481
+ private readonly store: ScheduleStore;
482
+ private readonly callbacks: SchedulerCallbacks;
483
+ private readonly now: () => number;
484
+ private readonly pollIntervalMs: number;
485
+ private timer: ReturnType<typeof setInterval> | undefined;
486
+ private readonly runningExecutions = new Map<
487
+ string,
488
+ SchedulerTriggerContext
489
+ >();
490
+
491
+ constructor(options: HubSchedulerOptions) {
492
+ this.store = options.store;
493
+ this.callbacks = options.callbacks;
494
+ this.now = options.now ?? (() => Date.now());
495
+ this.pollIntervalMs = Math.max(
496
+ 1_000,
497
+ Math.floor(options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS),
498
+ );
499
+ }
500
+
501
+ start(): void {
502
+ if (this.timer !== undefined) {
503
+ return;
504
+ }
505
+ this.timer = setInterval(() => {
506
+ void this.poll();
507
+ }, this.pollIntervalMs);
508
+ void this.poll();
509
+ }
510
+
511
+ stop(): void {
512
+ if (this.timer === undefined) {
513
+ return;
514
+ }
515
+ clearInterval(this.timer);
516
+ this.timer = undefined;
517
+ }
518
+
519
+ async triggerNow(scheduleId: string): Promise<ScheduleExecutionRecord> {
520
+ const record = this.store.get(scheduleId);
521
+ if (!record) {
522
+ throw new Error(`Schedule "${scheduleId}" not found`);
523
+ }
524
+ return await this.executeSchedule(record);
525
+ }
526
+
527
+ async notifySessionCompleted(
528
+ sessionId: string,
529
+ failed: boolean,
530
+ errorMessage?: string,
531
+ ): Promise<void> {
532
+ const entry = [...this.runningExecutions.entries()].find(
533
+ ([, context]) => context.sessionId === sessionId,
534
+ );
535
+ if (!entry) {
536
+ return;
537
+ }
538
+ const [executionId, context] = entry;
539
+ this.runningExecutions.delete(executionId);
540
+ const status: Exclude<ScheduleExecutionStatus, "running"> = failed
541
+ ? "failed"
542
+ : "completed";
543
+ const execution = this.store.completeExecution(
544
+ executionId,
545
+ status,
546
+ this.now(),
547
+ errorMessage,
548
+ );
549
+ if (failed) {
550
+ this.callbacks.onExecutionFailed(
551
+ context,
552
+ execution,
553
+ new Error(errorMessage),
554
+ );
555
+ } else {
556
+ this.callbacks.onExecutionCompleted(context, execution);
557
+ }
558
+ await this.callbacks.onPersist();
559
+ }
560
+
561
+ get scheduleStore(): ScheduleStore {
562
+ return this.store;
563
+ }
564
+
565
+ private async poll(): Promise<void> {
566
+ const due = this.store.getDueSchedules(this.now());
567
+ for (const record of due) {
568
+ const alreadyRunning = [...this.runningExecutions.values()].some(
569
+ (context) => context.scheduleId === record.scheduleId,
570
+ );
571
+ if (alreadyRunning) {
572
+ continue;
573
+ }
574
+ void this.executeSchedule(record);
575
+ }
576
+ }
577
+
578
+ private async executeSchedule(
579
+ record: ScheduleRecord,
580
+ ): Promise<ScheduleExecutionRecord> {
581
+ const startedAt = this.now();
582
+
583
+ let sessionId: string;
584
+ try {
585
+ const result = await this.callbacks.onTrigger(record);
586
+ sessionId = result.sessionId;
587
+ } catch (error) {
588
+ const execution = this.store.startExecution(
589
+ record.scheduleId,
590
+ "",
591
+ startedAt,
592
+ );
593
+ const failed = this.store.completeExecution(
594
+ execution.executionId,
595
+ "failed",
596
+ this.now(),
597
+ error instanceof Error ? error.message : String(error),
598
+ );
599
+ const updatedSchedule = this.store.recordTriggered(
600
+ record.scheduleId,
601
+ startedAt,
602
+ );
603
+ this.callbacks.onScheduleUpdated(updatedSchedule);
604
+ this.callbacks.onExecutionFailed(
605
+ {
606
+ scheduleId: record.scheduleId,
607
+ sessionId: "",
608
+ executionId: execution.executionId,
609
+ },
610
+ failed,
611
+ error,
612
+ );
613
+ await this.callbacks.onPersist();
614
+ return failed;
615
+ }
616
+
617
+ const execution = this.store.startExecution(
618
+ record.scheduleId,
619
+ sessionId,
620
+ startedAt,
621
+ );
622
+ this.runningExecutions.set(execution.executionId, {
623
+ scheduleId: record.scheduleId,
624
+ sessionId,
625
+ executionId: execution.executionId,
626
+ });
627
+
628
+ const updatedSchedule = this.store.recordTriggered(
629
+ record.scheduleId,
630
+ startedAt,
631
+ );
632
+ this.callbacks.onScheduleUpdated(updatedSchedule);
633
+ await this.callbacks.onPersist();
634
+
635
+ return execution;
636
+ }
637
+ }