Use Spark for repo Face heartbeats

This commit is contained in:
Meta Vens 2026-05-21 00:14:07 +01:00
commit 8507d7337f
4 changed files with 37 additions and 2 deletions

View file

@ -73,6 +73,8 @@ REPO_FACE_HEARTBEAT_TASK_NAME=VoidBot Repo Face Heartbeats
REPO_FACE_HEARTBEAT_INTERVAL_MINUTES=15
REPO_FACE_HEARTBEAT_MAX_JOBS_PER_TICK=2
REPO_FACE_HEARTBEAT_DEFAULT_CHANNEL_ID=
REPO_FACE_HEARTBEAT_CODEX_MODEL=gpt-5.3-codex-spark
REPO_FACE_HEARTBEAT_CODEX_REASONING_EFFORT=low
EPIPHANY_AGENT_ROOT=E:/Projects/EpiphanyAgent
BIFROST_ROOT=E:/Projects/Bifrost
BIFROST_HEALTH_URL=http://127.0.0.1:5080/healthz

View file

@ -297,6 +297,7 @@ async function processJob(job: JobRecord): Promise<void> {
const request = provider.buildRequest(job.contextBundle, {
jobId: job.id,
command: job.command,
...repoFaceHeartbeatCodexOptions(job),
});
const response = await provider.execute(request);
const artifactPaths = await writeArtifacts(job.id, response.artifacts ?? []);
@ -459,6 +460,19 @@ async function processJob(job: JobRecord): Promise<void> {
}
}
function repoFaceHeartbeatCodexOptions(job: JobRecord): Record<string, string> {
if (job.command !== "repo-face-rumination") {
return {};
}
return {
...(config.repoFaceHeartbeats.codexModel ? { model: config.repoFaceHeartbeats.codexModel } : {}),
...(config.repoFaceHeartbeats.codexModelReasoningEffort
? { reasoningEffort: config.repoFaceHeartbeats.codexModelReasoningEffort }
: {}),
};
}
async function maybeNotifyOwnerOfJobFailure(job: JobRecord, errorMessage: string): Promise<void> {
if (job.requester.id !== config.ownerDiscordId) {
return;

View file

@ -116,6 +116,10 @@ const envSchema = z.object({
REPO_FACE_HEARTBEAT_GLOBAL_HEAT: z.coerce.number().positive().default(1),
REPO_FACE_HEARTBEAT_SPEED_OVERRIDES: z.string().default(""),
REPO_FACE_HEARTBEAT_HEAT_OVERRIDES: z.string().default(""),
REPO_FACE_HEARTBEAT_CODEX_MODEL: optionalNonEmptyString,
REPO_FACE_HEARTBEAT_CODEX_REASONING_EFFORT: z
.enum(["low", "medium", "high", "xhigh"])
.optional(),
EPIPHANY_AGENT_ROOT: z.string().min(1).default("E:/Projects/EpiphanyAgent"),
INDEX_ALL_CHANNELS: booleanFromEnv.default(false),
INDEXED_CHANNEL_IDS: z.string().default(""),
@ -167,6 +171,8 @@ export interface AppConfig {
globalHeat: number;
speedOverrides: Record<string, number>;
heatOverrides: Record<string, number>;
codexModel?: string;
codexModelReasoningEffort?: "low" | "medium" | "high" | "xhigh";
};
epiphanyAgentRoot: string;
indexAllChannels: boolean;
@ -387,6 +393,8 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
globalHeat: parsed.REPO_FACE_HEARTBEAT_GLOBAL_HEAT,
speedOverrides: parseNumericMap(parsed.REPO_FACE_HEARTBEAT_SPEED_OVERRIDES),
heatOverrides: parseNumericMap(parsed.REPO_FACE_HEARTBEAT_HEAT_OVERRIDES),
codexModel: parsed.REPO_FACE_HEARTBEAT_CODEX_MODEL,
codexModelReasoningEffort: parsed.REPO_FACE_HEARTBEAT_CODEX_REASONING_EFFORT,
},
epiphanyAgentRoot: resolve(parsed.EPIPHANY_AGENT_ROOT),
indexAllChannels: parsed.INDEX_ALL_CHANNELS,

View file

@ -185,8 +185,8 @@ export class OwnerCodexProvider implements ProviderAdapter {
result = await runCodexExec({
executable: this.options.executable,
executableArgs: this.options.executableArgs,
model: this.options.model,
reasoningEffort: this.options.reasoningEffort,
model: getStringOption(request.options?.model) ?? this.options.model,
reasoningEffort: getReasoningEffortOption(request.options?.reasoningEffort) ?? this.options.reasoningEffort,
timeoutMs: this.options.timeoutMs,
workingDirectory: this.options.workingDirectory,
prompt: codexPrompt,
@ -387,3 +387,14 @@ export class OwnerCodexProvider implements ProviderAdapter {
};
}
}
function getStringOption(value: unknown): string | undefined {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
}
function getReasoningEffortOption(value: unknown): "low" | "medium" | "high" | "xhigh" | undefined {
if (value === "low" || value === "medium" || value === "high" || value === "xhigh") {
return value;
}
return undefined;
}