@herdctl/core 0.0.1 → 0.1.0

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 (284) hide show
  1. package/dist/config/__tests__/agent.test.js +61 -13
  2. package/dist/config/__tests__/agent.test.js.map +1 -1
  3. package/dist/config/__tests__/merge.test.js +10 -3
  4. package/dist/config/__tests__/merge.test.js.map +1 -1
  5. package/dist/config/__tests__/schema.test.js +350 -1
  6. package/dist/config/__tests__/schema.test.js.map +1 -1
  7. package/dist/config/index.d.ts +1 -1
  8. package/dist/config/index.d.ts.map +1 -1
  9. package/dist/config/index.js +3 -1
  10. package/dist/config/index.js.map +1 -1
  11. package/dist/config/schema.d.ts +841 -27
  12. package/dist/config/schema.d.ts.map +1 -1
  13. package/dist/config/schema.js +129 -10
  14. package/dist/config/schema.js.map +1 -1
  15. package/dist/fleet-manager/__tests__/coverage.test.js +14 -331
  16. package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -1
  17. package/dist/fleet-manager/__tests__/errors.test.js +1 -49
  18. package/dist/fleet-manager/__tests__/errors.test.js.map +1 -1
  19. package/dist/fleet-manager/__tests__/integration.test.js +114 -0
  20. package/dist/fleet-manager/__tests__/integration.test.js.map +1 -1
  21. package/dist/fleet-manager/__tests__/job-control.test.js +13 -14
  22. package/dist/fleet-manager/__tests__/job-control.test.js.map +1 -1
  23. package/dist/fleet-manager/__tests__/reload.test.js +12 -2
  24. package/dist/fleet-manager/__tests__/reload.test.js.map +1 -1
  25. package/dist/fleet-manager/__tests__/status-queries.test.js +6 -0
  26. package/dist/fleet-manager/__tests__/status-queries.test.js.map +1 -1
  27. package/dist/fleet-manager/__tests__/trigger.test.js +10 -2
  28. package/dist/fleet-manager/__tests__/trigger.test.js.map +1 -1
  29. package/dist/fleet-manager/config-reload.d.ts +164 -0
  30. package/dist/fleet-manager/config-reload.d.ts.map +1 -0
  31. package/dist/fleet-manager/config-reload.js +445 -0
  32. package/dist/fleet-manager/config-reload.js.map +1 -0
  33. package/dist/fleet-manager/context.d.ts +76 -0
  34. package/dist/fleet-manager/context.d.ts.map +1 -0
  35. package/dist/fleet-manager/context.js +11 -0
  36. package/dist/fleet-manager/context.js.map +1 -0
  37. package/dist/fleet-manager/errors.d.ts +0 -25
  38. package/dist/fleet-manager/errors.d.ts.map +1 -1
  39. package/dist/fleet-manager/errors.js +0 -38
  40. package/dist/fleet-manager/errors.js.map +1 -1
  41. package/dist/fleet-manager/event-emitters.d.ts +123 -0
  42. package/dist/fleet-manager/event-emitters.d.ts.map +1 -0
  43. package/dist/fleet-manager/event-emitters.js +136 -0
  44. package/dist/fleet-manager/event-emitters.js.map +1 -0
  45. package/dist/fleet-manager/event-types.d.ts +0 -15
  46. package/dist/fleet-manager/event-types.d.ts.map +1 -1
  47. package/dist/fleet-manager/fleet-manager.d.ts +40 -653
  48. package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
  49. package/dist/fleet-manager/fleet-manager.js +95 -1720
  50. package/dist/fleet-manager/fleet-manager.js.map +1 -1
  51. package/dist/fleet-manager/index.d.ts +13 -2
  52. package/dist/fleet-manager/index.d.ts.map +1 -1
  53. package/dist/fleet-manager/index.js +19 -6
  54. package/dist/fleet-manager/index.js.map +1 -1
  55. package/dist/fleet-manager/job-control.d.ts +67 -0
  56. package/dist/fleet-manager/job-control.d.ts.map +1 -0
  57. package/dist/fleet-manager/job-control.js +333 -0
  58. package/dist/fleet-manager/job-control.js.map +1 -0
  59. package/dist/fleet-manager/log-streaming.d.ts +171 -0
  60. package/dist/fleet-manager/log-streaming.d.ts.map +1 -0
  61. package/dist/fleet-manager/log-streaming.js +503 -0
  62. package/dist/fleet-manager/log-streaming.js.map +1 -0
  63. package/dist/fleet-manager/schedule-executor.d.ts +63 -0
  64. package/dist/fleet-manager/schedule-executor.d.ts.map +1 -0
  65. package/dist/fleet-manager/schedule-executor.js +209 -0
  66. package/dist/fleet-manager/schedule-executor.js.map +1 -0
  67. package/dist/fleet-manager/schedule-management.d.ts +71 -0
  68. package/dist/fleet-manager/schedule-management.d.ts.map +1 -0
  69. package/dist/fleet-manager/schedule-management.js +171 -0
  70. package/dist/fleet-manager/schedule-management.js.map +1 -0
  71. package/dist/fleet-manager/status-queries.d.ts +105 -0
  72. package/dist/fleet-manager/status-queries.d.ts.map +1 -0
  73. package/dist/fleet-manager/status-queries.js +247 -0
  74. package/dist/fleet-manager/status-queries.js.map +1 -0
  75. package/dist/fleet-manager/types.d.ts +0 -39
  76. package/dist/fleet-manager/types.d.ts.map +1 -1
  77. package/dist/runner/__tests__/job-executor.test.js +206 -1
  78. package/dist/runner/__tests__/job-executor.test.js.map +1 -1
  79. package/dist/runner/job-executor.d.ts +9 -0
  80. package/dist/runner/job-executor.d.ts.map +1 -1
  81. package/dist/runner/job-executor.js +78 -4
  82. package/dist/runner/job-executor.js.map +1 -1
  83. package/dist/runner/message-processor.d.ts.map +1 -1
  84. package/dist/runner/message-processor.js +53 -0
  85. package/dist/runner/message-processor.js.map +1 -1
  86. package/dist/runner/types.d.ts +3 -1
  87. package/dist/runner/types.d.ts.map +1 -1
  88. package/dist/scheduler/__tests__/cron.test.d.ts +2 -0
  89. package/dist/scheduler/__tests__/cron.test.d.ts.map +1 -0
  90. package/dist/scheduler/__tests__/cron.test.js +867 -0
  91. package/dist/scheduler/__tests__/cron.test.js.map +1 -0
  92. package/dist/scheduler/__tests__/scheduler.test.js +164 -5
  93. package/dist/scheduler/__tests__/scheduler.test.js.map +1 -1
  94. package/dist/scheduler/cron.d.ts +126 -0
  95. package/dist/scheduler/cron.d.ts.map +1 -0
  96. package/dist/scheduler/cron.js +390 -0
  97. package/dist/scheduler/cron.js.map +1 -0
  98. package/dist/scheduler/errors.d.ts +81 -1
  99. package/dist/scheduler/errors.d.ts.map +1 -1
  100. package/dist/scheduler/errors.js +81 -6
  101. package/dist/scheduler/errors.js.map +1 -1
  102. package/dist/scheduler/index.d.ts +1 -0
  103. package/dist/scheduler/index.d.ts.map +1 -1
  104. package/dist/scheduler/index.js +2 -0
  105. package/dist/scheduler/index.js.map +1 -1
  106. package/dist/scheduler/schedule-runner.d.ts +2 -2
  107. package/dist/scheduler/schedule-runner.d.ts.map +1 -1
  108. package/dist/scheduler/schedule-runner.js +20 -8
  109. package/dist/scheduler/schedule-runner.js.map +1 -1
  110. package/dist/scheduler/scheduler.d.ts +4 -4
  111. package/dist/scheduler/scheduler.d.ts.map +1 -1
  112. package/dist/scheduler/scheduler.js +95 -20
  113. package/dist/scheduler/scheduler.js.map +1 -1
  114. package/dist/scheduler/types.d.ts +1 -1
  115. package/dist/scheduler/types.d.ts.map +1 -1
  116. package/dist/state/schemas/job-metadata.d.ts +2 -2
  117. package/package.json +33 -8
  118. package/.turbo/turbo-build.log +0 -4
  119. package/.turbo/turbo-test.log +0 -219
  120. package/.turbo/turbo-typecheck.log +0 -4
  121. package/coverage/base.css +0 -224
  122. package/coverage/block-navigation.js +0 -87
  123. package/coverage/coverage-final.json +0 -51
  124. package/coverage/favicon.png +0 -0
  125. package/coverage/index.html +0 -251
  126. package/coverage/prettify.css +0 -1
  127. package/coverage/prettify.js +0 -2
  128. package/coverage/sort-arrow-sprite.png +0 -0
  129. package/coverage/sorter.js +0 -210
  130. package/coverage/src/config/index.html +0 -191
  131. package/coverage/src/config/index.ts.html +0 -442
  132. package/coverage/src/config/interpolate.ts.html +0 -652
  133. package/coverage/src/config/loader.ts.html +0 -1501
  134. package/coverage/src/config/merge.ts.html +0 -823
  135. package/coverage/src/config/parser.ts.html +0 -1213
  136. package/coverage/src/config/schema.ts.html +0 -1123
  137. package/coverage/src/fleet-manager/errors.ts.html +0 -2326
  138. package/coverage/src/fleet-manager/event-types.ts.html +0 -1219
  139. package/coverage/src/fleet-manager/fleet-manager.ts.html +0 -7030
  140. package/coverage/src/fleet-manager/index.html +0 -206
  141. package/coverage/src/fleet-manager/index.ts.html +0 -469
  142. package/coverage/src/fleet-manager/job-manager.ts.html +0 -2074
  143. package/coverage/src/fleet-manager/job-queue.ts.html +0 -2479
  144. package/coverage/src/fleet-manager/types.ts.html +0 -2602
  145. package/coverage/src/index.html +0 -116
  146. package/coverage/src/index.ts.html +0 -181
  147. package/coverage/src/runner/errors.ts.html +0 -1006
  148. package/coverage/src/runner/index.html +0 -191
  149. package/coverage/src/runner/index.ts.html +0 -256
  150. package/coverage/src/runner/job-executor.ts.html +0 -1429
  151. package/coverage/src/runner/message-processor.ts.html +0 -1150
  152. package/coverage/src/runner/sdk-adapter.ts.html +0 -658
  153. package/coverage/src/runner/types.ts.html +0 -559
  154. package/coverage/src/scheduler/errors.ts.html +0 -388
  155. package/coverage/src/scheduler/index.html +0 -206
  156. package/coverage/src/scheduler/index.ts.html +0 -244
  157. package/coverage/src/scheduler/interval.ts.html +0 -652
  158. package/coverage/src/scheduler/schedule-runner.ts.html +0 -1411
  159. package/coverage/src/scheduler/schedule-state.ts.html +0 -718
  160. package/coverage/src/scheduler/scheduler.ts.html +0 -1795
  161. package/coverage/src/scheduler/types.ts.html +0 -733
  162. package/coverage/src/state/directory.ts.html +0 -736
  163. package/coverage/src/state/errors.ts.html +0 -376
  164. package/coverage/src/state/fleet-state.ts.html +0 -937
  165. package/coverage/src/state/index.html +0 -221
  166. package/coverage/src/state/index.ts.html +0 -322
  167. package/coverage/src/state/job-metadata.ts.html +0 -1420
  168. package/coverage/src/state/job-output.ts.html +0 -1033
  169. package/coverage/src/state/schemas/fleet-state.ts.html +0 -445
  170. package/coverage/src/state/schemas/index.html +0 -176
  171. package/coverage/src/state/schemas/index.ts.html +0 -286
  172. package/coverage/src/state/schemas/job-metadata.ts.html +0 -628
  173. package/coverage/src/state/schemas/job-output.ts.html +0 -616
  174. package/coverage/src/state/schemas/session-info.ts.html +0 -361
  175. package/coverage/src/state/session.ts.html +0 -844
  176. package/coverage/src/state/types.ts.html +0 -262
  177. package/coverage/src/state/utils/atomic.ts.html +0 -748
  178. package/coverage/src/state/utils/index.html +0 -146
  179. package/coverage/src/state/utils/index.ts.html +0 -103
  180. package/coverage/src/state/utils/reads.ts.html +0 -1621
  181. package/coverage/src/work-sources/adapters/github.ts.html +0 -3583
  182. package/coverage/src/work-sources/adapters/index.html +0 -131
  183. package/coverage/src/work-sources/adapters/index.ts.html +0 -277
  184. package/coverage/src/work-sources/errors.ts.html +0 -298
  185. package/coverage/src/work-sources/index.html +0 -176
  186. package/coverage/src/work-sources/index.ts.html +0 -529
  187. package/coverage/src/work-sources/manager.ts.html +0 -1324
  188. package/coverage/src/work-sources/registry.ts.html +0 -619
  189. package/coverage/src/work-sources/types.ts.html +0 -568
  190. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +0 -7
  191. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +0 -1
  192. package/dist/fleet-manager/__tests__/event-helpers.test.js +0 -368
  193. package/dist/fleet-manager/__tests__/event-helpers.test.js.map +0 -1
  194. package/src/config/__tests__/agent.test.ts +0 -864
  195. package/src/config/__tests__/interpolate.test.ts +0 -644
  196. package/src/config/__tests__/loader.test.ts +0 -784
  197. package/src/config/__tests__/merge.test.ts +0 -751
  198. package/src/config/__tests__/parser.test.ts +0 -533
  199. package/src/config/__tests__/schema.test.ts +0 -873
  200. package/src/config/index.ts +0 -119
  201. package/src/config/interpolate.ts +0 -189
  202. package/src/config/loader.ts +0 -472
  203. package/src/config/merge.ts +0 -246
  204. package/src/config/parser.ts +0 -376
  205. package/src/config/schema.ts +0 -346
  206. package/src/fleet-manager/__tests__/coverage.test.ts +0 -2869
  207. package/src/fleet-manager/__tests__/errors.test.ts +0 -660
  208. package/src/fleet-manager/__tests__/event-helpers.test.ts +0 -448
  209. package/src/fleet-manager/__tests__/integration.test.ts +0 -1209
  210. package/src/fleet-manager/__tests__/job-control.test.ts +0 -283
  211. package/src/fleet-manager/__tests__/job-manager.test.ts +0 -869
  212. package/src/fleet-manager/__tests__/job-queue.test.ts +0 -401
  213. package/src/fleet-manager/__tests__/reload.test.ts +0 -751
  214. package/src/fleet-manager/__tests__/status-queries.test.ts +0 -595
  215. package/src/fleet-manager/__tests__/trigger.test.ts +0 -601
  216. package/src/fleet-manager/errors.ts +0 -747
  217. package/src/fleet-manager/event-types.ts +0 -378
  218. package/src/fleet-manager/fleet-manager.ts +0 -2315
  219. package/src/fleet-manager/index.ts +0 -128
  220. package/src/fleet-manager/job-manager.ts +0 -663
  221. package/src/fleet-manager/job-queue.ts +0 -798
  222. package/src/fleet-manager/types.ts +0 -839
  223. package/src/index.ts +0 -32
  224. package/src/runner/__tests__/errors.test.ts +0 -382
  225. package/src/runner/__tests__/job-executor.test.ts +0 -1708
  226. package/src/runner/__tests__/message-processor.test.ts +0 -960
  227. package/src/runner/__tests__/sdk-adapter.test.ts +0 -626
  228. package/src/runner/errors.ts +0 -307
  229. package/src/runner/index.ts +0 -57
  230. package/src/runner/job-executor.ts +0 -448
  231. package/src/runner/message-processor.ts +0 -355
  232. package/src/runner/sdk-adapter.ts +0 -191
  233. package/src/runner/types.ts +0 -158
  234. package/src/scheduler/__tests__/errors.test.ts +0 -159
  235. package/src/scheduler/__tests__/interval.test.ts +0 -515
  236. package/src/scheduler/__tests__/schedule-runner.test.ts +0 -798
  237. package/src/scheduler/__tests__/schedule-state.test.ts +0 -671
  238. package/src/scheduler/__tests__/scheduler.test.ts +0 -1280
  239. package/src/scheduler/errors.ts +0 -101
  240. package/src/scheduler/index.ts +0 -53
  241. package/src/scheduler/interval.ts +0 -189
  242. package/src/scheduler/schedule-runner.ts +0 -442
  243. package/src/scheduler/schedule-state.ts +0 -211
  244. package/src/scheduler/scheduler.ts +0 -570
  245. package/src/scheduler/types.ts +0 -216
  246. package/src/state/__tests__/directory.test.ts +0 -595
  247. package/src/state/__tests__/fleet-state.test.ts +0 -868
  248. package/src/state/__tests__/job-metadata-schema.test.ts +0 -414
  249. package/src/state/__tests__/job-metadata.test.ts +0 -831
  250. package/src/state/__tests__/job-output.test.ts +0 -856
  251. package/src/state/__tests__/session-schema.test.ts +0 -378
  252. package/src/state/__tests__/session.test.ts +0 -604
  253. package/src/state/directory.ts +0 -217
  254. package/src/state/errors.ts +0 -97
  255. package/src/state/fleet-state.ts +0 -284
  256. package/src/state/index.ts +0 -79
  257. package/src/state/job-metadata.ts +0 -445
  258. package/src/state/job-output.ts +0 -316
  259. package/src/state/schemas/__tests__/job-output.test.ts +0 -338
  260. package/src/state/schemas/fleet-state.ts +0 -120
  261. package/src/state/schemas/index.ts +0 -67
  262. package/src/state/schemas/job-metadata.ts +0 -181
  263. package/src/state/schemas/job-output.ts +0 -177
  264. package/src/state/schemas/session-info.ts +0 -92
  265. package/src/state/session.ts +0 -253
  266. package/src/state/types.ts +0 -59
  267. package/src/state/utils/__tests__/atomic.test.ts +0 -723
  268. package/src/state/utils/__tests__/reads.test.ts +0 -1071
  269. package/src/state/utils/atomic.ts +0 -221
  270. package/src/state/utils/index.ts +0 -6
  271. package/src/state/utils/reads.ts +0 -512
  272. package/src/work-sources/__tests__/github.test.ts +0 -1800
  273. package/src/work-sources/__tests__/manager.test.ts +0 -529
  274. package/src/work-sources/__tests__/registry.test.ts +0 -477
  275. package/src/work-sources/__tests__/types.test.ts +0 -479
  276. package/src/work-sources/adapters/github.ts +0 -1166
  277. package/src/work-sources/adapters/index.ts +0 -64
  278. package/src/work-sources/errors.ts +0 -71
  279. package/src/work-sources/index.ts +0 -148
  280. package/src/work-sources/manager.ts +0 -413
  281. package/src/work-sources/registry.ts +0 -178
  282. package/src/work-sources/types.ts +0 -161
  283. package/tsconfig.json +0 -9
  284. package/vitest.config.ts +0 -19
