Scope files jobs and plugins to request profile

This commit is contained in:
ekko
2026-05-24 10:11:03 +08:00
committed by ekko
parent 289a958684
commit 9708a6a521
23 changed files with 353 additions and 117 deletions
@@ -2,18 +2,21 @@ import type { Context } from 'koa'
import { readdir, stat, readFile } from 'fs/promises'
import { join } from 'path'
import { existsSync } from 'fs'
import { getActiveProfileDir } from '../../services/hermes/hermes-profile'
import { getActiveProfileName, getProfileDir } from '../../services/hermes/hermes-profile'
const SYNTHETIC_RUN_FILE = '__scheduler_metadata__.md'
function getCronOutputDir(): string {
// Use the active profile's directory, so cron history follows profile switches
const profileDir = getActiveProfileDir()
function requestedProfile(ctx: Context): string {
return ctx.state?.profile?.name || getActiveProfileName() || 'default'
}
function getCronOutputDir(profile: string): string {
const profileDir = getProfileDir(profile)
return join(profileDir, 'cron', 'output')
}
function getCronJobsFile(): string {
const profileDir = getActiveProfileDir()
function getCronJobsFile(profile: string): string {
const profileDir = getProfileDir(profile)
return join(profileDir, 'cron', 'jobs.json')
}
@@ -69,8 +72,8 @@ function normaliseJobsPayload(payload: unknown): CronJobMetadata[] {
return []
}
async function readCronJobs(): Promise<CronJobMetadata[]> {
const jobsFile = getCronJobsFile()
async function readCronJobs(profile: string): Promise<CronJobMetadata[]> {
const jobsFile = getCronJobsFile(profile)
if (!existsSync(jobsFile)) return []
try {
@@ -182,7 +185,8 @@ function buildSyntheticContent(job: CronJobMetadata, runTime: string): string {
/** List all run output files, optionally filtered by job ID */
export async function listRuns(ctx: Context) {
const jobId = ctx.query.jobId as string | undefined
const cronOutput = getCronOutputDir()
const profile = requestedProfile(ctx)
const cronOutput = getCronOutputDir(profile)
try {
const runs: RunEntry[] = []
@@ -220,7 +224,7 @@ export async function listRuns(ctx: Context) {
}
}
const jobs = await readCronJobs()
const jobs = await readCronJobs(profile)
const targetJobs = jobId ? jobs.filter(job => getJobId(job) === jobId) : jobs
for (const job of targetJobs) {
const id = getJobId(job)
@@ -242,6 +246,7 @@ export async function listRuns(ctx: Context) {
/** Read a specific run output file */
export async function readRun(ctx: Context) {
const { jobId, fileName } = ctx.params
const profile = requestedProfile(ctx)
if (!jobId || !fileName) {
ctx.status = 400
@@ -264,7 +269,7 @@ export async function readRun(ctx: Context) {
}
if (fileName === SYNTHETIC_RUN_FILE) {
const jobs = await readCronJobs()
const jobs = await readCronJobs(profile)
const job = jobs.find(candidate => getJobId(candidate) === jobId)
const synthetic = job ? syntheticRunEntry(job) : null
if (!job || !synthetic) {
@@ -282,7 +287,7 @@ export async function readRun(ctx: Context) {
return
}
const cronOutput = getCronOutputDir()
const cronOutput = getCronOutputDir(profile)
const filePath = join(cronOutput, jobId, fileName)
if (!existsSync(filePath)) {
@@ -12,7 +12,7 @@ const TIMEOUT_MS = 60_000
type JobRecord = Record<string, any>
function resolveProfile(ctx: Context): string {
const requestedProfile = ctx.get('x-hermes-profile') || (ctx.query.profile as string)
const requestedProfile = ctx.state?.profile?.name
return requestedProfile || getActiveProfileName()
}
@@ -2,7 +2,7 @@ import { listHermesPlugins } from '../../services/hermes/plugins'
export async function list(ctx: any) {
try {
ctx.body = await listHermesPlugins()
ctx.body = await listHermesPlugins(ctx.state?.profile?.name)
} catch (err: any) {
ctx.status = 500
ctx.body = { error: err.message || 'Failed to discover Hermes plugins' }