@herdctl/core 0.0.1 → 0.0.2

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 (275) hide show
  1. package/dist/config/__tests__/agent.test.js +31 -13
  2. package/dist/config/__tests__/agent.test.js.map +1 -1
  3. package/dist/config/__tests__/merge.test.js +9 -2
  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 +828 -24
  12. package/dist/config/schema.d.ts.map +1 -1
  13. package/dist/config/schema.js +118 -6
  14. package/dist/config/schema.js.map +1 -1
  15. package/dist/fleet-manager/__tests__/coverage.test.js +11 -332
  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 +109 -0
  20. package/dist/fleet-manager/__tests__/integration.test.js.map +1 -1
  21. package/dist/fleet-manager/__tests__/reload.test.js +1 -1
  22. package/dist/fleet-manager/__tests__/reload.test.js.map +1 -1
  23. package/dist/fleet-manager/config-reload.d.ts +164 -0
  24. package/dist/fleet-manager/config-reload.d.ts.map +1 -0
  25. package/dist/fleet-manager/config-reload.js +445 -0
  26. package/dist/fleet-manager/config-reload.js.map +1 -0
  27. package/dist/fleet-manager/context.d.ts +76 -0
  28. package/dist/fleet-manager/context.d.ts.map +1 -0
  29. package/dist/fleet-manager/context.js +11 -0
  30. package/dist/fleet-manager/context.js.map +1 -0
  31. package/dist/fleet-manager/errors.d.ts +0 -25
  32. package/dist/fleet-manager/errors.d.ts.map +1 -1
  33. package/dist/fleet-manager/errors.js +0 -38
  34. package/dist/fleet-manager/errors.js.map +1 -1
  35. package/dist/fleet-manager/event-emitters.d.ts +123 -0
  36. package/dist/fleet-manager/event-emitters.d.ts.map +1 -0
  37. package/dist/fleet-manager/event-emitters.js +136 -0
  38. package/dist/fleet-manager/event-emitters.js.map +1 -0
  39. package/dist/fleet-manager/event-types.d.ts +0 -15
  40. package/dist/fleet-manager/event-types.d.ts.map +1 -1
  41. package/dist/fleet-manager/fleet-manager.d.ts +40 -653
  42. package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
  43. package/dist/fleet-manager/fleet-manager.js +95 -1720
  44. package/dist/fleet-manager/fleet-manager.js.map +1 -1
  45. package/dist/fleet-manager/index.d.ts +13 -2
  46. package/dist/fleet-manager/index.d.ts.map +1 -1
  47. package/dist/fleet-manager/index.js +19 -6
  48. package/dist/fleet-manager/index.js.map +1 -1
  49. package/dist/fleet-manager/job-control.d.ts +64 -0
  50. package/dist/fleet-manager/job-control.d.ts.map +1 -0
  51. package/dist/fleet-manager/job-control.js +296 -0
  52. package/dist/fleet-manager/job-control.js.map +1 -0
  53. package/dist/fleet-manager/log-streaming.d.ts +171 -0
  54. package/dist/fleet-manager/log-streaming.d.ts.map +1 -0
  55. package/dist/fleet-manager/log-streaming.js +503 -0
  56. package/dist/fleet-manager/log-streaming.js.map +1 -0
  57. package/dist/fleet-manager/schedule-executor.d.ts +63 -0
  58. package/dist/fleet-manager/schedule-executor.d.ts.map +1 -0
  59. package/dist/fleet-manager/schedule-executor.js +209 -0
  60. package/dist/fleet-manager/schedule-executor.js.map +1 -0
  61. package/dist/fleet-manager/schedule-management.d.ts +71 -0
  62. package/dist/fleet-manager/schedule-management.d.ts.map +1 -0
  63. package/dist/fleet-manager/schedule-management.js +171 -0
  64. package/dist/fleet-manager/schedule-management.js.map +1 -0
  65. package/dist/fleet-manager/status-queries.d.ts +105 -0
  66. package/dist/fleet-manager/status-queries.d.ts.map +1 -0
  67. package/dist/fleet-manager/status-queries.js +247 -0
  68. package/dist/fleet-manager/status-queries.js.map +1 -0
  69. package/dist/fleet-manager/types.d.ts +0 -39
  70. package/dist/fleet-manager/types.d.ts.map +1 -1
  71. package/dist/runner/__tests__/job-executor.test.js +206 -1
  72. package/dist/runner/__tests__/job-executor.test.js.map +1 -1
  73. package/dist/runner/job-executor.d.ts +9 -0
  74. package/dist/runner/job-executor.d.ts.map +1 -1
  75. package/dist/runner/job-executor.js +78 -4
  76. package/dist/runner/job-executor.js.map +1 -1
  77. package/dist/runner/types.d.ts +2 -0
  78. package/dist/runner/types.d.ts.map +1 -1
  79. package/dist/scheduler/__tests__/cron.test.d.ts +2 -0
  80. package/dist/scheduler/__tests__/cron.test.d.ts.map +1 -0
  81. package/dist/scheduler/__tests__/cron.test.js +867 -0
  82. package/dist/scheduler/__tests__/cron.test.js.map +1 -0
  83. package/dist/scheduler/__tests__/scheduler.test.js +164 -5
  84. package/dist/scheduler/__tests__/scheduler.test.js.map +1 -1
  85. package/dist/scheduler/cron.d.ts +126 -0
  86. package/dist/scheduler/cron.d.ts.map +1 -0
  87. package/dist/scheduler/cron.js +390 -0
  88. package/dist/scheduler/cron.js.map +1 -0
  89. package/dist/scheduler/errors.d.ts +81 -1
  90. package/dist/scheduler/errors.d.ts.map +1 -1
  91. package/dist/scheduler/errors.js +81 -6
  92. package/dist/scheduler/errors.js.map +1 -1
  93. package/dist/scheduler/index.d.ts +1 -0
  94. package/dist/scheduler/index.d.ts.map +1 -1
  95. package/dist/scheduler/index.js +2 -0
  96. package/dist/scheduler/index.js.map +1 -1
  97. package/dist/scheduler/schedule-runner.d.ts +2 -2
  98. package/dist/scheduler/schedule-runner.d.ts.map +1 -1
  99. package/dist/scheduler/schedule-runner.js +20 -8
  100. package/dist/scheduler/schedule-runner.js.map +1 -1
  101. package/dist/scheduler/scheduler.d.ts +4 -4
  102. package/dist/scheduler/scheduler.d.ts.map +1 -1
  103. package/dist/scheduler/scheduler.js +86 -20
  104. package/dist/scheduler/scheduler.js.map +1 -1
  105. package/dist/scheduler/types.d.ts +1 -1
  106. package/dist/scheduler/types.d.ts.map +1 -1
  107. package/dist/state/schemas/job-metadata.d.ts +2 -2
  108. package/package.json +33 -8
  109. package/.turbo/turbo-build.log +0 -4
  110. package/.turbo/turbo-test.log +0 -219
  111. package/.turbo/turbo-typecheck.log +0 -4
  112. package/coverage/base.css +0 -224
  113. package/coverage/block-navigation.js +0 -87
  114. package/coverage/coverage-final.json +0 -51
  115. package/coverage/favicon.png +0 -0
  116. package/coverage/index.html +0 -251
  117. package/coverage/prettify.css +0 -1
  118. package/coverage/prettify.js +0 -2
  119. package/coverage/sort-arrow-sprite.png +0 -0
  120. package/coverage/sorter.js +0 -210
  121. package/coverage/src/config/index.html +0 -191
  122. package/coverage/src/config/index.ts.html +0 -442
  123. package/coverage/src/config/interpolate.ts.html +0 -652
  124. package/coverage/src/config/loader.ts.html +0 -1501
  125. package/coverage/src/config/merge.ts.html +0 -823
  126. package/coverage/src/config/parser.ts.html +0 -1213
  127. package/coverage/src/config/schema.ts.html +0 -1123
  128. package/coverage/src/fleet-manager/errors.ts.html +0 -2326
  129. package/coverage/src/fleet-manager/event-types.ts.html +0 -1219
  130. package/coverage/src/fleet-manager/fleet-manager.ts.html +0 -7030
  131. package/coverage/src/fleet-manager/index.html +0 -206
  132. package/coverage/src/fleet-manager/index.ts.html +0 -469
  133. package/coverage/src/fleet-manager/job-manager.ts.html +0 -2074
  134. package/coverage/src/fleet-manager/job-queue.ts.html +0 -2479
  135. package/coverage/src/fleet-manager/types.ts.html +0 -2602
  136. package/coverage/src/index.html +0 -116
  137. package/coverage/src/index.ts.html +0 -181
  138. package/coverage/src/runner/errors.ts.html +0 -1006
  139. package/coverage/src/runner/index.html +0 -191
  140. package/coverage/src/runner/index.ts.html +0 -256
  141. package/coverage/src/runner/job-executor.ts.html +0 -1429
  142. package/coverage/src/runner/message-processor.ts.html +0 -1150
  143. package/coverage/src/runner/sdk-adapter.ts.html +0 -658
  144. package/coverage/src/runner/types.ts.html +0 -559
  145. package/coverage/src/scheduler/errors.ts.html +0 -388
  146. package/coverage/src/scheduler/index.html +0 -206
  147. package/coverage/src/scheduler/index.ts.html +0 -244
  148. package/coverage/src/scheduler/interval.ts.html +0 -652
  149. package/coverage/src/scheduler/schedule-runner.ts.html +0 -1411
  150. package/coverage/src/scheduler/schedule-state.ts.html +0 -718
  151. package/coverage/src/scheduler/scheduler.ts.html +0 -1795
  152. package/coverage/src/scheduler/types.ts.html +0 -733
  153. package/coverage/src/state/directory.ts.html +0 -736
  154. package/coverage/src/state/errors.ts.html +0 -376
  155. package/coverage/src/state/fleet-state.ts.html +0 -937
  156. package/coverage/src/state/index.html +0 -221
  157. package/coverage/src/state/index.ts.html +0 -322
  158. package/coverage/src/state/job-metadata.ts.html +0 -1420
  159. package/coverage/src/state/job-output.ts.html +0 -1033
  160. package/coverage/src/state/schemas/fleet-state.ts.html +0 -445
  161. package/coverage/src/state/schemas/index.html +0 -176
  162. package/coverage/src/state/schemas/index.ts.html +0 -286
  163. package/coverage/src/state/schemas/job-metadata.ts.html +0 -628
  164. package/coverage/src/state/schemas/job-output.ts.html +0 -616
  165. package/coverage/src/state/schemas/session-info.ts.html +0 -361
  166. package/coverage/src/state/session.ts.html +0 -844
  167. package/coverage/src/state/types.ts.html +0 -262
  168. package/coverage/src/state/utils/atomic.ts.html +0 -748
  169. package/coverage/src/state/utils/index.html +0 -146
  170. package/coverage/src/state/utils/index.ts.html +0 -103
  171. package/coverage/src/state/utils/reads.ts.html +0 -1621
  172. package/coverage/src/work-sources/adapters/github.ts.html +0 -3583
  173. package/coverage/src/work-sources/adapters/index.html +0 -131
  174. package/coverage/src/work-sources/adapters/index.ts.html +0 -277
  175. package/coverage/src/work-sources/errors.ts.html +0 -298
  176. package/coverage/src/work-sources/index.html +0 -176
  177. package/coverage/src/work-sources/index.ts.html +0 -529
  178. package/coverage/src/work-sources/manager.ts.html +0 -1324
  179. package/coverage/src/work-sources/registry.ts.html +0 -619
  180. package/coverage/src/work-sources/types.ts.html +0 -568
  181. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +0 -7
  182. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +0 -1
  183. package/dist/fleet-manager/__tests__/event-helpers.test.js +0 -368
  184. package/dist/fleet-manager/__tests__/event-helpers.test.js.map +0 -1
  185. package/src/config/__tests__/agent.test.ts +0 -864
  186. package/src/config/__tests__/interpolate.test.ts +0 -644
  187. package/src/config/__tests__/loader.test.ts +0 -784
  188. package/src/config/__tests__/merge.test.ts +0 -751
  189. package/src/config/__tests__/parser.test.ts +0 -533
  190. package/src/config/__tests__/schema.test.ts +0 -873
  191. package/src/config/index.ts +0 -119
  192. package/src/config/interpolate.ts +0 -189
  193. package/src/config/loader.ts +0 -472
  194. package/src/config/merge.ts +0 -246
  195. package/src/config/parser.ts +0 -376
  196. package/src/config/schema.ts +0 -346
  197. package/src/fleet-manager/__tests__/coverage.test.ts +0 -2869
  198. package/src/fleet-manager/__tests__/errors.test.ts +0 -660
  199. package/src/fleet-manager/__tests__/event-helpers.test.ts +0 -448
  200. package/src/fleet-manager/__tests__/integration.test.ts +0 -1209
  201. package/src/fleet-manager/__tests__/job-control.test.ts +0 -283
  202. package/src/fleet-manager/__tests__/job-manager.test.ts +0 -869
  203. package/src/fleet-manager/__tests__/job-queue.test.ts +0 -401
  204. package/src/fleet-manager/__tests__/reload.test.ts +0 -751
  205. package/src/fleet-manager/__tests__/status-queries.test.ts +0 -595
  206. package/src/fleet-manager/__tests__/trigger.test.ts +0 -601
  207. package/src/fleet-manager/errors.ts +0 -747
  208. package/src/fleet-manager/event-types.ts +0 -378
  209. package/src/fleet-manager/fleet-manager.ts +0 -2315
  210. package/src/fleet-manager/index.ts +0 -128
  211. package/src/fleet-manager/job-manager.ts +0 -663
  212. package/src/fleet-manager/job-queue.ts +0 -798
  213. package/src/fleet-manager/types.ts +0 -839
  214. package/src/index.ts +0 -32
  215. package/src/runner/__tests__/errors.test.ts +0 -382
  216. package/src/runner/__tests__/job-executor.test.ts +0 -1708
  217. package/src/runner/__tests__/message-processor.test.ts +0 -960
  218. package/src/runner/__tests__/sdk-adapter.test.ts +0 -626
  219. package/src/runner/errors.ts +0 -307
  220. package/src/runner/index.ts +0 -57
  221. package/src/runner/job-executor.ts +0 -448
  222. package/src/runner/message-processor.ts +0 -355
  223. package/src/runner/sdk-adapter.ts +0 -191
  224. package/src/runner/types.ts +0 -158
  225. package/src/scheduler/__tests__/errors.test.ts +0 -159
  226. package/src/scheduler/__tests__/interval.test.ts +0 -515
  227. package/src/scheduler/__tests__/schedule-runner.test.ts +0 -798
  228. package/src/scheduler/__tests__/schedule-state.test.ts +0 -671
  229. package/src/scheduler/__tests__/scheduler.test.ts +0 -1280
  230. package/src/scheduler/errors.ts +0 -101
  231. package/src/scheduler/index.ts +0 -53
  232. package/src/scheduler/interval.ts +0 -189
  233. package/src/scheduler/schedule-runner.ts +0 -442
  234. package/src/scheduler/schedule-state.ts +0 -211
  235. package/src/scheduler/scheduler.ts +0 -570
  236. package/src/scheduler/types.ts +0 -216
  237. package/src/state/__tests__/directory.test.ts +0 -595
  238. package/src/state/__tests__/fleet-state.test.ts +0 -868
  239. package/src/state/__tests__/job-metadata-schema.test.ts +0 -414
  240. package/src/state/__tests__/job-metadata.test.ts +0 -831
  241. package/src/state/__tests__/job-output.test.ts +0 -856
  242. package/src/state/__tests__/session-schema.test.ts +0 -378
  243. package/src/state/__tests__/session.test.ts +0 -604
  244. package/src/state/directory.ts +0 -217
  245. package/src/state/errors.ts +0 -97
  246. package/src/state/fleet-state.ts +0 -284
  247. package/src/state/index.ts +0 -79
  248. package/src/state/job-metadata.ts +0 -445
  249. package/src/state/job-output.ts +0 -316
  250. package/src/state/schemas/__tests__/job-output.test.ts +0 -338
  251. package/src/state/schemas/fleet-state.ts +0 -120
  252. package/src/state/schemas/index.ts +0 -67
  253. package/src/state/schemas/job-metadata.ts +0 -181
  254. package/src/state/schemas/job-output.ts +0 -177
  255. package/src/state/schemas/session-info.ts +0 -92
  256. package/src/state/session.ts +0 -253
  257. package/src/state/types.ts +0 -59
  258. package/src/state/utils/__tests__/atomic.test.ts +0 -723
  259. package/src/state/utils/__tests__/reads.test.ts +0 -1071
  260. package/src/state/utils/atomic.ts +0 -221
  261. package/src/state/utils/index.ts +0 -6
  262. package/src/state/utils/reads.ts +0 -512
  263. package/src/work-sources/__tests__/github.test.ts +0 -1800
  264. package/src/work-sources/__tests__/manager.test.ts +0 -529
  265. package/src/work-sources/__tests__/registry.test.ts +0 -477
  266. package/src/work-sources/__tests__/types.test.ts +0 -479
  267. package/src/work-sources/adapters/github.ts +0 -1166
  268. package/src/work-sources/adapters/index.ts +0 -64
  269. package/src/work-sources/errors.ts +0 -71
  270. package/src/work-sources/index.ts +0 -148
  271. package/src/work-sources/manager.ts +0 -413
  272. package/src/work-sources/registry.ts +0 -178
  273. package/src/work-sources/types.ts +0 -161
  274. package/tsconfig.json +0 -9
  275. 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
- }