@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,703 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type {
3
+ BasicLogger,
4
+ ChatRunTurnRequest,
5
+ ChatStartSessionArtifacts,
6
+ ChatStartSessionRequest,
7
+ HubScheduleCreateInput,
8
+ HubScheduleUpdateInput,
9
+ ScheduleExecutionRecord,
10
+ ScheduleExecutionStatus,
11
+ ScheduleRecord,
12
+ } from "@clinebot/shared";
13
+ import { nowIso } from "@clinebot/shared/db";
14
+ import { ResourceLimiter } from "./resource-limiter";
15
+ import { validateCronPattern } from "./scheduler";
16
+ import {
17
+ type ListScheduleExecutionsOptions,
18
+ type ListSchedulesOptions,
19
+ type ScheduleClaimRecord,
20
+ type ScheduleExecutionStats,
21
+ SqliteHubScheduleStore,
22
+ } from "./sqlite-schedule-store";
23
+
24
+ function toErrorMessage(error: unknown): string {
25
+ return error instanceof Error ? error.message : String(error);
26
+ }
27
+
28
+ type HubScheduleTurnResult = {
29
+ text: string;
30
+ usage?: {
31
+ inputTokens?: number;
32
+ outputTokens?: number;
33
+ cacheReadTokens?: number;
34
+ cacheWriteTokens?: number;
35
+ totalCost?: number;
36
+ };
37
+ inputTokens?: number;
38
+ outputTokens?: number;
39
+ iterations?: number;
40
+ finishReason?: string;
41
+ messages?: unknown[];
42
+ toolCalls?: Array<{
43
+ name: string;
44
+ input?: unknown;
45
+ output?: unknown;
46
+ error?: string;
47
+ durationMs?: number;
48
+ }>;
49
+ };
50
+
51
+ function parseTurnMetrics(result: HubScheduleTurnResult): {
52
+ iterations?: number;
53
+ tokensUsed?: number;
54
+ costUsd?: number;
55
+ } {
56
+ const inputTokens =
57
+ typeof result.inputTokens === "number" ? result.inputTokens : undefined;
58
+ const outputTokens =
59
+ typeof result.outputTokens === "number" ? result.outputTokens : undefined;
60
+ return {
61
+ iterations:
62
+ typeof result.iterations === "number" ? result.iterations : undefined,
63
+ tokensUsed:
64
+ inputTokens !== undefined && outputTokens !== undefined
65
+ ? inputTokens + outputTokens
66
+ : undefined,
67
+ costUsd:
68
+ typeof result.usage?.totalCost === "number"
69
+ ? result.usage.totalCost
70
+ : undefined,
71
+ };
72
+ }
73
+
74
+ interface AggregatedTurnMetrics {
75
+ iterations?: number;
76
+ tokensUsed?: number;
77
+ costUsd?: number;
78
+ }
79
+
80
+ function addTurnMetrics(
81
+ current: AggregatedTurnMetrics,
82
+ result: HubScheduleTurnResult,
83
+ ): AggregatedTurnMetrics {
84
+ const next = { ...current };
85
+ const metrics = parseTurnMetrics(result);
86
+ if (typeof metrics.iterations === "number") {
87
+ next.iterations = (next.iterations ?? 0) + metrics.iterations;
88
+ }
89
+ if (typeof metrics.tokensUsed === "number") {
90
+ next.tokensUsed = (next.tokensUsed ?? 0) + metrics.tokensUsed;
91
+ }
92
+ if (typeof metrics.costUsd === "number") {
93
+ next.costUsd = (next.costUsd ?? 0) + metrics.costUsd;
94
+ }
95
+ return next;
96
+ }
97
+
98
+ function optionsOrDefault(value: number | undefined, fallback: number): number {
99
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
100
+ }
101
+
102
+ function asPositiveSeconds(value: unknown, fallback: number): number {
103
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
104
+ return fallback;
105
+ }
106
+ return Math.max(1, Math.floor(value));
107
+ }
108
+
109
+ const DEFAULT_AUTONOMOUS_IDLE_TIMEOUT_SECONDS = 60;
110
+ const DEFAULT_AUTONOMOUS_POLL_INTERVAL_SECONDS = 5;
111
+ const AUTONOMOUS_IDLE_NOOP_TOKEN = "<idle-noop/>";
112
+
113
+ export interface ScheduleAutonomousOptions {
114
+ enabled?: boolean;
115
+ idleTimeoutSeconds?: number;
116
+ pollIntervalSeconds?: number;
117
+ }
118
+
119
+ function getAutonomousOptions(
120
+ schedule: ScheduleRecord,
121
+ ): ScheduleAutonomousOptions | undefined {
122
+ const metadata = schedule.metadata;
123
+ if (!metadata || typeof metadata !== "object") {
124
+ return undefined;
125
+ }
126
+ const raw =
127
+ metadata.autonomous &&
128
+ typeof metadata.autonomous === "object" &&
129
+ !Array.isArray(metadata.autonomous)
130
+ ? (metadata.autonomous as Record<string, unknown>)
131
+ : undefined;
132
+ if (!raw || raw.enabled !== true) {
133
+ return undefined;
134
+ }
135
+ return {
136
+ enabled: true,
137
+ idleTimeoutSeconds: asPositiveSeconds(
138
+ raw.idleTimeoutSeconds,
139
+ DEFAULT_AUTONOMOUS_IDLE_TIMEOUT_SECONDS,
140
+ ),
141
+ pollIntervalSeconds: asPositiveSeconds(
142
+ raw.pollIntervalSeconds,
143
+ DEFAULT_AUTONOMOUS_POLL_INTERVAL_SECONDS,
144
+ ),
145
+ };
146
+ }
147
+
148
+ function buildSchedulePrompt(
149
+ schedule: ScheduleRecord,
150
+ autonomous: ScheduleAutonomousOptions | undefined,
151
+ ): string {
152
+ if (!autonomous?.enabled) {
153
+ return schedule.prompt;
154
+ }
155
+ return `${schedule.prompt}
156
+
157
+ When you finish the immediate scheduled work, remain available for autonomous follow-up. During idle polling, inspect team mailbox and team tasks. Use team_task with action="list" to find ready unassigned work, claim it with team_task and action="claim", and resume execution when work exists. Reply exactly ${AUTONOMOUS_IDLE_NOOP_TOKEN} only when the poll finds no actionable work.`;
158
+ }
159
+
160
+ function buildAutonomousPollPrompt(
161
+ autonomous: ScheduleAutonomousOptions,
162
+ ): string {
163
+ return `Autonomous idle poll. Check team_read_mailbox for unread messages and use team_task with action="list" to find ready unassigned tasks. Claim and execute one task if actionable work exists. If there is nothing to do right now, reply exactly ${AUTONOMOUS_IDLE_NOOP_TOKEN} and nothing else. Poll cadence is ${autonomous.pollIntervalSeconds}s and the idle shutdown window is ${autonomous.idleTimeoutSeconds}s.`;
164
+ }
165
+
166
+ function isAutonomousNoop(result: HubScheduleTurnResult): boolean {
167
+ return result.text?.trim() === AUTONOMOUS_IDLE_NOOP_TOKEN;
168
+ }
169
+
170
+ function sleep(ms: number): Promise<void> {
171
+ return new Promise((resolve) => setTimeout(resolve, ms));
172
+ }
173
+
174
+ class TimeoutError extends Error {
175
+ constructor(message: string) {
176
+ super(message);
177
+ this.name = "TimeoutError";
178
+ }
179
+ }
180
+
181
+ async function withTimeout<T>(
182
+ promise: Promise<T>,
183
+ timeoutMs: number,
184
+ ): Promise<T> {
185
+ if (timeoutMs <= 0) {
186
+ return await promise;
187
+ }
188
+ let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
189
+ const timeoutPromise = new Promise<never>((_, reject) => {
190
+ timeoutHandle = setTimeout(() => {
191
+ reject(new TimeoutError("scheduled execution timed out"));
192
+ }, timeoutMs);
193
+ });
194
+ try {
195
+ return await Promise.race([promise, timeoutPromise]);
196
+ } finally {
197
+ if (timeoutHandle) {
198
+ clearTimeout(timeoutHandle);
199
+ }
200
+ }
201
+ }
202
+
203
+ export interface HubScheduleRuntimeHandlers {
204
+ startSession(request: ChatStartSessionRequest): Promise<{
205
+ sessionId: string;
206
+ startResult?: ChatStartSessionArtifacts;
207
+ }>;
208
+ sendSession(
209
+ sessionId: string,
210
+ request: ChatRunTurnRequest,
211
+ ): Promise<{
212
+ result: HubScheduleTurnResult;
213
+ }>;
214
+ abortSession(sessionId: string): Promise<{ applied: boolean }>;
215
+ stopSession(sessionId: string): Promise<{ applied: boolean }>;
216
+ }
217
+
218
+ export interface ActiveScheduledExecution {
219
+ executionId: string;
220
+ scheduleId: string;
221
+ sessionId: string;
222
+ startedAt: string;
223
+ timeoutAt?: string;
224
+ }
225
+
226
+ export interface HubScheduleServiceOptions {
227
+ runtimeHandlers: HubScheduleRuntimeHandlers;
228
+ eventPublisher?: (eventType: string, payload: unknown) => void;
229
+ logger?: BasicLogger;
230
+ sessionsDbPath?: string;
231
+ pollIntervalMs?: number;
232
+ globalMaxConcurrency?: number;
233
+ claimLeaseSeconds?: number;
234
+ }
235
+
236
+ export class HubScheduleService {
237
+ private readonly store: SqliteHubScheduleStore;
238
+ private readonly resourceLimiter: ResourceLimiter;
239
+ private readonly options: HubScheduleServiceOptions;
240
+ private readonly claimLeaseMs: number;
241
+ private readonly activeExecutions = new Map<
242
+ string,
243
+ ActiveScheduledExecution
244
+ >();
245
+ private timer: ReturnType<typeof setInterval> | undefined;
246
+ private started = false;
247
+ private ticking = false;
248
+ private disposed = false;
249
+
250
+ constructor(options: HubScheduleServiceOptions) {
251
+ this.options = options;
252
+ this.store = new SqliteHubScheduleStore({
253
+ sessionsDbPath: options.sessionsDbPath,
254
+ });
255
+ this.resourceLimiter = new ResourceLimiter(
256
+ options.globalMaxConcurrency ?? 10,
257
+ );
258
+ this.claimLeaseMs = Math.max(
259
+ 5_000,
260
+ optionsOrDefault(options.claimLeaseSeconds, 90) * 1000,
261
+ );
262
+ }
263
+
264
+ public async start(): Promise<void> {
265
+ if (this.disposed) {
266
+ throw new Error("HubScheduleService has been disposed");
267
+ }
268
+ if (this.started) {
269
+ return;
270
+ }
271
+ this.started = true;
272
+ const intervalMs = Math.max(
273
+ 5_000,
274
+ optionsOrDefault(this.options.pollIntervalMs, 30_000),
275
+ );
276
+ this.options.logger?.log("hub.schedule.started", {
277
+ pollIntervalMs: intervalMs,
278
+ });
279
+ await this.tick();
280
+ this.timer = setInterval(() => {
281
+ void this.tick();
282
+ }, intervalMs);
283
+ }
284
+
285
+ public async stop(): Promise<void> {
286
+ const wasStarted = this.started;
287
+ this.started = false;
288
+ if (this.timer) {
289
+ clearInterval(this.timer);
290
+ this.timer = undefined;
291
+ }
292
+ if (wasStarted) {
293
+ this.options.logger?.log("hub.schedule.stopped", {});
294
+ }
295
+ const active = Array.from(this.activeExecutions.values());
296
+ await Promise.all(
297
+ active.map(async (execution) => {
298
+ try {
299
+ await this.options.runtimeHandlers.abortSession(execution.sessionId);
300
+ } catch {
301
+ // best effort
302
+ }
303
+ }),
304
+ );
305
+ }
306
+
307
+ public async dispose(): Promise<void> {
308
+ if (this.disposed) {
309
+ return;
310
+ }
311
+ this.disposed = true;
312
+ await this.stop();
313
+ this.store.close();
314
+ }
315
+
316
+ public createSchedule(input: HubScheduleCreateInput): ScheduleRecord {
317
+ validateCronPattern(input.cronPattern);
318
+ if (!input.workspaceRoot?.trim()) {
319
+ throw new Error("workspaceRoot is required for schedules");
320
+ }
321
+ return this.store.createSchedule(input);
322
+ }
323
+
324
+ public getSchedule(scheduleId: string): ScheduleRecord | undefined {
325
+ return this.store.getSchedule(scheduleId);
326
+ }
327
+
328
+ public listSchedules(options: ListSchedulesOptions = {}): ScheduleRecord[] {
329
+ return this.store.listSchedules(options);
330
+ }
331
+
332
+ public updateSchedule(
333
+ scheduleId: string,
334
+ updates: HubScheduleUpdateInput,
335
+ ): ScheduleRecord | undefined {
336
+ if (updates.cronPattern !== undefined) {
337
+ validateCronPattern(updates.cronPattern);
338
+ }
339
+ const current = this.store.getSchedule(scheduleId);
340
+ if (!current) {
341
+ return undefined;
342
+ }
343
+ const nextWorkspaceRoot =
344
+ updates.workspaceRoot !== undefined
345
+ ? updates.workspaceRoot.trim()
346
+ : current.workspaceRoot;
347
+ const nextEnabled = updates.enabled ?? current.enabled;
348
+ if (nextEnabled && !nextWorkspaceRoot) {
349
+ throw new Error("workspaceRoot is required for enabled schedules");
350
+ }
351
+ return this.store.updateSchedule(scheduleId, {
352
+ ...updates,
353
+ scheduleId,
354
+ });
355
+ }
356
+
357
+ public deleteSchedule(scheduleId: string): boolean {
358
+ return this.store.deleteSchedule(scheduleId);
359
+ }
360
+
361
+ public pauseSchedule(scheduleId: string): ScheduleRecord | undefined {
362
+ return this.updateSchedule(scheduleId, { scheduleId, enabled: false });
363
+ }
364
+
365
+ public resumeSchedule(scheduleId: string): ScheduleRecord | undefined {
366
+ return this.updateSchedule(scheduleId, { scheduleId, enabled: true });
367
+ }
368
+
369
+ public async triggerScheduleNow(
370
+ scheduleId: string,
371
+ ): Promise<ScheduleExecutionRecord | undefined> {
372
+ const schedule = this.store.getSchedule(scheduleId);
373
+ if (!schedule) {
374
+ return undefined;
375
+ }
376
+ return await this.executeSchedule(schedule, nowIso(), "manual");
377
+ }
378
+
379
+ public listScheduleExecutions(
380
+ options: ListScheduleExecutionsOptions,
381
+ ): ScheduleExecutionRecord[] {
382
+ return this.store.listExecutions(options);
383
+ }
384
+
385
+ public getScheduleStats(scheduleId: string): ScheduleExecutionStats {
386
+ return this.store.getExecutionStats(scheduleId);
387
+ }
388
+
389
+ public getActiveExecutions(): ActiveScheduledExecution[] {
390
+ return Array.from(this.activeExecutions.values());
391
+ }
392
+
393
+ public getUpcomingRuns(limit = 20): Array<{
394
+ scheduleId: string;
395
+ name: string;
396
+ nextRunAt: string;
397
+ }> {
398
+ return this.store.listUpcomingRuns(limit);
399
+ }
400
+
401
+ private async tick(): Promise<void> {
402
+ if (this.ticking) {
403
+ return;
404
+ }
405
+ this.ticking = true;
406
+ try {
407
+ const claims = this.store.claimDueSchedules(nowIso(), this.claimLeaseMs);
408
+ await Promise.allSettled(
409
+ claims.map((claim) => this.executeClaimedSchedule(claim)),
410
+ );
411
+ } catch (error) {
412
+ const logger = this.options.logger;
413
+ if (logger) {
414
+ if (logger.error) {
415
+ logger.error("hub.schedule.tick.failed", { error });
416
+ } else {
417
+ logger.log("hub.schedule.tick.failed", {
418
+ error,
419
+ severity: "error",
420
+ });
421
+ }
422
+ }
423
+ } finally {
424
+ this.ticking = false;
425
+ }
426
+ }
427
+
428
+ private async executeClaimedSchedule(
429
+ claim: ScheduleClaimRecord,
430
+ ): Promise<void> {
431
+ const releaseLeaseHeartbeat = this.startClaimLeaseHeartbeat(claim);
432
+ try {
433
+ const result = await this.executeSchedule(
434
+ claim.schedule,
435
+ new Date(claim.triggeredAt).toISOString(),
436
+ "scheduled",
437
+ );
438
+ const completedAt = result.startedAt ?? claim.triggeredAt;
439
+ this.store.completeScheduleClaim(
440
+ claim.schedule.scheduleId,
441
+ claim.claimToken,
442
+ new Date(completedAt).toISOString(),
443
+ );
444
+ } finally {
445
+ releaseLeaseHeartbeat();
446
+ }
447
+ }
448
+
449
+ private startClaimLeaseHeartbeat(claim: ScheduleClaimRecord): () => void {
450
+ const heartbeatMs = Math.max(1_000, Math.floor(this.claimLeaseMs / 2));
451
+ const interval = setInterval(() => {
452
+ const leaseUntilAt = new Date(
453
+ Date.now() + this.claimLeaseMs,
454
+ ).toISOString();
455
+ const renewed = this.store.renewScheduleClaim(
456
+ claim.schedule.scheduleId,
457
+ claim.claimToken,
458
+ leaseUntilAt,
459
+ );
460
+ if (!renewed) {
461
+ clearInterval(interval);
462
+ }
463
+ }, heartbeatMs);
464
+ return () => clearInterval(interval);
465
+ }
466
+
467
+ private buildStartRequest(schedule: ScheduleRecord): ChatStartSessionRequest {
468
+ const workspaceRoot = schedule.workspaceRoot.trim();
469
+ const provider = schedule.modelSelection?.providerId?.trim();
470
+ const model = schedule.modelSelection?.modelId?.trim();
471
+ if (!workspaceRoot) {
472
+ throw new Error("schedule requires workspaceRoot");
473
+ }
474
+ if (!provider || !model) {
475
+ throw new Error(
476
+ "schedule requires modelSelection.providerId and modelSelection.modelId",
477
+ );
478
+ }
479
+ return {
480
+ workspaceRoot,
481
+ cwd: schedule.cwd?.trim() || workspaceRoot,
482
+ provider,
483
+ model,
484
+ mode:
485
+ schedule.mode === "plan"
486
+ ? "plan"
487
+ : schedule.mode === "yolo"
488
+ ? "yolo"
489
+ : "act",
490
+ systemPrompt: schedule.systemPrompt,
491
+ maxIterations: schedule.maxIterations,
492
+ enableTools: schedule.runtimeOptions?.enableTools ?? true,
493
+ enableSpawn: schedule.runtimeOptions?.enableSpawn ?? true,
494
+ enableTeams: schedule.runtimeOptions?.enableTeams ?? true,
495
+ autoApproveTools: schedule.runtimeOptions?.autoApproveTools ?? true,
496
+ };
497
+ }
498
+
499
+ private async executeSchedule(
500
+ schedule: ScheduleRecord,
501
+ triggeredAt: string,
502
+ trigger: "scheduled" | "manual",
503
+ ): Promise<ScheduleExecutionRecord> {
504
+ const executionId = `exec_${randomUUID()}`;
505
+ const triggeredAtMs = new Date(triggeredAt).getTime();
506
+ const pending: ScheduleExecutionRecord = {
507
+ executionId,
508
+ scheduleId: schedule.scheduleId,
509
+ triggeredAt: triggeredAtMs,
510
+ status: "pending",
511
+ };
512
+ this.store.recordExecution(pending);
513
+
514
+ const acquired = this.resourceLimiter.acquire(
515
+ schedule.scheduleId,
516
+ executionId,
517
+ schedule.maxParallel ?? 1,
518
+ );
519
+ if (!acquired) {
520
+ const skipped: ScheduleExecutionRecord = {
521
+ ...pending,
522
+ status: "failed",
523
+ endedAt: Date.now(),
524
+ errorMessage: "concurrency limit reached",
525
+ };
526
+ this.store.recordExecution(skipped);
527
+ return skipped;
528
+ }
529
+
530
+ let sessionId: string | undefined;
531
+ let startedAt: number | undefined;
532
+ let timeoutAt: string | undefined;
533
+ let executionDeadlineMs: number | undefined;
534
+
535
+ try {
536
+ const startRequest = this.buildStartRequest(schedule);
537
+ const startResponse =
538
+ await this.options.runtimeHandlers.startSession(startRequest);
539
+ sessionId = startResponse.sessionId.trim();
540
+ if (!sessionId) {
541
+ throw new Error("runtime start returned empty sessionId");
542
+ }
543
+
544
+ startedAt = Date.now();
545
+ timeoutAt =
546
+ typeof schedule.timeoutSeconds === "number" &&
547
+ schedule.timeoutSeconds > 0
548
+ ? new Date(startedAt + schedule.timeoutSeconds * 1000).toISOString()
549
+ : undefined;
550
+ executionDeadlineMs = timeoutAt
551
+ ? new Date(timeoutAt).getTime()
552
+ : undefined;
553
+
554
+ const runningState: ScheduleExecutionRecord = {
555
+ ...pending,
556
+ sessionId,
557
+ startedAt,
558
+ status: "running",
559
+ };
560
+ this.store.recordExecution(runningState);
561
+ this.activeExecutions.set(executionId, {
562
+ executionId,
563
+ scheduleId: schedule.scheduleId,
564
+ sessionId,
565
+ startedAt: new Date(startedAt).toISOString(),
566
+ timeoutAt,
567
+ });
568
+ this.publishEvent("schedule.execution.started", {
569
+ scheduleId: schedule.scheduleId,
570
+ executionId,
571
+ sessionId,
572
+ trigger,
573
+ triggeredAt,
574
+ });
575
+
576
+ const turnRequest: ChatRunTurnRequest = {
577
+ config: startRequest,
578
+ prompt: buildSchedulePrompt(schedule, getAutonomousOptions(schedule)),
579
+ };
580
+ const sendTurn = async (
581
+ request: ChatRunTurnRequest,
582
+ ): Promise<HubScheduleTurnResult> => {
583
+ const sendPromise = this.options.runtimeHandlers.sendSession(
584
+ sessionId!,
585
+ request,
586
+ );
587
+ const timeoutMs = executionDeadlineMs
588
+ ? Math.max(1, executionDeadlineMs - Date.now())
589
+ : 0;
590
+ const sendResult = await withTimeout(sendPromise, timeoutMs);
591
+ return sendResult.result;
592
+ };
593
+
594
+ let metrics = addTurnMetrics({}, await sendTurn(turnRequest));
595
+ const autonomous = getAutonomousOptions(schedule);
596
+ if (autonomous?.enabled) {
597
+ metrics = await this.runAutonomousIdleLoop({
598
+ sessionId,
599
+ startRequest,
600
+ autonomous,
601
+ metrics,
602
+ sendTurn,
603
+ executionDeadlineMs,
604
+ });
605
+ }
606
+
607
+ const completed: ScheduleExecutionRecord = {
608
+ ...runningState,
609
+ status: "success",
610
+ endedAt: Date.now(),
611
+ iterations: metrics.iterations,
612
+ tokensUsed: metrics.tokensUsed,
613
+ costUsd: metrics.costUsd,
614
+ };
615
+ this.store.recordExecution(completed);
616
+ this.publishEvent("schedule.execution.completed", {
617
+ scheduleId: schedule.scheduleId,
618
+ executionId,
619
+ sessionId,
620
+ status: completed.status,
621
+ durationMs:
622
+ completed.startedAt && completed.endedAt
623
+ ? completed.endedAt - completed.startedAt
624
+ : undefined,
625
+ });
626
+ return completed;
627
+ } catch (error) {
628
+ const status: ScheduleExecutionStatus =
629
+ error instanceof TimeoutError ? "timeout" : "failed";
630
+ if (sessionId && status === "timeout") {
631
+ try {
632
+ await this.options.runtimeHandlers.abortSession(sessionId);
633
+ } catch {
634
+ // best effort
635
+ }
636
+ }
637
+ const failed: ScheduleExecutionRecord = {
638
+ executionId,
639
+ scheduleId: schedule.scheduleId,
640
+ sessionId,
641
+ triggeredAt: triggeredAtMs,
642
+ startedAt,
643
+ endedAt: Date.now(),
644
+ status,
645
+ errorMessage: toErrorMessage(error),
646
+ };
647
+ this.store.recordExecution(failed);
648
+ this.publishEvent("schedule.execution.completed", {
649
+ scheduleId: schedule.scheduleId,
650
+ executionId,
651
+ sessionId,
652
+ status: failed.status,
653
+ errorMessage: failed.errorMessage,
654
+ });
655
+ return failed;
656
+ } finally {
657
+ if (sessionId) {
658
+ try {
659
+ await this.options.runtimeHandlers.stopSession(sessionId);
660
+ } catch {
661
+ // best effort
662
+ }
663
+ }
664
+ this.activeExecutions.delete(executionId);
665
+ this.resourceLimiter.release(schedule.scheduleId, executionId);
666
+ }
667
+ }
668
+
669
+ private publishEvent(eventType: string, payload: unknown): void {
670
+ this.options.eventPublisher?.(eventType, payload);
671
+ }
672
+
673
+ private async runAutonomousIdleLoop(options: {
674
+ sessionId: string;
675
+ startRequest: ChatStartSessionRequest;
676
+ autonomous: ScheduleAutonomousOptions;
677
+ metrics: AggregatedTurnMetrics;
678
+ sendTurn: (request: ChatRunTurnRequest) => Promise<HubScheduleTurnResult>;
679
+ executionDeadlineMs?: number;
680
+ }): Promise<AggregatedTurnMetrics> {
681
+ let metrics = options.metrics;
682
+ const idleDeadline =
683
+ Date.now() + options.autonomous.idleTimeoutSeconds! * 1000;
684
+ while (Date.now() < idleDeadline) {
685
+ if (
686
+ options.executionDeadlineMs !== undefined &&
687
+ Date.now() >= options.executionDeadlineMs
688
+ ) {
689
+ throw new TimeoutError("scheduled execution timed out");
690
+ }
691
+ await sleep(options.autonomous.pollIntervalSeconds! * 1000);
692
+ const result = await options.sendTurn({
693
+ config: options.startRequest,
694
+ prompt: buildAutonomousPollPrompt(options.autonomous),
695
+ });
696
+ metrics = addTurnMetrics(metrics, result);
697
+ if (isAutonomousNoop(result)) {
698
+ break;
699
+ }
700
+ }
701
+ return metrics;
702
+ }
703
+ }