aidp 0.39.8 → 0.39.9
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.
- checksums.yaml +4 -4
- data/lib/aidp/jobs/background_runner.rb +76 -10
- data/lib/aidp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48e9a4d5c62e99992a0b61d3547b20b2a7598c13dbc6ba8b23a4f8126cd74c35
|
|
4
|
+
data.tar.gz: 9427e78702251d548a938d5b93f466b9514686128162c4be5bb52eb8375013a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb8f0d861f8b85f8df71034a2e6cbef875be34dcf28eacec009aaa65c8589527b904fa060598b43c9854e63c9b354ca0299bb70b96eb5da80e36c19b6ea0a033
|
|
7
|
+
data.tar.gz: a46a2734b9094b966c242a284b1673cd956df4281294557b813925d19f8204bdf470ad93d4a1c01e27a2846de2b6544258d062f0f36dacd29607827a7bc3049c
|
|
@@ -13,6 +13,8 @@ module Aidp
|
|
|
13
13
|
# Manages background execution of work loops
|
|
14
14
|
# Runs harness in daemon process and tracks job metadata
|
|
15
15
|
class BackgroundRunner
|
|
16
|
+
StartError = Class.new(StandardError)
|
|
17
|
+
|
|
16
18
|
include Aidp::MessageDisplay
|
|
17
19
|
include Aidp::RescueLogging
|
|
18
20
|
include Aidp::SafeDirectory
|
|
@@ -34,9 +36,11 @@ module Aidp
|
|
|
34
36
|
# Start a background job
|
|
35
37
|
# Returns job_id
|
|
36
38
|
def start(mode, options = {})
|
|
39
|
+
ensure_jobs_directory
|
|
37
40
|
job_id = generate_job_id
|
|
38
|
-
log_file =
|
|
39
|
-
pid_file =
|
|
41
|
+
log_file = job_file_path(job_id, "output.log")
|
|
42
|
+
pid_file = job_file_path(job_id, "job.pid")
|
|
43
|
+
raise_start_error unless log_file && pid_file
|
|
40
44
|
|
|
41
45
|
# Create job directory
|
|
42
46
|
FileUtils.mkdir_p(File.dirname(log_file))
|
|
@@ -109,6 +113,9 @@ module Aidp
|
|
|
109
113
|
metadata = load_job_metadata(job_id)
|
|
110
114
|
return nil unless metadata
|
|
111
115
|
|
|
116
|
+
log_file = job_log_path(job_id)
|
|
117
|
+
return nil unless log_file
|
|
118
|
+
|
|
112
119
|
# Check if process is still running
|
|
113
120
|
pid = metadata[:pid]
|
|
114
121
|
running = pid && process_running?(pid)
|
|
@@ -125,7 +132,7 @@ module Aidp
|
|
|
125
132
|
started_at: metadata[:started_at],
|
|
126
133
|
completed_at: metadata[:completed_at],
|
|
127
134
|
checkpoint: checkpoint,
|
|
128
|
-
log_file:
|
|
135
|
+
log_file: log_file
|
|
129
136
|
}
|
|
130
137
|
end
|
|
131
138
|
|
|
@@ -169,12 +176,12 @@ module Aidp
|
|
|
169
176
|
|
|
170
177
|
# Get job logs
|
|
171
178
|
def job_logs(job_id, options = {})
|
|
172
|
-
log_file =
|
|
179
|
+
log_file = job_log_path(job_id)
|
|
180
|
+
return nil unless log_file
|
|
173
181
|
return nil unless File.exist?(log_file)
|
|
174
182
|
|
|
175
183
|
if options[:tail]
|
|
176
|
-
|
|
177
|
-
`tail -n #{lines} #{log_file}`
|
|
184
|
+
tail_job_logs(log_file, options[:lines])
|
|
178
185
|
else
|
|
179
186
|
File.read(log_file)
|
|
180
187
|
end
|
|
@@ -182,7 +189,8 @@ module Aidp
|
|
|
182
189
|
|
|
183
190
|
# Follow job logs in real-time
|
|
184
191
|
def follow_job_logs(job_id)
|
|
185
|
-
log_file =
|
|
192
|
+
log_file = job_log_path(job_id)
|
|
193
|
+
return unless log_file
|
|
186
194
|
return unless File.exist?(log_file)
|
|
187
195
|
|
|
188
196
|
# Use tail -f to follow logs
|
|
@@ -195,6 +203,10 @@ module Aidp
|
|
|
195
203
|
@jobs_dir = safe_mkdir_p(@jobs_dir, component_name: "BackgroundRunner")
|
|
196
204
|
end
|
|
197
205
|
|
|
206
|
+
def raise_start_error
|
|
207
|
+
raise StartError, "Unable to create or access background job directory: #{@jobs_dir}"
|
|
208
|
+
end
|
|
209
|
+
|
|
198
210
|
def generate_job_id
|
|
199
211
|
timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
|
|
200
212
|
random = SecureRandom.hex(4)
|
|
@@ -202,7 +214,8 @@ module Aidp
|
|
|
202
214
|
end
|
|
203
215
|
|
|
204
216
|
def save_job_metadata(job_id, pid, mode, options)
|
|
205
|
-
metadata_file =
|
|
217
|
+
metadata_file = job_metadata_path(job_id)
|
|
218
|
+
return unless metadata_file
|
|
206
219
|
|
|
207
220
|
metadata = {
|
|
208
221
|
job_id: job_id,
|
|
@@ -217,7 +230,8 @@ module Aidp
|
|
|
217
230
|
end
|
|
218
231
|
|
|
219
232
|
def load_job_metadata(job_id)
|
|
220
|
-
metadata_file =
|
|
233
|
+
metadata_file = job_metadata_path(job_id)
|
|
234
|
+
return nil unless metadata_file
|
|
221
235
|
return nil unless File.exist?(metadata_file)
|
|
222
236
|
|
|
223
237
|
# Return raw metadata with times as ISO8601 strings to avoid unsafe class loading
|
|
@@ -231,7 +245,8 @@ module Aidp
|
|
|
231
245
|
return unless metadata
|
|
232
246
|
|
|
233
247
|
metadata.merge!(updates)
|
|
234
|
-
metadata_file =
|
|
248
|
+
metadata_file = job_metadata_path(job_id)
|
|
249
|
+
return unless metadata_file
|
|
235
250
|
File.write(metadata_file, metadata.to_yaml)
|
|
236
251
|
end
|
|
237
252
|
|
|
@@ -298,6 +313,57 @@ module Aidp
|
|
|
298
313
|
metadata[:status] || "completed"
|
|
299
314
|
end
|
|
300
315
|
end
|
|
316
|
+
|
|
317
|
+
def job_log_path(job_id)
|
|
318
|
+
job_file_path(job_id, "output.log")
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def job_metadata_path(job_id)
|
|
322
|
+
job_file_path(job_id, "metadata.yml")
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def job_file_path(job_id, file_name)
|
|
326
|
+
return unless Dir.exist?(@jobs_dir)
|
|
327
|
+
|
|
328
|
+
jobs_dir = File.realpath(@jobs_dir)
|
|
329
|
+
resolved_job_dir = resolved_job_dir_path(jobs_dir, job_id)
|
|
330
|
+
return unless resolved_job_dir
|
|
331
|
+
return unless resolved_job_dir.start_with?("#{jobs_dir}/")
|
|
332
|
+
|
|
333
|
+
resolved_job_file_path(resolved_job_dir, file_name)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def tail_job_logs(log_file, requested_lines)
|
|
337
|
+
lines = Integer(requested_lines || 50, exception: false)
|
|
338
|
+
lines = 50 unless lines&.positive?
|
|
339
|
+
|
|
340
|
+
IO.popen(["tail", "-n", lines.to_s, log_file], &:read)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def resolved_job_dir_path(jobs_dir, job_id)
|
|
344
|
+
job_dir = File.join(@jobs_dir, job_id.to_s)
|
|
345
|
+
|
|
346
|
+
if File.exist?(job_dir)
|
|
347
|
+
File.realpath(job_dir)
|
|
348
|
+
else
|
|
349
|
+
File.join(jobs_dir, job_id.to_s)
|
|
350
|
+
end
|
|
351
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
352
|
+
nil
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def resolved_job_file_path(job_dir, file_name)
|
|
356
|
+
candidate = File.join(job_dir, file_name)
|
|
357
|
+
return candidate unless File.exist?(candidate) || File.symlink?(candidate)
|
|
358
|
+
return if File.symlink?(candidate)
|
|
359
|
+
|
|
360
|
+
resolved_file = File.realpath(candidate)
|
|
361
|
+
return unless resolved_file.start_with?("#{job_dir}/")
|
|
362
|
+
|
|
363
|
+
resolved_file
|
|
364
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
365
|
+
nil
|
|
366
|
+
end
|
|
301
367
|
end
|
|
302
368
|
end
|
|
303
369
|
end
|
data/lib/aidp/version.rb
CHANGED