@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.
- package/dist/config/__tests__/agent.test.js +31 -13
- package/dist/config/__tests__/agent.test.js.map +1 -1
- package/dist/config/__tests__/merge.test.js +9 -2
- package/dist/config/__tests__/merge.test.js.map +1 -1
- package/dist/config/__tests__/schema.test.js +350 -1
- package/dist/config/__tests__/schema.test.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +828 -24
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +118 -6
- package/dist/config/schema.js.map +1 -1
- package/dist/fleet-manager/__tests__/coverage.test.js +11 -332
- package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/errors.test.js +1 -49
- package/dist/fleet-manager/__tests__/errors.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/integration.test.js +109 -0
- package/dist/fleet-manager/__tests__/integration.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/reload.test.js +1 -1
- package/dist/fleet-manager/__tests__/reload.test.js.map +1 -1
- package/dist/fleet-manager/config-reload.d.ts +164 -0
- package/dist/fleet-manager/config-reload.d.ts.map +1 -0
- package/dist/fleet-manager/config-reload.js +445 -0
- package/dist/fleet-manager/config-reload.js.map +1 -0
- package/dist/fleet-manager/context.d.ts +76 -0
- package/dist/fleet-manager/context.d.ts.map +1 -0
- package/dist/fleet-manager/context.js +11 -0
- package/dist/fleet-manager/context.js.map +1 -0
- package/dist/fleet-manager/errors.d.ts +0 -25
- package/dist/fleet-manager/errors.d.ts.map +1 -1
- package/dist/fleet-manager/errors.js +0 -38
- package/dist/fleet-manager/errors.js.map +1 -1
- package/dist/fleet-manager/event-emitters.d.ts +123 -0
- package/dist/fleet-manager/event-emitters.d.ts.map +1 -0
- package/dist/fleet-manager/event-emitters.js +136 -0
- package/dist/fleet-manager/event-emitters.js.map +1 -0
- package/dist/fleet-manager/event-types.d.ts +0 -15
- package/dist/fleet-manager/event-types.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.d.ts +40 -653
- package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.js +95 -1720
- package/dist/fleet-manager/fleet-manager.js.map +1 -1
- package/dist/fleet-manager/index.d.ts +13 -2
- package/dist/fleet-manager/index.d.ts.map +1 -1
- package/dist/fleet-manager/index.js +19 -6
- package/dist/fleet-manager/index.js.map +1 -1
- package/dist/fleet-manager/job-control.d.ts +64 -0
- package/dist/fleet-manager/job-control.d.ts.map +1 -0
- package/dist/fleet-manager/job-control.js +296 -0
- package/dist/fleet-manager/job-control.js.map +1 -0
- package/dist/fleet-manager/log-streaming.d.ts +171 -0
- package/dist/fleet-manager/log-streaming.d.ts.map +1 -0
- package/dist/fleet-manager/log-streaming.js +503 -0
- package/dist/fleet-manager/log-streaming.js.map +1 -0
- package/dist/fleet-manager/schedule-executor.d.ts +63 -0
- package/dist/fleet-manager/schedule-executor.d.ts.map +1 -0
- package/dist/fleet-manager/schedule-executor.js +209 -0
- package/dist/fleet-manager/schedule-executor.js.map +1 -0
- package/dist/fleet-manager/schedule-management.d.ts +71 -0
- package/dist/fleet-manager/schedule-management.d.ts.map +1 -0
- package/dist/fleet-manager/schedule-management.js +171 -0
- package/dist/fleet-manager/schedule-management.js.map +1 -0
- package/dist/fleet-manager/status-queries.d.ts +105 -0
- package/dist/fleet-manager/status-queries.d.ts.map +1 -0
- package/dist/fleet-manager/status-queries.js +247 -0
- package/dist/fleet-manager/status-queries.js.map +1 -0
- package/dist/fleet-manager/types.d.ts +0 -39
- package/dist/fleet-manager/types.d.ts.map +1 -1
- package/dist/runner/__tests__/job-executor.test.js +206 -1
- package/dist/runner/__tests__/job-executor.test.js.map +1 -1
- package/dist/runner/job-executor.d.ts +9 -0
- package/dist/runner/job-executor.d.ts.map +1 -1
- package/dist/runner/job-executor.js +78 -4
- package/dist/runner/job-executor.js.map +1 -1
- package/dist/runner/types.d.ts +2 -0
- package/dist/runner/types.d.ts.map +1 -1
- package/dist/scheduler/__tests__/cron.test.d.ts +2 -0
- package/dist/scheduler/__tests__/cron.test.d.ts.map +1 -0
- package/dist/scheduler/__tests__/cron.test.js +867 -0
- package/dist/scheduler/__tests__/cron.test.js.map +1 -0
- package/dist/scheduler/__tests__/scheduler.test.js +164 -5
- package/dist/scheduler/__tests__/scheduler.test.js.map +1 -1
- package/dist/scheduler/cron.d.ts +126 -0
- package/dist/scheduler/cron.d.ts.map +1 -0
- package/dist/scheduler/cron.js +390 -0
- package/dist/scheduler/cron.js.map +1 -0
- package/dist/scheduler/errors.d.ts +81 -1
- package/dist/scheduler/errors.d.ts.map +1 -1
- package/dist/scheduler/errors.js +81 -6
- package/dist/scheduler/errors.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -0
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +2 -0
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/schedule-runner.d.ts +2 -2
- package/dist/scheduler/schedule-runner.d.ts.map +1 -1
- package/dist/scheduler/schedule-runner.js +20 -8
- package/dist/scheduler/schedule-runner.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts +4 -4
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/scheduler.js +86 -20
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/scheduler/types.d.ts +1 -1
- package/dist/scheduler/types.d.ts.map +1 -1
- package/dist/state/schemas/job-metadata.d.ts +2 -2
- package/package.json +33 -8
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test.log +0 -219
- package/.turbo/turbo-typecheck.log +0 -4
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/coverage-final.json +0 -51
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -251
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/config/index.html +0 -191
- package/coverage/src/config/index.ts.html +0 -442
- package/coverage/src/config/interpolate.ts.html +0 -652
- package/coverage/src/config/loader.ts.html +0 -1501
- package/coverage/src/config/merge.ts.html +0 -823
- package/coverage/src/config/parser.ts.html +0 -1213
- package/coverage/src/config/schema.ts.html +0 -1123
- package/coverage/src/fleet-manager/errors.ts.html +0 -2326
- package/coverage/src/fleet-manager/event-types.ts.html +0 -1219
- package/coverage/src/fleet-manager/fleet-manager.ts.html +0 -7030
- package/coverage/src/fleet-manager/index.html +0 -206
- package/coverage/src/fleet-manager/index.ts.html +0 -469
- package/coverage/src/fleet-manager/job-manager.ts.html +0 -2074
- package/coverage/src/fleet-manager/job-queue.ts.html +0 -2479
- package/coverage/src/fleet-manager/types.ts.html +0 -2602
- package/coverage/src/index.html +0 -116
- package/coverage/src/index.ts.html +0 -181
- package/coverage/src/runner/errors.ts.html +0 -1006
- package/coverage/src/runner/index.html +0 -191
- package/coverage/src/runner/index.ts.html +0 -256
- package/coverage/src/runner/job-executor.ts.html +0 -1429
- package/coverage/src/runner/message-processor.ts.html +0 -1150
- package/coverage/src/runner/sdk-adapter.ts.html +0 -658
- package/coverage/src/runner/types.ts.html +0 -559
- package/coverage/src/scheduler/errors.ts.html +0 -388
- package/coverage/src/scheduler/index.html +0 -206
- package/coverage/src/scheduler/index.ts.html +0 -244
- package/coverage/src/scheduler/interval.ts.html +0 -652
- package/coverage/src/scheduler/schedule-runner.ts.html +0 -1411
- package/coverage/src/scheduler/schedule-state.ts.html +0 -718
- package/coverage/src/scheduler/scheduler.ts.html +0 -1795
- package/coverage/src/scheduler/types.ts.html +0 -733
- package/coverage/src/state/directory.ts.html +0 -736
- package/coverage/src/state/errors.ts.html +0 -376
- package/coverage/src/state/fleet-state.ts.html +0 -937
- package/coverage/src/state/index.html +0 -221
- package/coverage/src/state/index.ts.html +0 -322
- package/coverage/src/state/job-metadata.ts.html +0 -1420
- package/coverage/src/state/job-output.ts.html +0 -1033
- package/coverage/src/state/schemas/fleet-state.ts.html +0 -445
- package/coverage/src/state/schemas/index.html +0 -176
- package/coverage/src/state/schemas/index.ts.html +0 -286
- package/coverage/src/state/schemas/job-metadata.ts.html +0 -628
- package/coverage/src/state/schemas/job-output.ts.html +0 -616
- package/coverage/src/state/schemas/session-info.ts.html +0 -361
- package/coverage/src/state/session.ts.html +0 -844
- package/coverage/src/state/types.ts.html +0 -262
- package/coverage/src/state/utils/atomic.ts.html +0 -748
- package/coverage/src/state/utils/index.html +0 -146
- package/coverage/src/state/utils/index.ts.html +0 -103
- package/coverage/src/state/utils/reads.ts.html +0 -1621
- package/coverage/src/work-sources/adapters/github.ts.html +0 -3583
- package/coverage/src/work-sources/adapters/index.html +0 -131
- package/coverage/src/work-sources/adapters/index.ts.html +0 -277
- package/coverage/src/work-sources/errors.ts.html +0 -298
- package/coverage/src/work-sources/index.html +0 -176
- package/coverage/src/work-sources/index.ts.html +0 -529
- package/coverage/src/work-sources/manager.ts.html +0 -1324
- package/coverage/src/work-sources/registry.ts.html +0 -619
- package/coverage/src/work-sources/types.ts.html +0 -568
- package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +0 -7
- package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +0 -1
- package/dist/fleet-manager/__tests__/event-helpers.test.js +0 -368
- package/dist/fleet-manager/__tests__/event-helpers.test.js.map +0 -1
- package/src/config/__tests__/agent.test.ts +0 -864
- package/src/config/__tests__/interpolate.test.ts +0 -644
- package/src/config/__tests__/loader.test.ts +0 -784
- package/src/config/__tests__/merge.test.ts +0 -751
- package/src/config/__tests__/parser.test.ts +0 -533
- package/src/config/__tests__/schema.test.ts +0 -873
- package/src/config/index.ts +0 -119
- package/src/config/interpolate.ts +0 -189
- package/src/config/loader.ts +0 -472
- package/src/config/merge.ts +0 -246
- package/src/config/parser.ts +0 -376
- package/src/config/schema.ts +0 -346
- package/src/fleet-manager/__tests__/coverage.test.ts +0 -2869
- package/src/fleet-manager/__tests__/errors.test.ts +0 -660
- package/src/fleet-manager/__tests__/event-helpers.test.ts +0 -448
- package/src/fleet-manager/__tests__/integration.test.ts +0 -1209
- package/src/fleet-manager/__tests__/job-control.test.ts +0 -283
- package/src/fleet-manager/__tests__/job-manager.test.ts +0 -869
- package/src/fleet-manager/__tests__/job-queue.test.ts +0 -401
- package/src/fleet-manager/__tests__/reload.test.ts +0 -751
- package/src/fleet-manager/__tests__/status-queries.test.ts +0 -595
- package/src/fleet-manager/__tests__/trigger.test.ts +0 -601
- package/src/fleet-manager/errors.ts +0 -747
- package/src/fleet-manager/event-types.ts +0 -378
- package/src/fleet-manager/fleet-manager.ts +0 -2315
- package/src/fleet-manager/index.ts +0 -128
- package/src/fleet-manager/job-manager.ts +0 -663
- package/src/fleet-manager/job-queue.ts +0 -798
- package/src/fleet-manager/types.ts +0 -839
- package/src/index.ts +0 -32
- package/src/runner/__tests__/errors.test.ts +0 -382
- package/src/runner/__tests__/job-executor.test.ts +0 -1708
- package/src/runner/__tests__/message-processor.test.ts +0 -960
- package/src/runner/__tests__/sdk-adapter.test.ts +0 -626
- package/src/runner/errors.ts +0 -307
- package/src/runner/index.ts +0 -57
- package/src/runner/job-executor.ts +0 -448
- package/src/runner/message-processor.ts +0 -355
- package/src/runner/sdk-adapter.ts +0 -191
- package/src/runner/types.ts +0 -158
- package/src/scheduler/__tests__/errors.test.ts +0 -159
- package/src/scheduler/__tests__/interval.test.ts +0 -515
- package/src/scheduler/__tests__/schedule-runner.test.ts +0 -798
- package/src/scheduler/__tests__/schedule-state.test.ts +0 -671
- package/src/scheduler/__tests__/scheduler.test.ts +0 -1280
- package/src/scheduler/errors.ts +0 -101
- package/src/scheduler/index.ts +0 -53
- package/src/scheduler/interval.ts +0 -189
- package/src/scheduler/schedule-runner.ts +0 -442
- package/src/scheduler/schedule-state.ts +0 -211
- package/src/scheduler/scheduler.ts +0 -570
- package/src/scheduler/types.ts +0 -216
- package/src/state/__tests__/directory.test.ts +0 -595
- package/src/state/__tests__/fleet-state.test.ts +0 -868
- package/src/state/__tests__/job-metadata-schema.test.ts +0 -414
- package/src/state/__tests__/job-metadata.test.ts +0 -831
- package/src/state/__tests__/job-output.test.ts +0 -856
- package/src/state/__tests__/session-schema.test.ts +0 -378
- package/src/state/__tests__/session.test.ts +0 -604
- package/src/state/directory.ts +0 -217
- package/src/state/errors.ts +0 -97
- package/src/state/fleet-state.ts +0 -284
- package/src/state/index.ts +0 -79
- package/src/state/job-metadata.ts +0 -445
- package/src/state/job-output.ts +0 -316
- package/src/state/schemas/__tests__/job-output.test.ts +0 -338
- package/src/state/schemas/fleet-state.ts +0 -120
- package/src/state/schemas/index.ts +0 -67
- package/src/state/schemas/job-metadata.ts +0 -181
- package/src/state/schemas/job-output.ts +0 -177
- package/src/state/schemas/session-info.ts +0 -92
- package/src/state/session.ts +0 -253
- package/src/state/types.ts +0 -59
- package/src/state/utils/__tests__/atomic.test.ts +0 -723
- package/src/state/utils/__tests__/reads.test.ts +0 -1071
- package/src/state/utils/atomic.ts +0 -221
- package/src/state/utils/index.ts +0 -6
- package/src/state/utils/reads.ts +0 -512
- package/src/work-sources/__tests__/github.test.ts +0 -1800
- package/src/work-sources/__tests__/manager.test.ts +0 -529
- package/src/work-sources/__tests__/registry.test.ts +0 -477
- package/src/work-sources/__tests__/types.test.ts +0 -479
- package/src/work-sources/adapters/github.ts +0 -1166
- package/src/work-sources/adapters/index.ts +0 -64
- package/src/work-sources/errors.ts +0 -71
- package/src/work-sources/index.ts +0 -148
- package/src/work-sources/manager.ts +0 -413
- package/src/work-sources/registry.ts +0 -178
- package/src/work-sources/types.ts +0 -161
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -19
|
@@ -1,445 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job metadata persistence operations
|
|
3
|
-
*
|
|
4
|
-
* Provides CRUD operations for job metadata files stored at
|
|
5
|
-
* .herdctl/jobs/job-<id>.yaml
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readdir } from "node:fs/promises";
|
|
9
|
-
import { join, basename } from "node:path";
|
|
10
|
-
import { atomicWriteYaml } from "./utils/atomic.js";
|
|
11
|
-
import { safeReadYaml } from "./utils/reads.js";
|
|
12
|
-
import {
|
|
13
|
-
JobMetadataSchema,
|
|
14
|
-
createJobMetadata,
|
|
15
|
-
generateJobId,
|
|
16
|
-
type JobMetadata,
|
|
17
|
-
type JobStatus,
|
|
18
|
-
type CreateJobOptions,
|
|
19
|
-
} from "./schemas/job-metadata.js";
|
|
20
|
-
import { StateFileError } from "./errors.js";
|
|
21
|
-
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// Types
|
|
24
|
-
// =============================================================================
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Options for reading/writing job metadata
|
|
28
|
-
*/
|
|
29
|
-
export interface JobMetadataOptions {
|
|
30
|
-
/** Logger for warnings */
|
|
31
|
-
logger?: JobLogger;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Logger interface for job operations
|
|
36
|
-
*/
|
|
37
|
-
export interface JobLogger {
|
|
38
|
-
warn: (message: string) => void;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Partial updates for job metadata
|
|
43
|
-
*/
|
|
44
|
-
export type JobMetadataUpdates = Partial<
|
|
45
|
-
Omit<JobMetadata, "id" | "agent" | "trigger_type" | "started_at">
|
|
46
|
-
>;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Filter options for listing jobs
|
|
50
|
-
*/
|
|
51
|
-
export interface ListJobsFilter {
|
|
52
|
-
/** Filter by agent name */
|
|
53
|
-
agent?: string;
|
|
54
|
-
/** Filter by job status */
|
|
55
|
-
status?: JobStatus;
|
|
56
|
-
/** Filter jobs started on or after this date (ISO string or Date) */
|
|
57
|
-
startedAfter?: string | Date;
|
|
58
|
-
/** Filter jobs started on or before this date (ISO string or Date) */
|
|
59
|
-
startedBefore?: string | Date;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Result of listing jobs
|
|
64
|
-
*/
|
|
65
|
-
export interface ListJobsResult {
|
|
66
|
-
/** Array of job metadata */
|
|
67
|
-
jobs: JobMetadata[];
|
|
68
|
-
/** Number of jobs that failed to parse */
|
|
69
|
-
errors: number;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// =============================================================================
|
|
73
|
-
// Helper Functions
|
|
74
|
-
// =============================================================================
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Get the file path for a job
|
|
78
|
-
*/
|
|
79
|
-
function getJobFilePath(jobsDir: string, jobId: string): string {
|
|
80
|
-
return join(jobsDir, `${jobId}.yaml`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Parse an ISO date string or Date to a Date object
|
|
85
|
-
*/
|
|
86
|
-
function toDate(value: string | Date): Date {
|
|
87
|
-
return value instanceof Date ? value : new Date(value);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Calculate duration in seconds between two ISO timestamps
|
|
92
|
-
*/
|
|
93
|
-
function calculateDuration(startedAt: string, finishedAt: string): number {
|
|
94
|
-
const start = new Date(startedAt).getTime();
|
|
95
|
-
const end = new Date(finishedAt).getTime();
|
|
96
|
-
return Math.round((end - start) / 1000);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// =============================================================================
|
|
100
|
-
// CRUD Operations
|
|
101
|
-
// =============================================================================
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Create a new job and persist it to disk
|
|
105
|
-
*
|
|
106
|
-
* Creates a job metadata file at .herdctl/jobs/job-<id>.yaml
|
|
107
|
-
*
|
|
108
|
-
* @param jobsDir - Path to the jobs directory
|
|
109
|
-
* @param options - Job creation options
|
|
110
|
-
* @returns The created job metadata
|
|
111
|
-
* @throws StateFileError if the file cannot be written
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* ```typescript
|
|
115
|
-
* const job = await createJob('/path/to/.herdctl/jobs', {
|
|
116
|
-
* agent: 'my-agent',
|
|
117
|
-
* trigger_type: 'manual',
|
|
118
|
-
* prompt: 'Fix the bug in auth.ts'
|
|
119
|
-
* });
|
|
120
|
-
* console.log(job.id); // 'job-2024-01-15-abc123'
|
|
121
|
-
* ```
|
|
122
|
-
*/
|
|
123
|
-
export async function createJob(
|
|
124
|
-
jobsDir: string,
|
|
125
|
-
options: CreateJobOptions
|
|
126
|
-
): Promise<JobMetadata> {
|
|
127
|
-
const job = createJobMetadata(options, generateJobId);
|
|
128
|
-
|
|
129
|
-
// Validate the generated job metadata
|
|
130
|
-
const validated = JobMetadataSchema.parse(job);
|
|
131
|
-
|
|
132
|
-
const filePath = getJobFilePath(jobsDir, validated.id);
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
await atomicWriteYaml(filePath, validated);
|
|
136
|
-
} catch (error) {
|
|
137
|
-
throw new StateFileError(
|
|
138
|
-
`Failed to create job file: ${(error as Error).message}`,
|
|
139
|
-
filePath,
|
|
140
|
-
"write",
|
|
141
|
-
error as Error
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return validated;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Update an existing job's metadata
|
|
150
|
-
*
|
|
151
|
-
* Uses atomic writes to prevent corruption. Automatically calculates
|
|
152
|
-
* duration_seconds when finished_at is set.
|
|
153
|
-
*
|
|
154
|
-
* @param jobsDir - Path to the jobs directory
|
|
155
|
-
* @param jobId - The job ID to update
|
|
156
|
-
* @param updates - Partial updates to apply
|
|
157
|
-
* @returns The updated job metadata
|
|
158
|
-
* @throws StateFileError if the file cannot be read or written
|
|
159
|
-
*
|
|
160
|
-
* @example
|
|
161
|
-
* ```typescript
|
|
162
|
-
* const job = await updateJob('/path/to/.herdctl/jobs', 'job-2024-01-15-abc123', {
|
|
163
|
-
* status: 'completed',
|
|
164
|
-
* exit_reason: 'success',
|
|
165
|
-
* finished_at: new Date().toISOString(),
|
|
166
|
-
* summary: 'Fixed the auth bug'
|
|
167
|
-
* });
|
|
168
|
-
* ```
|
|
169
|
-
*/
|
|
170
|
-
export async function updateJob(
|
|
171
|
-
jobsDir: string,
|
|
172
|
-
jobId: string,
|
|
173
|
-
updates: JobMetadataUpdates
|
|
174
|
-
): Promise<JobMetadata> {
|
|
175
|
-
const filePath = getJobFilePath(jobsDir, jobId);
|
|
176
|
-
|
|
177
|
-
// Read existing job
|
|
178
|
-
const result = await safeReadYaml<unknown>(filePath);
|
|
179
|
-
|
|
180
|
-
if (!result.success) {
|
|
181
|
-
throw new StateFileError(
|
|
182
|
-
`Failed to read job file for update: ${result.error.message}`,
|
|
183
|
-
filePath,
|
|
184
|
-
"read",
|
|
185
|
-
result.error
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Parse and validate existing job
|
|
190
|
-
const parseResult = JobMetadataSchema.safeParse(result.data);
|
|
191
|
-
if (!parseResult.success) {
|
|
192
|
-
throw new StateFileError(
|
|
193
|
-
`Job file is corrupted: ${parseResult.error.message}`,
|
|
194
|
-
filePath,
|
|
195
|
-
"read"
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const existingJob = parseResult.data;
|
|
200
|
-
|
|
201
|
-
// Apply updates
|
|
202
|
-
const updatedJob: JobMetadata = {
|
|
203
|
-
...existingJob,
|
|
204
|
-
...updates,
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// Auto-calculate duration if finished_at is being set
|
|
208
|
-
if (updates.finished_at && !updates.duration_seconds) {
|
|
209
|
-
updatedJob.duration_seconds = calculateDuration(
|
|
210
|
-
existingJob.started_at,
|
|
211
|
-
updates.finished_at
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Validate the updated job
|
|
216
|
-
const validated = JobMetadataSchema.parse(updatedJob);
|
|
217
|
-
|
|
218
|
-
// Write atomically
|
|
219
|
-
try {
|
|
220
|
-
await atomicWriteYaml(filePath, validated);
|
|
221
|
-
} catch (error) {
|
|
222
|
-
throw new StateFileError(
|
|
223
|
-
`Failed to update job file: ${(error as Error).message}`,
|
|
224
|
-
filePath,
|
|
225
|
-
"write",
|
|
226
|
-
error as Error
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return validated;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Get a job by its ID
|
|
235
|
-
*
|
|
236
|
-
* @param jobsDir - Path to the jobs directory
|
|
237
|
-
* @param jobId - The job ID to retrieve
|
|
238
|
-
* @returns The job metadata, or null if not found
|
|
239
|
-
* @throws StateFileError if the file exists but cannot be parsed
|
|
240
|
-
*
|
|
241
|
-
* @example
|
|
242
|
-
* ```typescript
|
|
243
|
-
* const job = await getJob('/path/to/.herdctl/jobs', 'job-2024-01-15-abc123');
|
|
244
|
-
* if (job) {
|
|
245
|
-
* console.log(job.status); // 'running'
|
|
246
|
-
* }
|
|
247
|
-
* ```
|
|
248
|
-
*/
|
|
249
|
-
export async function getJob(
|
|
250
|
-
jobsDir: string,
|
|
251
|
-
jobId: string,
|
|
252
|
-
options: JobMetadataOptions = {}
|
|
253
|
-
): Promise<JobMetadata | null> {
|
|
254
|
-
const { logger = console } = options;
|
|
255
|
-
const filePath = getJobFilePath(jobsDir, jobId);
|
|
256
|
-
|
|
257
|
-
const result = await safeReadYaml<unknown>(filePath);
|
|
258
|
-
|
|
259
|
-
if (!result.success) {
|
|
260
|
-
// File not found is not an error - return null
|
|
261
|
-
if (result.error.code === "ENOENT") {
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
throw new StateFileError(
|
|
266
|
-
`Failed to read job file: ${result.error.message}`,
|
|
267
|
-
filePath,
|
|
268
|
-
"read",
|
|
269
|
-
result.error
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Parse and validate
|
|
274
|
-
const parseResult = JobMetadataSchema.safeParse(result.data);
|
|
275
|
-
if (!parseResult.success) {
|
|
276
|
-
logger.warn(
|
|
277
|
-
`Corrupted job file ${filePath}: ${parseResult.error.message}. Skipping.`
|
|
278
|
-
);
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return parseResult.data;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* List all jobs, optionally filtered
|
|
287
|
-
*
|
|
288
|
-
* Supports filtering by agent, status, and date range. Returns jobs
|
|
289
|
-
* sorted by started_at in descending order (most recent first).
|
|
290
|
-
*
|
|
291
|
-
* @param jobsDir - Path to the jobs directory
|
|
292
|
-
* @param filter - Optional filter criteria
|
|
293
|
-
* @param options - Optional operation options
|
|
294
|
-
* @returns List of matching jobs and count of parse errors
|
|
295
|
-
*
|
|
296
|
-
* @example
|
|
297
|
-
* ```typescript
|
|
298
|
-
* // List all jobs for an agent
|
|
299
|
-
* const { jobs } = await listJobs('/path/to/.herdctl/jobs', {
|
|
300
|
-
* agent: 'my-agent'
|
|
301
|
-
* });
|
|
302
|
-
*
|
|
303
|
-
* // List failed jobs from the last 24 hours
|
|
304
|
-
* const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
305
|
-
* const { jobs } = await listJobs('/path/to/.herdctl/jobs', {
|
|
306
|
-
* status: 'failed',
|
|
307
|
-
* startedAfter: yesterday
|
|
308
|
-
* });
|
|
309
|
-
* ```
|
|
310
|
-
*/
|
|
311
|
-
export async function listJobs(
|
|
312
|
-
jobsDir: string,
|
|
313
|
-
filter: ListJobsFilter = {},
|
|
314
|
-
options: JobMetadataOptions = {}
|
|
315
|
-
): Promise<ListJobsResult> {
|
|
316
|
-
const { logger = console } = options;
|
|
317
|
-
|
|
318
|
-
// Read directory
|
|
319
|
-
let files: string[];
|
|
320
|
-
try {
|
|
321
|
-
files = await readdir(jobsDir);
|
|
322
|
-
} catch (error) {
|
|
323
|
-
// Directory doesn't exist - return empty list
|
|
324
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
325
|
-
return { jobs: [], errors: 0 };
|
|
326
|
-
}
|
|
327
|
-
throw new StateFileError(
|
|
328
|
-
`Failed to read jobs directory: ${(error as Error).message}`,
|
|
329
|
-
jobsDir,
|
|
330
|
-
"read",
|
|
331
|
-
error as Error
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Filter to job YAML files
|
|
336
|
-
const jobFiles = files.filter(
|
|
337
|
-
(f) => f.startsWith("job-") && f.endsWith(".yaml")
|
|
338
|
-
);
|
|
339
|
-
|
|
340
|
-
const jobs: JobMetadata[] = [];
|
|
341
|
-
let errors = 0;
|
|
342
|
-
|
|
343
|
-
// Parse date filters once
|
|
344
|
-
const startedAfter = filter.startedAfter
|
|
345
|
-
? toDate(filter.startedAfter)
|
|
346
|
-
: undefined;
|
|
347
|
-
const startedBefore = filter.startedBefore
|
|
348
|
-
? toDate(filter.startedBefore)
|
|
349
|
-
: undefined;
|
|
350
|
-
|
|
351
|
-
// Read and filter each job
|
|
352
|
-
for (const file of jobFiles) {
|
|
353
|
-
const filePath = join(jobsDir, file);
|
|
354
|
-
const result = await safeReadYaml<unknown>(filePath);
|
|
355
|
-
|
|
356
|
-
if (!result.success) {
|
|
357
|
-
logger.warn(`Failed to read job file ${filePath}: ${result.error.message}`);
|
|
358
|
-
errors++;
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const parseResult = JobMetadataSchema.safeParse(result.data);
|
|
363
|
-
if (!parseResult.success) {
|
|
364
|
-
logger.warn(
|
|
365
|
-
`Corrupted job file ${filePath}: ${parseResult.error.message}`
|
|
366
|
-
);
|
|
367
|
-
errors++;
|
|
368
|
-
continue;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const job = parseResult.data;
|
|
372
|
-
|
|
373
|
-
// Apply filters
|
|
374
|
-
if (filter.agent && job.agent !== filter.agent) {
|
|
375
|
-
continue;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (filter.status && job.status !== filter.status) {
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (startedAfter) {
|
|
383
|
-
const jobDate = new Date(job.started_at);
|
|
384
|
-
if (jobDate < startedAfter) {
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (startedBefore) {
|
|
390
|
-
const jobDate = new Date(job.started_at);
|
|
391
|
-
if (jobDate > startedBefore) {
|
|
392
|
-
continue;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
jobs.push(job);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Sort by started_at descending (most recent first)
|
|
400
|
-
jobs.sort((a, b) => {
|
|
401
|
-
const dateA = new Date(a.started_at).getTime();
|
|
402
|
-
const dateB = new Date(b.started_at).getTime();
|
|
403
|
-
return dateB - dateA;
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
return { jobs, errors };
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Delete a job's metadata file
|
|
411
|
-
*
|
|
412
|
-
* @param jobsDir - Path to the jobs directory
|
|
413
|
-
* @param jobId - The job ID to delete
|
|
414
|
-
* @returns true if deleted, false if not found
|
|
415
|
-
*
|
|
416
|
-
* @example
|
|
417
|
-
* ```typescript
|
|
418
|
-
* const deleted = await deleteJob('/path/to/.herdctl/jobs', 'job-2024-01-15-abc123');
|
|
419
|
-
* if (deleted) {
|
|
420
|
-
* console.log('Job deleted');
|
|
421
|
-
* }
|
|
422
|
-
* ```
|
|
423
|
-
*/
|
|
424
|
-
export async function deleteJob(
|
|
425
|
-
jobsDir: string,
|
|
426
|
-
jobId: string
|
|
427
|
-
): Promise<boolean> {
|
|
428
|
-
const { unlink } = await import("node:fs/promises");
|
|
429
|
-
const filePath = getJobFilePath(jobsDir, jobId);
|
|
430
|
-
|
|
431
|
-
try {
|
|
432
|
-
await unlink(filePath);
|
|
433
|
-
return true;
|
|
434
|
-
} catch (error) {
|
|
435
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
436
|
-
return false;
|
|
437
|
-
}
|
|
438
|
-
throw new StateFileError(
|
|
439
|
-
`Failed to delete job file: ${(error as Error).message}`,
|
|
440
|
-
filePath,
|
|
441
|
-
"write",
|
|
442
|
-
error as Error
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
}
|