src / worker / workerShared.ts
src / worker / workerShared.ts
import fs from "fs";
import path from "path";
// dist/index.js (plugin host) and dist/worker.js (background worker) are both bundled into dist/,
// so the plugin root is one level above __dirname for either process.
export const PLUGIN_ROOT = path.resolve(__dirname, "..");
export const WORKER_STATE_DIR = path.join(PLUGIN_ROOT, ".state", "worker");
export const JOBS_DIR = path.join(WORKER_STATE_DIR, "jobs");
export const PID_FILE = path.join(WORKER_STATE_DIR, "worker.pid");
export const WORKER_LOG_FILE = path.join(WORKER_STATE_DIR, "worker.log");
export const WORKER_SCRIPT = path.join(__dirname, "worker.js");
/** The worker touches the PID file at this interval; a PID file older than the max age is not trusted. */
export const HEARTBEAT_INTERVAL_MS = 2_000;
export const HEARTBEAT_MAX_AGE_MS = 30_000;
export const JOB_FILE_SUFFIX = ".job";
export const CLAIMED_INFIX = ".claimed-";
export type WorkerJob = {
requestId: string;
chatId: string;
/** Chat working directory; also the delivery target for the result. */
workingDir: string;
tool: "generate_image";
/** Tool arguments as received by the plugin, without the `runInBackground` flag. */
args: Record<string, unknown>;
/** Config values the render pipeline reads from process.env (exported by toolsProvider in the plugin host). */
env: Record<string, string>;
/** Absolute path of custom_configs.json as configured in the plugin host, or null when disabled. */
customConfigsPath: string | null;
submittedAt: string;
};
/** PID of the running worker, or null when no live worker is registered (missing, stale or dead PID file). */
export function readLiveWorkerPid(): number | null {
let raw: string;
let mtimeMs: number;
try {
raw = fs.readFileSync(PID_FILE, "utf8");
mtimeMs = fs.statSync(PID_FILE).mtimeMs;
} catch (e: any) {
if (e?.code === "ENOENT") return null;
throw e;
}
const pid = parseInt(raw.trim(), 10);
if (!Number.isFinite(pid) || pid <= 0) return null;
if (Date.now() - mtimeMs > HEARTBEAT_MAX_AGE_MS) return null;
try {
process.kill(pid, 0);
} catch (e: any) {
return e?.code === "EPERM" ? pid : null;
}
return pid;
}
import fs from "fs";
import path from "path";
// dist/index.js (plugin host) and dist/worker.js (background worker) are both bundled into dist/,
// so the plugin root is one level above __dirname for either process.
export const PLUGIN_ROOT = path.resolve(__dirname, "..");
export const WORKER_STATE_DIR = path.join(PLUGIN_ROOT, ".state", "worker");
export const JOBS_DIR = path.join(WORKER_STATE_DIR, "jobs");
export const PID_FILE = path.join(WORKER_STATE_DIR, "worker.pid");
export const WORKER_LOG_FILE = path.join(WORKER_STATE_DIR, "worker.log");
export const WORKER_SCRIPT = path.join(__dirname, "worker.js");
/** The worker touches the PID file at this interval; a PID file older than the max age is not trusted. */
export const HEARTBEAT_INTERVAL_MS = 2_000;
export const HEARTBEAT_MAX_AGE_MS = 30_000;
export const JOB_FILE_SUFFIX = ".job";
export const CLAIMED_INFIX = ".claimed-";
export type WorkerJob = {
requestId: string;
chatId: string;
/** Chat working directory; also the delivery target for the result. */
workingDir: string;
tool: "generate_image";
/** Tool arguments as received by the plugin, without the `runInBackground` flag. */
args: Record<string, unknown>;
/** Config values the render pipeline reads from process.env (exported by toolsProvider in the plugin host). */
env: Record<string, string>;
/** Absolute path of custom_configs.json as configured in the plugin host, or null when disabled. */
customConfigsPath: string | null;
submittedAt: string;
};
/** PID of the running worker, or null when no live worker is registered (missing, stale or dead PID file). */
export function readLiveWorkerPid(): number | null {
let raw: string;
let mtimeMs: number;
try {
raw = fs.readFileSync(PID_FILE, "utf8");
mtimeMs = fs.statSync(PID_FILE).mtimeMs;
} catch (e: any) {
if (e?.code === "ENOENT") return null;
throw e;
}
const pid = parseInt(raw.trim(), 10);
if (!Number.isFinite(pid) || pid <= 0) return null;
if (Date.now() - mtimeMs > HEARTBEAT_MAX_AGE_MS) return null;
try {
process.kill(pid, 0);
} catch (e: any) {
return e?.code === "EPERM" ? pid : null;
}
return pid;
}