@damian87/omp 0.4.1 → 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/agents/researcher.md +7 -6
- package/.github/copilot-instructions.md +23 -0
- package/.github/skills/daily-log/SKILL.md +64 -0
- package/.github/skills/goal/SKILL.md +33 -0
- package/.github/skills/schedule/SKILL.md +71 -0
- package/README.md +19 -2
- package/dist/src/cli.js +272 -9
- package/dist/src/cli.js.map +1 -1
- package/dist/src/comms/index.d.ts +116 -0
- package/dist/src/comms/index.js +258 -0
- package/dist/src/comms/index.js.map +1 -0
- package/dist/src/comms/resolve-session.d.ts +35 -0
- package/dist/src/comms/resolve-session.js +53 -0
- package/dist/src/comms/resolve-session.js.map +1 -0
- package/dist/src/daily-log.d.ts +18 -0
- package/dist/src/daily-log.js +138 -0
- package/dist/src/daily-log.js.map +1 -0
- package/dist/src/goal.d.ts +4 -0
- package/dist/src/goal.js +44 -0
- package/dist/src/goal.js.map +1 -0
- package/dist/src/instructions-memory.d.ts +9 -0
- package/dist/src/instructions-memory.js +72 -0
- package/dist/src/instructions-memory.js.map +1 -0
- package/dist/src/mcp/tools/daily-log.d.ts +2 -0
- package/dist/src/mcp/tools/daily-log.js +148 -0
- package/dist/src/mcp/tools/daily-log.js.map +1 -0
- package/dist/src/omp-root.d.ts +1 -0
- package/dist/src/omp-root.js +19 -0
- package/dist/src/omp-root.js.map +1 -0
- package/dist/src/project-memory.d.ts +13 -0
- package/dist/src/project-memory.js +105 -0
- package/dist/src/project-memory.js.map +1 -0
- 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/dist/src/state.d.ts +17 -0
- package/dist/src/state.js +101 -0
- package/dist/src/state.js.map +1 -0
- package/dist/src/trace.d.ts +19 -0
- package/dist/src/trace.js +74 -0
- package/dist/src/trace.js.map +1 -0
- package/dist/test/catalog.test.d.ts +1 -0
- package/dist/test/catalog.test.js +21 -0
- package/dist/test/catalog.test.js.map +1 -0
- package/dist/test/jira.test.d.ts +1 -0
- package/dist/test/jira.test.js +26 -0
- package/dist/test/jira.test.js.map +1 -0
- package/dist/test/lint.test.d.ts +1 -0
- package/dist/test/lint.test.js +9 -0
- package/dist/test/lint.test.js.map +1 -0
- package/dist/test/sync.test.d.ts +1 -0
- package/dist/test/sync.test.js +15 -0
- package/dist/test/sync.test.js.map +1 -0
- package/docs/research/2026-06-01-schedule-cron-feature.md +346 -0
- package/package.json +1 -1
- package/scripts/lib/daily-log.mjs +155 -0
- package/scripts/lib/hook-output.mjs +2 -1
- package/scripts/lib/omp-root.mjs +15 -0
- package/scripts/lib/project-memory.mjs +21 -0
- package/scripts/lib/schedule-results.mjs +88 -0
- package/scripts/prompt-submit.mjs +14 -2
- package/scripts/session-end.mjs +6 -1
- package/scripts/session-start.mjs +60 -2
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
export function launchdLabel(id) {
|
|
6
|
+
return `com.omp.schedule.${id}`;
|
|
7
|
+
}
|
|
8
|
+
export function launchdPlistPath(id) {
|
|
9
|
+
return join(homedir(), "Library", "LaunchAgents", `${launchdLabel(id)}.plist`);
|
|
10
|
+
}
|
|
11
|
+
const STEP_EVERY_MIN = /^\*\/(\d+)$/;
|
|
12
|
+
/**
|
|
13
|
+
* Translate a simple 5-field cron to a launchd schedule. Returns null for
|
|
14
|
+
* patterns launchd's StartCalendarInterval cannot cleanly express (lists,
|
|
15
|
+
* ranges, multi-step) — the caller then falls back to crontab.
|
|
16
|
+
*/
|
|
17
|
+
export function cronToLaunchdInterval(cron) {
|
|
18
|
+
const parts = cron.trim().split(/\s+/);
|
|
19
|
+
if (parts.length !== 5)
|
|
20
|
+
return null;
|
|
21
|
+
const [min, hour, dom, mon, dow] = parts;
|
|
22
|
+
// every N minutes: */N * * * *
|
|
23
|
+
const minStep = STEP_EVERY_MIN.exec(min);
|
|
24
|
+
if (minStep && hour === "*" && dom === "*" && mon === "*" && dow === "*") {
|
|
25
|
+
return { startInterval: Number(minStep[1]) * 60 };
|
|
26
|
+
}
|
|
27
|
+
// every N hours: 0 */N * * *
|
|
28
|
+
const hourStep = STEP_EVERY_MIN.exec(hour);
|
|
29
|
+
if (min === "0" && hourStep && dom === "*" && mon === "*" && dow === "*") {
|
|
30
|
+
return { startInterval: Number(hourStep[1]) * 3600 };
|
|
31
|
+
}
|
|
32
|
+
const isNum = (s) => /^\d+$/.test(s);
|
|
33
|
+
// daily at H:M — minute & hour numeric, date/dow wild
|
|
34
|
+
if (isNum(min) && isNum(hour) && dom === "*" && mon === "*" && dow === "*") {
|
|
35
|
+
return { startCalendarInterval: { Minute: Number(min), Hour: Number(hour) } };
|
|
36
|
+
}
|
|
37
|
+
// weekly at H:M on a single weekday
|
|
38
|
+
if (isNum(min) && isNum(hour) && dom === "*" && mon === "*" && isNum(dow)) {
|
|
39
|
+
return { startCalendarInterval: { Minute: Number(min), Hour: Number(hour), Weekday: Number(dow) } };
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function xmlEscape(s) {
|
|
44
|
+
return s
|
|
45
|
+
.replace(/&/g, "&")
|
|
46
|
+
.replace(/</g, "<")
|
|
47
|
+
.replace(/>/g, ">")
|
|
48
|
+
.replace(/"/g, """)
|
|
49
|
+
.replace(/'/g, "'");
|
|
50
|
+
}
|
|
51
|
+
function calendarXml(cal) {
|
|
52
|
+
const entries = Object.entries(cal)
|
|
53
|
+
.filter(([, v]) => v !== undefined)
|
|
54
|
+
.map(([k, v]) => ` <key>${k}</key>\n <integer>${v}</integer>`)
|
|
55
|
+
.join("\n");
|
|
56
|
+
return ` <key>StartCalendarInterval</key>\n <dict>\n${entries}\n </dict>`;
|
|
57
|
+
}
|
|
58
|
+
/** Generate the LaunchAgent plist XML. Deliberately omits KeepAlive (timer jobs must not auto-restart). */
|
|
59
|
+
export function generatePlist(job, sched, logsDir, stateRoot) {
|
|
60
|
+
const label = launchdLabel(job.id);
|
|
61
|
+
const outLog = join(logsDir, job.id, `${job.id}.launchd.out.log`);
|
|
62
|
+
const errLog = join(logsDir, job.id, `${job.id}.launchd.err.log`);
|
|
63
|
+
const scheduleXml = sched.startInterval !== undefined
|
|
64
|
+
? ` <key>StartInterval</key>\n <integer>${sched.startInterval}</integer>`
|
|
65
|
+
: calendarXml(sched.startCalendarInterval ?? {});
|
|
66
|
+
// run from stateRoot and pass --root so state resolves correctly; the agent
|
|
67
|
+
// subprocess cwd (job.cwd) is set by the runner, not by WorkingDirectory.
|
|
68
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
69
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
70
|
+
<plist version="1.0">
|
|
71
|
+
<dict>
|
|
72
|
+
<key>Label</key>
|
|
73
|
+
<string>${xmlEscape(label)}</string>
|
|
74
|
+
<key>ProgramArguments</key>
|
|
75
|
+
<array>
|
|
76
|
+
<string>${xmlEscape(job.ompBinPath)}</string>
|
|
77
|
+
<string>schedule</string>
|
|
78
|
+
<string>run</string>
|
|
79
|
+
<string>--id</string>
|
|
80
|
+
<string>${xmlEscape(job.id)}</string>
|
|
81
|
+
<string>--root</string>
|
|
82
|
+
<string>${xmlEscape(stateRoot)}</string>
|
|
83
|
+
</array>
|
|
84
|
+
<key>WorkingDirectory</key>
|
|
85
|
+
<string>${xmlEscape(stateRoot)}</string>
|
|
86
|
+
${scheduleXml}
|
|
87
|
+
<key>StandardOutPath</key>
|
|
88
|
+
<string>${xmlEscape(outLog)}</string>
|
|
89
|
+
<key>StandardErrorPath</key>
|
|
90
|
+
<string>${xmlEscape(errLog)}</string>
|
|
91
|
+
</dict>
|
|
92
|
+
</plist>
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
function gui() {
|
|
96
|
+
return `gui/${process.getuid?.() ?? 0}`;
|
|
97
|
+
}
|
|
98
|
+
export function installLaunchd(job, sched, logsDir, stateRoot) {
|
|
99
|
+
const plistPath = launchdPlistPath(job.id);
|
|
100
|
+
mkdirSync(dirname(plistPath), { recursive: true });
|
|
101
|
+
mkdirSync(join(logsDir, job.id), { recursive: true });
|
|
102
|
+
writeFileSync(plistPath, generatePlist(job, sched, logsDir, stateRoot), "utf8");
|
|
103
|
+
try {
|
|
104
|
+
execFileSync("launchctl", ["bootout", `${gui()}/${launchdLabel(job.id)}`], { stdio: "ignore" });
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// not loaded yet — fine
|
|
108
|
+
}
|
|
109
|
+
execFileSync("launchctl", ["bootstrap", gui(), plistPath], { stdio: "ignore" });
|
|
110
|
+
}
|
|
111
|
+
export function uninstallLaunchd(id) {
|
|
112
|
+
try {
|
|
113
|
+
execFileSync("launchctl", ["bootout", `${gui()}/${launchdLabel(id)}`], { stdio: "ignore" });
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// already gone
|
|
117
|
+
}
|
|
118
|
+
const plistPath = launchdPlistPath(id);
|
|
119
|
+
if (existsSync(plistPath))
|
|
120
|
+
unlinkSync(plistPath);
|
|
121
|
+
}
|
|
122
|
+
export function statusLaunchd(id) {
|
|
123
|
+
return existsSync(launchdPlistPath(id));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=launchd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launchd.js","sourceRoot":"","sources":["../../../../src/schedule/installers/launchd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,OAAO,oBAAoB,EAAE,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAU;IACzC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,YAAY,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;AACjF,CAAC;AAOD,MAAM,cAAc,GAAG,aAAa,CAAC;AAErC;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;IAEzC,+BAA+B;IAC/B,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,OAAO,IAAI,IAAI,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;QACzE,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;IACpD,CAAC;IACD,6BAA6B;IAC7B,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,GAAG,KAAK,GAAG,IAAI,QAAQ,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;QACzE,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;IACvD,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7C,sDAAsD;IACtD,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;QAC3E,OAAO,EAAE,qBAAqB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IAChF,CAAC;IACD,oCAAoC;IACpC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1E,OAAO,EAAE,qBAAqB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IACtG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,CAAS;IAC1B,OAAO,CAAC;SACL,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,WAAW,CAAC,GAA0D;IAC7E,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;SAClC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,0BAA0B,CAAC,YAAY,CAAC;SACvE,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO,iDAAiD,OAAO,aAAa,CAAC;AAC/E,CAAC;AAED,2GAA2G;AAC3G,MAAM,UAAU,aAAa,CAAC,GAAgB,EAAE,KAAsB,EAAE,OAAe,EAAE,SAAiB;IACxG,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,GAAG,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,GAAG,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAClE,MAAM,WAAW,GACf,KAAK,CAAC,aAAa,KAAK,SAAS;QAC/B,CAAC,CAAC,0CAA0C,KAAK,CAAC,aAAa,YAAY;QAC3E,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,qBAAqB,IAAI,EAAE,CAAC,CAAC;IACrD,4EAA4E;IAC5E,0EAA0E;IAC1E,OAAO;;;;;YAKG,SAAS,CAAC,KAAK,CAAC;;;cAGd,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;;;;cAIzB,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;;cAEjB,SAAS,CAAC,SAAS,CAAC;;;YAGtB,SAAS,CAAC,SAAS,CAAC;EAC9B,WAAW;;YAED,SAAS,CAAC,MAAM,CAAC;;YAEjB,SAAS,CAAC,MAAM,CAAC;;;CAG5B,CAAC;AACF,CAAC;AAED,SAAS,GAAG;IACV,OAAO,OAAO,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAgB,EAAE,KAAsB,EAAE,OAAe,EAAE,SAAiB;IACzG,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3C,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,aAAa,CAAC,SAAS,EAAE,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;IAChF,IAAI,CAAC;QACH,YAAY,CAAC,WAAW,EAAE,CAAC,SAAS,EAAE,GAAG,GAAG,EAAE,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClG,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;IACD,YAAY,CAAC,WAAW,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAU;IACzC,IAAI,CAAC;QACH,YAAY,CAAC,WAAW,EAAE,CAAC,SAAS,EAAE,GAAG,GAAG,EAAE,IAAI,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9F,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IACD,MAAM,SAAS,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;IACvC,IAAI,UAAU,CAAC,SAAS,CAAC;QAAE,UAAU,CAAC,SAAS,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,OAAO,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ScheduleJob } from "../types.js";
|
|
2
|
+
export declare function unitBaseName(id: string): string;
|
|
3
|
+
export declare function servicePath(id: string): string;
|
|
4
|
+
export declare function timerPath(id: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Translate a 5-field cron to a systemd OnCalendar expression. Covers the
|
|
7
|
+
* common patterns (every-N-min, every-N-hours, daily/weekly at H:M including
|
|
8
|
+
* day-of-week); approximates the rest with a best-effort date-time mapping.
|
|
9
|
+
*/
|
|
10
|
+
export declare function cronToSystemdCalendar(cron: string): string;
|
|
11
|
+
export declare function generateService(job: ScheduleJob, stateRoot: string): string;
|
|
12
|
+
export declare function generateTimer(job: ScheduleJob): string;
|
|
13
|
+
export declare function installSystemd(job: ScheduleJob, stateRoot: string): void;
|
|
14
|
+
export declare function uninstallSystemd(id: string): void;
|
|
15
|
+
export declare function statusSystemd(id: string): boolean;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
export function unitBaseName(id) {
|
|
6
|
+
return `omp-schedule-${id}`;
|
|
7
|
+
}
|
|
8
|
+
function userUnitDir() {
|
|
9
|
+
return join(homedir(), ".config", "systemd", "user");
|
|
10
|
+
}
|
|
11
|
+
export function servicePath(id) {
|
|
12
|
+
return join(userUnitDir(), `${unitBaseName(id)}.service`);
|
|
13
|
+
}
|
|
14
|
+
export function timerPath(id) {
|
|
15
|
+
return join(userUnitDir(), `${unitBaseName(id)}.timer`);
|
|
16
|
+
}
|
|
17
|
+
const STEP_EVERY = /^\*\/(\d+)$/;
|
|
18
|
+
const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
19
|
+
function dayName(n) {
|
|
20
|
+
const v = Number(n);
|
|
21
|
+
if (!Number.isInteger(v))
|
|
22
|
+
return undefined;
|
|
23
|
+
return DAY_NAMES[v % 7]; // cron allows 0 and 7 for Sunday
|
|
24
|
+
}
|
|
25
|
+
/** Translate a cron day-of-week field to a systemd day prefix (empty when wild/unparseable). */
|
|
26
|
+
function dowToSystemd(dow) {
|
|
27
|
+
if (dow === "*")
|
|
28
|
+
return "";
|
|
29
|
+
if (/^\d+$/.test(dow))
|
|
30
|
+
return dayName(dow) ?? "";
|
|
31
|
+
const range = /^(\d+)-(\d+)$/.exec(dow);
|
|
32
|
+
if (range) {
|
|
33
|
+
const a = dayName(range[1]);
|
|
34
|
+
const b = dayName(range[2]);
|
|
35
|
+
return a && b ? `${a}..${b}` : "";
|
|
36
|
+
}
|
|
37
|
+
if (dow.includes(",")) {
|
|
38
|
+
const names = dow.split(",").map((d) => dayName(d.trim()));
|
|
39
|
+
if (names.every(Boolean))
|
|
40
|
+
return names.join(",");
|
|
41
|
+
}
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Translate a 5-field cron to a systemd OnCalendar expression. Covers the
|
|
46
|
+
* common patterns (every-N-min, every-N-hours, daily/weekly at H:M including
|
|
47
|
+
* day-of-week); approximates the rest with a best-effort date-time mapping.
|
|
48
|
+
*/
|
|
49
|
+
export function cronToSystemdCalendar(cron) {
|
|
50
|
+
const parts = cron.trim().split(/\s+/);
|
|
51
|
+
if (parts.length !== 5)
|
|
52
|
+
return cron;
|
|
53
|
+
const [min, hour, dom, mon, dow] = parts;
|
|
54
|
+
const minStep = STEP_EVERY.exec(min);
|
|
55
|
+
if (minStep && hour === "*" && dom === "*" && mon === "*" && dow === "*") {
|
|
56
|
+
return `*:0/${minStep[1]}`; // every N minutes
|
|
57
|
+
}
|
|
58
|
+
const hourStep = STEP_EVERY.exec(hour);
|
|
59
|
+
if (min === "0" && hourStep && dom === "*" && mon === "*" && dow === "*") {
|
|
60
|
+
return `0/${hourStep[1]}:00`; // every N hours
|
|
61
|
+
}
|
|
62
|
+
const pad = (s) => (/^\d+$/.test(s) ? s.padStart(2, "0") : "*");
|
|
63
|
+
const time = `${pad(hour)}:${pad(min)}:00`;
|
|
64
|
+
const dayPrefix = dowToSystemd(dow);
|
|
65
|
+
const prefix = dayPrefix ? `${dayPrefix} ` : "";
|
|
66
|
+
if (dom === "*" && mon === "*") {
|
|
67
|
+
return `${prefix}*-*-* ${time}`;
|
|
68
|
+
}
|
|
69
|
+
// best effort with date components
|
|
70
|
+
return `${prefix}*-${pad(mon)}-${pad(dom)} ${time}`;
|
|
71
|
+
}
|
|
72
|
+
/** Double-quote a value for a systemd unit (handles spaces; escapes backslash and quote). */
|
|
73
|
+
function sdq(s) {
|
|
74
|
+
return `"${s.replace(/(["\\])/g, "\\$1")}"`;
|
|
75
|
+
}
|
|
76
|
+
export function generateService(job, stateRoot) {
|
|
77
|
+
// Run from stateRoot with --root so state resolves independent of agent cwd.
|
|
78
|
+
return `[Unit]
|
|
79
|
+
Description=omp scheduled job ${job.id}
|
|
80
|
+
|
|
81
|
+
[Service]
|
|
82
|
+
Type=oneshot
|
|
83
|
+
ExecStart=${sdq(job.ompBinPath)} schedule run --id ${job.id} --root ${sdq(stateRoot)}
|
|
84
|
+
WorkingDirectory=${sdq(stateRoot)}
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
export function generateTimer(job) {
|
|
88
|
+
return `[Unit]
|
|
89
|
+
Description=Timer for omp scheduled job ${job.id}
|
|
90
|
+
|
|
91
|
+
[Timer]
|
|
92
|
+
OnCalendar=${cronToSystemdCalendar(job.cron)}
|
|
93
|
+
Persistent=true
|
|
94
|
+
|
|
95
|
+
[Install]
|
|
96
|
+
WantedBy=timers.target
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
export function installSystemd(job, stateRoot) {
|
|
100
|
+
mkdirSync(userUnitDir(), { recursive: true });
|
|
101
|
+
writeFileSync(servicePath(job.id), generateService(job, stateRoot), "utf8");
|
|
102
|
+
writeFileSync(timerPath(job.id), generateTimer(job), "utf8");
|
|
103
|
+
execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
|
|
104
|
+
execFileSync("systemctl", ["--user", "enable", "--now", `${unitBaseName(job.id)}.timer`], { stdio: "ignore" });
|
|
105
|
+
}
|
|
106
|
+
export function uninstallSystemd(id) {
|
|
107
|
+
try {
|
|
108
|
+
execFileSync("systemctl", ["--user", "disable", "--now", `${unitBaseName(id)}.timer`], { stdio: "ignore" });
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// already disabled
|
|
112
|
+
}
|
|
113
|
+
for (const p of [servicePath(id), timerPath(id)]) {
|
|
114
|
+
if (existsSync(p))
|
|
115
|
+
unlinkSync(p);
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// best effort
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function statusSystemd(id) {
|
|
125
|
+
try {
|
|
126
|
+
const out = execFileSync("systemctl", ["--user", "is-active", `${unitBaseName(id)}.timer`], {
|
|
127
|
+
encoding: "utf8",
|
|
128
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
129
|
+
});
|
|
130
|
+
return out.trim() === "active";
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return existsSync(timerPath(id));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=systemd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"systemd.js","sourceRoot":"","sources":["../../../../src/schedule/installers/systemd.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ScheduleJob, ScheduleRunResult } from "./types.js";
|
|
2
|
+
/** Atomic write (tmp + rename), mirroring task-store.writeTask. */
|
|
3
|
+
export declare function writeJob(jobPath: string, job: ScheduleJob): void;
|
|
4
|
+
export declare function readJob(jobPath: string): ScheduleJob | undefined;
|
|
5
|
+
export declare function listJobs(jobsDir: string): ScheduleJob[];
|
|
6
|
+
export declare function deleteJob(jobPath: string): void;
|
|
7
|
+
/** Append a result line. Append-only — never read-modify-write (race-free under concurrent runs). */
|
|
8
|
+
export declare function appendRunResult(resultsPath: string, result: ScheduleRunResult): void;
|
|
9
|
+
/** Atomic write of the byte-offset cursor (tmp + rename). */
|
|
10
|
+
export declare function advanceCursor(cursorPath: string, bytes: number): void;
|
|
11
|
+
export interface ResultsScan {
|
|
12
|
+
results: ScheduleRunResult[];
|
|
13
|
+
newCursor: number;
|
|
14
|
+
cursor: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read result lines from the stored byte offset to EOF, parsing only complete
|
|
18
|
+
* lines. Mirrors outbox.scanFromCursor. `maxBytes` bounds the read so a hook
|
|
19
|
+
* with a tight time budget never scans an unbounded file.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readResultsFrom(resultsPath: string, cursorPath: string, maxBytes?: number): ResultsScan;
|
|
@@ -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>;
|