@damian87/omp 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/skills/schedule/SKILL.md +71 -0
- package/README.md +19 -2
- package/dist/src/cli.js +83 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/schedule/commands.d.ts +34 -0
- package/dist/src/schedule/commands.js +130 -0
- package/dist/src/schedule/commands.js.map +1 -0
- package/dist/src/schedule/installer.d.ts +20 -0
- package/dist/src/schedule/installer.js +76 -0
- package/dist/src/schedule/installer.js.map +1 -0
- package/dist/src/schedule/installers/crontab.d.ts +17 -0
- package/dist/src/schedule/installers/crontab.js +112 -0
- package/dist/src/schedule/installers/crontab.js.map +1 -0
- package/dist/src/schedule/installers/launchd.d.ts +22 -0
- package/dist/src/schedule/installers/launchd.js +125 -0
- package/dist/src/schedule/installers/launchd.js.map +1 -0
- package/dist/src/schedule/installers/systemd.d.ts +15 -0
- package/dist/src/schedule/installers/systemd.js +136 -0
- package/dist/src/schedule/installers/systemd.js.map +1 -0
- package/dist/src/schedule/job-store.d.ts +21 -0
- package/dist/src/schedule/job-store.js +102 -0
- package/dist/src/schedule/job-store.js.map +1 -0
- package/dist/src/schedule/lock.d.ts +9 -0
- package/dist/src/schedule/lock.js +83 -0
- package/dist/src/schedule/lock.js.map +1 -0
- package/dist/src/schedule/paths.d.ts +15 -0
- package/dist/src/schedule/paths.js +36 -0
- package/dist/src/schedule/paths.js.map +1 -0
- package/dist/src/schedule/runner.d.ts +8 -0
- package/dist/src/schedule/runner.js +151 -0
- package/dist/src/schedule/runner.js.map +1 -0
- package/dist/src/schedule/types.d.ts +60 -0
- package/dist/src/schedule/types.js +5 -0
- package/dist/src/schedule/types.js.map +1 -0
- package/docs/research/2026-06-01-schedule-cron-feature.md +346 -0
- package/package.json +1 -1
- package/scripts/lib/schedule-results.mjs +88 -0
- package/scripts/session-start.mjs +8 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
/** Atomic write (tmp + rename), mirroring task-store.writeTask. */
|
|
4
|
+
export function writeJob(jobPath, job) {
|
|
5
|
+
mkdirSync(dirname(jobPath), { recursive: true });
|
|
6
|
+
const tmp = `${jobPath}.tmp.${process.pid}.${Date.now()}`;
|
|
7
|
+
writeFileSync(tmp, JSON.stringify(job, null, 2), "utf8");
|
|
8
|
+
renameSync(tmp, jobPath);
|
|
9
|
+
}
|
|
10
|
+
export function readJob(jobPath) {
|
|
11
|
+
if (!existsSync(jobPath))
|
|
12
|
+
return undefined;
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(readFileSync(jobPath, "utf8"));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function listJobs(jobsDir) {
|
|
21
|
+
if (!existsSync(jobsDir))
|
|
22
|
+
return [];
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const entry of readdirSync(jobsDir, { withFileTypes: true })) {
|
|
25
|
+
if (!entry.isFile() || !entry.name.endsWith(".json"))
|
|
26
|
+
continue;
|
|
27
|
+
const job = readJob(join(jobsDir, entry.name));
|
|
28
|
+
if (job)
|
|
29
|
+
out.push(job);
|
|
30
|
+
}
|
|
31
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
32
|
+
}
|
|
33
|
+
export function deleteJob(jobPath) {
|
|
34
|
+
if (existsSync(jobPath))
|
|
35
|
+
unlinkSync(jobPath);
|
|
36
|
+
}
|
|
37
|
+
/** Append a result line. Append-only — never read-modify-write (race-free under concurrent runs). */
|
|
38
|
+
export function appendRunResult(resultsPath, result) {
|
|
39
|
+
mkdirSync(dirname(resultsPath), { recursive: true });
|
|
40
|
+
appendFileSync(resultsPath, `${JSON.stringify(result)}\n`, "utf8");
|
|
41
|
+
}
|
|
42
|
+
function readCursorBytes(cursorPath) {
|
|
43
|
+
if (!existsSync(cursorPath))
|
|
44
|
+
return 0;
|
|
45
|
+
try {
|
|
46
|
+
const data = JSON.parse(readFileSync(cursorPath, "utf8"));
|
|
47
|
+
return Number(data.bytesRead) || 0;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Atomic write of the byte-offset cursor (tmp + rename). */
|
|
54
|
+
export function advanceCursor(cursorPath, bytes) {
|
|
55
|
+
mkdirSync(dirname(cursorPath), { recursive: true });
|
|
56
|
+
const tmp = `${cursorPath}.tmp.${process.pid}.${Date.now()}`;
|
|
57
|
+
writeFileSync(tmp, JSON.stringify({ bytesRead: bytes }), "utf8");
|
|
58
|
+
renameSync(tmp, cursorPath);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Read result lines from the stored byte offset to EOF, parsing only complete
|
|
62
|
+
* lines. Mirrors outbox.scanFromCursor. `maxBytes` bounds the read so a hook
|
|
63
|
+
* with a tight time budget never scans an unbounded file.
|
|
64
|
+
*/
|
|
65
|
+
export function readResultsFrom(resultsPath, cursorPath, maxBytes = 16_384) {
|
|
66
|
+
let cursor = readCursorBytes(cursorPath);
|
|
67
|
+
if (!existsSync(resultsPath))
|
|
68
|
+
return { results: [], newCursor: cursor, cursor };
|
|
69
|
+
const stats = statSync(resultsPath);
|
|
70
|
+
if (cursor > stats.size)
|
|
71
|
+
cursor = 0; // file truncated/rotated → re-read from start
|
|
72
|
+
if (cursor >= stats.size)
|
|
73
|
+
return { results: [], newCursor: cursor, cursor };
|
|
74
|
+
const remaining = Math.min(stats.size - cursor, maxBytes);
|
|
75
|
+
const fd = openSync(resultsPath, "r");
|
|
76
|
+
const buf = Buffer.alloc(remaining);
|
|
77
|
+
try {
|
|
78
|
+
readSync(fd, buf, 0, remaining, cursor);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
closeSync(fd);
|
|
82
|
+
}
|
|
83
|
+
const text = buf.toString("utf8");
|
|
84
|
+
const lastNewline = text.lastIndexOf("\n");
|
|
85
|
+
if (lastNewline === -1)
|
|
86
|
+
return { results: [], newCursor: cursor, cursor };
|
|
87
|
+
const consumed = text.slice(0, lastNewline + 1);
|
|
88
|
+
const newCursor = cursor + Buffer.byteLength(consumed, "utf8");
|
|
89
|
+
const results = [];
|
|
90
|
+
for (const line of consumed.split("\n")) {
|
|
91
|
+
if (!line.trim())
|
|
92
|
+
continue;
|
|
93
|
+
try {
|
|
94
|
+
results.push(JSON.parse(line));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// ignore unparseable line; cursor still advances past it
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { results, newCursor, cursor };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=job-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-store.js","sourceRoot":"","sources":["../../../src/schedule/job-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface LockHandle {
|
|
2
|
+
acquired: boolean;
|
|
3
|
+
release: () => void;
|
|
4
|
+
}
|
|
5
|
+
/** Acquire an exclusive lock via `openSync(..., 'wx')` (mirrors task-store). */
|
|
6
|
+
export declare function acquireLock(lockPath: string): LockHandle;
|
|
7
|
+
/** A lock is stale if its owner PID is dead or it is older than 2× maxAgeMs. */
|
|
8
|
+
export declare function isLockStale(lockPath: string, maxAgeMs: number): boolean;
|
|
9
|
+
export declare function forceReleaseStaleLock(lockPath: string, maxAgeMs: number): void;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
function readLock(lockPath) {
|
|
5
|
+
try {
|
|
6
|
+
return JSON.parse(readFileSync(lockPath, "utf8"));
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Acquire an exclusive lock via `openSync(..., 'wx')` (mirrors task-store). */
|
|
13
|
+
export function acquireLock(lockPath) {
|
|
14
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
15
|
+
let fd;
|
|
16
|
+
try {
|
|
17
|
+
fd = openSync(lockPath, "wx");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { acquired: false, release: () => { } };
|
|
21
|
+
}
|
|
22
|
+
const token = randomUUID();
|
|
23
|
+
try {
|
|
24
|
+
writeFileSync(lockPath, JSON.stringify({ pid: process.pid, acquiredAt: new Date().toISOString(), token }));
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
closeSync(fd);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
acquired: true,
|
|
31
|
+
// Only delete the lock if it is still OURS — a stale-steal may have replaced
|
|
32
|
+
// it with a newer holder's lock, which we must not unlink.
|
|
33
|
+
release: () => {
|
|
34
|
+
if (readLock(lockPath)?.token === token) {
|
|
35
|
+
try {
|
|
36
|
+
unlinkSync(lockPath);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// ignore: already gone
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function pidAlive(pid) {
|
|
46
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
47
|
+
return false;
|
|
48
|
+
try {
|
|
49
|
+
process.kill(pid, 0);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
// ESRCH => no such process (dead). EPERM => exists but not ours (alive).
|
|
54
|
+
return err.code === "EPERM";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** A lock is stale if its owner PID is dead or it is older than 2× maxAgeMs. */
|
|
58
|
+
export function isLockStale(lockPath, maxAgeMs) {
|
|
59
|
+
if (!existsSync(lockPath))
|
|
60
|
+
return false;
|
|
61
|
+
let data;
|
|
62
|
+
try {
|
|
63
|
+
data = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return true; // unparseable lock is stale
|
|
67
|
+
}
|
|
68
|
+
if (!pidAlive(data.pid))
|
|
69
|
+
return true;
|
|
70
|
+
const age = Date.now() - Date.parse(data.acquiredAt);
|
|
71
|
+
return Number.isFinite(age) && age > 2 * maxAgeMs;
|
|
72
|
+
}
|
|
73
|
+
export function forceReleaseStaleLock(lockPath, maxAgeMs) {
|
|
74
|
+
if (isLockStale(lockPath, maxAgeMs)) {
|
|
75
|
+
try {
|
|
76
|
+
unlinkSync(lockPath);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ignore
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../src/schedule/lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface SchedulePaths {
|
|
2
|
+
cwd: string;
|
|
3
|
+
scheduleRoot: string;
|
|
4
|
+
jobsDir: string;
|
|
5
|
+
logsDir: string;
|
|
6
|
+
resultsDir: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function resolveSchedulePaths(cwd: string): SchedulePaths;
|
|
9
|
+
export declare function jobFilePath(jobsDir: string, id: string): string;
|
|
10
|
+
export declare function jobLockPath(jobsDir: string, id: string): string;
|
|
11
|
+
export declare function runLogDir(logsDir: string, id: string): string;
|
|
12
|
+
export declare function resultsFilePath(resultsDir: string, id: string): string;
|
|
13
|
+
/** Byte-offset cursor for "seen" results (mirrors team outbox `.offset`). */
|
|
14
|
+
export declare function resultsCursorPath(resultsDir: string, id: string): string;
|
|
15
|
+
export declare function ensureScheduleDirs(paths: SchedulePaths): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
export function resolveSchedulePaths(cwd) {
|
|
4
|
+
const root = resolve(cwd);
|
|
5
|
+
const scheduleRoot = join(root, ".omp", "state", "schedule");
|
|
6
|
+
return {
|
|
7
|
+
cwd: root,
|
|
8
|
+
scheduleRoot,
|
|
9
|
+
jobsDir: join(scheduleRoot, "jobs"),
|
|
10
|
+
logsDir: join(scheduleRoot, "logs"),
|
|
11
|
+
resultsDir: join(scheduleRoot, "results"),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function jobFilePath(jobsDir, id) {
|
|
15
|
+
return join(jobsDir, `${id}.json`);
|
|
16
|
+
}
|
|
17
|
+
export function jobLockPath(jobsDir, id) {
|
|
18
|
+
return join(jobsDir, `${id}.lock`);
|
|
19
|
+
}
|
|
20
|
+
export function runLogDir(logsDir, id) {
|
|
21
|
+
return join(logsDir, id);
|
|
22
|
+
}
|
|
23
|
+
export function resultsFilePath(resultsDir, id) {
|
|
24
|
+
return join(resultsDir, `${id}.jsonl`);
|
|
25
|
+
}
|
|
26
|
+
/** Byte-offset cursor for "seen" results (mirrors team outbox `.offset`). */
|
|
27
|
+
export function resultsCursorPath(resultsDir, id) {
|
|
28
|
+
return join(resultsDir, `${id}.offset`);
|
|
29
|
+
}
|
|
30
|
+
export function ensureScheduleDirs(paths) {
|
|
31
|
+
for (const dir of [paths.scheduleRoot, paths.jobsDir, paths.logsDir, paths.resultsDir]) {
|
|
32
|
+
if (!existsSync(dir))
|
|
33
|
+
mkdirSync(dir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/schedule/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAU1C,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;IAC7D,OAAO;QACL,GAAG,EAAE,IAAI;QACT,YAAY;QACZ,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC;QACnC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC;QACnC,UAAU,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,EAAU;IACrD,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,EAAU;IACrD,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,EAAU;IACnD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,UAAkB,EAAE,EAAU;IAC5D,OAAO,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;AACzC,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,iBAAiB,CAAC,UAAkB,EAAE,EAAU;IAC9D,OAAO,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAoB;IACrD,KAAK,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QACvF,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type SchedulePaths } from "./paths.js";
|
|
2
|
+
import { type ScheduleJob, type ScheduleRunResult } from "./types.js";
|
|
3
|
+
export interface RunOptions {
|
|
4
|
+
/** Called when a job has expired (TTL passed or maxRuns reached) so the caller can uninstall the OS entry. */
|
|
5
|
+
onExpire?: (job: ScheduleJob) => void;
|
|
6
|
+
}
|
|
7
|
+
/** Execute one scheduled run (expiry check, overlap lock, timeout, log capture, result append). */
|
|
8
|
+
export declare function runScheduledJob(job: ScheduleJob, paths: SchedulePaths, opts?: RunOptions): Promise<ScheduleRunResult>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { resolveCopilotBin } from "../copilot/launch.js";
|
|
5
|
+
import { appendRunResult, readJob, writeJob } from "./job-store.js";
|
|
6
|
+
import { acquireLock, forceReleaseStaleLock, isLockStale } from "./lock.js";
|
|
7
|
+
import { jobFilePath, jobLockPath, resultsFilePath, runLogDir } from "./paths.js";
|
|
8
|
+
import { DEFAULT_TIMEOUT_MS } from "./types.js";
|
|
9
|
+
function isExpired(job) {
|
|
10
|
+
if (job.maxRuns !== undefined && job.runCount >= job.maxRuns)
|
|
11
|
+
return true;
|
|
12
|
+
if (job.expiresAt && Date.now() > Date.parse(job.expiresAt))
|
|
13
|
+
return true;
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
function timestampSlug() {
|
|
17
|
+
return new Date().toISOString().replace(/[:.]/g, "-");
|
|
18
|
+
}
|
|
19
|
+
/** Keep only the newest `keep` per-run logs in a job's log dir (timestamp slugs sort chronologically). */
|
|
20
|
+
function rotateLogs(logDir, keep = 50) {
|
|
21
|
+
if (!existsSync(logDir))
|
|
22
|
+
return;
|
|
23
|
+
const files = readdirSync(logDir)
|
|
24
|
+
.filter((f) => f.endsWith(".log"))
|
|
25
|
+
.sort();
|
|
26
|
+
for (const f of files.slice(0, Math.max(0, files.length - keep))) {
|
|
27
|
+
try {
|
|
28
|
+
unlinkSync(join(logDir, f));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// best effort
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Execute one scheduled run (expiry check, overlap lock, timeout, log capture, result append). */
|
|
36
|
+
export async function runScheduledJob(job, paths, opts = {}) {
|
|
37
|
+
const timeoutMs = job.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
38
|
+
const jobPath = jobFilePath(paths.jobsDir, job.id);
|
|
39
|
+
const resultsPath = resultsFilePath(paths.resultsDir, job.id);
|
|
40
|
+
const persist = (status, result, incrementRun) => {
|
|
41
|
+
appendRunResult(resultsPath, result);
|
|
42
|
+
const current = readJob(jobPath) ?? job;
|
|
43
|
+
writeJob(jobPath, {
|
|
44
|
+
...current,
|
|
45
|
+
runCount: incrementRun ? current.runCount + 1 : current.runCount,
|
|
46
|
+
lastRunAt: result.ts,
|
|
47
|
+
lastStatus: status,
|
|
48
|
+
lastSummary: result.summary,
|
|
49
|
+
lastLogPath: result.logPath,
|
|
50
|
+
active: status === "expired" ? false : current.active,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
// 1. Expiry / max-runs check BEFORE any spawn.
|
|
54
|
+
if (isExpired(job)) {
|
|
55
|
+
const result = {
|
|
56
|
+
ts: new Date().toISOString(),
|
|
57
|
+
exitCode: 0,
|
|
58
|
+
status: "expired",
|
|
59
|
+
summary: "job expired (TTL or max-runs reached); deactivated",
|
|
60
|
+
logPath: "",
|
|
61
|
+
durationMs: 0,
|
|
62
|
+
};
|
|
63
|
+
persist("expired", result, false);
|
|
64
|
+
opts.onExpire?.(job);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
// 2. Overlap lock.
|
|
68
|
+
const lockPath = jobLockPath(paths.jobsDir, job.id);
|
|
69
|
+
forceReleaseStaleLock(lockPath, timeoutMs);
|
|
70
|
+
let lock = acquireLock(lockPath);
|
|
71
|
+
if (!lock.acquired) {
|
|
72
|
+
if (isLockStale(lockPath, timeoutMs)) {
|
|
73
|
+
forceReleaseStaleLock(lockPath, timeoutMs);
|
|
74
|
+
lock = acquireLock(lockPath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!lock.acquired) {
|
|
78
|
+
const result = {
|
|
79
|
+
ts: new Date().toISOString(),
|
|
80
|
+
exitCode: -1,
|
|
81
|
+
status: "locked",
|
|
82
|
+
summary: "previous run still in progress; skipped",
|
|
83
|
+
logPath: "",
|
|
84
|
+
durationMs: 0,
|
|
85
|
+
};
|
|
86
|
+
appendRunResult(resultsPath, result);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
// 3. Spawn the agent and capture output.
|
|
90
|
+
const startedAt = Date.now();
|
|
91
|
+
const logDir = runLogDir(paths.logsDir, job.id);
|
|
92
|
+
mkdirSync(logDir, { recursive: true });
|
|
93
|
+
const logPath = join(logDir, `${timestampSlug()}.log`);
|
|
94
|
+
try {
|
|
95
|
+
const bin = resolveCopilotBin(job.bin);
|
|
96
|
+
const args = [];
|
|
97
|
+
if (job.model)
|
|
98
|
+
args.push("--model", job.model);
|
|
99
|
+
args.push("-p", job.prompt);
|
|
100
|
+
if (job.allowAllTools)
|
|
101
|
+
args.push("--allow-all-tools");
|
|
102
|
+
const result = await new Promise((resolveFn) => {
|
|
103
|
+
const child = spawn(bin, args, { stdio: ["ignore", "pipe", "pipe"], cwd: job.cwd });
|
|
104
|
+
let stdout = "";
|
|
105
|
+
let stderr = "";
|
|
106
|
+
let timedOut = false;
|
|
107
|
+
let settled = false;
|
|
108
|
+
let killTimer;
|
|
109
|
+
const timer = setTimeout(() => {
|
|
110
|
+
timedOut = true;
|
|
111
|
+
child.kill("SIGTERM");
|
|
112
|
+
// Escalate to SIGKILL if the child ignores SIGTERM, so a run can't hang forever.
|
|
113
|
+
killTimer = setTimeout(() => child.kill("SIGKILL"), 5000);
|
|
114
|
+
}, timeoutMs);
|
|
115
|
+
child.stdout?.on("data", (d) => {
|
|
116
|
+
stdout += d.toString();
|
|
117
|
+
});
|
|
118
|
+
child.stderr?.on("data", (d) => {
|
|
119
|
+
stderr += d.toString();
|
|
120
|
+
});
|
|
121
|
+
const finish = (exitCode) => {
|
|
122
|
+
if (settled)
|
|
123
|
+
return;
|
|
124
|
+
settled = true;
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
if (killTimer)
|
|
127
|
+
clearTimeout(killTimer);
|
|
128
|
+
writeFileSync(logPath, `$ ${bin} ${args.join(" ")}\n\n[stdout]\n${stdout}\n[stderr]\n${stderr}\n`, "utf8");
|
|
129
|
+
rotateLogs(logDir);
|
|
130
|
+
const status = timedOut ? "timeout" : exitCode === 0 ? "ok" : "error";
|
|
131
|
+
const summarySource = (stdout.trim() || stderr.trim()).replace(/\s+/g, " ");
|
|
132
|
+
resolveFn({
|
|
133
|
+
ts: new Date().toISOString(),
|
|
134
|
+
exitCode,
|
|
135
|
+
status,
|
|
136
|
+
summary: summarySource.slice(0, 200) || `exit ${exitCode}`,
|
|
137
|
+
logPath,
|
|
138
|
+
durationMs: Date.now() - startedAt,
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
child.on("error", () => finish(127));
|
|
142
|
+
child.on("close", (code) => finish(typeof code === "number" ? code : timedOut ? 124 : 1));
|
|
143
|
+
});
|
|
144
|
+
persist(result.status, result, true);
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
lock.release();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/schedule/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Default per-run timeout for a scheduled agent process (5 minutes). */
|
|
2
|
+
export declare const DEFAULT_TIMEOUT_MS = 300000;
|
|
3
|
+
/** Default time-to-live for a scheduled job (3 days), matching Claude Code's expiry. */
|
|
4
|
+
export declare const DEFAULT_TTL_HOURS = 72;
|
|
5
|
+
/** Which OS scheduler owns the time trigger for a job. */
|
|
6
|
+
export type OsBackend = "launchd" | "systemd" | "crontab";
|
|
7
|
+
/** Terminal status of a single scheduled run. */
|
|
8
|
+
export type ScheduleRunStatus = "ok" | "error" | "timeout" | "locked" | "expired";
|
|
9
|
+
/**
|
|
10
|
+
* Persisted scheduled job (`.omp/state/schedule/jobs/<id>.json`).
|
|
11
|
+
* The portable source of truth; the OS-scheduler entry merely mirrors the trigger.
|
|
12
|
+
*/
|
|
13
|
+
export interface ScheduleJob {
|
|
14
|
+
id: string;
|
|
15
|
+
/** 5-field cron expression, interpreted in local time. */
|
|
16
|
+
cron: string;
|
|
17
|
+
prompt: string;
|
|
18
|
+
/** Agent CLI to spawn. Resolved via resolveCopilotBin. */
|
|
19
|
+
bin: string;
|
|
20
|
+
model?: string;
|
|
21
|
+
cwd: string;
|
|
22
|
+
timeoutMs: number;
|
|
23
|
+
/** When true, the agent is spawned with `--allow-all-tools` (unattended full access). */
|
|
24
|
+
allowAllTools: boolean;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
/** ISO timestamp after which the job auto-deactivates. */
|
|
27
|
+
expiresAt?: string;
|
|
28
|
+
maxRuns?: number;
|
|
29
|
+
runCount: number;
|
|
30
|
+
/** OS backend that owns this job's trigger (set at install time). */
|
|
31
|
+
backend: OsBackend;
|
|
32
|
+
ompBinPath: string;
|
|
33
|
+
lastRunAt?: string;
|
|
34
|
+
lastStatus?: ScheduleRunStatus;
|
|
35
|
+
lastSummary?: string;
|
|
36
|
+
lastLogPath?: string;
|
|
37
|
+
active: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** One line in `results/<id>.jsonl`. "Seen" state lives in the cursor, not here. */
|
|
40
|
+
export interface ScheduleRunResult {
|
|
41
|
+
ts: string;
|
|
42
|
+
exitCode: number;
|
|
43
|
+
status: ScheduleRunStatus;
|
|
44
|
+
summary: string;
|
|
45
|
+
logPath: string;
|
|
46
|
+
durationMs: number;
|
|
47
|
+
}
|
|
48
|
+
export interface ScheduleAddOptions {
|
|
49
|
+
id: string;
|
|
50
|
+
cron: string;
|
|
51
|
+
prompt: string;
|
|
52
|
+
bin?: string;
|
|
53
|
+
model?: string;
|
|
54
|
+
cwd?: string;
|
|
55
|
+
timeoutMs?: number;
|
|
56
|
+
maxRuns?: number;
|
|
57
|
+
ttlHours?: number;
|
|
58
|
+
allowAllTools?: boolean;
|
|
59
|
+
dryRun?: boolean;
|
|
60
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Default per-run timeout for a scheduled agent process (5 minutes). */
|
|
2
|
+
export const DEFAULT_TIMEOUT_MS = 300_000;
|
|
3
|
+
/** Default time-to-live for a scheduled job (3 days), matching Claude Code's expiry. */
|
|
4
|
+
export const DEFAULT_TTL_HOURS = 72;
|
|
5
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/schedule/types.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAE1C,wFAAwF;AACxF,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC"}
|