@@ -1,442 +0,0 @@
1
- /**
2
- * Schedule Runner for executing triggered schedules
3
- *
4
- * Handles the actual execution of scheduled work by:
5
- * 1. Fetching work items from work sources (if configured)
6
- * 2. Building prompts from schedule config and work items
7
- * 3. Invoking the JobExecutor to run the agent
8
- * 4. Updating schedule state and reporting outcomes
9
- */
10
-
11
- import type { ResolvedAgent, Schedule } from "../config/index.js";
12
- import type { ScheduleState } from "../state/schemas/fleet-state.js";
13
- import type { RunnerResult, SDKQueryFunction } from "../runner/index.js";
14
- import type {
15
- WorkSourceManager,
16
- WorkItem,
17
- WorkResult,
18
- WorkOutcome,
19
- } from "../work-sources/index.js";
20
- import { JobExecutor, type JobExecutorOptions } from "../runner/index.js";
21
- import {
22
- updateScheduleState,
23
- type ScheduleStateLogger,
24
- } from "./schedule-state.js";
25
- import { calculateNextTrigger } from "./interval.js";
26
-
27
- // =============================================================================
28
- // Types
29
- // =============================================================================
30
-
31
- /**
32
- * Logger interface for schedule runner
33
- */
34
- export interface ScheduleRunnerLogger {
35
- debug: (message: string) => void;
36
- info: (message: string) => void;
37
- warn: (message: string) => void;
38
- error: (message: string) => void;
39
- }
40
-
41
- /**
42
- * Trigger metadata passed to the JobExecutor
43
- */
44
- export interface TriggerMetadata {
45
- /** The type of trigger */
46
- triggerType: "interval";
47
- /** The name of the schedule that triggered */
48
- schedule: string;
49
- /** Work item ID if this run is processing a work item */
50
- workItemId?: string;
51
- /** Work item title for reference */
52
- workItemTitle?: string;
53
- }
54
-
55
- /**
56
- * Options for running a schedule
57
- */
58
- export interface RunScheduleOptions {
59
- /** The agent to run */
60
- agent: ResolvedAgent;
61
- /** The name of the schedule */
62
- scheduleName: string;
63
- /** The schedule configuration */
64
- schedule: Schedule;
65
- /** Current schedule state */
66
- scheduleState: ScheduleState;
67
- /** Path to the state directory (e.g., .herdctl) */
68
- stateDir: string;
69
- /** The SDK query function for agent execution */
70
- sdkQuery: SDKQueryFunction;
71
- /** Optional work source manager for fetching work items */
72
- workSourceManager?: WorkSourceManager;
73
- /** Optional logger */
74
- logger?: ScheduleRunnerLogger;
75
- /** Optional job executor options */
76
- executorOptions?: JobExecutorOptions;
77
- }
78
-
79
- /**
80
- * Result of running a schedule, extending RunnerResult with schedule-specific info
81
- */
82
- export interface ScheduleRunResult extends RunnerResult {
83
- /** The work item that was processed (if any) */
84
- workItem?: WorkItem;
85
- /** Whether a work item was fetched and processed */
86
- processedWorkItem: boolean;
87
- }
88
-
89
- // =============================================================================
90
- // Default Logger
91
- // =============================================================================
92
-
93
- const defaultLogger: ScheduleRunnerLogger = {
94
- debug: (message: string) => console.debug(`[schedule-runner] ${message}`),
95
- info: (message: string) => console.info(`[schedule-runner] ${message}`),
96
- warn: (message: string) => console.warn(`[schedule-runner] ${message}`),
97
- error: (message: string) => console.error(`[schedule-runner] ${message}`),
98
- };
99
-
100
- // =============================================================================
101
- // Build Schedule Prompt
102
- // =============================================================================
103
-
104
- /**
105
- * Build the prompt for a scheduled run
106
- *
107
- * Constructs the prompt by combining:
108
- * 1. The schedule's configured prompt (if any)
109
- * 2. Work item details (if a work item was fetched)
110
- *
111
- * @param schedule - The schedule configuration
112
- * @param workItem - Optional work item fetched from work source
113
- * @returns The constructed prompt string
114
- *
115
- * @example Without work item
116
- * ```typescript
117
- * const prompt = buildSchedulePrompt(
118
- * { type: 'interval', interval: '1h', prompt: 'Check for updates' }
119
- * );
120
- * // => "Check for updates"
121
- * ```
122
- *
123
- * @example With work item
124
- * ```typescript
125
- * const prompt = buildSchedulePrompt(
126
- * { type: 'interval', interval: '1h', prompt: 'Process this issue:' },
127
- * { title: 'Fix bug', description: 'There is a bug in auth.ts', ... }
128
- * );
129
- * // => "Process this issue:\n\n## Work Item: Fix bug\n\nThere is a bug in auth.ts\n\n..."
130
- * ```
131
- */
132
- export function buildSchedulePrompt(
133
- schedule: Schedule,
134
- workItem?: WorkItem
135
- ): string {
136
- const parts: string[] = [];
137
-
138
- // Add schedule prompt if configured
139
- if (schedule.prompt) {
140
- parts.push(schedule.prompt);
141
- }
142
-
143
- // Add work item details if provided
144
- if (workItem) {
145
- const workItemSection = formatWorkItem(workItem);
146
- parts.push(workItemSection);
147
- }
148
-
149
- // If no prompt and no work item, provide a default
150
- if (parts.length === 0) {
151
- return "Execute scheduled task.";
152
- }
153
-
154
- return parts.join("\n\n");
155
- }
156
-
157
- /**
158
- * Format a work item for inclusion in a prompt
159
- */
160
- function formatWorkItem(workItem: WorkItem): string {
161
- const lines: string[] = [];
162
-
163
- lines.push(`## Work Item: ${workItem.title}`);
164
- lines.push("");
165
-
166
- if (workItem.description) {
167
- lines.push(workItem.description);
168
- lines.push("");
169
- }
170
-
171
- // Add metadata
172
- const metadata: string[] = [];
173
- metadata.push(`- **Source:** ${workItem.source}`);
174
- metadata.push(`- **ID:** ${workItem.externalId}`);
175
- metadata.push(`- **Priority:** ${workItem.priority}`);
176
-
177
- if (workItem.labels.length > 0) {
178
- metadata.push(`- **Labels:** ${workItem.labels.join(", ")}`);
179
- }
180
-
181
- if (workItem.url) {
182
- metadata.push(`- **URL:** ${workItem.url}`);
183
- }
184
-
185
- lines.push(metadata.join("\n"));
186
-
187
- return lines.join("\n");
188
- }
189
-
190
- // =============================================================================
191
- // Run Schedule
192
- // =============================================================================
193
-
194
- /**
195
- * Execute a triggered schedule
196
- *
197
- * This function handles the complete lifecycle of a scheduled execution:
198
- * 1. Updates schedule state to 'running'
199
- * 2. Fetches work item from work source (if configured)
200
- * 3. Builds the prompt from schedule config and work item
201
- * 4. Invokes the JobExecutor to run the agent
202
- * 5. Reports outcome to work source (if applicable)
203
- * 6. Updates schedule state with last_run_at and next_run_at
204
- *
205
- * @param options - Options for running the schedule
206
- * @returns Result of the schedule execution
207
- *
208
- * @example Basic usage
209
- * ```typescript
210
- * const result = await runSchedule({
211
- * agent: resolvedAgent,
212
- * scheduleName: 'hourly',
213
- * schedule: { type: 'interval', interval: '1h', prompt: 'Check status' },
214
- * scheduleState: { status: 'idle', last_run_at: null },
215
- * stateDir: '.herdctl',
216
- * sdkQuery: query,
217
- * });
218
- *
219
- * console.log(`Job ${result.jobId} completed: ${result.success}`);
220
- * ```
221
- *
222
- * @example With work source
223
- * ```typescript
224
- * const result = await runSchedule({
225
- * agent: resolvedAgent,
226
- * scheduleName: 'issue-processor',
227
- * schedule: {
228
- * type: 'interval',
229
- * interval: '5m',
230
- * prompt: 'Process this GitHub issue:',
231
- * work_source: { type: 'github', owner: 'org', repo: 'repo' }
232
- * },
233
- * scheduleState: { status: 'idle', last_run_at: null },
234
- * stateDir: '.herdctl',
235
- * sdkQuery: query,
236
- * workSourceManager: manager,
237
- * });
238
- *
239
- * if (result.workItem) {
240
- * console.log(`Processed work item: ${result.workItem.title}`);
241
- * }
242
- * ```
243
- */
244
- export async function runSchedule(
245
- options: RunScheduleOptions
246
- ): Promise<ScheduleRunResult> {
247
- const {
248
- agent,
249
- scheduleName,
250
- schedule,
251
- stateDir,
252
- sdkQuery,
253
- workSourceManager,
254
- logger = defaultLogger,
255
- executorOptions,
256
- } = options;
257
-
258
- const stateLogger: ScheduleStateLogger = { warn: logger.warn };
259
-
260
- logger.info(`Running schedule ${agent.name}/${scheduleName}`);
261
-
262
- // Step 1: Update schedule state to 'running'
263
- await updateScheduleState(
264
- stateDir,
265
- agent.name,
266
- scheduleName,
267
- {
268
- status: "running",
269
- last_run_at: new Date().toISOString(),
270
- },
271
- { logger: stateLogger }
272
- );
273
-
274
- let workItem: WorkItem | undefined;
275
- let processedWorkItem = false;
276
-
277
- try {
278
- // Step 2: Fetch work item if schedule has work_source configured
279
- if (schedule.work_source && workSourceManager) {
280
- logger.debug(
281
- `Fetching work item for ${agent.name}/${scheduleName} from ${schedule.work_source.type}`
282
- );
283
-
284
- const workResult = await workSourceManager.getNextWorkItem(agent, {
285
- autoClaim: true,
286
- });
287
-
288
- if (workResult.item && workResult.claimed) {
289
- workItem = workResult.item;
290
- processedWorkItem = true;
291
- logger.info(
292
- `Claimed work item ${workItem.id}: ${workItem.title}`
293
- );
294
- } else if (workResult.item && !workResult.claimed) {
295
- // Work item found but claim failed (race condition)
296
- logger.warn(
297
- `Work item ${workResult.item.id} found but claim failed: ${workResult.claimResult?.reason}`
298
- );
299
- } else {
300
- // No work available
301
- logger.debug(
302
- `No work items available for ${agent.name}/${scheduleName}`
303
- );
304
- }
305
- }
306
-
307
- // Step 3: Build the prompt
308
- const prompt = buildSchedulePrompt(schedule, workItem);
309
-
310
- // Step 4: Build trigger metadata
311
- const triggerMetadata: TriggerMetadata = {
312
- triggerType: "interval",
313
- schedule: scheduleName,
314
- };
315
-
316
- if (workItem) {
317
- triggerMetadata.workItemId = workItem.id;
318
- triggerMetadata.workItemTitle = workItem.title;
319
- }
320
-
321
- // Step 5: Execute the agent via JobExecutor
322
- const executor = new JobExecutor(sdkQuery, executorOptions);
323
-
324
- const runnerResult = await executor.execute({
325
- agent,
326
- prompt,
327
- stateDir,
328
- triggerType: "schedule",
329
- schedule: scheduleName,
330
- });
331
-
332
- // Step 6: Report outcome to work source if we processed a work item
333
- if (workItem && workSourceManager) {
334
- const workResult = buildWorkResult(runnerResult);
335
-
336
- try {
337
- await workSourceManager.reportOutcome(workItem.id, workResult, {
338
- agent,
339
- });
340
- logger.info(
341
- `Reported outcome for work item ${workItem.id}: ${workResult.outcome}`
342
- );
343
- } catch (reportError) {
344
- logger.error(
345
- `Failed to report outcome for work item ${workItem.id}: ${(reportError as Error).message}`
346
- );
347
- // Don't fail the overall run if reporting fails
348
- }
349
- }
350
-
351
- // Step 7: Calculate next trigger time
352
- const nextTrigger = schedule.interval
353
- ? calculateNextTrigger(new Date(), schedule.interval)
354
- : null;
355
-
356
- // Step 8: Update schedule state with success
357
- await updateScheduleState(
358
- stateDir,
359
- agent.name,
360
- scheduleName,
361
- {
362
- status: "idle",
363
- next_run_at: nextTrigger?.toISOString() ?? null,
364
- last_error: runnerResult.success ? null : runnerResult.error?.message,
365
- },
366
- { logger: stateLogger }
367
- );
368
-
369
- logger.info(
370
- `Completed schedule ${agent.name}/${scheduleName}: ${runnerResult.success ? "success" : "failed"}`
371
- );
372
-
373
- return {
374
- ...runnerResult,
375
- workItem,
376
- processedWorkItem,
377
- };
378
- } catch (error) {
379
- const errorMessage =
380
- error instanceof Error ? error.message : String(error);
381
-
382
- logger.error(
383
- `Error running schedule ${agent.name}/${scheduleName}: ${errorMessage}`
384
- );
385
-
386
- // Release work item if we claimed one and execution failed unexpectedly
387
- if (workItem && workSourceManager) {
388
- try {
389
- await workSourceManager.releaseWorkItem(workItem.id, {
390
- agent,
391
- reason: `Unexpected error: ${errorMessage}`,
392
- addComment: true,
393
- });
394
- logger.info(`Released work item ${workItem.id} due to error`);
395
- } catch (releaseError) {
396
- logger.error(
397
- `Failed to release work item ${workItem.id}: ${(releaseError as Error).message}`
398
- );
399
- }
400
- }
401
-
402
- // Calculate next trigger time even on error
403
- const nextTrigger = schedule.interval
404
- ? calculateNextTrigger(new Date(), schedule.interval)
405
- : null;
406
-
407
- // Update schedule state with error
408
- await updateScheduleState(
409
- stateDir,
410
- agent.name,
411
- scheduleName,
412
- {
413
- status: "idle",
414
- next_run_at: nextTrigger?.toISOString() ?? null,
415
- last_error: errorMessage,
416
- },
417
- { logger: stateLogger }
418
- );
419
-
420
- // Re-throw to let caller handle
421
- throw error;
422
- }
423
- }
424
-
425
- // =============================================================================
426
- // Helper Functions
427
- // =============================================================================
428
-
429
- /**
430
- * Build a WorkResult from a RunnerResult for reporting to work sources
431
- */
432
- function buildWorkResult(runnerResult: RunnerResult): WorkResult {
433
- const outcome: WorkOutcome = runnerResult.success ? "success" : "failure";
434
-
435
- return {
436
- outcome,
437
- summary:
438
- runnerResult.summary ?? (runnerResult.success ? "Task completed successfully" : "Task failed"),
439
- error: runnerResult.error?.message,
440
- artifacts: runnerResult.jobId ? [`job:${runnerResult.jobId}`] : undefined,
441
- };
442
- }
@@ -1,211 +0,0 @@
1
- /**
2
- * Schedule state management
3
- *
4
- * Provides functions for reading and updating schedule state within fleet state.
5
- * Schedule state is stored per-agent in the `schedules` map within AgentState.
6
- */
7
-
8
- import { join } from "node:path";
9
- import {
10
- type ScheduleState,
11
- type FleetState,
12
- createDefaultScheduleState,
13
- } from "../state/schemas/fleet-state.js";
14
- import { readFleetState, writeFleetState } from "../state/fleet-state.js";
15
- import { STATE_FILE_NAME } from "../state/types.js";
16
-
17
- /**
18
- * Logger interface for warning messages
19
- */
20
- export interface ScheduleStateLogger {
21
- warn: (message: string) => void;
22
- }
23
-
24
- /**
25
- * Default console logger
26
- */
27
- const defaultLogger: ScheduleStateLogger = {
28
- warn: (message: string) => console.warn(`[herdctl] ${message}`),
29
- };
30
-
31
- /**
32
- * Options for schedule state operations
33
- */
34
- export interface ScheduleStateOptions {
35
- /**
36
- * Logger for warning messages
37
- * Default: console.warn
38
- */
39
- logger?: ScheduleStateLogger;
40
- }
41
-
42
- /**
43
- * Partial updates for schedule state
44
- */
45
- export type ScheduleStateUpdates = Partial<ScheduleState>;
46
-
47
- /**
48
- * Get the state file path from a state directory
49
- */
50
- function getStateFilePath(stateDir: string): string {
51
- return join(stateDir, STATE_FILE_NAME);
52
- }
53
-
54
- /**
55
- * Get the schedule state for a specific agent and schedule
56
- *
57
- * Returns default state if the agent or schedule doesn't exist.
58
- *
59
- * @param stateDir - Path to the state directory (e.g., .herdctl)
60
- * @param agentName - Name of the agent
61
- * @param scheduleName - Name of the schedule
62
- * @param options - Options including logger
63
- * @returns The schedule state, or default state if not found
64
- *
65
- * @example
66
- * ```typescript
67
- * const state = await getScheduleState('.herdctl', 'my-agent', 'hourly');
68
- * console.log(state.last_run_at);
69
- * console.log(state.next_run_at);
70
- * console.log(state.status);
71
- * ```
72
- */
73
- export async function getScheduleState(
74
- stateDir: string,
75
- agentName: string,
76
- scheduleName: string,
77
- options: ScheduleStateOptions = {}
78
- ): Promise<ScheduleState> {
79
- const stateFilePath = getStateFilePath(stateDir);
80
- const fleetState = await readFleetState(stateFilePath, options);
81
-
82
- const agentState = fleetState.agents[agentName];
83
- if (!agentState || !agentState.schedules) {
84
- return createDefaultScheduleState();
85
- }
86
-
87
- const scheduleState = agentState.schedules[scheduleName];
88
- if (!scheduleState) {
89
- return createDefaultScheduleState();
90
- }
91
-
92
- return scheduleState;
93
- }
94
-
95
- /**
96
- * Update the schedule state for a specific agent and schedule
97
- *
98
- * This function:
99
- * 1. Reads current fleet state
100
- * 2. Applies partial updates to the specified schedule
101
- * 3. Writes the updated state back atomically
102
- *
103
- * If the agent or schedule doesn't exist, it will be created.
104
- *
105
- * @param stateDir - Path to the state directory (e.g., .herdctl)
106
- * @param agentName - Name of the agent
107
- * @param scheduleName - Name of the schedule
108
- * @param updates - Partial ScheduleState updates to apply
109
- * @param options - Options including logger
110
- * @returns The updated ScheduleState
111
- *
112
- * @example
113
- * ```typescript
114
- * // Mark schedule as running
115
- * await updateScheduleState('.herdctl', 'my-agent', 'hourly', {
116
- * status: 'running',
117
- * last_run_at: new Date().toISOString(),
118
- * });
119
- *
120
- * // Record error
121
- * await updateScheduleState('.herdctl', 'my-agent', 'hourly', {
122
- * status: 'idle',
123
- * last_error: 'Container exited with code 1',
124
- * });
125
- *
126
- * // Clear error
127
- * await updateScheduleState('.herdctl', 'my-agent', 'hourly', {
128
- * last_error: null,
129
- * });
130
- * ```
131
- */
132
- export async function updateScheduleState(
133
- stateDir: string,
134
- agentName: string,
135
- scheduleName: string,
136
- updates: ScheduleStateUpdates,
137
- options: ScheduleStateOptions = {}
138
- ): Promise<ScheduleState> {
139
- const stateFilePath = getStateFilePath(stateDir);
140
- const fleetState = await readFleetState(stateFilePath, options);
141
-
142
- // Get or create agent state
143
- const currentAgentState = fleetState.agents[agentName] ?? { status: "idle" };
144
-
145
- // Get current schedules map or create empty one
146
- const currentSchedules = currentAgentState.schedules ?? {};
147
-
148
- // Get current schedule state or create default
149
- const currentScheduleState =
150
- currentSchedules[scheduleName] ?? createDefaultScheduleState();
151
-
152
- // Merge updates
153
- const updatedScheduleState: ScheduleState = {
154
- ...currentScheduleState,
155
- ...updates,
156
- };
157
-
158
- // Update the fleet state
159
- const updatedFleetState: FleetState = {
160
- ...fleetState,
161
- agents: {
162
- ...fleetState.agents,
163
- [agentName]: {
164
- ...currentAgentState,
165
- schedules: {
166
- ...currentSchedules,
167
- [scheduleName]: updatedScheduleState,
168
- },
169
- },
170
- },
171
- };
172
-
173
- // Write back
174
- await writeFleetState(stateFilePath, updatedFleetState);
175
-
176
- return updatedScheduleState;
177
- }
178
-
179
- /**
180
- * Get all schedule states for a specific agent
181
- *
182
- * Returns an empty object if the agent doesn't exist or has no schedules.
183
- *
184
- * @param stateDir - Path to the state directory (e.g., .herdctl)
185
- * @param agentName - Name of the agent
186
- * @param options - Options including logger
187
- * @returns Map of schedule names to their state
188
- *
189
- * @example
190
- * ```typescript
191
- * const schedules = await getAgentScheduleStates('.herdctl', 'my-agent');
192
- * for (const [name, state] of Object.entries(schedules)) {
193
- * console.log(`${name}: ${state.status}, last run: ${state.last_run_at}`);
194
- * }
195
- * ```
196
- */
197
- export async function getAgentScheduleStates(
198
- stateDir: string,
199
- agentName: string,
200
- options: ScheduleStateOptions = {}
201
- ): Promise<Record<string, ScheduleState>> {
202
- const stateFilePath = getStateFilePath(stateDir);
203
- const fleetState = await readFleetState(stateFilePath, options);
204
-
205
- const agentState = fleetState.agents[agentName];
206
- if (!agentState || !agentState.schedules) {
207
- return {};
208
- }
209
-
210
- return agentState.schedules;
211
- }