@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
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Log Streaming Module (US-8)
3
+ *
4
+ * Provides log streaming functionality as async generators for the FleetManager.
5
+ * Isolates the async generator patterns for streaming logs from agents, jobs,
6
+ * and the fleet.
7
+ *
8
+ * This module provides three main streaming functions:
9
+ * - `streamLogs` - Stream all fleet logs with filtering options
10
+ * - `streamJobOutput` - Stream output from a specific job
11
+ * - `streamAgentLogs` - Stream logs for a specific agent
12
+ */
13
+ import type { JobMetadata } from "../state/schemas/job-metadata.js";
14
+ import type { LogLevel, LogEntry, LogStreamOptions } from "./types.js";
15
+ import type { FleetManagerContext } from "./context.js";
16
+ /**
17
+ * LogStreaming provides log streaming operations for the FleetManager.
18
+ *
19
+ * This class encapsulates the logic for streaming logs from agents, jobs,
20
+ * and the fleet using the FleetManagerContext pattern.
21
+ */
22
+ export declare class LogStreaming {
23
+ private ctx;
24
+ constructor(ctx: FleetManagerContext);
25
+ private getDeps;
26
+ /**
27
+ * Stream all fleet logs as an async iterable
28
+ *
29
+ * Provides a unified stream of logs from all sources in the fleet including
30
+ * agents, jobs, and the scheduler. Logs can be filtered by level and optionally
31
+ * by agent or job.
32
+ *
33
+ * @param options - Options for filtering and configuring the stream
34
+ * @returns An async iterable of LogEntry objects
35
+ */
36
+ streamLogs(options?: LogStreamOptions): AsyncIterable<LogEntry>;
37
+ /**
38
+ * Stream output from a specific job as an async iterable
39
+ *
40
+ * @param jobId - The ID of the job to stream output from
41
+ * @returns An async iterable of LogEntry objects
42
+ * @throws {JobNotFoundError} If the job doesn't exist
43
+ */
44
+ streamJobOutput(jobId: string): AsyncIterable<LogEntry>;
45
+ /**
46
+ * Stream logs for a specific agent as an async iterable
47
+ *
48
+ * @param agentName - The name of the agent to stream logs for
49
+ * @returns An async iterable of LogEntry objects
50
+ * @throws {AgentNotFoundError} If the agent doesn't exist in the configuration
51
+ */
52
+ streamAgentLogs(agentName: string): AsyncIterable<LogEntry>;
53
+ }
54
+ /**
55
+ * Convert a job output message to a LogEntry
56
+ *
57
+ * Transforms raw job output (as stored in JSONL files) into a structured
58
+ * LogEntry for the streaming API.
59
+ *
60
+ * @param job - The job metadata
61
+ * @param msg - The raw output message
62
+ * @returns A LogEntry representing the message
63
+ */
64
+ export declare function jobOutputToLogEntry(job: JobMetadata, msg: {
65
+ type: string;
66
+ content?: string;
67
+ timestamp?: string;
68
+ }): LogEntry;
69
+ /**
70
+ * Determine if a log entry should be yielded based on filters
71
+ *
72
+ * Checks log level, agent name, and job ID filters to determine
73
+ * if an entry should be included in the stream.
74
+ *
75
+ * @param entry - The log entry to check
76
+ * @param minLevel - Minimum log level to include
77
+ * @param agentFilter - Optional agent name filter
78
+ * @param jobFilter - Optional job ID filter
79
+ * @returns True if the entry should be yielded
80
+ */
81
+ export declare function shouldYieldLog(entry: LogEntry, minLevel: LogLevel, agentFilter?: string, jobFilter?: string): boolean;
82
+ /**
83
+ * Get the log level order value
84
+ *
85
+ * Utility function to get the numeric ordering of a log level.
86
+ *
87
+ * @param level - The log level
88
+ * @returns The numeric ordering (0=debug, 1=info, 2=warn, 3=error)
89
+ */
90
+ export declare function getLogLevelOrder(level: LogLevel): number;
91
+ /**
92
+ * Compare two log levels
93
+ *
94
+ * @param a - First log level
95
+ * @param b - Second log level
96
+ * @returns Negative if a < b, 0 if equal, positive if a > b
97
+ */
98
+ export declare function compareLogLevels(a: LogLevel, b: LogLevel): number;
99
+ /**
100
+ * Check if a log level meets the minimum threshold
101
+ *
102
+ * @param level - The log level to check
103
+ * @param minLevel - The minimum required level
104
+ * @returns True if level >= minLevel
105
+ */
106
+ export declare function meetsLogLevel(level: LogLevel, minLevel: LogLevel): boolean;
107
+ /**
108
+ * Filter log entries by level
109
+ *
110
+ * Creates a filtering function that only passes logs at or above
111
+ * the specified level.
112
+ *
113
+ * @param minLevel - Minimum log level to include
114
+ * @returns A filter function
115
+ */
116
+ export declare function createLogLevelFilter(minLevel: LogLevel): (entry: LogEntry) => boolean;
117
+ /**
118
+ * Filter log entries by agent
119
+ *
120
+ * Creates a filtering function that only passes logs from
121
+ * the specified agent.
122
+ *
123
+ * @param agentName - Agent name to filter by
124
+ * @returns A filter function
125
+ */
126
+ export declare function createAgentFilter(agentName: string): (entry: LogEntry) => boolean;
127
+ /**
128
+ * Filter log entries by job
129
+ *
130
+ * Creates a filtering function that only passes logs from
131
+ * the specified job.
132
+ *
133
+ * @param jobId - Job ID to filter by
134
+ * @returns A filter function
135
+ */
136
+ export declare function createJobFilter(jobId: string): (entry: LogEntry) => boolean;
137
+ /**
138
+ * Combine multiple log filters
139
+ *
140
+ * Creates a filter that passes only if all provided filters pass.
141
+ *
142
+ * @param filters - Array of filter functions
143
+ * @returns A combined filter function
144
+ */
145
+ export declare function combineLogFilters(...filters: ((entry: LogEntry) => boolean)[]): (entry: LogEntry) => boolean;
146
+ /**
147
+ * Create a log entry from raw data
148
+ *
149
+ * Factory function for creating LogEntry objects with proper defaults.
150
+ *
151
+ * @param data - Partial log entry data
152
+ * @returns A complete LogEntry
153
+ */
154
+ export declare function createLogEntry(data: Partial<LogEntry> & {
155
+ message: string;
156
+ }): LogEntry;
157
+ /**
158
+ * Format a log entry as a string
159
+ *
160
+ * Creates a human-readable string representation of a log entry.
161
+ *
162
+ * @param entry - The log entry to format
163
+ * @param options - Formatting options
164
+ * @returns A formatted string
165
+ */
166
+ export declare function formatLogEntry(entry: LogEntry, options?: {
167
+ includeTimestamp?: boolean;
168
+ includeSource?: boolean;
169
+ includeContext?: boolean;
170
+ }): string;
171
+ //# sourceMappingURL=log-streaming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-streaming.d.ts","sourceRoot":"","sources":["../../src/fleet-manager/log-streaming.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,KAAK,EAEV,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAEjB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AA+BxD;;;;;GAKG;AACH,qBAAa,YAAY;IACX,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,mBAAmB;IAE5C,OAAO,CAAC,OAAO;IASf;;;;;;;;;OASG;IACI,UAAU,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,aAAa,CAAC,QAAQ,CAAC;IAItE;;;;;;OAMG;IACI,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC;IAI9D;;;;;;OAMG;IACI,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC;CAGnE;AAMD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D,QAAQ,CAmBV;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,QAAQ,EACf,QAAQ,EAAE,QAAQ,EAClB,WAAW,CAAC,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAiBT;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,MAAM,CAEjE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAE1E;AAgSD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,GACjB,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAE9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAChB,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAE9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAE3E;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,EAAE,GAC3C,CAAC,KAAK,EAAE,QAAQ,KAAK,OAAO,CAE9B;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAC5C,QAAQ,CAWV;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,QAAQ,EACf,OAAO,CAAC,EAAE;IACR,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GACA,MAAM,CAyBR"}
@@ -0,0 +1,503 @@
1
+ /**
2
+ * Log Streaming Module (US-8)
3
+ *
4
+ * Provides log streaming functionality as async generators for the FleetManager.
5
+ * Isolates the async generator patterns for streaming logs from agents, jobs,
6
+ * and the fleet.
7
+ *
8
+ * This module provides three main streaming functions:
9
+ * - `streamLogs` - Stream all fleet logs with filtering options
10
+ * - `streamJobOutput` - Stream output from a specific job
11
+ * - `streamAgentLogs` - Stream logs for a specific agent
12
+ */
13
+ import { join } from "node:path";
14
+ import { AgentNotFoundError, JobNotFoundError } from "./errors.js";
15
+ // =============================================================================
16
+ // Constants
17
+ // =============================================================================
18
+ /**
19
+ * Log level severity ordering for filtering
20
+ */
21
+ const LOG_LEVEL_ORDER = {
22
+ debug: 0,
23
+ info: 1,
24
+ warn: 2,
25
+ error: 3,
26
+ };
27
+ /**
28
+ * LogStreaming provides log streaming operations for the FleetManager.
29
+ *
30
+ * This class encapsulates the logic for streaming logs from agents, jobs,
31
+ * and the fleet using the FleetManagerContext pattern.
32
+ */
33
+ export class LogStreaming {
34
+ ctx;
35
+ constructor(ctx) {
36
+ this.ctx = ctx;
37
+ }
38
+ getDeps() {
39
+ return {
40
+ stateDir: this.ctx.getStateDir(),
41
+ config: this.ctx.getConfig(),
42
+ logger: this.ctx.getLogger(),
43
+ emitter: this.ctx.getEmitter(),
44
+ };
45
+ }
46
+ /**
47
+ * Stream all fleet logs as an async iterable
48
+ *
49
+ * Provides a unified stream of logs from all sources in the fleet including
50
+ * agents, jobs, and the scheduler. Logs can be filtered by level and optionally
51
+ * by agent or job.
52
+ *
53
+ * @param options - Options for filtering and configuring the stream
54
+ * @returns An async iterable of LogEntry objects
55
+ */
56
+ async *streamLogs(options) {
57
+ yield* streamLogsImpl(this.getDeps(), options);
58
+ }
59
+ /**
60
+ * Stream output from a specific job as an async iterable
61
+ *
62
+ * @param jobId - The ID of the job to stream output from
63
+ * @returns An async iterable of LogEntry objects
64
+ * @throws {JobNotFoundError} If the job doesn't exist
65
+ */
66
+ async *streamJobOutput(jobId) {
67
+ yield* streamJobOutputImpl(this.getDeps(), jobId);
68
+ }
69
+ /**
70
+ * Stream logs for a specific agent as an async iterable
71
+ *
72
+ * @param agentName - The name of the agent to stream logs for
73
+ * @returns An async iterable of LogEntry objects
74
+ * @throws {AgentNotFoundError} If the agent doesn't exist in the configuration
75
+ */
76
+ async *streamAgentLogs(agentName) {
77
+ yield* streamAgentLogsImpl(this.getDeps(), agentName);
78
+ }
79
+ }
80
+ // =============================================================================
81
+ // Helper Functions
82
+ // =============================================================================
83
+ /**
84
+ * Convert a job output message to a LogEntry
85
+ *
86
+ * Transforms raw job output (as stored in JSONL files) into a structured
87
+ * LogEntry for the streaming API.
88
+ *
89
+ * @param job - The job metadata
90
+ * @param msg - The raw output message
91
+ * @returns A LogEntry representing the message
92
+ */
93
+ export function jobOutputToLogEntry(job, msg) {
94
+ // Determine log level based on message type
95
+ let level = "info";
96
+ if (msg.type === "error") {
97
+ level = "error";
98
+ }
99
+ else if (msg.type === "system") {
100
+ level = "debug";
101
+ }
102
+ return {
103
+ timestamp: msg.timestamp ?? new Date().toISOString(),
104
+ level,
105
+ source: "job",
106
+ agentName: job.agent,
107
+ jobId: job.id,
108
+ scheduleName: job.schedule ?? undefined,
109
+ message: msg.content ?? "",
110
+ data: { type: msg.type },
111
+ };
112
+ }
113
+ /**
114
+ * Determine if a log entry should be yielded based on filters
115
+ *
116
+ * Checks log level, agent name, and job ID filters to determine
117
+ * if an entry should be included in the stream.
118
+ *
119
+ * @param entry - The log entry to check
120
+ * @param minLevel - Minimum log level to include
121
+ * @param agentFilter - Optional agent name filter
122
+ * @param jobFilter - Optional job ID filter
123
+ * @returns True if the entry should be yielded
124
+ */
125
+ export function shouldYieldLog(entry, minLevel, agentFilter, jobFilter) {
126
+ // Check log level
127
+ if (LOG_LEVEL_ORDER[entry.level] < LOG_LEVEL_ORDER[minLevel]) {
128
+ return false;
129
+ }
130
+ // Check agent filter
131
+ if (agentFilter && entry.agentName !== agentFilter) {
132
+ return false;
133
+ }
134
+ // Check job filter
135
+ if (jobFilter && entry.jobId !== jobFilter) {
136
+ return false;
137
+ }
138
+ return true;
139
+ }
140
+ /**
141
+ * Get the log level order value
142
+ *
143
+ * Utility function to get the numeric ordering of a log level.
144
+ *
145
+ * @param level - The log level
146
+ * @returns The numeric ordering (0=debug, 1=info, 2=warn, 3=error)
147
+ */
148
+ export function getLogLevelOrder(level) {
149
+ return LOG_LEVEL_ORDER[level];
150
+ }
151
+ /**
152
+ * Compare two log levels
153
+ *
154
+ * @param a - First log level
155
+ * @param b - Second log level
156
+ * @returns Negative if a < b, 0 if equal, positive if a > b
157
+ */
158
+ export function compareLogLevels(a, b) {
159
+ return LOG_LEVEL_ORDER[a] - LOG_LEVEL_ORDER[b];
160
+ }
161
+ /**
162
+ * Check if a log level meets the minimum threshold
163
+ *
164
+ * @param level - The log level to check
165
+ * @param minLevel - The minimum required level
166
+ * @returns True if level >= minLevel
167
+ */
168
+ export function meetsLogLevel(level, minLevel) {
169
+ return LOG_LEVEL_ORDER[level] >= LOG_LEVEL_ORDER[minLevel];
170
+ }
171
+ // =============================================================================
172
+ // Internal Streaming Implementation Functions
173
+ // =============================================================================
174
+ /**
175
+ * Internal implementation for streaming fleet logs
176
+ */
177
+ async function* streamLogsImpl(deps, options) {
178
+ const level = options?.level ?? "info";
179
+ const includeHistory = options?.includeHistory ?? true;
180
+ const historyLimit = options?.historyLimit ?? 1000;
181
+ const agentFilter = options?.agentName;
182
+ const jobFilter = options?.jobId;
183
+ const jobsDir = join(deps.stateDir, "jobs");
184
+ const { readJobOutputAll } = await import("../state/job-output.js");
185
+ const { listJobs } = await import("../state/index.js");
186
+ // Replay historical logs if requested
187
+ if (includeHistory) {
188
+ // Get jobs to replay history from
189
+ const jobsResult = await listJobs(jobsDir, agentFilter ? { agent: agentFilter } : {}, { logger: deps.logger });
190
+ // Filter by job ID if specified
191
+ let jobs = jobsResult.jobs;
192
+ if (jobFilter) {
193
+ jobs = jobs.filter((j) => j.id === jobFilter);
194
+ }
195
+ // Sort by started_at ascending to replay in chronological order
196
+ jobs.sort((a, b) => new Date(a.started_at).getTime() - new Date(b.started_at).getTime());
197
+ let yielded = 0;
198
+ for (const job of jobs) {
199
+ if (yielded >= historyLimit)
200
+ break;
201
+ // Read job output and convert to log entries
202
+ const output = await readJobOutputAll(jobsDir, job.id, {
203
+ skipInvalidLines: true,
204
+ logger: deps.logger,
205
+ });
206
+ for (const msg of output) {
207
+ if (yielded >= historyLimit)
208
+ break;
209
+ const logEntry = jobOutputToLogEntry(job, msg);
210
+ if (shouldYieldLog(logEntry, level, agentFilter, jobFilter)) {
211
+ yield logEntry;
212
+ yielded++;
213
+ }
214
+ }
215
+ }
216
+ }
217
+ // For running jobs, subscribe to job:output events
218
+ const outputQueue = [];
219
+ let resolveWait = null;
220
+ let stopped = false;
221
+ const outputHandler = (payload) => {
222
+ if (stopped)
223
+ return;
224
+ const logEntry = {
225
+ timestamp: payload.timestamp,
226
+ level: "info",
227
+ source: "job",
228
+ agentName: payload.agentName,
229
+ jobId: payload.jobId,
230
+ message: payload.output,
231
+ };
232
+ if (shouldYieldLog(logEntry, level, agentFilter, jobFilter)) {
233
+ outputQueue.push(logEntry);
234
+ if (resolveWait) {
235
+ resolveWait();
236
+ resolveWait = null;
237
+ }
238
+ }
239
+ };
240
+ deps.emitter.on("job:output", outputHandler);
241
+ try {
242
+ // Yield queued entries as they arrive
243
+ while (!stopped) {
244
+ while (outputQueue.length > 0) {
245
+ const entry = outputQueue.shift();
246
+ yield entry;
247
+ }
248
+ // Wait for more entries
249
+ await new Promise((resolve) => {
250
+ resolveWait = resolve;
251
+ // Add timeout to prevent hanging forever
252
+ setTimeout(resolve, 1000);
253
+ });
254
+ }
255
+ }
256
+ finally {
257
+ stopped = true;
258
+ deps.emitter.off("job:output", outputHandler);
259
+ }
260
+ }
261
+ /**
262
+ * Internal implementation for streaming job output
263
+ */
264
+ async function* streamJobOutputImpl(deps, jobId) {
265
+ const jobsDir = join(deps.stateDir, "jobs");
266
+ const { getJob } = await import("../state/index.js");
267
+ // Verify job exists
268
+ const job = await getJob(jobsDir, jobId, { logger: deps.logger });
269
+ if (!job) {
270
+ throw new JobNotFoundError(jobId);
271
+ }
272
+ const { readJobOutputAll, getJobOutputPath } = await import("../state/job-output.js");
273
+ const { watch } = await import("node:fs");
274
+ const { stat } = await import("node:fs/promises");
275
+ const { createReadStream } = await import("node:fs");
276
+ const { createInterface } = await import("node:readline");
277
+ const outputPath = getJobOutputPath(jobsDir, jobId);
278
+ // First, replay all existing output
279
+ const existingOutput = await readJobOutputAll(jobsDir, jobId, {
280
+ skipInvalidLines: true,
281
+ logger: deps.logger,
282
+ });
283
+ for (const msg of existingOutput) {
284
+ yield jobOutputToLogEntry(job, msg);
285
+ }
286
+ // If job is already completed, we're done
287
+ if (job.status !== "running" && job.status !== "pending") {
288
+ return;
289
+ }
290
+ // For running jobs, watch for new output
291
+ const outputQueue = [];
292
+ let resolveWait = null;
293
+ let stopped = false;
294
+ let lastReadPosition = 0;
295
+ // Get current file position
296
+ try {
297
+ const stats = await stat(outputPath);
298
+ lastReadPosition = stats.size;
299
+ }
300
+ catch {
301
+ // File doesn't exist yet
302
+ }
303
+ // Watch for file changes
304
+ let watcher = null;
305
+ try {
306
+ watcher = watch(outputPath, async (eventType) => {
307
+ if (stopped || eventType !== "change")
308
+ return;
309
+ try {
310
+ const currentStats = await stat(outputPath);
311
+ if (currentStats.size > lastReadPosition) {
312
+ // Read new content
313
+ const fileStream = createReadStream(outputPath, {
314
+ encoding: "utf-8",
315
+ start: lastReadPosition,
316
+ });
317
+ const rl = createInterface({
318
+ input: fileStream,
319
+ crlfDelay: Infinity,
320
+ });
321
+ for await (const line of rl) {
322
+ if (stopped)
323
+ break;
324
+ const trimmedLine = line.trim();
325
+ if (trimmedLine === "")
326
+ continue;
327
+ try {
328
+ const parsed = JSON.parse(trimmedLine);
329
+ const logEntry = jobOutputToLogEntry(job, parsed);
330
+ outputQueue.push(logEntry);
331
+ if (resolveWait) {
332
+ resolveWait();
333
+ resolveWait = null;
334
+ }
335
+ }
336
+ catch {
337
+ // Skip malformed lines
338
+ }
339
+ }
340
+ rl.close();
341
+ fileStream.destroy();
342
+ lastReadPosition = currentStats.size;
343
+ }
344
+ }
345
+ catch (err) {
346
+ deps.logger.warn(`Error reading output file: ${err.message}`);
347
+ }
348
+ });
349
+ }
350
+ catch {
351
+ // Can't watch file - might not exist yet
352
+ }
353
+ // Poll for job completion
354
+ const checkJobComplete = async () => {
355
+ const { getJob: getJobFn } = await import("../state/index.js");
356
+ const currentJob = await getJobFn(jobsDir, jobId, { logger: deps.logger });
357
+ return (!currentJob ||
358
+ (currentJob.status !== "running" && currentJob.status !== "pending"));
359
+ };
360
+ try {
361
+ // Yield queued entries as they arrive
362
+ while (!stopped) {
363
+ while (outputQueue.length > 0) {
364
+ const entry = outputQueue.shift();
365
+ yield entry;
366
+ }
367
+ // Check if job is complete
368
+ if (await checkJobComplete()) {
369
+ stopped = true;
370
+ break;
371
+ }
372
+ // Wait for more entries
373
+ await new Promise((resolve) => {
374
+ resolveWait = resolve;
375
+ setTimeout(resolve, 1000);
376
+ });
377
+ }
378
+ }
379
+ finally {
380
+ stopped = true;
381
+ if (watcher) {
382
+ watcher.close();
383
+ }
384
+ }
385
+ }
386
+ /**
387
+ * Internal implementation for streaming agent logs
388
+ */
389
+ async function* streamAgentLogsImpl(deps, agentName) {
390
+ // Verify agent exists
391
+ const agents = deps.config?.agents ?? [];
392
+ const agent = agents.find((a) => a.name === agentName);
393
+ if (!agent) {
394
+ throw new AgentNotFoundError(agentName, {
395
+ availableAgents: agents.map((a) => a.name),
396
+ });
397
+ }
398
+ // Delegate to streamLogsImpl with agent filter
399
+ yield* streamLogsImpl(deps, {
400
+ agentName,
401
+ includeHistory: true,
402
+ });
403
+ }
404
+ // =============================================================================
405
+ // Utility Functions for Log Processing
406
+ // =============================================================================
407
+ /**
408
+ * Filter log entries by level
409
+ *
410
+ * Creates a filtering function that only passes logs at or above
411
+ * the specified level.
412
+ *
413
+ * @param minLevel - Minimum log level to include
414
+ * @returns A filter function
415
+ */
416
+ export function createLogLevelFilter(minLevel) {
417
+ return (entry) => meetsLogLevel(entry.level, minLevel);
418
+ }
419
+ /**
420
+ * Filter log entries by agent
421
+ *
422
+ * Creates a filtering function that only passes logs from
423
+ * the specified agent.
424
+ *
425
+ * @param agentName - Agent name to filter by
426
+ * @returns A filter function
427
+ */
428
+ export function createAgentFilter(agentName) {
429
+ return (entry) => entry.agentName === agentName;
430
+ }
431
+ /**
432
+ * Filter log entries by job
433
+ *
434
+ * Creates a filtering function that only passes logs from
435
+ * the specified job.
436
+ *
437
+ * @param jobId - Job ID to filter by
438
+ * @returns A filter function
439
+ */
440
+ export function createJobFilter(jobId) {
441
+ return (entry) => entry.jobId === jobId;
442
+ }
443
+ /**
444
+ * Combine multiple log filters
445
+ *
446
+ * Creates a filter that passes only if all provided filters pass.
447
+ *
448
+ * @param filters - Array of filter functions
449
+ * @returns A combined filter function
450
+ */
451
+ export function combineLogFilters(...filters) {
452
+ return (entry) => filters.every((f) => f(entry));
453
+ }
454
+ /**
455
+ * Create a log entry from raw data
456
+ *
457
+ * Factory function for creating LogEntry objects with proper defaults.
458
+ *
459
+ * @param data - Partial log entry data
460
+ * @returns A complete LogEntry
461
+ */
462
+ export function createLogEntry(data) {
463
+ return {
464
+ timestamp: data.timestamp ?? new Date().toISOString(),
465
+ level: data.level ?? "info",
466
+ source: data.source ?? "fleet",
467
+ agentName: data.agentName,
468
+ jobId: data.jobId,
469
+ scheduleName: data.scheduleName,
470
+ message: data.message,
471
+ data: data.data,
472
+ };
473
+ }
474
+ /**
475
+ * Format a log entry as a string
476
+ *
477
+ * Creates a human-readable string representation of a log entry.
478
+ *
479
+ * @param entry - The log entry to format
480
+ * @param options - Formatting options
481
+ * @returns A formatted string
482
+ */
483
+ export function formatLogEntry(entry, options) {
484
+ const parts = [];
485
+ if (options?.includeTimestamp !== false) {
486
+ parts.push(`[${entry.timestamp}]`);
487
+ }
488
+ parts.push(`[${entry.level.toUpperCase()}]`);
489
+ if (options?.includeSource !== false && entry.source) {
490
+ parts.push(`[${entry.source}]`);
491
+ }
492
+ if (options?.includeContext !== false) {
493
+ if (entry.agentName) {
494
+ parts.push(`[${entry.agentName}]`);
495
+ }
496
+ if (entry.jobId) {
497
+ parts.push(`[${entry.jobId}]`);
498
+ }
499
+ }
500
+ parts.push(entry.message);
501
+ return parts.join(" ");
502
+ }
503
+ //# sourceMappingURL=log-streaming.js.map