Files
pikasTech-unidesk/src/components/microservices/code-queue/src/index.ts
T
2026-05-17 10:05:47 +00:00

4959 lines
210 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 重构前 index.ts 只读参考:commit 6a04144d3f5103014f75b637d7e6bc2f45bf007fblob 56e590c1a6b5ca7ad128bf2c992f60e46c355a58;可用 `git show 6a04144d3f5103014f75b637d7e6bc2f45bf007f:src/components/microservices/code-queue/src/index.ts` 查看。
import { spawnSync } from "node:child_process";
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { Database } from "bun:sqlite";
import postgres from "postgres";
import { createHourlyJsonlWriter, logRetentionBytesForService } from "../../../shared/src/rotating-jsonl";
import type {
AttemptSummary,
CgroupMemoryUsage,
CodexEventSummary,
CodexRunResult,
JudgeProbeCase,
JudgeResult,
JsonValue,
LiveOutput,
OutputChannel,
PersistedState,
PromptHistoryItem,
QueueRecord,
QueuedStatusReason,
QueueTask,
QueueTaskRequest,
RunMode,
RuntimeConfig,
CodeQueueServiceRole,
TaskStatus,
WorkdirRecord,
} from "./types";
import {
codeAgentPortForModel,
defaultCodeModels,
extractRecord,
extractString,
normalizeCodeExecutionMode,
normalizeCodeModel,
terminalStatus,
} from "./code-agent/common";
import type { ActiveRun, ActiveRunSlotWaiter } from "./code-agent/common";
import { configureCodexPort, runCodexTurn } from "./code-agent/codex";
import { configureOpenCodePort, runOpenCodeTurn } from "./code-agent/opencode";
import {
compactRetryTaskContext,
compactContinuationPromptTargetChars,
configureJudge,
explicitUserInterrupt,
judgeFailContinuationPrompt,
judgeReasonForPrompt,
judgeTaskInputDiagnostics,
judgeTask,
queueRecoveryRetryPrompt,
retryPrompt,
} from "./judge";
import { defaultJudgeProbeCases } from "./judge-probes";
import { injectCodeQueueEnvironmentHint, promptWithCodeQueueEnvironmentHint, userPromptForDisplay } from "./prompts";
import { configureDevContainers, devContainerStatus, ensureTaskExecutionContainer, startDevContainer } from "./dev-containers";
import { appendOutput, appendOutputArchive, configureTaskOutput, taskFullOutput } from "./task-output";
import {
buildDevContainerPlan,
configureProviderRuntime,
containerTunnelStartScript,
defaultWorkdirForProvider,
devContainerPingScript,
executionModeOptions,
executionProviderOptions,
masterKeyReadScript,
masterKeySetupScript,
masterProxyEvidenceScript,
masterProxyFinishScript,
masterProxyPrepareScript,
normalizeProviderId,
normalizeTaskProviderId,
providerIsMain,
remoteAppServerCommand,
remoteCodexConfigInstallScript,
remoteCodexRuntimePrepareScript,
remoteContainerStartScript,
remoteHostWorkdirForTask,
remoteKeyInstallScript,
resolveTaskCwd,
runCodeQueueSsh,
shellQuote,
throwIfCommandFailed,
windowsNativeAppServerCommand,
} from "./provider-runtime";
import {
armIdleNotification,
backfillClaudeQqTaskNotifications,
claudeQqNotificationItems,
claudeQqNotificationOutboxItemCount,
claudeQqNotificationOutboxStats,
configureNotifications,
drainClaudeQqNotificationOutbox,
loadClaudeQqNotificationOutboxFromDatabase,
maybeNotifyQueueIdle,
notificationTargetConfigured,
notificationTargetLabel,
notifyTaskTerminal,
scheduleClaudeQqNotificationDrain,
} from "./notifications";
import {
configureQueueApi,
loadAllTasksForRead,
outputChunkResponse,
perQueueSummaries,
queueSummary,
queueSummaryForHealth,
queueSummaryForResponse,
taskMatchesSearch,
taskPageRows,
taskSearchTerms,
tasksOverviewResponse,
taskForListResponse,
transcriptChunkResponse,
} from "./queue-api";
import { ReferenceTaskLookupError, configureReferences, injectReferencedTaskContext, taskReferenceIds } from "./references";
import {
applyOaTraceStatsToTaskJson,
configureOaEvents,
oaEventPublisherStatus,
publishCodeQueueJudgeEvent,
publishCodeQueueQueueUpdated,
publishCodeQueueTaskUpdated,
publishCodeQueueTraceStatsSnapshot,
publishCodeQueueTraceStep,
outputTraceKind,
readOaTraceStatsForTask,
readOaTraceStatsForTaskAttempts,
readOaTraceStatsForTasks,
} from "./oa-events";
import { configureSelfTests, runJudgeInfraSelfTest, runQueueClaimMoveSelfTest, runQueueOrderingSelfTest, runReferenceInjectionSelfTest, runTracePortSelfTest } from "./self-tests";
import {
codexToolLifecycleStartedBeforeIn,
configureTaskView,
formatCommandOutput,
isCodexToolLifecycleOutput,
lastAssistantMessage,
promptLineCount,
recordNumberField,
recordStringField,
safePreview,
setAttemptFeedbackPrompt,
setAttemptInputPrompt,
statsDaysFromUrl,
taskForMetaResponse,
taskForResponse,
taskListStepCount,
taskOutputMaxSeq as taskViewOutputMaxSeq,
taskStatisticsSummary,
taskSummaryResponse,
timestampMs,
nonNegativeElapsed,
taskTraceStepDetailResponse,
taskTraceStepsResponse,
taskTraceSummaryResponse,
taskPromptDetailResponse,
} from "./task-view";
type SqlClient = postgres.Sql;
type SqlExecutor = postgres.Sql | postgres.TransactionSql;
const recentLogs: JsonValue[] = [];
const serviceStartedAt = new Date().toISOString();
const defaultQueueId = "default";
const firstPaintOverviewWarmUrl = "http://code-queue.local/api/tasks/overview?limit=12&transcriptLimit=1&compact=1&selected=0&includeActive=0&stats=0&skipTrace=1";
let firstPaintOverviewWarmInFlight: Promise<void> | null = null;
const judgeFailRetryLimit = 3;
const fallbackJudgeRetryLimit = 3;
const maxTaskAttempts = 99;
const referenceInjectionMaxRounds: number | null = null;
const retryBackoffBaseMs = 1000;
const retryBackoffMaxMs = 10 * 60 * 1000;
const queueIdPattern = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/u;
const queueNameMaxLength = 80;
const workdirMaxLength = 512;
const config = readConfig();
const logger = createLogger("code-queue", config.logFile);
configureOaEvents({ baseUrl: config.oaEventFlowBaseUrl, logger, nowIso });
const providerGatewayEgressProxy = configureProviderGatewayEgressProxy();
if (providerGatewayEgressProxy.enabled) {
const proxyEnv = providerGatewayEgressProxy.proxyUrl;
process.env.HTTP_PROXY = proxyEnv;
process.env.HTTPS_PROXY = proxyEnv;
process.env.http_proxy = proxyEnv;
process.env.https_proxy = proxyEnv;
process.env.ALL_PROXY = proxyEnv;
process.env.all_proxy = proxyEnv;
process.env.NO_PROXY = providerGatewayEgressProxy.noProxy;
process.env.no_proxy = providerGatewayEgressProxy.noProxy;
}
const state = emptyState();
let processing = false;
const processingQueues = new Set<string>();
const mergingQueues = new Set<string>();
const activeRuns = new Map<string, ActiveRun>();
const activeRunSlotReservations = new Set<string>();
const activeRunSlotWaiters: ActiveRunSlotWaiter[] = [];
const devContainerEnsurePromises = new Map<string, Promise<void>>();
let nextActiveRunSlotWaiterId = 1;
let devReadyCache: { checkedAtMs: number; value: JsonValue } | null = null;
let persistTimer: ReturnType<typeof setTimeout> | null = null;
let persistDirty = false;
let shutdownRequested = false;
let serviceReady = false;
const sql: SqlClient = postgres(config.databaseUrl, {
max: config.databasePoolMax,
idle_timeout: 20,
connect_timeout: 10,
connection: { application_name: "unidesk-code-queue" },
});
let databaseReady = false;
let databaseLastError: string | null = null;
let databaseFlushTimer: ReturnType<typeof setTimeout> | null = null;
let databaseFlushInFlight = false;
const dirtyDatabaseTaskIds = new Set<string>();
const dirtyDatabaseQueueIds = new Set<string>();
const workdirRecords = new Map<string, WorkdirRecord>();
function envString(name: string, fallback: string): string {
const value = process.env[name];
return value === undefined || value.length === 0 ? fallback : value;
}
function envNullableString(name: string): string | null {
const value = process.env[name];
return value === undefined || value.length === 0 ? null : value;
}
function envRequiredString(name: string): string {
const value = process.env[name];
if (value === undefined || value.trim().length === 0) throw new Error(`${name} is required`);
return value;
}
function envNumber(name: string, fallback: number): number {
const raw = process.env[name];
if (raw === undefined || raw.length === 0) return fallback;
const value = Number(raw);
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
}
function envNonNegativeNumber(name: string, fallback: number): number {
const raw = process.env[name];
if (raw === undefined || raw.length === 0) return fallback;
const value = Number(raw);
return Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;
}
function clampTaskAttempts(value: number): number {
const parsed = Number(value);
if (!Number.isFinite(parsed)) return maxTaskAttempts;
return Math.max(1, Math.min(maxTaskAttempts, Math.floor(parsed)));
}
function reserveNextAttemptBudget(task: QueueTask): boolean {
const nextAttempt = task.attempts.length + 1;
if (nextAttempt > maxTaskAttempts) return false;
task.maxAttempts = clampTaskAttempts(Math.max(task.maxAttempts, nextAttempt));
return true;
}
function envBool(name: string, fallback: boolean): boolean {
const raw = process.env[name];
if (raw === undefined || raw.trim().length === 0) return fallback;
const value = raw.trim().toLowerCase();
if (value === "1" || value === "true" || value === "yes" || value === "on") return true;
if (value === "0" || value === "false" || value === "no" || value === "off") return false;
return fallback;
}
function serviceRoleValue(raw: string): CodeQueueServiceRole {
const normalized = raw.trim().toLowerCase();
if (normalized === "read" || normalized === "write" || normalized === "scheduler" || normalized === "combined") return normalized;
return "combined";
}
function serviceRoleAllowsWrite(role: CodeQueueServiceRole): boolean {
return role === "combined" || role === "write";
}
function serviceRoleAllowsScheduler(role: CodeQueueServiceRole): boolean {
return role === "combined" || role === "scheduler";
}
function serviceRoleReadOnly(role: CodeQueueServiceRole): boolean {
return role === "read";
}
function envList(name: string, fallback: string[]): string[] {
const raw = process.env[name];
const source = raw === undefined || raw.length === 0 ? fallback.join(",") : raw;
return Array.from(new Set(source.split(",").map((item) => item.trim()).filter(Boolean)));
}
function envModelReasoningEfforts(name: string, fallback: Record<string, string>): Record<string, string> {
const raw = process.env[name];
const map: Record<string, string> = { ...fallback };
if (raw === undefined || raw.trim().length === 0) return map;
for (const item of raw.split(/[,;]+/u)) {
const [model, effort] = item.split("=", 2).map((part) => part.trim());
if (model && effort) map[model.toLowerCase()] = effort;
}
return map;
}
function withRequiredModel(models: string[], model: string): string[] {
return models.includes(model) ? models : [model, ...models];
}
function uniqueModels(models: string[]): string[] {
return Array.from(new Set(models.map((item) => item.trim()).filter(Boolean)));
}
function sandboxValue(raw: string): RuntimeConfig["sandbox"] {
if (raw === "read-only" || raw === "workspace-write" || raw === "danger-full-access") return raw;
return "danger-full-access";
}
function approvalValue(raw: string): RuntimeConfig["approvalPolicy"] {
if (raw === "untrusted" || raw === "on-failure" || raw === "on-request" || raw === "never") return raw;
return "never";
}
function readConfig(): RuntimeConfig {
const defaultModel = normalizeCodeModel(envString("CODE_QUEUE_DEFAULT_MODEL", "gpt-5.5"));
const codeModels = uniqueModels(withRequiredModel(envList("CODE_QUEUE_MODELS", defaultCodeModels).map(normalizeCodeModel), defaultModel));
const dataDir = envString("CODE_QUEUE_DATA_DIR", "/var/lib/unidesk/code-queue");
const notifyTargetTypeRaw = envString("CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE", "private").toLowerCase();
const mainProviderId = envString("CODE_QUEUE_MAIN_PROVIDER_ID", "D601");
const devContainerDefaultProviderId = envString("CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID", "D601");
const remoteDefaultWorkdir = envString("CODE_QUEUE_REMOTE_WORKDIR", "/home/ubuntu");
const defaultWorkdir = envString("CODE_QUEUE_WORKDIR", "/workspace");
const devContainerMasterHost = envString("CODE_QUEUE_DEV_CONTAINER_MASTER_HOST", "74.48.78.17");
const defaultProviderGatewayProxyHost = `unidesk-provider-gateway-${mainProviderId}`;
const serviceRole = serviceRoleValue(envString("CODE_QUEUE_SERVICE_ROLE", "combined"));
const executionProviderIds = Array.from(new Set([
mainProviderId,
...envList("CODE_QUEUE_EXECUTION_PROVIDER_IDS", [devContainerDefaultProviderId]),
].map((item) => item.trim()).filter(Boolean)));
return {
host: envString("HOST", "0.0.0.0"),
port: envNumber("PORT", 4222),
dataDir,
instanceId: envString("CODE_QUEUE_INSTANCE_ID", mainProviderId),
deployCommit: envString("CODE_QUEUE_DEPLOY_COMMIT", "unknown"),
deployRequestedCommit: envString("CODE_QUEUE_DEPLOY_REQUESTED_COMMIT", ""),
serviceRole,
schedulerEnabled: envBool("CODE_QUEUE_SCHEDULER_ENABLED", serviceRoleAllowsScheduler(serviceRole)),
schedulerPollIntervalMs: Math.max(500, Math.min(30_000, envNumber("CODE_QUEUE_SCHEDULER_POLL_INTERVAL_MS", 2000))),
startupOaBackfillEnabled: envBool("CODE_QUEUE_STARTUP_OA_BACKFILL_ENABLED", false),
outputArchiveDir: envString("CODE_QUEUE_OUTPUT_ARCHIVE_DIR", resolve(dataDir, "output-archive")),
logFile: envString("LOG_FILE", "/var/log/unidesk/code-queue.jsonl"),
defaultWorkdir,
mainProviderId,
remoteDefaultWorkdir,
executionProviderIds,
remoteCodexEnvKeys: envList("CODE_QUEUE_REMOTE_CODEX_ENV_KEYS", ["OPENAI_API_KEY", "CRS_OAI_KEY", "OPENAI_BASE_URL", "OPENAI_API_BASE", "MINIMAX_API_KEY", "MINIMAX_API_BASE", "MINIMAX_MODEL"]),
codexHome: envString("CODE_QUEUE_CODEX_HOME", "/var/lib/unidesk/code-queue/codex-home"),
opencodeXdgDir: envString("CODE_QUEUE_OPENCODE_XDG_DIR", resolve(dataDir, "opencode-xdg")),
sourceCodexConfig: envString("CODE_QUEUE_SOURCE_CODEX_CONFIG", "/root/.codex/config.toml"),
codexSqliteLogExportEnabled: envBool("CODE_QUEUE_CODEX_SQLITE_LOG_EXPORT_ENABLED", true),
codexSqliteLogExportIntervalMs: Math.max(10_000, Math.min(10 * 60_000, envNumber("CODE_QUEUE_CODEX_SQLITE_LOG_EXPORT_INTERVAL_MS", 60_000))),
codexSqliteLogExportBatchSize: Math.max(100, Math.min(20_000, envNumber("CODE_QUEUE_CODEX_SQLITE_LOG_EXPORT_BATCH_SIZE", 500))),
codexSqliteLogMaxBytes: logRetentionBytesForService("codex-app-server", ["CODE_QUEUE_CODEX_SQLITE_LOG_MAX_BYTES"]),
memoryWatchdogThresholdBytes: Math.max(0, envNumber("CODE_QUEUE_MEMORY_WATCHDOG_THRESHOLD_BYTES", 0)),
memoryWatchdogIntervalMs: Math.max(1000, Math.min(60_000, envNumber("CODE_QUEUE_MEMORY_WATCHDOG_INTERVAL_MS", 2000))),
defaultModel,
codexModels: codeModels,
defaultReasoningEffort: envNullableString("CODE_QUEUE_REASONING_EFFORT"),
modelReasoningEfforts: envModelReasoningEfforts("CODE_QUEUE_MODEL_REASONING_EFFORTS", { "gpt-5.5": "xhigh" }),
sandbox: sandboxValue(envString("CODE_QUEUE_SANDBOX", "danger-full-access")),
approvalPolicy: approvalValue(envString("CODE_QUEUE_APPROVAL_POLICY", "never")),
defaultMaxAttempts: clampTaskAttempts(envNumber("CODE_QUEUE_MAX_ATTEMPTS", maxTaskAttempts)),
codeModels,
minimaxApiKey: envString("MINIMAX_API_KEY", ""),
minimaxApiBase: envString("MINIMAX_API_BASE", "https://api.minimaxi.com/v1").replace(/\/+$/u, ""),
minimaxModel: envString("MINIMAX_MODEL", "MiniMax-M2.7"),
judgeTimeoutMs: envNumber("MINIMAX_JUDGE_TIMEOUT_MS", 90_000),
judgeRepairAttempts: Math.max(0, Math.min(5, envNumber("MINIMAX_JUDGE_REPAIR_ATTEMPTS", 2))),
judgeMaxTokens: Math.max(800, Math.min(4000, envNumber("MINIMAX_JUDGE_MAX_TOKENS", 1800))),
turnNoActivityTimeoutMs: Math.max(60_000, Math.min(30 * 60_000, envNumber("CODEX_TURN_NO_ACTIVITY_TIMEOUT_MS", 6 * 60_000))),
databaseUrl: envRequiredString("DATABASE_URL"),
databasePoolMax: Math.max(1, Math.min(8, envNumber("CODE_QUEUE_DATABASE_POOL_MAX", envNumber("DATABASE_POOL_MAX", 2)))),
databaseFlushIntervalMs: Math.max(100, Math.min(10_000, envNumber("CODE_QUEUE_DATABASE_FLUSH_INTERVAL_MS", 1000))),
oaEventFlowBaseUrl: envString("OA_EVENT_FLOW_BASE_URL", "http://oa-event-flow:4255").replace(/\/+$/u, ""),
notifyClaudeQqEnabled: envBool("CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED", false),
notifyClaudeQqBaseUrl: envString("CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL", "http://claudeqq.unidesk.svc.cluster.local:3290").replace(/\/+$/u, ""),
notifyClaudeQqTargetType: notifyTargetTypeRaw === "group" ? "group" : "private",
notifyClaudeQqUserId: envString("CODE_QUEUE_NOTIFY_CLAUDEQQ_USER_ID", "645275593").trim(),
notifyClaudeQqGroupId: envString("CODE_QUEUE_NOTIFY_CLAUDEQQ_GROUP_ID", "").trim(),
notifyClaudeQqMaxResponseChars: Math.max(500, Math.min(50_000, envNumber("CODE_QUEUE_NOTIFY_CLAUDEQQ_MAX_RESPONSE_CHARS", 12_000))),
notifyClaudeQqTimeoutMs: Math.max(1000, Math.min(60_000, envNumber("CODE_QUEUE_NOTIFY_CLAUDEQQ_TIMEOUT_MS", 15_000))),
notifyClaudeQqSendAttempts: Math.max(1, Math.min(10, envNumber("CODE_QUEUE_NOTIFY_CLAUDEQQ_SEND_ATTEMPTS", 3))),
notifyClaudeQqRetryIntervalMs: Math.max(5_000, Math.min(10 * 60_000, envNumber("CODE_QUEUE_NOTIFY_CLAUDEQQ_RETRY_INTERVAL_MS", 60_000))),
notifyClaudeQqMaxOutboxItems: Math.max(100, Math.min(10_000, envNumber("CODE_QUEUE_NOTIFY_CLAUDEQQ_MAX_OUTBOX_ITEMS", 2000))),
maxInMemoryOutputRecords: envNonNegativeNumber("CODE_QUEUE_IN_MEMORY_OUTPUT_RECORDS", 10),
maxInMemoryEventRecords: envNonNegativeNumber("CODE_QUEUE_IN_MEMORY_EVENT_RECORDS", 10),
maxActiveQueues: Math.max(0, Math.min(32, envNumber("CODE_QUEUE_MAX_ACTIVE_QUEUES", 0))),
egressProxyEnabled: envBool("CODE_QUEUE_EGRESS_PROXY_ENABLED", true),
egressProxyUrl: envString("CODE_QUEUE_EGRESS_PROXY_URL", `http://${defaultProviderGatewayProxyHost}:18789`).replace(/\/+$/u, ""),
egressProxyNoProxy: envString("CODE_QUEUE_EGRESS_PROXY_NO_PROXY", [
"localhost",
"127.0.0.1",
"::1",
"host.docker.internal",
defaultProviderGatewayProxyHost,
devContainerMasterHost,
"74.48.78.17",
"backend-core",
"oa-event-flow",
"database",
"hyueapi.com",
".hyueapi.com",
].join(",")),
devContainerMasterHost,
devContainerDefaultProviderId,
devContainerImage: envString("CODE_QUEUE_DEV_CONTAINER_IMAGE", ""),
devContainerWorkdir: envString("CODE_QUEUE_DEV_CONTAINER_WORKDIR", remoteDefaultWorkdir),
windowsNativeCodexBridgeDir: envString("CODE_QUEUE_WINDOWS_NATIVE_CODEX_BRIDGE_DIR", "/home/ubuntu/.unidesk/code-queue/windows-native-codex"),
windowsNativeCodexCommand: envString("CODE_QUEUE_WINDOWS_NATIVE_CODEX_COMMAND", "codex app-server --listen stdio://"),
windowsNativeCodexConnectHost: envString("CODE_QUEUE_WINDOWS_NATIVE_CODEX_CONNECT_HOST", "host.docker.internal"),
windowsNativeCodexDefaultWorkdir: envString("CODE_QUEUE_WINDOWS_NATIVE_CODEX_DEFAULT_WORKDIR", "/mnt/f/Work/ConStart"),
windowsNativeCodexIdleTimeoutMs: Math.max(30_000, Math.min(24 * 60 * 60_000, envNumber("CODE_QUEUE_WINDOWS_NATIVE_CODEX_IDLE_TIMEOUT_MS", 10 * 60_000))),
};
}
function configureProviderGatewayEgressProxy(): { enabled: boolean; proxyUrl: string; noProxy: string; channel: string } {
if (!config.egressProxyEnabled) {
return { enabled: false, proxyUrl: "", noProxy: config.egressProxyNoProxy, channel: "provider-gateway" };
}
return {
enabled: true,
proxyUrl: config.egressProxyUrl,
noProxy: config.egressProxyNoProxy,
channel: "provider-gateway",
};
}
async function providerGatewayEgressProxyStatus(): Promise<JsonValue> {
if (!providerGatewayEgressProxy.enabled) return { enabled: false, channel: "provider-gateway" };
const base = {
enabled: true,
channel: providerGatewayEgressProxy.channel,
proxyUrl: providerGatewayEgressProxy.proxyUrl,
noProxy: providerGatewayEgressProxy.noProxy,
};
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 600);
try {
const url = new URL(providerGatewayEgressProxy.proxyUrl);
url.pathname = "/__unidesk/egress-proxy/health";
url.search = "";
const response = await fetch(url, { signal: controller.signal });
const bodyText = await response.text();
let upstream: JsonValue = bodyText.slice(0, 1000);
try {
upstream = JSON.parse(bodyText) as JsonValue;
} catch {
// Keep the bounded body text as evidence if the proxy returned a non-JSON failure page.
}
return { ...base, connected: response.ok, status: response.status, upstream };
} catch (error) {
return { ...base, connected: false, error: error instanceof Error ? error.message : String(error) };
} finally {
clearTimeout(timer);
}
}
function createLogger(service: string, logFile: string) {
const writer = createHourlyJsonlWriter({
baseLogFile: logFile,
service,
maxBytes: logRetentionBytesForService(service, ["CODE_QUEUE_SERVICE_LOG_MAX_BYTES"]),
});
writer.prune();
return (level: "debug" | "info" | "warn" | "error", message: string, data?: JsonValue): void => {
const entry = data === undefined
? { ts: new Date().toISOString(), service, level, message }
: { ts: new Date().toISOString(), service, level, message, data };
recentLogs.push(entry as JsonValue);
while (recentLogs.length > 500) recentLogs.shift();
const line = `${JSON.stringify(entry)}\n`;
try {
writer.appendLine(line, new Date(entry.ts));
} catch (error) {
console.error(JSON.stringify({ ts: new Date().toISOString(), service, level: "error", message: "log_write_failed", error: String(error) }));
}
const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
consoleMethod(line.trimEnd());
};
}
interface CodexSqliteLogRow {
id: number;
ts: number;
ts_nanos: number;
level: string;
target: string;
feedback_log_body: string | null;
module_path: string | null;
file: string | null;
line: number | null;
thread_id: string | null;
process_uuid: string | null;
estimated_bytes: number | null;
}
const codexSqliteLogExporter = {
running: false,
lastRunAt: null as string | null,
lastExportedRows: 0,
totalExportedRows: 0,
lastDeletedRows: 0,
lastVacuumAt: null as string | null,
lastError: null as string | null,
};
function codexLogDate(row: CodexSqliteLogRow): Date {
const seconds = Number(row.ts);
const nanos = Number(row.ts_nanos);
const millis = seconds * 1000 + Math.floor((Number.isFinite(nanos) ? nanos : 0) / 1_000_000);
return new Date(Number.isFinite(millis) ? millis : Date.now());
}
function codexLogJson(row: CodexSqliteLogRow, sqlitePath: string): Record<string, JsonValue> {
return {
ts: codexLogDate(row).toISOString(),
service: "codex-app-server",
source: "codex-log-db",
sqlitePath,
id: row.id,
level: row.level,
target: row.target,
message: row.feedback_log_body ?? "",
modulePath: row.module_path,
file: row.file,
line: row.line,
threadId: row.thread_id,
processUuid: row.process_uuid,
estimatedBytes: row.estimated_bytes ?? 0,
};
}
function codexLogSqliteFiles(): string[] {
if (!existsSync(config.codexHome)) return [];
return readdirSync(config.codexHome, { withFileTypes: true })
.filter((entry) => entry.isFile() && /^logs_\d+\.sqlite$/u.test(entry.name))
.map((entry) => resolve(config.codexHome, entry.name))
.sort();
}
function runSqliteMaintenance(db: Database, sqlitePath: string, forceVacuum: boolean): void {
try {
db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
} catch {
// Active Codex app-server writers may hold a lock; the next pass will retry.
}
if (!forceVacuum) return;
try {
db.exec("VACUUM");
codexSqliteLogExporter.lastVacuumAt = new Date().toISOString();
} catch (error) {
logger("warn", "codex_sqlite_log_vacuum_failed", { sqlitePath, error: errorToJson(error) });
}
}
function exportCodexSqliteLogFile(sqlitePath: string, writer: ReturnType<typeof createHourlyJsonlWriter>): number {
const db = new Database(sqlitePath);
let exported = 0;
let deleted = 0;
try {
db.exec("PRAGMA busy_timeout = 2000");
const rows = db.query("SELECT id, ts, ts_nanos, level, target, feedback_log_body, module_path, file, line, thread_id, process_uuid, estimated_bytes FROM logs ORDER BY id ASC LIMIT ?")
.all(config.codexSqliteLogExportBatchSize) as unknown as CodexSqliteLogRow[];
for (const row of rows) {
writer.appendJson(codexLogJson(row, sqlitePath), codexLogDate(row));
exported += 1;
}
const maxId = rows.at(-1)?.id;
if (typeof maxId === "number" && Number.isFinite(maxId)) {
const result = db.query("DELETE FROM logs WHERE id <= ?").run(maxId);
deleted = Number(result.changes ?? 0);
codexSqliteLogExporter.lastDeletedRows = deleted;
}
const size = statSync(sqlitePath).size;
runSqliteMaintenance(db, sqlitePath, size > config.codexSqliteLogMaxBytes);
return exported;
} finally {
db.close();
}
}
function exportCodexSqliteLogsOnce(): void {
if (!config.codexSqliteLogExportEnabled || codexSqliteLogExporter.running) return;
codexSqliteLogExporter.running = true;
codexSqliteLogExporter.lastRunAt = new Date().toISOString();
codexSqliteLogExporter.lastExportedRows = 0;
try {
const writer = createHourlyJsonlWriter({
baseLogFile: config.logFile,
service: "codex-app-server",
maxBytes: config.codexSqliteLogMaxBytes,
});
let exported = 0;
for (const sqlitePath of codexLogSqliteFiles()) {
for (let batch = 0; batch < 12; batch += 1) {
const batchRows = exportCodexSqliteLogFile(sqlitePath, writer);
exported += batchRows;
if (batchRows < config.codexSqliteLogExportBatchSize) break;
}
}
writer.prune();
codexSqliteLogExporter.lastExportedRows = exported;
codexSqliteLogExporter.totalExportedRows += exported;
codexSqliteLogExporter.lastError = null;
if (exported > 0) logger("info", "codex_sqlite_logs_exported", { rows: exported, totalRows: codexSqliteLogExporter.totalExportedRows });
} catch (error) {
codexSqliteLogExporter.lastError = error instanceof Error ? error.message : String(error);
logger("warn", "codex_sqlite_log_export_failed", { error: errorToJson(error) });
} finally {
codexSqliteLogExporter.running = false;
}
}
function startCodexSqliteLogExporter(): void {
if (!config.codexSqliteLogExportEnabled) return;
setTimeout(exportCodexSqliteLogsOnce, 1000);
setInterval(exportCodexSqliteLogsOnce, config.codexSqliteLogExportIntervalMs);
}
function readCgroupMemoryValue(path: string): number | null {
try {
const raw = readFileSync(path, "utf8").trim();
if (raw === "max" || raw.length === 0) return null;
const value = Number(raw);
return Number.isFinite(value) && value > 0 ? value : null;
} catch {
return null;
}
}
function readCgroupMemoryNonNegativeValue(path: string): number | null {
try {
const raw = readFileSync(path, "utf8").trim();
if (raw === "max" || raw.length === 0) return null;
const value = Number(raw);
return Number.isFinite(value) && value >= 0 ? value : null;
} catch {
return null;
}
}
function readCgroupMemoryStatValue(name: string): number {
try {
const raw = readFileSync("/sys/fs/cgroup/memory.stat", "utf8");
for (const line of raw.split(/\n/u)) {
const [key, value] = line.trim().split(/\s+/u);
if (key !== name) continue;
const parsed = Number(value);
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
}
} catch {
// memory.stat is optional outside cgroup v2; fall back to raw memory.current.
}
return 0;
}
function readCgroupMemoryUsage(): CgroupMemoryUsage | null {
const current = readCgroupMemoryValue("/sys/fs/cgroup/memory.current");
if (current === null) return null;
const inactiveFile = readCgroupMemoryStatValue("inactive_file");
const swapMax = readCgroupMemoryNonNegativeValue("/sys/fs/cgroup/memory.swap.max");
return {
currentBytes: current,
inactiveFileBytes: inactiveFile,
workingSetBytes: Math.max(0, current - inactiveFile),
swapCurrentBytes: readCgroupMemoryNonNegativeValue("/sys/fs/cgroup/memory.swap.current") ?? 0,
swapMaxBytes: swapMax,
};
}
function memoryWatchdogThreshold(): number | null {
if (config.memoryWatchdogThresholdBytes > 0) return config.memoryWatchdogThresholdBytes;
const cgroupMax = readCgroupMemoryValue("/sys/fs/cgroup/memory.max");
return cgroupMax === null ? null : Math.floor(cgroupMax * 0.98);
}
function activeRunMemoryPressure(): (CgroupMemoryUsage & { thresholdBytes: number }) | null {
const threshold = memoryWatchdogThreshold();
if (threshold === null || threshold <= 0) return null;
let usage = readCgroupMemoryUsage();
if (usage === null || usage.workingSetBytes < threshold) return null;
try {
(Bun as unknown as { gc?: (force?: boolean) => void }).gc?.(true);
usage = readCgroupMemoryUsage();
} catch (error) {
logger("debug", "memory_watchdog_gc_failed", { error: errorToJson(error) });
}
return usage !== null && usage.workingSetBytes >= threshold ? { ...usage, thresholdBytes: threshold } : null;
}
function startMemoryWatchdog(): void {
const threshold = memoryWatchdogThreshold();
if (threshold === null || threshold <= 0) return;
const interval = setInterval(() => {
if (shutdownRequested || activeRuns.size === 0) return;
const usage = activeRunMemoryPressure();
if (usage === null) return;
const runs = Array.from(activeRuns.values());
logger("warn", "memory_watchdog_stopping_active_runs", {
currentBytes: usage.currentBytes,
inactiveFileBytes: usage.inactiveFileBytes,
workingSetBytes: usage.workingSetBytes,
swapCurrentBytes: usage.swapCurrentBytes,
swapMaxBytes: usage.swapMaxBytes,
thresholdBytes: threshold,
activeRunCount: runs.length,
taskIds: runs.map((run) => run.taskId),
});
for (const run of runs) {
const task = findTask(run.taskId);
if (task !== null) {
appendOutput(task, "error", `Code Queue memory watchdog stopped the active ${run.port} run at ${Math.round(usage.workingSetBytes / 1024 / 1024)}MiB working set (${Math.round(usage.currentBytes / 1024 / 1024)}MiB cgroup current) to keep the memory-capped container alive.\n`, "memory/watchdog");
}
run.app.stop();
}
}, config.memoryWatchdogIntervalMs);
interval.unref?.();
}
function nowIso(): string {
return new Date().toISOString();
}
function taskOutputMaxSeq(task: QueueTask): number {
return taskViewOutputMaxSeq(task);
}
function resolveReasoningEffort(model: string, explicit?: string | null): string | null {
const requested = explicit?.trim();
if (requested) return requested;
const modelEffort = config.modelReasoningEfforts[model.toLowerCase()];
return modelEffort ?? config.defaultReasoningEffort;
}
function normalizeQueueId(value: unknown, fallback = defaultQueueId): string {
const text = typeof value === "string" ? value.trim() : "";
if (text.length === 0) return fallback;
if (!queueIdPattern.test(text)) throw new Error("queueId must match /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/");
return text;
}
function safeQueueId(value: unknown): string {
try {
return normalizeQueueId(value);
} catch {
return defaultQueueId;
}
}
function normalizeQueueName(value: unknown, queueId: string): string {
const fallback = queueId.trim().length > 0 ? queueId : defaultQueueId;
const text = typeof value === "string" ? value.replace(/[\u0000-\u001F\u007F]+/gu, " ").replace(/\s+/gu, " ").trim() : "";
if (text.length === 0) return fallback;
if (text.length > queueNameMaxLength) throw new Error(`queue name must be ${queueNameMaxLength} characters or fewer`);
return text;
}
function safeQueueName(value: unknown, queueId: string): string {
try {
return normalizeQueueName(value, queueId);
} catch {
return queueId;
}
}
function normalizeWorkdirPath(value: unknown, providerId: string): string {
const raw = typeof value === "string" ? value.trim() : "";
if (raw.length === 0) throw new Error("workdir path is required");
if (raw.length > workdirMaxLength) throw new Error(`workdir path must be ${workdirMaxLength} characters or fewer`);
if (raw.includes("\u0000")) throw new Error("workdir path contains an invalid character");
return resolveTaskCwd(providerId, raw).replace(/\/+$/u, "") || "/";
}
function workdirRecordKey(providerId: string, executionMode: ReturnType<typeof normalizeCodeExecutionMode>, path: string): string {
return `${providerId}\u0000${executionMode}\u0000${path}`;
}
function sortedWorkdirRecords(): WorkdirRecord[] {
return Array.from(workdirRecords.values()).sort((left, right) => {
const providerDelta = left.providerId.localeCompare(right.providerId);
if (providerDelta !== 0) return providerDelta;
const modeDelta = left.executionMode.localeCompare(right.executionMode);
if (modeDelta !== 0) return modeDelta;
return left.path.localeCompare(right.path);
});
}
function rememberWorkdir(providerIdValue: unknown, executionModeValue: unknown, pathValue: unknown, timestamp = nowIso()): WorkdirRecord {
const providerId = normalizeTaskProviderId(providerIdValue);
const executionMode = normalizeCodeExecutionMode(executionModeValue);
const path = normalizeWorkdirPath(pathValue, providerId);
const key = workdirRecordKey(providerId, executionMode, path);
const existing = workdirRecords.get(key);
if (existing !== undefined) {
existing.updatedAt = timestamp;
return existing;
}
const record: WorkdirRecord = { providerId, executionMode, path, createdAt: timestamp, updatedAt: timestamp };
workdirRecords.set(key, record);
return record;
}
function ensureDefaultWorkdirRecords(): void {
const timestamp = nowIso();
for (const provider of executionProviderOptions() as Array<Record<string, JsonValue>>) {
const providerId = normalizeProviderId(provider.id);
if (providerId === null) continue;
rememberWorkdir(providerId, "default", String(provider.defaultWorkdir || defaultWorkdirForProvider(providerId)), timestamp);
if (provider.supportsWindowsNativeCodex === true && typeof provider.windowsNativeDefaultWorkdir === "string" && provider.windowsNativeDefaultWorkdir.length > 0) {
rememberWorkdir(providerId, "windows-native", provider.windowsNativeDefaultWorkdir, timestamp);
}
}
}
function ensureLocalWorkdir(path: string): { ok: boolean; created: boolean; error: string | null } {
const existed = existsSync(path);
mkdirSync(path, { recursive: true });
return { ok: true, created: !existed, error: null };
}
function emptyState(): PersistedState {
const at = nowIso();
return { version: 1, updatedAt: at, nextSeq: 1, queues: [{ id: defaultQueueId, name: defaultQueueId, createdAt: at, updatedAt: at }], tasks: [] };
}
function ensureQueue(queueId: string, queueName?: unknown): QueueRecord {
const id = normalizeQueueId(queueId);
const existing = state.queues.find((queue) => queue.id === id);
if (existing !== undefined) {
existing.name = safeQueueName(existing.name, id);
if (queueName !== undefined) existing.name = normalizeQueueName(queueName, id);
return existing;
}
const at = nowIso();
const queue = { id, name: normalizeQueueName(queueName, id), createdAt: at, updatedAt: at };
state.queues.push(queue);
state.queues.sort((left, right) => left.id.localeCompare(right.id));
markQueueDirty(id);
return queue;
}
function queueIdOf(task: QueueTask): string {
return safeQueueId(task.queueId);
}
function taskQueueEnteredAt(task: QueueTask): string {
const raw = (task as QueueTask & { queueEnteredAt?: unknown }).queueEnteredAt;
const existing = taskTimestamp(typeof raw === "string" ? raw : null);
if (existing !== null) return existing;
const output = Array.isArray(task.output) ? task.output : [];
const entryEvents = output
.filter((item) => item.method === "queue/move" || item.method === "manual-retry")
.map((item) => taskTimestamp(item.at))
.filter((at): at is string => at !== null)
.sort((left, right) => (timestampMs(right) ?? 0) - (timestampMs(left) ?? 0));
return entryEvents[0] ?? taskTimestamp(task.createdAt) ?? taskTimestamp(task.updatedAt) ?? nowIso();
}
function normalizeSteerPromptText(text: string): string {
return text.replace(/^\s*\[steer\]\s*/u, "").trimEnd();
}
function outputPromptHistory(task: QueueTask): PromptHistoryItem[] {
return task.output
.filter((item) => item.channel === "user" && item.method === "turn/steer")
.map((item) => ({
seq: item.seq,
at: item.at,
method: "turn/steer" as const,
text: normalizeSteerPromptText(item.text),
}))
.filter((item) => item.text.trim().length > 0);
}
function mergePromptHistory(items: PromptHistoryItem[]): PromptHistoryItem[] {
const byKey = new Map<string, PromptHistoryItem>();
for (const item of items) {
const seq = Number(item.seq);
const text = normalizeSteerPromptText(String(item.text || ""));
if (!Number.isFinite(seq) || text.trim().length === 0) continue;
byKey.set(`${seq}:${item.method}`, { seq, at: item.at || nowIso(), method: "turn/steer", text });
}
return Array.from(byKey.values()).sort((left, right) => left.seq - right.seq);
}
function judgeFailCountFromOutput(task: QueueTask): number {
return (task.output ?? []).filter((item) => item.method === "judge" && /\bjudge=fail\b/u.test(item.text)).length;
}
function fallbackJudgeRetryCount(task: QueueTask): number {
const attemptCount = (task.attempts ?? []).filter((attempt) => attempt.judge?.source === "fallback" && attempt.judge.decision === "retry").length;
const outputCount = (task.output ?? []).filter((item) => item.method === "judge" && /\bjudge=retry\b/u.test(item.text) && /\bsource=fallback\b/u.test(item.text)).length;
return Math.max(attemptCount, outputCount);
}
function pruneTaskHotState(task: QueueTask): void {
if (config.maxInMemoryOutputRecords > 0 && task.output.length > config.maxInMemoryOutputRecords) {
task.output.splice(0, task.output.length - config.maxInMemoryOutputRecords);
}
if (config.maxInMemoryEventRecords > 0 && task.events.length > config.maxInMemoryEventRecords) {
task.events.splice(0, task.events.length - config.maxInMemoryEventRecords);
}
}
function taskRetainedTraceStepCount(task: QueueTask): number {
return task.output.reduce((count, output) => count + (outputStartsTraceStep(task, output) ? 1 : 0), 0);
}
function normalizeTaskMetric(value: unknown): number | null {
const parsed = Number(value);
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : null;
}
function normalizeTask(task: QueueTask): QueueTask {
task.queueId = safeQueueId(task.queueId);
task.queueEnteredAt = taskQueueEnteredAt(task);
task.output ??= [];
task.events ??= [];
task.attempts ??= [];
task.readAt = typeof task.readAt === "string" && task.readAt.length > 0 ? task.readAt : null;
if (task.status !== "succeeded" && task.status !== "failed" && task.status !== "canceled") {
task.finishedAt = null;
task.readAt = null;
}
task.activeTurnId ??= null;
task.providerId = normalizeTaskProviderId(task.providerId);
task.model ||= config.defaultModel;
task.executionMode = normalizeCodeExecutionMode(task.executionMode);
task.cwd = resolveTaskCwd(task.providerId, task.cwd);
task.reasoningEffort = resolveReasoningEffort(task.model, task.reasoningEffort);
task.maxAttempts = clampTaskAttempts(task.maxAttempts || config.defaultMaxAttempts);
task.basePrompt ||= userPromptForDisplay(task.prompt);
task.referenceTaskIds ??= referenceTaskIdsFromPrompt(task.prompt);
task.referenceInjection ??= null;
task.judgeFailCount = Number.isInteger(task.judgeFailCount) && task.judgeFailCount >= 0 ? task.judgeFailCount : judgeFailCountFromOutput(task);
const persistedPromptHistory = Array.isArray(task.promptHistory) ? task.promptHistory : [];
task.promptHistory = mergePromptHistory([...persistedPromptHistory, ...outputPromptHistory(task)]);
const storedStepCount = normalizeTaskMetric(task.stepCount) ?? normalizeTaskMetric(task.llmStepCount);
const stepCount = storedStepCount ?? taskRetainedTraceStepCount(task);
task.stepCount = stepCount;
task.llmStepCount = stepCount;
task.outputMaxSeq = Math.max(normalizeTaskMetric(task.outputMaxSeq) ?? 0, taskViewOutputMaxSeq(task));
pruneTaskHotState(task);
return task;
}
function persistState(markAllDatabaseTasks = false): void {
persistDirty = false;
if (persistTimer !== null) {
clearTimeout(persistTimer);
persistTimer = null;
}
for (const task of state.tasks) pruneTaskHotState(task);
state.updatedAt = nowIso();
if (markAllDatabaseTasks) markAllDatabaseTasksDirty();
scheduleDatabaseFlush();
}
function schedulePersistState(delayMs = 1000): void {
persistDirty = true;
if (persistTimer !== null) return;
persistTimer = setTimeout(() => {
persistTimer = null;
if (persistDirty) persistState(false);
}, delayMs);
}
function redactDatabaseUrl(value: string): string {
try {
const url = new URL(value);
if (url.password) url.password = "***";
return url.toString();
} catch {
return "<invalid-url>";
}
}
function publishTaskOaEvent(task: QueueTask, reason: string, options: { onlyStepChange?: boolean; stepChanged?: boolean } = {}): void {
const stepCount = taskListStepCount(task);
const outputMaxSeq = taskOutputMaxSeq(task);
if (options.onlyStepChange === true && options.stepChanged !== true) return;
const queueId = queueIdOf(task);
publishCodeQueueTaskUpdated(task, queueId, reason, stepCount, outputMaxSeq);
publishCodeQueueTraceStatsSnapshot(task, queueId, reason, stepCount, outputMaxSeq);
}
function publishQueueEvent(reason: string, queueId = ""): void {
publishCodeQueueQueueUpdated(queueId, reason);
}
function isOpenCodeStepBoundaryMethod(method: string | undefined): boolean {
return method === "opencode/step-start" || method === "opencode/step-finish";
}
function outputCanChangeStepCount(output: LiveOutput): boolean {
if (output.channel === "user" && output.method === "enqueue") return false;
if (output.channel === "system") return false;
return !isOpenCodeStepBoundaryMethod(output.method);
}
function commandStartedBeforeIn(outputs: LiveOutput[], output: LiveOutput): boolean {
if (typeof output.itemId !== "string") return false;
return outputs.some((item) => item !== output && item.itemId === output.itemId && item.channel === "command" && item.method === "item/started");
}
function outputStartsTraceStepInHistory(outputs: LiveOutput[], output: LiveOutput): boolean {
if (output.channel === "user" && output.method === "enqueue") return false;
if (isOpenCodeStepBoundaryMethod(output.method)) return false;
if (output.channel === "system") return false;
if (codexToolLifecycleStartedBeforeIn(outputs, output)) return false;
if (output.channel === "diff" || output.channel === "tool" || output.channel === "error" || output.channel === "assistant" || output.channel === "reasoning") return true;
if (output.channel === "user") return true;
if (output.channel !== "command") return true;
const method = String(output.method || "");
if (method === "item/started") return true;
if (method === "item/commandExecution/outputDelta") return false;
if (method === "item/completed") return !commandStartedBeforeIn(outputs, output);
return true;
}
function outputStartsTraceStep(task: QueueTask, output: LiveOutput): boolean {
return outputStartsTraceStepInHistory(task.output, output);
}
function traceStatsFromOutputs(outputs: LiveOutput[]): { stepCount: number; readCount: number; editCount: number; runCount: number; errorCount: number } {
const visibleOutputs = outputs.filter((output) => outputStartsTraceStepInHistory(outputs, output));
const stats = { stepCount: visibleOutputs.length, readCount: 0, editCount: 0, runCount: 0, errorCount: 0 };
for (const output of visibleOutputs) {
const kind = outputTraceKind(output);
if (kind === "read") stats.readCount += 1;
if (kind === "edit") stats.editCount += 1;
if (kind === "run") stats.runCount += 1;
if (kind === "error") stats.errorCount += 1;
}
return stats;
}
function attemptIndexFromOutput(output: LiveOutput): number | null {
const match = String(output.text || "").match(/\battempt\s+(\d+)\s*\/\s*\d+\b/iu);
const value = Number(match?.[1] ?? NaN);
return Number.isInteger(value) && value > 0 ? value : null;
}
function outputAttemptIndexMap(outputs: LiveOutput[]): Map<number, number> {
const result = new Map<number, number>();
let currentAttempt = 0;
for (const output of outputs.slice().sort((left, right) => Number(left.seq) - Number(right.seq))) {
const startedAttempt = attemptIndexFromOutput(output);
if (startedAttempt !== null) currentAttempt = startedAttempt;
if (currentAttempt > 0 && outputStartsTraceStepInHistory(outputs, output)) result.set(output.seq, currentAttempt);
}
return result;
}
function traceAttemptIndexesForTask(task: QueueTask): number[] {
const indexes = new Set<number>();
for (const attempt of task.attempts) {
const index = Number(attempt.index);
if (Number.isInteger(index) && index > 0) indexes.add(index);
}
const currentAttempt = Number(task.currentAttempt || 0);
const maxAttempt = Math.max(currentAttempt, ...Array.from(indexes), 0);
for (let index = 1; index <= maxAttempt; index += 1) indexes.add(index);
return Array.from(indexes).sort((left, right) => left - right);
}
function recordTaskOutputMetrics(task: QueueTask, output: LiveOutput, op: "set" | "append"): boolean {
task.outputMaxSeq = Math.max(taskOutputMaxSeq(task), Number.isFinite(Number(output.seq)) ? Number(output.seq) : 0);
if (op === "append" || !outputStartsTraceStep(task, output)) return false;
const storedStepCount = Number(task.stepCount ?? task.llmStepCount);
const nextStepCount = Number.isFinite(storedStepCount) && storedStepCount >= 0
? Math.floor(storedStepCount) + 1
: task.output.reduce((count, item) => count + (outputStartsTraceStep(task, item) ? 1 : 0), 0);
task.stepCount = nextStepCount;
task.llmStepCount = nextStepCount;
return true;
}
function outputUpdatesExistingTraceStep(output: LiveOutput): boolean {
if (output.channel === "assistant" || output.channel === "reasoning" || output.channel === "diff") return true;
if (isCodexToolLifecycleOutput(output) && output.method === "item/completed") return true;
return false;
}
function traceStepOutputForProjection(task: QueueTask, output: LiveOutput): LiveOutput {
if (!isCodexToolLifecycleOutput(output) || output.method !== "item/completed" || typeof output.itemId !== "string") return output;
const started = taskFullOutput(task)
.filter((item) => item !== output && isCodexToolLifecycleOutput(item) && item.itemId === output.itemId && item.method === "item/started")
.sort((left, right) => Number(left.seq) - Number(right.seq))[0];
return started === undefined ? output : { ...output, seq: started.seq, at: output.at, itemId: output.itemId, rawSeqs: [started.seq, output.seq] } as LiveOutput;
}
function errorToJson(error: unknown): JsonValue {
if (error instanceof Error) return { name: error.name, message: error.message, stack: error.stack ?? null };
return String(error);
}
function judgeFailureDetailsForOutput(judge: JudgeResult): string {
const details = judge.failureDetails;
if (details === undefined || details === null) return "";
return [
"",
`MiniMax failure details: stage=${details.stage}`,
`timedOut=${details.timedOut}`,
`durationMs=${details.durationMs}`,
`timeoutMs=${details.timeoutMs}`,
details.promptChars === undefined ? "" : `promptChars=${details.promptChars}`,
details.promptLines === undefined ? "" : `promptLines=${details.promptLines}`,
details.payloadBytes === undefined ? "" : `payloadBytes=${details.payloadBytes}`,
details.responseStatus === undefined || details.responseStatus === null ? "" : `responseStatus=${details.responseStatus}`,
`errorName=${details.errorName}`,
`error=${safePreview(details.errorMessage, 220)}`,
].filter((part) => part.length > 0).join(" ");
}
function databaseErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
return String(error);
}
function markTaskDirty(taskId: string): void {
dirtyDatabaseTaskIds.add(taskId);
scheduleDatabaseFlush();
}
function persistTaskState(task: QueueTask): void {
markTaskDirty(task.id);
persistState(false);
publishTaskOaEvent(task, "persist");
}
function markQueueDirty(queueId: string): void {
dirtyDatabaseQueueIds.add(queueId);
scheduleDatabaseFlush();
}
function markAllDatabaseTasksDirty(): void {
for (const task of state.tasks) dirtyDatabaseTaskIds.add(task.id);
for (const queue of state.queues) dirtyDatabaseQueueIds.add(queue.id);
}
function runGarbageCollection(): void {
const gc = (Bun as typeof Bun & { gc?: (force?: boolean) => void }).gc;
if (typeof gc === "function") gc(true);
}
function scheduleDatabaseFlush(delayMs = config.databaseFlushIntervalMs): void {
if (serviceRoleReadOnly(config.serviceRole)) return;
if (!databaseReady || (dirtyDatabaseTaskIds.size === 0 && dirtyDatabaseQueueIds.size === 0) || shutdownRequested) return;
if (databaseFlushTimer !== null) return;
databaseFlushTimer = setTimeout(() => {
databaseFlushTimer = null;
void flushDirtyTasksToDatabase().catch((error) => {
databaseLastError = databaseErrorMessage(error);
logger("error", "database_flush_failed", { error: errorToJson(error), dirtyTaskCount: dirtyDatabaseTaskIds.size });
});
}, delayMs);
}
function taskTimestamp(value: string | null): string | null {
if (value === null || value.length === 0) return null;
const time = Date.parse(value);
return Number.isFinite(time) ? new Date(time).toISOString() : null;
}
function lastOutputSeq(task: QueueTask): number {
return taskOutputMaxSeq(task);
}
function updateNextSeqFromTasks(): void {
let nextSeq = Math.max(1, Number.isFinite(state.nextSeq) ? Math.floor(state.nextSeq) : 1);
for (const task of state.tasks) nextSeq = Math.max(nextSeq, lastOutputSeq(task) + 1);
state.nextSeq = nextSeq;
}
interface UpsertTaskOptions {
claimQueueId?: string | null;
}
async function upsertTaskToDatabase(client: SqlExecutor, task: QueueTask, options: UpsertTaskOptions = {}): Promise<boolean> {
const claimQueueId = options.claimQueueId ?? null;
const rows = await client<Array<{ read_at: Date | string | null }>>`
INSERT INTO unidesk_code_queue_tasks (
id,
queue_id,
status,
provider_id,
execution_mode,
model,
cwd,
prompt,
base_prompt,
reference_task_ids,
reference_injection,
reasoning_effort,
max_attempts,
current_attempt,
current_mode,
codex_thread_id,
active_turn_id,
created_at,
updated_at,
started_at,
finished_at,
read_at,
last_error,
last_judge,
output_count,
event_count,
attempt_count,
last_output_seq,
task_json
) VALUES (
${task.id},
${queueIdOf(task)},
${task.status},
${task.providerId},
${task.executionMode},
${task.model},
${task.cwd},
${task.prompt},
${task.basePrompt},
${client.json(task.referenceTaskIds as unknown as postgres.JSONValue)},
${task.referenceInjection === null ? null : client.json(task.referenceInjection as unknown as postgres.JSONValue)},
${task.reasoningEffort},
${task.maxAttempts},
${task.currentAttempt},
${task.currentMode},
${task.codexThreadId},
${task.activeTurnId},
${taskTimestamp(task.createdAt) ?? nowIso()},
${taskTimestamp(task.updatedAt) ?? nowIso()},
${taskTimestamp(task.startedAt)},
${taskTimestamp(task.finishedAt)},
${taskTimestamp(task.readAt)},
${task.lastError},
${task.lastJudge === null ? null : client.json(task.lastJudge as unknown as postgres.JSONValue)},
${task.output.length},
${task.events.length},
${task.attempts.length},
${lastOutputSeq(task)},
${client.json(task as unknown as postgres.JSONValue)}
)
ON CONFLICT (id) DO UPDATE SET
status = EXCLUDED.status,
queue_id = EXCLUDED.queue_id,
provider_id = EXCLUDED.provider_id,
execution_mode = EXCLUDED.execution_mode,
model = EXCLUDED.model,
cwd = EXCLUDED.cwd,
prompt = EXCLUDED.prompt,
base_prompt = EXCLUDED.base_prompt,
reference_task_ids = EXCLUDED.reference_task_ids,
reference_injection = EXCLUDED.reference_injection,
reasoning_effort = EXCLUDED.reasoning_effort,
max_attempts = EXCLUDED.max_attempts,
current_attempt = EXCLUDED.current_attempt,
current_mode = EXCLUDED.current_mode,
codex_thread_id = EXCLUDED.codex_thread_id,
active_turn_id = EXCLUDED.active_turn_id,
created_at = EXCLUDED.created_at,
updated_at = EXCLUDED.updated_at,
started_at = EXCLUDED.started_at,
finished_at = EXCLUDED.finished_at,
read_at = CASE
WHEN EXCLUDED.status IN ('queued', 'running', 'judging', 'retry_wait') THEN NULL
WHEN unidesk_code_queue_tasks.read_at IS NOT NULL AND EXCLUDED.read_at IS NOT NULL THEN GREATEST(unidesk_code_queue_tasks.read_at, EXCLUDED.read_at)
WHEN unidesk_code_queue_tasks.read_at IS NOT NULL THEN unidesk_code_queue_tasks.read_at
ELSE EXCLUDED.read_at
END,
last_error = EXCLUDED.last_error,
last_judge = EXCLUDED.last_judge,
output_count = EXCLUDED.output_count,
event_count = EXCLUDED.event_count,
attempt_count = EXCLUDED.attempt_count,
last_output_seq = EXCLUDED.last_output_seq,
task_json = jsonb_set(
EXCLUDED.task_json,
'{readAt}',
CASE
WHEN EXCLUDED.status IN ('queued', 'running', 'judging', 'retry_wait') THEN 'null'::jsonb
WHEN unidesk_code_queue_tasks.read_at IS NOT NULL AND EXCLUDED.read_at IS NOT NULL THEN to_jsonb(GREATEST(unidesk_code_queue_tasks.read_at, EXCLUDED.read_at))
WHEN unidesk_code_queue_tasks.read_at IS NOT NULL THEN to_jsonb(unidesk_code_queue_tasks.read_at)
ELSE COALESCE(to_jsonb(EXCLUDED.read_at), 'null'::jsonb)
END,
true
)
WHERE (
${claimQueueId === null}
OR (
unidesk_code_queue_tasks.queue_id = ${claimQueueId}
AND (
(
unidesk_code_queue_tasks.status = 'queued'
AND unidesk_code_queue_tasks.started_at IS NULL
AND unidesk_code_queue_tasks.current_attempt = 0
AND unidesk_code_queue_tasks.codex_thread_id IS NULL
AND unidesk_code_queue_tasks.active_turn_id IS NULL
)
OR (
unidesk_code_queue_tasks.status = 'retry_wait'
AND unidesk_code_queue_tasks.active_turn_id IS NULL
)
)
)
)
AND NOT (
EXCLUDED.status IN ('queued', 'retry_wait')
AND EXCLUDED.started_at IS NULL
AND EXCLUDED.current_attempt = 0
AND EXCLUDED.codex_thread_id IS NULL
AND EXCLUDED.active_turn_id IS NULL
AND (
unidesk_code_queue_tasks.status IN ('running', 'judging')
OR unidesk_code_queue_tasks.started_at IS NOT NULL
OR unidesk_code_queue_tasks.current_attempt > 0
OR unidesk_code_queue_tasks.codex_thread_id IS NOT NULL
OR unidesk_code_queue_tasks.active_turn_id IS NOT NULL
)
)
RETURNING read_at
`;
if (rows.length === 0) {
const current = await client<DatabaseTaskStatusRow[]>`
SELECT id, queue_id, status, started_at, current_attempt, codex_thread_id, active_turn_id
FROM unidesk_code_queue_tasks
WHERE id = ${task.id}
LIMIT 1
`;
logger("warn", "database_task_stale_unclaimed_write_rejected", {
taskId: task.id,
attemptedQueueId: queueIdOf(task),
attemptedStatus: task.status,
attemptedStartedAt: task.startedAt,
attemptedCurrentAttempt: task.currentAttempt,
attemptedCodexThreadId: task.codexThreadId,
attemptedActiveTurnId: task.activeTurnId,
current: databaseStatusRowJson(current[0] ?? null),
});
return false;
}
task.readAt = timestampToIso(rows[0]?.read_at ?? null);
return true;
}
async function upsertQueueToDatabase(client: SqlExecutor, queue: QueueRecord): Promise<void> {
await client`
INSERT INTO unidesk_code_queue_queues (
id,
name,
created_at,
updated_at
) VALUES (
${queue.id},
${safeQueueName(queue.name, queue.id)},
${taskTimestamp(queue.createdAt) ?? nowIso()},
${taskTimestamp(queue.updatedAt) ?? nowIso()}
)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
updated_at = EXCLUDED.updated_at
`;
}
async function upsertWorkdirsToDatabase(records: WorkdirRecord[]): Promise<void> {
if (records.length === 0) return;
for (const record of records) {
await sql`
INSERT INTO unidesk_code_queue_workdirs (
provider_id,
execution_mode,
path,
created_at,
updated_at
) VALUES (
${record.providerId},
${record.executionMode},
${record.path},
${taskTimestamp(record.createdAt) ?? nowIso()},
${taskTimestamp(record.updatedAt) ?? nowIso()}
)
ON CONFLICT (provider_id, execution_mode, path) DO UPDATE SET
updated_at = EXCLUDED.updated_at
`;
}
}
interface DatabaseTaskRow {
id: string;
updated_at: Date | string;
status: TaskStatus;
read_at: Date | string | null;
task_json: unknown;
}
interface DatabaseTaskStatusRow {
id: string;
queue_id: string;
status: TaskStatus;
started_at: Date | string | null;
current_attempt: number | string | null;
codex_thread_id: string | null;
active_turn_id: string | null;
}
interface DatabaseQueueRow {
id: string;
name: string;
created_at: Date | string;
updated_at: Date | string;
}
interface DatabaseTaskIdRow {
id: string;
}
function normalizeDatabaseTaskRows(rows: DatabaseTaskRow[], source: string): QueueTask[] {
const tasks: QueueTask[] = [];
for (const row of rows) {
try {
const taskJson = row.task_json;
if (typeof taskJson !== "object" || taskJson === null || Array.isArray(taskJson)) throw new Error("task_json is not an object");
tasks.push(normalizeTask({
...(taskJson as Record<string, unknown>),
status: row.status,
readAt: timestampToIso(row.read_at),
} as unknown as QueueTask));
} catch (error) {
logger("warn", "database_task_row_ignored", { source, id: String(row.id), error: errorToJson(error) });
}
}
return tasks.sort((left, right) => (timestampMs(left.createdAt) ?? 0) - (timestampMs(right.createdAt) ?? 0) || left.id.localeCompare(right.id));
}
function databaseStatusRowJson(row: DatabaseTaskStatusRow | null): JsonValue {
if (row === null) return null;
return {
id: row.id,
queueId: safeQueueId(row.queue_id),
status: row.status,
startedAt: timestampToIso(row.started_at),
currentAttempt: Number(row.current_attempt ?? 0),
codexThreadId: row.codex_thread_id,
activeTurnId: row.active_turn_id,
};
}
function taskIsUnclaimedMovable(task: QueueTask): boolean {
return (task.status === "queued" || task.status === "retry_wait")
&& task.startedAt === null
&& task.currentAttempt === 0
&& task.codexThreadId === null
&& task.activeTurnId === null;
}
function databaseTaskMoveBlocker(row: DatabaseTaskStatusRow | null): string {
if (row === null) return "task not found";
if (row.status !== "queued" && row.status !== "retry_wait") return `status=${row.status}`;
if (row.started_at !== null) return "task already has started_at";
if (Number(row.current_attempt ?? 0) !== 0) return `task already has current_attempt=${Number(row.current_attempt ?? 0)}`;
if (row.codex_thread_id !== null) return "task already has codex_thread_id";
if (row.active_turn_id !== null) return "task already has active_turn_id";
return "";
}
function taskMoveBlocker(task: QueueTask): string {
if (activeRunForTask(task) !== null) return "task has an active agent run";
if (processingQueues.has(queueIdOf(task))) return "queue processor is currently active";
if (activeRunSlotReservations.has(queueIdOf(task))) return "queue is reserving an active run slot";
if (activeRunSlotWaiters.some((waiter) => waiter.taskId === task.id || waiter.queueId === queueIdOf(task))) return "queue is waiting for an active run slot";
if (task.status !== "queued" && task.status !== "retry_wait") return `status=${task.status}`;
if (!taskIsUnclaimedMovable(task)) return "task has already been claimed";
return "";
}
function reconcileHotTaskFromDatabase(task: QueueTask): QueueTask {
const existing = findTask(task.id);
if (existing === null) return rememberHotTask(task);
if (activeRunForTask(existing) !== null) return existing;
Object.assign(existing, task);
return existing;
}
function taskHasClaimMarkers(task: QueueTask): boolean {
return task.status === "running"
|| task.status === "judging"
|| task.startedAt !== null
|| task.currentAttempt > 0
|| task.codexThreadId !== null
|| task.activeTurnId !== null;
}
function shouldPreferHotTaskOverDatabase(hotTask: QueueTask, databaseTask: QueueTask): boolean {
if (activeRunForTask(hotTask) !== null) return true;
if (taskIsUnclaimedMovable(hotTask) && taskHasClaimMarkers(databaseTask)) return false;
const hotUpdatedAt = timestampMs(hotTask.updatedAt) ?? 0;
const databaseUpdatedAt = timestampMs(databaseTask.updatedAt) ?? 0;
return hotUpdatedAt >= databaseUpdatedAt;
}
async function deleteTaskFromDatabase(taskId: string): Promise<void> {
if (!databaseReady) return;
await sql`
DELETE FROM unidesk_code_queue_tasks
WHERE id = ${taskId}
`;
}
async function claimTaskInDatabase(task: QueueTask, expectedQueueId: string): Promise<boolean> {
if (!databaseReady) return true;
const claimed = await sql.begin(async (client) => await upsertTaskToDatabase(client, task, { claimQueueId: expectedQueueId }));
if (claimed) return true;
const databaseTask = await loadTaskFromDatabase(task.id);
if (databaseTask !== null) reconcileHotTaskFromDatabase(databaseTask);
logger("warn", "task_claim_conflict", {
taskId: task.id,
expectedQueueId,
attemptedQueueId: queueIdOf(task),
attemptedStatus: task.status,
attemptedCurrentAttempt: task.currentAttempt,
});
return false;
}
async function runDatabaseClaimMoveSelfTest(): Promise<JsonValue | null> {
if (!databaseReady) return null;
const suffix = String(Date.now());
const taskId = `codex_claim_move_db_${suffix}`;
const queuedAt = nowIso();
const sourceQueueId = `claim_move_db_source_${suffix}`;
const targetQueueId = `claim_move_db_target_${suffix}`;
const before = state.tasks.slice();
const beforeQueues = state.queues.slice();
await deleteTaskFromDatabase(taskId);
try {
const queuedTask = normalizeTask({
...createTask({ prompt: "claim/move DB race self-test", queueId: sourceQueueId }),
id: taskId,
queueId: sourceQueueId,
queueEnteredAt: queuedAt,
createdAt: queuedAt,
updatedAt: queuedAt,
output: [],
});
await sql.begin(async (client) => {
await upsertQueueToDatabase(client, { id: sourceQueueId, name: sourceQueueId, createdAt: queuedAt, updatedAt: queuedAt });
await upsertTaskToDatabase(client, queuedTask);
});
const staleHotTask = normalizeTask(JSON.parse(JSON.stringify(queuedTask)) as QueueTask);
const claimedTask = normalizeTask(JSON.parse(JSON.stringify(queuedTask)) as QueueTask);
const claimedAt = nowIso();
claimedTask.status = "running";
claimedTask.startedAt = claimedAt;
claimedTask.currentAttempt = 1;
claimedTask.currentMode = "initial";
claimedTask.updatedAt = claimedAt;
const claimed = await claimTaskInDatabase(claimedTask, sourceQueueId);
if (!claimed) throw new Error("database claim self-test failed to claim queued task");
state.tasks.splice(0, state.tasks.length, staleHotTask);
const response = await moveTaskToQueue(staleHotTask, new Request(`http://code-queue.local/api/tasks/${taskId}/move`, {
method: "POST",
body: JSON.stringify({ queueId: targetQueueId }),
headers: { "content-type": "application/json" },
}), { bypassRoleCheck: true });
const after = await loadTaskFromDatabase(taskId);
const body = await response.json() as Record<string, JsonValue>;
if (response.status !== 409) throw new Error(`database stale move should return 409, got ${response.status}`);
if (after === null) throw new Error("database self-test task disappeared after stale move");
if (after.status !== "running") throw new Error(`database self-test task status changed to ${after.status}`);
if (queueIdOf(after) !== sourceQueueId) throw new Error(`database self-test task queue changed to ${queueIdOf(after)}`);
if (after.currentAttempt !== 1 || after.startedAt === null) throw new Error("database self-test task claim markers were lost");
return {
ok: true,
taskId,
moveStatus: response.status,
databaseStatus: after.status,
databaseQueueId: queueIdOf(after),
currentAttempt: after.currentAttempt,
startedAt: after.startedAt,
response: body as JsonValue,
} as unknown as JsonValue;
} finally {
await deleteTaskFromDatabase(taskId);
await deleteDatabaseQueues([sourceQueueId, targetQueueId]);
state.tasks.splice(0, state.tasks.length, ...before);
state.queues.splice(0, state.queues.length, ...beforeQueues);
}
}
async function loadPrunedDatabaseTaskRows(where: "all" | "hot"): Promise<DatabaseTaskRow[]> {
return await sql<DatabaseTaskRow[]>`
SELECT id, updated_at, status, read_at, task_json
FROM (
SELECT
id,
updated_at,
status,
read_at,
jsonb_set(
jsonb_set(
task_json,
'{output}',
CASE
WHEN ${config.maxInMemoryOutputRecords} > 0 THEN COALESCE((
SELECT jsonb_agg(value ORDER BY ord)
FROM (
SELECT value, ord
FROM jsonb_array_elements(
CASE
WHEN jsonb_typeof(task_json->'output') = 'array' THEN task_json->'output'
ELSE '[]'::jsonb
END
) WITH ORDINALITY AS output_items(value, ord)
ORDER BY ord DESC
LIMIT ${config.maxInMemoryOutputRecords}
) AS kept_output
), '[]'::jsonb)
ELSE CASE
WHEN jsonb_typeof(task_json->'output') = 'array' THEN task_json->'output'
ELSE '[]'::jsonb
END
END,
true
),
'{events}',
CASE
WHEN ${config.maxInMemoryEventRecords} > 0 THEN COALESCE((
SELECT jsonb_agg(value ORDER BY ord)
FROM (
SELECT value, ord
FROM jsonb_array_elements(
CASE
WHEN jsonb_typeof(task_json->'events') = 'array' THEN task_json->'events'
ELSE '[]'::jsonb
END
) WITH ORDINALITY AS event_items(value, ord)
ORDER BY ord DESC
LIMIT ${config.maxInMemoryEventRecords}
) AS kept_events
), '[]'::jsonb)
ELSE CASE
WHEN jsonb_typeof(task_json->'events') = 'array' THEN task_json->'events'
ELSE '[]'::jsonb
END
END,
true
) AS task_json,
created_at
FROM unidesk_code_queue_tasks
) AS pruned_tasks
WHERE ${where === "all"} OR status IN ('queued', 'running', 'judging', 'retry_wait')
ORDER BY created_at ASC, id ASC
`;
}
async function loadTasksFromDatabase(where: "all" | "hot" = "all"): Promise<QueueTask[]> {
return normalizeDatabaseTaskRows(await loadPrunedDatabaseTaskRows(where), where);
}
function databaseQueueRowsToRecords(rows: DatabaseQueueRow[]): QueueRecord[] {
const queueMap = new Map<string, QueueRecord>();
for (const row of rows) {
const id = safeQueueId(row.id);
queueMap.set(id, {
id,
name: safeQueueName(row.name, id),
createdAt: taskTimestamp(String(row.created_at)) ?? nowIso(),
updatedAt: taskTimestamp(String(row.updated_at)) ?? nowIso(),
});
}
if (!queueMap.has(defaultQueueId)) queueMap.set(defaultQueueId, { id: defaultQueueId, name: defaultQueueId, createdAt: state.updatedAt, updatedAt: state.updatedAt });
return Array.from(queueMap.values()).sort((left, right) => left.id.localeCompare(right.id));
}
async function loadQueuesFromDatabase(): Promise<QueueRecord[]> {
if (!databaseReady) return state.queues;
const queueRows = await sql<DatabaseQueueRow[]>`
SELECT id, name, created_at, updated_at
FROM unidesk_code_queue_queues
ORDER BY id ASC
`;
return databaseQueueRowsToRecords(queueRows);
}
async function loadTasksFromDatabaseByIds(taskIds: string[]): Promise<QueueTask[]> {
const ids = Array.from(new Set(taskIds.map((id) => id.trim()).filter(Boolean)));
if (ids.length === 0) return [];
const rows = await sql<DatabaseTaskRow[]>`
SELECT id, updated_at, status, read_at, task_json
FROM (
SELECT
id,
updated_at,
status,
read_at,
task_json - 'output' - 'events' - 'attempts' - 'promptHistory' AS task_json
FROM unidesk_code_queue_tasks
WHERE id IN ${sql(ids)}
) AS lite_tasks
`;
return normalizeDatabaseTaskRows(rows, "by_ids");
}
async function loadTaskFromDatabase(taskId: string): Promise<QueueTask | null> {
const rows = await sql<DatabaseTaskRow[]>`
SELECT id, updated_at, status, read_at, task_json
FROM (
SELECT
id,
updated_at,
status,
read_at,
jsonb_set(
jsonb_set(
task_json,
'{output}',
CASE
WHEN ${config.maxInMemoryOutputRecords} > 0 THEN COALESCE((
SELECT jsonb_agg(value ORDER BY ord)
FROM (
SELECT value, ord
FROM jsonb_array_elements(
CASE
WHEN jsonb_typeof(task_json->'output') = 'array' THEN task_json->'output'
ELSE '[]'::jsonb
END
) WITH ORDINALITY AS output_items(value, ord)
ORDER BY ord DESC
LIMIT ${config.maxInMemoryOutputRecords}
) AS kept_output
), '[]'::jsonb)
ELSE CASE
WHEN jsonb_typeof(task_json->'output') = 'array' THEN task_json->'output'
ELSE '[]'::jsonb
END
END,
true
),
'{events}',
CASE
WHEN ${config.maxInMemoryEventRecords} > 0 THEN COALESCE((
SELECT jsonb_agg(value ORDER BY ord)
FROM (
SELECT value, ord
FROM jsonb_array_elements(
CASE
WHEN jsonb_typeof(task_json->'events') = 'array' THEN task_json->'events'
ELSE '[]'::jsonb
END
) WITH ORDINALITY AS event_items(value, ord)
ORDER BY ord DESC
LIMIT ${config.maxInMemoryEventRecords}
) AS kept_events
), '[]'::jsonb)
ELSE CASE
WHEN jsonb_typeof(task_json->'events') = 'array' THEN task_json->'events'
ELSE '[]'::jsonb
END
END,
true
) AS task_json
FROM unidesk_code_queue_tasks
WHERE id = ${taskId}
) AS pruned_tasks
LIMIT 1
`;
return normalizeDatabaseTaskRows(rows, "single")[0] ?? null;
}
async function warmDatabaseOverviewQueries(): Promise<void> {
if (!databaseReady) return;
const started = performance.now();
try {
const [recentRows, unreadRows, activeRows] = await Promise.all([
sql<DatabaseTaskIdRow[]>`
SELECT id
FROM unidesk_code_queue_tasks
ORDER BY created_at DESC, id DESC
LIMIT 48
`,
sql<DatabaseTaskIdRow[]>`
SELECT id
FROM unidesk_code_queue_tasks
WHERE read_at IS NULL
AND status IN ('succeeded', 'failed', 'canceled')
ORDER BY updated_at DESC, id DESC
LIMIT 100
`,
sql<DatabaseTaskIdRow[]>`
SELECT id
FROM unidesk_code_queue_tasks
WHERE status IN ('running', 'judging', 'retry_wait')
ORDER BY updated_at DESC, id DESC
LIMIT 48
`,
]);
const ids = Array.from(new Set([...unreadRows, ...activeRows, ...recentRows].map((row) => row.id)));
await Promise.all([
queueSummaryForHealth(false),
loadTasksFromDatabaseByIds(ids),
]);
logger("info", "database_overview_warm_complete", {
taskIdCount: ids.length,
durationMs: Math.round(performance.now() - started),
});
} catch (error) {
logger("warn", "database_overview_warm_failed", { error: errorToJson(error) });
}
}
async function warmCodeQueueFirstPaintOverview(): Promise<void> {
if (!databaseReady) return;
const started = performance.now();
try {
const response = await tasksOverviewResponse(new URL(firstPaintOverviewWarmUrl));
await response.arrayBuffer();
logger("info", "database_first_paint_overview_warm_complete", {
status: response.status,
durationMs: Math.round(performance.now() - started),
});
} catch (error) {
logger("warn", "database_first_paint_overview_warm_failed", { error: errorToJson(error) });
}
}
async function warmCodeQueueFirstPaintOverviewDeduped(): Promise<void> {
if (firstPaintOverviewWarmInFlight !== null) return firstPaintOverviewWarmInFlight;
firstPaintOverviewWarmInFlight = warmCodeQueueFirstPaintOverview()
.finally(() => {
firstPaintOverviewWarmInFlight = null;
});
return firstPaintOverviewWarmInFlight;
}
async function ensureDatabaseIndexes(): Promise<void> {
logger("info", "database_index_maintenance_start", {});
const started = performance.now();
await sql`CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_unidesk_code_queue_tasks_status_updated ON unidesk_code_queue_tasks(status, updated_at DESC)`;
await sql`CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_unidesk_code_queue_tasks_queue_status_updated ON unidesk_code_queue_tasks(queue_id, status, updated_at DESC)`;
await sql`CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_unidesk_code_queue_tasks_provider_updated ON unidesk_code_queue_tasks(provider_id, updated_at DESC)`;
await sql`CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_unidesk_code_queue_tasks_execution_mode_updated ON unidesk_code_queue_tasks(execution_mode, updated_at DESC)`;
await sql`CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_unidesk_code_queue_tasks_created ON unidesk_code_queue_tasks(created_at DESC)`;
await sql`CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_unidesk_code_queue_tasks_queue_created ON unidesk_code_queue_tasks(queue_id, created_at DESC, id DESC)`;
await sql`CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_unidesk_code_queue_tasks_unread_terminal ON unidesk_code_queue_tasks(queue_id, updated_at DESC) WHERE read_at IS NULL AND status IN ('succeeded', 'failed', 'canceled')`;
await sql`CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_unidesk_code_queue_tasks_model_updated ON unidesk_code_queue_tasks(model, updated_at DESC)`;
logger("info", "database_index_maintenance_complete", { durationMs: Math.round(performance.now() - started) });
}
function scheduleStartupDatabaseMaintenance(): void {
setTimeout(() => {
void warmCodeQueueFirstPaintOverviewDeduped();
}, 100).unref?.();
setTimeout(() => {
void (async () => {
const started = performance.now();
logger("info", "database_startup_maintenance_start", {
queueCount: state.queues.length,
});
await warmCodeQueueFirstPaintOverviewDeduped();
for (const queue of state.queues) dirtyDatabaseQueueIds.add(queue.id);
await flushDirtyTasksToDatabase(true);
await loadClaudeQqNotificationOutboxFromDatabase();
await ensureDatabaseIndexes();
runGarbageCollection();
await warmDatabaseOverviewQueries();
await warmCodeQueueFirstPaintOverviewDeduped();
logger("info", "database_startup_maintenance_complete", {
databaseNotificationCount: claudeQqNotificationOutboxItemCount(),
durationMs: Math.round(performance.now() - started),
});
})().catch((error) => {
databaseLastError = databaseErrorMessage(error);
logger("warn", "database_startup_maintenance_failed", { error: errorToJson(error) });
});
}, 1000).unref?.();
}
function rememberHotTask(task: QueueTask): QueueTask {
const existing = findTask(task.id);
if (existing !== null) return existing;
state.tasks.push(task);
state.tasks.sort((left, right) => (timestampMs(left.createdAt) ?? 0) - (timestampMs(right.createdAt) ?? 0) || left.id.localeCompare(right.id));
updateNextSeqFromTasks();
return task;
}
async function findTaskForRead(taskId: string): Promise<QueueTask | null> {
const hotTask = findTask(taskId);
if (!databaseReady) return hotTask;
try {
const databaseTask = await loadTaskFromDatabase(taskId);
if (hotTask === null) return databaseTask;
if (databaseTask === null) return hotTask;
return shouldPreferHotTaskOverDatabase(hotTask, databaseTask) ? hotTask : databaseTask;
} catch (error) {
databaseLastError = databaseErrorMessage(error);
logger("warn", "read_database_fallback", { taskId, error: errorToJson(error) });
return hotTask;
}
}
async function findTaskForMutation(taskId: string): Promise<QueueTask | null> {
const hotTask = findTask(taskId);
if (!databaseReady) return hotTask;
try {
const databaseTask = await loadTaskFromDatabase(taskId);
if (databaseTask === null) return hotTask;
if (hotTask === null) return rememberHotTask(databaseTask);
return shouldPreferHotTaskOverDatabase(hotTask, databaseTask) ? hotTask : reconcileHotTaskFromDatabase(databaseTask);
} catch (error) {
databaseLastError = databaseErrorMessage(error);
logger("warn", "mutation_database_fallback", { taskId, error: errorToJson(error) });
return hotTask;
}
}
async function loadNextSeqFromDatabase(): Promise<number> {
const rows = await sql<Array<{ next_seq: string | number | null }>>`
SELECT COALESCE(MAX(last_output_seq), 0) + 1 AS next_seq
FROM unidesk_code_queue_tasks
`;
const value = Number(rows[0]?.next_seq ?? 1);
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 1;
}
async function flushDirtyTasksToDatabase(force = false): Promise<void> {
if (serviceRoleReadOnly(config.serviceRole)) {
dirtyDatabaseTaskIds.clear();
dirtyDatabaseQueueIds.clear();
return;
}
if (!databaseReady) return;
if (databaseFlushInFlight && !force) {
scheduleDatabaseFlush();
return;
}
const ids = Array.from(dirtyDatabaseTaskIds);
const queueIds = Array.from(dirtyDatabaseQueueIds);
if (ids.length === 0 && queueIds.length === 0) return;
dirtyDatabaseTaskIds.clear();
dirtyDatabaseQueueIds.clear();
databaseFlushInFlight = true;
const rejectedTaskIds: string[] = [];
try {
await sql.begin(async (client) => {
for (const id of queueIds) {
const queue = state.queues.find((item) => item.id === id);
if (queue !== undefined) await upsertQueueToDatabase(client, queue);
}
for (const id of ids) {
const task = state.tasks.find((item) => item.id === id);
if (task !== undefined && !await upsertTaskToDatabase(client, task)) rejectedTaskIds.push(id);
}
});
databaseLastError = null;
} catch (error) {
for (const id of ids) dirtyDatabaseTaskIds.add(id);
for (const id of queueIds) dirtyDatabaseQueueIds.add(id);
throw error;
} finally {
databaseFlushInFlight = false;
if (dirtyDatabaseTaskIds.size > 0 || dirtyDatabaseQueueIds.size > 0) scheduleDatabaseFlush();
}
for (const id of rejectedTaskIds) {
const databaseTask = await loadTaskFromDatabase(id);
if (databaseTask !== null) reconcileHotTaskFromDatabase(databaseTask);
}
}
async function initDatabasePersistence(): Promise<void> {
logger("info", "database_persistence_init_start", { databaseUrl: redactDatabaseUrl(config.databaseUrl) });
if (!serviceRoleReadOnly(config.serviceRole)) {
await sql`
CREATE TABLE IF NOT EXISTS unidesk_code_queue_tasks (
id TEXT PRIMARY KEY,
queue_id TEXT NOT NULL DEFAULT 'default',
status TEXT NOT NULL,
provider_id TEXT NOT NULL DEFAULT 'main-server',
execution_mode TEXT NOT NULL DEFAULT 'default',
model TEXT NOT NULL,
cwd TEXT NOT NULL,
prompt TEXT NOT NULL,
base_prompt TEXT NOT NULL DEFAULT '',
reference_task_ids JSONB NOT NULL DEFAULT '[]'::jsonb,
reference_injection JSONB,
reasoning_effort TEXT,
max_attempts INTEGER NOT NULL,
current_attempt INTEGER NOT NULL DEFAULT 0,
current_mode TEXT,
codex_thread_id TEXT,
active_turn_id TEXT,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
read_at TIMESTAMPTZ,
last_error TEXT,
last_judge JSONB,
output_count INTEGER NOT NULL DEFAULT 0,
event_count INTEGER NOT NULL DEFAULT 0,
attempt_count INTEGER NOT NULL DEFAULT 0,
last_output_seq BIGINT NOT NULL DEFAULT 0,
task_json JSONB NOT NULL
)
`;
await sql`
CREATE TABLE IF NOT EXISTS unidesk_code_queue_queues (
id TEXT PRIMARY KEY,
name TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
)
`;
await sql`
CREATE TABLE IF NOT EXISTS unidesk_code_queue_workdirs (
provider_id TEXT NOT NULL,
execution_mode TEXT NOT NULL DEFAULT 'default',
path TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (provider_id, execution_mode, path)
)
`;
await sql`
CREATE TABLE IF NOT EXISTS unidesk_code_queue_notifications (
id TEXT PRIMARY KEY,
kind TEXT NOT NULL,
dedup_key TEXT NOT NULL,
target TEXT NOT NULL,
message TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
attempts INTEGER NOT NULL DEFAULT 0,
next_attempt_at TIMESTAMPTZ NOT NULL,
last_error TEXT,
sent_at TIMESTAMPTZ
)
`;
await sql`ALTER TABLE unidesk_code_queue_tasks ADD COLUMN IF NOT EXISTS queue_id TEXT NOT NULL DEFAULT 'default'`;
await sql`ALTER TABLE unidesk_code_queue_tasks ADD COLUMN IF NOT EXISTS provider_id TEXT NOT NULL DEFAULT 'main-server'`;
await sql`ALTER TABLE unidesk_code_queue_tasks ADD COLUMN IF NOT EXISTS execution_mode TEXT NOT NULL DEFAULT 'default'`;
await sql`ALTER TABLE unidesk_code_queue_tasks ADD COLUMN IF NOT EXISTS base_prompt TEXT NOT NULL DEFAULT ''`;
await sql`ALTER TABLE unidesk_code_queue_tasks ADD COLUMN IF NOT EXISTS reference_task_ids JSONB NOT NULL DEFAULT '[]'::jsonb`;
await sql`ALTER TABLE unidesk_code_queue_tasks ADD COLUMN IF NOT EXISTS reference_injection JSONB`;
await sql`ALTER TABLE unidesk_code_queue_tasks ADD COLUMN IF NOT EXISTS read_at TIMESTAMPTZ`;
await sql`
UPDATE unidesk_code_queue_tasks
SET read_at = NULLIF(task_json->>'readAt', '')::timestamptz
WHERE read_at IS NULL
AND status IN ('succeeded', 'failed', 'canceled')
AND COALESCE(task_json->>'readAt', '') <> ''
AND (task_json->>'readAt') ~ '^\\d{4}-\\d{2}-\\d{2}T'
`;
await sql`ALTER TABLE unidesk_code_queue_queues ADD COLUMN IF NOT EXISTS name TEXT NOT NULL DEFAULT ''`;
await sql`ALTER TABLE unidesk_code_queue_workdirs ADD COLUMN IF NOT EXISTS execution_mode TEXT NOT NULL DEFAULT 'default'`;
}
const countRows = await sql<Array<{ count: string | number }>>`SELECT COUNT(*) AS count FROM unidesk_code_queue_tasks`;
const hotTasks = await loadTasksFromDatabase("hot");
state.tasks.splice(0, state.tasks.length, ...hotTasks);
state.nextSeq = await loadNextSeqFromDatabase();
state.updatedAt = nowIso();
logger("info", "database_task_rows_loaded", {
databaseTaskCount: Number(countRows[0]?.count ?? hotTasks.length),
hotTaskCount: hotTasks.length,
inMemoryOutputRecords: config.maxInMemoryOutputRecords,
inMemoryEventRecords: config.maxInMemoryEventRecords,
});
const queueRows = await sql<DatabaseQueueRow[]>`
SELECT id, name, created_at, updated_at
FROM unidesk_code_queue_queues
ORDER BY id ASC
`;
runGarbageCollection();
const queueMap = new Map(databaseQueueRowsToRecords(queueRows).map((queue) => [queue.id, queue]));
if (!queueMap.has(defaultQueueId)) queueMap.set(defaultQueueId, { id: defaultQueueId, name: defaultQueueId, createdAt: state.updatedAt, updatedAt: state.updatedAt });
for (const task of state.tasks) {
const id = queueIdOf(task);
const existing = queueMap.get(id);
if (existing === undefined) {
queueMap.set(id, { id, name: id, createdAt: task.createdAt, updatedAt: task.updatedAt });
} else if ((timestampMs(task.updatedAt) ?? 0) > (timestampMs(existing.updatedAt) ?? 0)) {
existing.updatedAt = task.updatedAt;
}
}
state.queues.splice(0, state.queues.length, ...Array.from(queueMap.values()).sort((left, right) => left.id.localeCompare(right.id)));
const workdirRows = await sql<Array<{ provider_id: string; execution_mode: string; path: string; created_at: Date | string; updated_at: Date | string }>>`
SELECT provider_id, execution_mode, path, created_at, updated_at
FROM unidesk_code_queue_workdirs
ORDER BY provider_id ASC, execution_mode ASC, path ASC
`;
for (const row of workdirRows) {
rememberWorkdir(row.provider_id, row.execution_mode, row.path, taskTimestamp(String(row.updated_at)) ?? nowIso());
const record = workdirRecords.get(workdirRecordKey(normalizeTaskProviderId(row.provider_id), normalizeCodeExecutionMode(row.execution_mode), normalizeWorkdirPath(row.path, normalizeTaskProviderId(row.provider_id))));
if (record !== undefined) {
record.createdAt = taskTimestamp(String(row.created_at)) ?? record.createdAt;
record.updatedAt = taskTimestamp(String(row.updated_at)) ?? record.updatedAt;
}
}
ensureDefaultWorkdirRecords();
if (!serviceRoleReadOnly(config.serviceRole)) await upsertWorkdirsToDatabase(sortedWorkdirRecords());
databaseReady = true;
if (config.serviceRole === "combined" || config.serviceRole === "scheduler") scheduleStartupDatabaseMaintenance();
runGarbageCollection();
logger("info", "database_persistence_init_complete", {
databaseTaskCount: Number(countRows[0]?.count ?? hotTasks.length),
hotTaskCount: state.tasks.length,
databaseQueueCount: queueRows.length,
taskCount: state.tasks.length,
queueCount: state.queues.length,
maintenanceMode: "background",
});
}
async function initDatabasePersistenceWithRetry(): Promise<void> {
const started = Date.now();
let attempt = 0;
let reportedLongWait = false;
while (!databaseReady) {
attempt += 1;
try {
await initDatabasePersistence();
return;
} catch (error) {
databaseLastError = databaseErrorMessage(error);
const elapsedMs = Date.now() - started;
logger("warn", "database_persistence_init_retry", { attempt, elapsedMs, error: errorToJson(error) });
if (elapsedMs > 90_000 && !reportedLongWait) {
reportedLongWait = true;
logger("error", "database_persistence_required_still_waiting", { attempt, elapsedMs, error: errorToJson(error) });
}
await Bun.sleep(Math.min(1000 * attempt, 10_000));
}
}
}
function prepareCodexHome(): void {
mkdirSync(config.codexHome, { recursive: true });
if (existsSync(config.sourceCodexConfig)) {
copyFileSync(config.sourceCodexConfig, resolve(config.codexHome, "config.toml"));
} else {
logger("warn", "codex_config_source_missing", { sourceCodexConfig: config.sourceCodexConfig });
}
const sourceCodexAuth = resolve(dirname(config.sourceCodexConfig), "auth.json");
if (existsSync(sourceCodexAuth)) {
copyFileSync(sourceCodexAuth, resolve(config.codexHome, "auth.json"));
} else {
logger("warn", "codex_auth_source_missing", { sourceCodexAuth });
}
}
function openCodeXdgEnv(root = config.opencodeXdgDir): Record<string, string> {
return {
XDG_DATA_HOME: resolve(root, "data"),
XDG_CONFIG_HOME: resolve(root, "config"),
XDG_CACHE_HOME: resolve(root, "cache"),
XDG_STATE_HOME: resolve(root, "state"),
};
}
function prepareOpenCodeHome(): void {
for (const dir of Object.values(openCodeXdgEnv())) mkdirSync(dir, { recursive: true });
}
function commandPath(command: string): string | null {
const result = spawnSync("sh", ["-lc", `command -v ${shellQuote(command)}`], { encoding: "utf8", timeout: 2_000 });
if (result.status !== 0) return null;
const stdout = typeof result.stdout === "string" ? result.stdout.trim() : "";
return stdout.length > 0 ? stdout.split(/\r?\n/u)[0] ?? null : null;
}
function runProbe(command: string, args: string[], timeout = 3_000): { ok: boolean; output: string } {
const result = spawnSync(command, args, { encoding: "utf8", timeout });
const stdout = typeof result.stdout === "string" ? result.stdout : "";
const stderr = typeof result.stderr === "string" ? result.stderr : "";
const output = safePreview(`${stdout}\n${stderr}`, 600);
return { ok: result.status === 0, output };
}
function collectDevReady(): JsonValue {
const now = Date.now();
if (devReadyCache !== null && now - devReadyCache.checkedAtMs < 30_000) return devReadyCache.value;
const requiredTools = [
"bash",
"bun",
"node",
"npm",
"npx",
"codex",
"opencode",
"git",
"rg",
"curl",
"python3",
"pip3",
"docker",
"docker-compose",
"jq",
"ssh",
"rsync",
"make",
"gcc",
"g++",
"tar",
"gzip",
"unzip",
];
const tools = requiredTools.map((name) => {
const path = commandPath(name);
return { name, ok: path !== null, path };
});
const missingTools = tools.filter((tool) => !tool.ok).map((tool) => tool.name);
const dockerProbe = runProbe("docker", ["version", "--format", "{{.Client.Version}} client / {{.Server.Version}} server"]);
const composeProbe = runProbe("docker", ["compose", "version"]);
const workdirExists = existsSync(config.defaultWorkdir);
const dockerSocketExists = existsSync("/var/run/docker.sock");
const homeCodexConfig = resolve(config.codexHome, "config.toml");
const sourceCodexAuth = resolve(dirname(config.sourceCodexConfig), "auth.json");
const homeCodexAuth = resolve(config.codexHome, "auth.json");
const codexConfigReady = existsSync(config.sourceCodexConfig) || existsSync(homeCodexConfig);
const sshKeyProbe = runProbe("sh", ["-lc", "test -d /root/.ssh && find /root/.ssh -maxdepth 1 -type f \\( -name 'id_*' ! -name '*.pub' \\) -perm -400 -print -quit"]);
const githubKnownHostProbe = runProbe("ssh-keygen", ["-F", "github.com", "-f", "/root/.ssh/known_hosts"]);
const sshSharedReady = existsSync("/root/.ssh") && sshKeyProbe.ok && sshKeyProbe.output.trim().length > 0;
const ok = missingTools.length === 0 && dockerProbe.ok && composeProbe.ok && workdirExists && dockerSocketExists && codexConfigReady && sshSharedReady;
const value: JsonValue = {
ok,
missingTools,
tools: tools as unknown as JsonValue,
workdir: { path: config.defaultWorkdir, exists: workdirExists },
docker: {
socketPath: "/var/run/docker.sock",
socketExists: dockerSocketExists,
versionOk: dockerProbe.ok,
version: dockerProbe.output,
composeOk: composeProbe.ok,
composeVersion: composeProbe.output,
},
codexConfig: {
sourcePath: config.sourceCodexConfig,
sourceExists: existsSync(config.sourceCodexConfig),
homeConfigPath: homeCodexConfig,
homeConfigExists: existsSync(homeCodexConfig),
sourceAuthPath: sourceCodexAuth,
sourceAuthExists: existsSync(sourceCodexAuth),
homeAuthPath: homeCodexAuth,
homeAuthExists: existsSync(homeCodexAuth),
ready: codexConfigReady,
},
ssh: {
rootSshPath: "/root/.ssh",
rootSshExists: existsSync("/root/.ssh"),
privateKeyPresent: sshSharedReady,
githubKnownHostPresent: githubKnownHostProbe.ok,
ready: sshSharedReady,
},
};
devReadyCache = { checkedAtMs: now, value };
return value;
}
function makeTaskId(): string {
return `codex_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;
}
function isCodexTaskId(value: string): boolean {
return /^codex_\d+_[A-Za-z0-9_-]+$/u.test(value.trim());
}
function addUniqueTaskId(ids: string[], value: string): void {
const id = value.trim();
if (isCodexTaskId(id) && !ids.includes(id)) ids.push(id);
}
function collectTaskIdsFromValue(value: unknown, ids: string[]): void {
if (typeof value === "string") {
for (const part of value.split(/[\s,;]+/u)) addUniqueTaskId(ids, part);
return;
}
if (Array.isArray(value)) {
for (const item of value) collectTaskIdsFromValue(item, ids);
}
}
function referenceTaskIdsFromPrompt(prompt: string): string[] {
const ids: string[] = [];
const patterns = [
/引用\s+Code Queue\s+任务\s+(codex_\d+_[A-Za-z0-9_-]+)/giu,
/\bcodex\s+task\s+(codex_\d+_[A-Za-z0-9_-]+)/giu,
/(?:引用|上下文|context|reference)[^\n]{0,160}\b(codex_\d+_[A-Za-z0-9_-]+)/giu,
];
for (const pattern of patterns) {
for (const match of prompt.matchAll(pattern)) addUniqueTaskId(ids, String(match[1] ?? ""));
}
return ids;
}
function collectReferenceTaskIds(record: Record<string, unknown>, prompt: string): string[] {
const ids: string[] = [];
collectTaskIdsFromValue(record.referenceTaskId, ids);
collectTaskIdsFromValue(record.referenceTaskIds, ids);
for (const id of referenceTaskIdsFromPrompt(prompt)) addUniqueTaskId(ids, id);
return ids;
}
function normalizeRequest(value: unknown): QueueTaskRequest {
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("request body must be an object");
const record = value as Record<string, unknown>;
if (typeof record.prompt !== "string" || record.prompt.trim().length === 0) throw new Error("prompt is required");
const request: QueueTaskRequest = { prompt: record.prompt };
if (typeof record.queueId === "string" && record.queueId.trim().length > 0) request.queueId = normalizeQueueId(record.queueId);
const providerId = normalizeProviderId(record.providerId);
if (providerId !== null) request.providerId = providerId;
if (typeof record.cwd === "string" && record.cwd.length > 0) request.cwd = record.cwd;
if (typeof record.model === "string" && record.model.length > 0) request.model = record.model;
if (typeof record.reasoningEffort === "string" && record.reasoningEffort.length > 0) request.reasoningEffort = record.reasoningEffort;
if (typeof record.executionMode === "string" && record.executionMode.length > 0) request.executionMode = normalizeCodeExecutionMode(record.executionMode);
if (typeof record.maxAttempts === "number" && Number.isInteger(record.maxAttempts) && record.maxAttempts > 0) request.maxAttempts = clampTaskAttempts(record.maxAttempts);
const referenceTaskIds = collectReferenceTaskIds(record, record.prompt);
if (referenceTaskIds.length > 0) request.referenceTaskIds = referenceTaskIds;
return request;
}
function validateExecutionModeForTask(providerId: string, cwd: string, model: string, executionMode: ReturnType<typeof normalizeCodeExecutionMode>): void {
if (executionMode !== "windows-native") return;
if (providerIsMain(providerId)) throw new Error("windows-native executionMode requires a non-main WSL provider");
if (codeAgentPortForModel(model) !== "codex") throw new Error("windows-native executionMode only supports Codex models");
if (!cwd.startsWith("/mnt/")) throw new Error("windows-native executionMode requires cwd under /mnt/<drive>");
}
function createTask(request: QueueTaskRequest): QueueTask {
const at = nowIso();
const basePrompt = request.basePrompt ?? userPromptForDisplay(request.prompt);
const referenceTaskIds = request.referenceTaskIds ?? [];
const providerId = normalizeTaskProviderId(request.providerId);
const model = normalizeCodeModel(request.model ?? config.defaultModel);
const executionMode = normalizeCodeExecutionMode(request.executionMode);
const cwd = resolveTaskCwd(providerId, request.cwd);
validateExecutionModeForTask(providerId, cwd, model, executionMode);
rememberWorkdir(providerId, executionMode, cwd, at);
const queueId = normalizeQueueId(request.queueId);
ensureQueue(queueId);
return {
id: makeTaskId(),
queueId,
queueEnteredAt: at,
prompt: request.prompt,
basePrompt,
referenceTaskIds,
referenceInjection: request.referenceInjection ?? null,
providerId,
cwd,
model,
reasoningEffort: resolveReasoningEffort(model, request.reasoningEffort),
executionMode,
maxAttempts: request.maxAttempts ?? config.defaultMaxAttempts,
status: "queued",
createdAt: at,
updatedAt: at,
startedAt: null,
finishedAt: null,
readAt: null,
currentAttempt: 0,
currentMode: null,
codexThreadId: null,
activeTurnId: null,
finalResponse: "",
lastError: null,
lastJudge: null,
judgeFailCount: 0,
promptHistory: [],
output: [],
events: [],
attempts: [],
cancelRequested: false,
nextPrompt: null,
nextMode: null,
};
}
function appendPromptHistory(task: QueueTask, output: LiveOutput | null, method: PromptHistoryItem["method"], text: string): void {
if (output === null) return;
task.promptHistory = mergePromptHistory([...(Array.isArray(task.promptHistory) ? task.promptHistory : []), {
seq: output.seq,
at: output.at,
method,
text,
}]);
markTaskDirty(task.id);
schedulePersistState();
}
function addEvent(task: QueueTask, event: CodexEventSummary): void {
task.events.push(event);
if (config.maxInMemoryEventRecords > 0 && task.events.length > config.maxInMemoryEventRecords) task.events.splice(0, task.events.length - config.maxInMemoryEventRecords);
markTaskDirty(task.id);
publishTaskOaEvent(task, "agent-event", { onlyStepChange: true });
}
function taskReferencesEqual(left: string[], right: string[]): boolean {
if (left.length !== right.length) return false;
return left.every((value, index) => value === right[index]);
}
function queuedTaskPromptEditable(task: QueueTask): boolean {
return task.status === "queued"
&& task.startedAt === null
&& task.currentAttempt === 0
&& task.codexThreadId === null
&& task.nextMode === null
&& task.nextPrompt === null
&& task.attempts.length === 0;
}
function normalizePromptEditRequest(task: QueueTask, value: unknown): QueueTaskRequest {
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("request body must be an object");
const record = value as Record<string, unknown>;
if (typeof record.prompt !== "string" || record.prompt.trim().length === 0) throw new Error("prompt is required");
const referenceTaskIds: string[] = [];
const hasExplicitReferenceIds = Object.prototype.hasOwnProperty.call(record, "referenceTaskId")
|| Object.prototype.hasOwnProperty.call(record, "referenceTaskIds");
if (hasExplicitReferenceIds) {
collectTaskIdsFromValue(record.referenceTaskId, referenceTaskIds);
collectTaskIdsFromValue(record.referenceTaskIds, referenceTaskIds);
} else {
for (const id of task.referenceTaskIds ?? []) addUniqueTaskId(referenceTaskIds, id);
}
for (const id of referenceTaskIdsFromPrompt(record.prompt)) addUniqueTaskId(referenceTaskIds, id);
if (referenceTaskIds.includes(task.id)) throw new Error("a task cannot reference itself while editing prompt");
return {
prompt: record.prompt,
basePrompt: userPromptForDisplay(record.prompt),
referenceTaskIds,
queueId: queueIdOf(task),
providerId: task.providerId,
cwd: task.cwd,
model: task.model,
reasoningEffort: task.reasoningEffort ?? undefined,
maxAttempts: task.maxAttempts,
};
}
async function buildQueuedPromptUpdate(task: QueueTask, body: unknown): Promise<QueueTaskRequest> {
return injectCodeQueueEnvironmentHint(await injectReferencedTaskContext(normalizePromptEditRequest(task, body)));
}
function rewriteEnqueueOutput(task: QueueTask): void {
const text = `${task.prompt}\n`;
const output = taskFullOutput(task).find((item) => item.channel === "user" && item.method === "enqueue") ?? null;
if (output === null) return;
const nextOutput = { ...output, at: task.updatedAt, text };
const inMemory = task.output.find((item) => item.seq === output.seq);
if (inMemory !== undefined) {
inMemory.at = nextOutput.at;
inMemory.text = nextOutput.text;
}
appendOutputArchive(task, nextOutput, "set", text);
}
function truthyParam(url: URL, name: string): boolean {
const value = url.searchParams.get(name);
return value === "1" || value === "true" || value === "yes";
}
function parseSeqParam(url: URL, name: string, defaultValue: number | null): number | null {
const raw = url.searchParams.get(name);
if (raw === null) return defaultValue;
const value = Number(raw);
return Number.isFinite(value) ? value : defaultValue;
}
function parseTextLimit(url: URL): number {
const value = Number(url.searchParams.get("maxTextChars") ?? 12_000);
return Number.isInteger(value) && value > 0 ? Math.min(500_000, value) : 12_000;
}
function pageBySeq<T extends { seq: number }>(items: T[], url: URL, limit: number): {
mode: "tail" | "after" | "before";
afterSeq: number;
beforeSeq: number | null;
nextAfterSeq: number;
previousBeforeSeq: number | null;
hasMore: boolean;
hasBefore: boolean;
chunk: T[];
} {
const beforeSeq = parseSeqParam(url, "beforeSeq", null);
const afterSeq = parseSeqParam(url, "afterSeq", 0) ?? 0;
const mode = beforeSeq !== null ? "before" : truthyParam(url, "tail") ? "tail" : "after";
const boundedBeforeSeq = beforeSeq ?? 0;
const chunk = mode === "before"
? items.filter((item) => Number(item.seq) < boundedBeforeSeq).slice(-limit)
: mode === "tail"
? items.slice(-limit)
: items.filter((item) => Number(item.seq) > afterSeq).slice(0, limit);
const firstSeq = chunk[0]?.seq;
const lastSeq = chunk.at(-1)?.seq;
return {
mode,
afterSeq,
beforeSeq,
nextAfterSeq: lastSeq ?? afterSeq,
previousBeforeSeq: firstSeq ?? beforeSeq,
hasMore: lastSeq !== undefined && items.some((item) => Number(item.seq) > Number(lastSeq)),
hasBefore: firstSeq !== undefined && items.some((item) => Number(item.seq) < Number(firstSeq)),
chunk,
};
}
function terminalTask(task: QueueTask): boolean {
return task.status === "succeeded" || task.status === "failed" || task.status === "canceled";
}
function terminalTaskUnread(task: QueueTask): boolean {
return terminalTask(task) && task.readAt === null;
}
function durationMsBetween(startAt: string | null | undefined, endAt: string | null | undefined): number | null {
const startMs = timestampMs(startAt);
const endMs = timestampMs(endAt);
return nonNegativeElapsed(startMs, endMs);
}
function formatDurationMs(value: number | null): string {
if (value === null) return "-";
const totalSeconds = Math.max(0, Math.floor(value / 1000));
const days = Math.floor(totalSeconds / 86_400);
const hours = Math.floor((totalSeconds % 86_400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const parts: string[] = [];
if (days > 0) parts.push(`${days}d`);
if (hours > 0 || parts.length > 0) parts.push(`${hours}h`);
if (minutes > 0 || parts.length > 0) parts.push(`${minutes}m`);
parts.push(`${seconds}s`);
return parts.join(" ");
}
function openCodeFreshRecoveryPrompt(task: QueueTask, prompt: string, reason: string): string {
const previous = safePreview(task.finalResponse, 2000);
return [
"OpenCode port recovery:上一个 OpenCode session 无法恢复;Code Queue 将为同一任务开启新的 OpenCode session,避免对缺失 session 无限重试。",
`恢复原因:${judgeReasonForPrompt(reason)}`,
"原始任务摘要/按需查询:",
compactRetryTaskContext(task),
previous.length > 0 ? `上一轮可见 assistant response 摘要:\n${previous}` : "",
"本轮 recovery continuation prompt",
prompt,
].filter((line) => line.length > 0).join("\n\n");
}
function codexFreshRecoveryPrompt(task: QueueTask, prompt: string, reason: string): string {
const previous = safePreview(task.finalResponse, 2000);
return [
"Codex port recovery:上一个 Codex threadId 缺失;Code Queue 将为同一任务开启新的 Codex thread,避免对缺失 thread 无限重试。",
`恢复原因:${judgeReasonForPrompt(reason)}`,
"原始任务摘要/按需查询:",
compactRetryTaskContext(task),
previous.length > 0 ? `上一轮可见 assistant response 摘要:\n${previous}` : "",
"本轮 recovery continuation prompt",
prompt,
].filter((line) => line.length > 0).join("\n\n");
}
async function runCodeAgentTurn(task: QueueTask, prompt: string): Promise<CodexRunResult> {
return codeAgentPortForModel(task.model) === "opencode" ? runOpenCodeTurn(task, prompt) : runCodexTurn(task, prompt);
}
configureProviderRuntime({
config,
safePreview,
});
ensureDefaultWorkdirRecords();
configureTaskOutput({
config,
allocateSeq: () => state.nextSeq++,
errorToJson,
logger,
markTaskDirty,
nowIso,
onOutputAppended: (task, output, op) => {
const archiveOp = op === "append" ? "append" : "set";
const stepChanged = recordTaskOutputMetrics(task, output, archiveOp);
const projectionOutput = traceStepOutputForProjection(task, output);
if (stepChanged) publishCodeQueueTraceStep(task, queueIdOf(task), projectionOutput, taskOutputMaxSeq(task));
else if ((archiveOp === "append" || output.method === "item/completed") && outputUpdatesExistingTraceStep(output)) publishCodeQueueTraceStep(task, queueIdOf(task), projectionOutput, taskOutputMaxSeq(task), null, String(output.text || "").length);
if (archiveOp === "append" && !outputCanChangeStepCount(output)) return;
publishTaskOaEvent(task, "output", { onlyStepChange: archiveOp === "append", stepChanged });
},
schedulePersistState,
});
configureTaskView({
config,
errorToJson,
jsonResponse,
logger,
mergePromptHistory,
nowIso,
outputPromptHistory,
pageBySeq,
parseLimit,
parseSeqParam,
queueIdOf,
queuedStatusReason,
queuedTaskPromptEditable,
taskQueueEnteredAt,
});
configureNotifications({
config,
activeRunCount: () => activeRuns.size,
activeRunSlotCount,
activeRunTaskIds: () => Array.from(activeRuns.values()).map((run) => run.taskId),
databaseReady: () => databaseReady,
errorToJson,
hasRunnableTask,
lastAssistantMessage: (task) => lastAssistantMessage(task),
loadAllTasksForRead,
logger,
nonNegativeElapsed,
nowIso,
processingQueueCount: () => processingQueues.size,
queueCount: () => perQueueSummaries().length,
queueIdOf,
safePreview,
shutdownRequested: () => shutdownRequested,
sql,
taskTimestamp,
tasks: () => state.tasks,
timestampMs,
});
configureQueueApi({
config,
activeRunSlotQueueIds,
activeRunSlotWaiterSummaries,
activeRuns,
codexSqliteLogExporter: () => codexSqliteLogExporter as unknown as Record<string, JsonValue>,
collectDevReady,
compactJsonResponse,
databaseLastError: () => databaseLastError,
databaseReady: () => databaseReady,
defaultQueueId,
dirtyDatabaseTaskCount: () => dirtyDatabaseTaskIds.size,
jsonResponse,
judgeFailRetryLimit,
loadQueuesFromDatabase,
loadTaskFromDatabase,
loadTasksFromDatabase,
loadTasksFromDatabaseByIds,
pageBySeq,
parseLimit,
parseTextLimit,
processing: () => processing,
processingQueues,
queueHeadTask,
queueIdOf,
queues: () => state.queues,
queueTaskIsRunnable,
queuedStatusReason,
queuedTaskPromptEditable,
runGarbageCollection,
safeQueueId,
safeQueueName,
sql,
taskQueueEnteredAt,
traceStatsForTasks: readOaTraceStatsForTasks,
tasks: () => state.tasks,
truthyParam,
});
configureReferences({
addUniqueTaskId,
findTask: findTaskForRead,
nowIso,
referenceInjectionMaxRounds,
referenceTaskIdsFromPrompt,
});
configureSelfTests({
config,
activeRunSlotCount,
activeRunSlotReservations,
activeRunSlotWaiters,
availableQueueStartSlotsFor,
defaultQueueId,
enqueueActiveRunSlotWaiter,
injectReferencedTaskContext,
moveTaskToQueueForTest: (task, req) => moveTaskToQueue(task, req, { bypassRoleCheck: true }),
nextRunnableTaskFrom,
normalizeTask,
nowIso,
processingQueues,
queueHeadTask,
queuedStatusReason,
removeActiveRunSlotWaiter,
resolveReasoningEffort,
runDatabaseClaimMoveSelfTest,
tasks: () => state.tasks,
updateProcessingFlag,
});
configureJudge({
config,
logger,
safePreview,
userPromptForDisplay,
taskFullOutput,
taskReferenceIds,
extractRecord,
extractString,
promptLineCount,
judgeFailRetryLimit,
publishJudgeEvent: (task, type, payload) => publishCodeQueueJudgeEvent(task, queueIdOf(task), type, payload),
});
configureCodexPort({
config,
activeRuns,
appendOutput,
addEvent,
ensureTaskExecutionContainer,
formatCommandOutput,
logger,
persistTaskState,
providerIsMain,
queueIdOf,
recordNumberField,
recordStringField,
remoteAppServerCommand,
windowsNativeAppServerCommand,
resolveReasoningEffort,
safePreview,
nowIso,
});
configureOpenCodePort({
config,
activeRuns,
addEvent,
appendOutput,
buildDevContainerPlan,
compactRetryTaskContext,
ensureTaskExecutionContainer,
judgeReasonForPrompt,
logger,
nowIso,
openCodeFreshRecoveryPrompt,
openCodeXdgEnv,
persistTaskState,
providerIsMain,
queueIdOf,
recordStringField,
remoteHostWorkdirForTask,
safePreview,
shellQuote,
shutdownRequested: () => shutdownRequested,
});
configureDevContainers({
config,
appendOutput,
buildDevContainerPlan,
containerTunnelStartScript,
devContainerEnsurePromises,
devContainerPingScript,
errorToJson,
extractRecord,
jsonResponse,
logger,
masterKeyReadScript,
masterKeySetupScript,
masterProxyEvidenceScript,
masterProxyFinishScript,
masterProxyPrepareScript,
normalizeProviderId,
providerIsMain,
readJson,
remoteCodexConfigInstallScript,
remoteCodexRuntimePrepareScript,
remoteContainerStartScript,
remoteHostWorkdirForTask,
remoteKeyInstallScript,
runCodeQueueSsh,
safePreview,
shellQuote,
throwIfCommandFailed,
});
function outputForProbe(item: { channel: OutputChannel; text: string; method?: string }, index: number): LiveOutput {
return {
seq: index + 1,
at: nowIso(),
channel: item.channel,
text: item.text,
method: item.method,
};
}
function taskForJudgeProbe(probe: JudgeProbeCase): QueueTask {
const at = nowIso();
return {
id: `judge_probe_${probe.id}`,
queueId: defaultQueueId,
queueEnteredAt: at,
prompt: probe.prompt,
basePrompt: probe.prompt,
referenceTaskIds: [],
referenceInjection: null,
providerId: config.mainProviderId,
cwd: config.defaultWorkdir,
model: config.defaultModel,
reasoningEffort: resolveReasoningEffort(config.defaultModel, config.defaultReasoningEffort),
executionMode: "default",
maxAttempts: 3,
status: "judging",
createdAt: at,
updatedAt: at,
startedAt: at,
finishedAt: null,
readAt: null,
currentAttempt: 1,
currentMode: "initial",
codexThreadId: "judge-probe-thread",
activeTurnId: null,
finalResponse: probe.finalResponse,
lastError: null,
lastJudge: null,
judgeFailCount: 0,
promptHistory: [],
output: (probe.outputs ?? []).map(outputForProbe),
events: probe.events ?? [],
attempts: [],
cancelRequested: probe.cancelRequested ?? false,
nextPrompt: null,
nextMode: null,
};
}
function resultForJudgeProbe(probe: JudgeProbeCase, task: QueueTask): CodexRunResult {
return {
threadId: "judge-probe-thread",
turnId: "judge-probe-turn",
finalResponse: probe.finalResponse,
terminalStatus: probe.terminalStatus,
terminalError: probe.terminalError ?? null,
transportClosedBeforeTerminal: probe.transportClosedBeforeTerminal ?? false,
appServerExit: {
code: probe.transportClosedBeforeTerminal ? 1 : 0,
signal: null,
stderrTail: probe.stderrTail ?? "",
},
events: task.events,
};
}
async function runJudgeProbe(): Promise<Response> {
const results = await Promise.all(defaultJudgeProbeCases.map(async (probe) => {
const task = taskForJudgeProbe(probe);
const result = resultForJudgeProbe(probe, task);
const startedAt = nowIso();
const finishedAt = nowIso();
task.attempts.push(attemptFromResult(task, "initial", startedAt, finishedAt, result));
const judge = await judgeTask(task, result);
const expectedContinuePromptIncludes = probe.expectedContinuePromptIncludes ?? [];
const expectedContinuePromptExcludes = probe.expectedContinuePromptExcludes ?? [];
const continuePrompt = judge.continuePrompt ?? "";
const continuePromptHit = expectedContinuePromptIncludes.every((text) => continuePrompt.includes(text));
const continuePromptExclusionHit = expectedContinuePromptExcludes.every((text) => !continuePrompt.includes(text));
const continuePromptMaxCharsHit = probe.expectedContinuePromptMaxChars === undefined || continuePrompt.length <= probe.expectedContinuePromptMaxChars;
const continuePromptMaxLinesHit = probe.expectedContinuePromptMaxLines === undefined || promptLineCount(continuePrompt) <= probe.expectedContinuePromptMaxLines;
return {
id: probe.id,
expected: probe.expected,
decision: judge.decision,
hit: judge.decision === probe.expected && continuePromptHit && continuePromptExclusionHit && continuePromptMaxCharsHit && continuePromptMaxLinesHit,
continuePromptHit,
continuePromptExclusionHit,
continuePromptMaxCharsHit,
continuePromptMaxLinesHit,
expectedContinuePromptIncludes,
expectedContinuePromptExcludes,
expectedContinuePromptMaxChars: probe.expectedContinuePromptMaxChars ?? null,
expectedContinuePromptMaxLines: probe.expectedContinuePromptMaxLines ?? null,
confidence: judge.confidence,
source: judge.source,
reason: judge.reason,
continuePrompt: continuePrompt.length > 0 ? continuePrompt : null,
};
}));
const hits = results.filter((result) => result.hit).length;
const hitRate = results.length === 0 ? 0 : hits / results.length;
logger("info", "judge_probe_completed", { configured: config.minimaxApiKey.length > 0, model: config.minimaxModel, hits, total: results.length, hitRate });
return jsonResponse({
ok: true,
configured: config.minimaxApiKey.length > 0,
model: config.minimaxModel,
hits,
total: results.length,
hitRate,
results,
});
}
function cloneJson<T>(value: T): T {
return JSON.parse(JSON.stringify(value)) as T;
}
function numberOrNull(value: unknown): number | null {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
function maxOutputSeqBefore(output: LiveOutput[], exclusiveSeq: number): number | null {
let maxSeq: number | null = null;
for (const item of output) {
const seq = numberOrNull(item.seq);
if (seq === null || seq >= exclusiveSeq) continue;
maxSeq = maxSeq === null ? seq : Math.max(maxSeq, seq);
}
return maxSeq;
}
function outputSeqAtOrBeforeTime(output: LiveOutput[], at: string | null): number | null {
const atMs = timestampMs(at);
if (atMs === null) return null;
let maxSeq: number | null = null;
for (const item of output) {
const itemMs = timestampMs(item.at);
if (itemMs === null || itemMs > atMs) continue;
const seq = numberOrNull(item.seq);
if (seq === null) continue;
maxSeq = maxSeq === null ? seq : Math.max(maxSeq, seq);
}
return maxSeq;
}
function preJudgeOutputEndSeq(attempt: AttemptSummary, output: LiveOutput[]): { endSeq: number | null; source: string } {
const judgeSeq = numberOrNull(attempt.judgeSeq);
if (judgeSeq !== null) return { endSeq: maxOutputSeqBefore(output, judgeSeq), source: "before-judgeSeq" };
const explicitEnd = numberOrNull(attempt.outputEndSeq);
if (explicitEnd !== null) {
const explicitItem = output.find((item) => numberOrNull(item.seq) === explicitEnd);
if (explicitItem?.method === "judge") return { endSeq: maxOutputSeqBefore(output, explicitEnd), source: "before-outputEndSeq-judge" };
return { endSeq: explicitEnd, source: "outputEndSeq" };
}
const byFinishedAt = outputSeqAtOrBeforeTime(output, attempt.finishedAt);
return { endSeq: byFinishedAt, source: byFinishedAt === null ? "unbounded" : "finishedAt" };
}
function outputBeforeJudge(output: LiveOutput[], endSeq: number | null): LiveOutput[] {
if (endSeq === null) return output;
return output.filter((item) => numberOrNull(item.seq) !== null && Number(item.seq) <= endSeq);
}
function currentAttemptOutputCountForReplay(attempt: AttemptSummary, output: LiveOutput[], hotOutput: LiveOutput[], endSeq: number | null): number {
const startSeq = numberOrNull(attempt.outputStartSeq);
if (startSeq === null) return hotOutput.slice(-80).length;
return output.filter((item) => {
const seq = numberOrNull(item.seq);
return seq !== null && seq >= startSeq && (endSeq === null || seq <= endSeq);
}).length;
}
function finalResponseForTaskBeforeJudge(attempts: AttemptSummary[]): string {
for (let index = attempts.length - 1; index >= 0; index -= 1) {
const finalResponse = String(attempts[index]?.finalResponse ?? "");
if (finalResponse.trim().length > 0) return finalResponse;
}
return "";
}
function retainedEventsForAttempt(task: QueueTask, attempt: AttemptSummary): CodexRunResult["events"] {
const startedMs = timestampMs(attempt.startedAt);
const finishedMs = timestampMs(attempt.finishedAt);
return task.events.filter((event) => {
const eventMs = timestampMs(event.at);
if (eventMs === null) return false;
if (startedMs !== null && eventMs < startedMs) return false;
if (finishedMs !== null && eventMs > finishedMs + 1000) return false;
return true;
});
}
function attemptEventsForReplay(task: QueueTask, attempt: AttemptSummary): { events: CodexRunResult["events"]; source: string; exact: boolean } {
if (Array.isArray(attempt.events)) return { events: cloneJson(attempt.events), source: "attempt.events", exact: true };
return { events: cloneJson(retainedEventsForAttempt(task, attempt)), source: "retained-task-events", exact: false };
}
function replayAttemptIndexFromUrl(task: QueueTask, url: URL): number {
const raw = url.searchParams.get("attempt") ?? url.searchParams.get("attemptId") ?? url.searchParams.get("attemptIndex");
if (raw !== null) {
const value = Number(raw);
if (!Number.isInteger(value) || value <= 0) throw new Error("judge replay attempt must be a positive integer");
return value;
}
const latest = task.attempts.at(-1)?.index ?? task.currentAttempt;
if (!Number.isInteger(latest) || latest <= 0) throw new Error("task has no completed attempt to judge");
return latest;
}
function buildJudgeReplay(task: QueueTask, attemptIndex: number): {
task: QueueTask;
result: CodexRunResult;
attempt: AttemptSummary;
replay: Record<string, JsonValue>;
} {
const attempt = task.attempts.find((item) => item.index === attemptIndex);
if (attempt === undefined) throw new Error(`attempt ${attemptIndex} not found on task ${task.id}`);
const fullOutput = taskFullOutput(task);
const preJudge = preJudgeOutputEndSeq(attempt, fullOutput);
const beforeJudge = outputBeforeJudge(fullOutput, preJudge.endSeq);
const hotOutput = config.maxInMemoryOutputRecords > 0 ? beforeJudge.slice(-config.maxInMemoryOutputRecords) : beforeJudge;
const attempts = task.attempts
.filter((item) => item.index <= attempt.index)
.map((item) => cloneJson(item));
const latestAttempt = attempts[attempts.length - 1];
if (latestAttempt === undefined) throw new Error(`attempt ${attemptIndex} could not be prepared`);
latestAttempt.judge = null;
latestAttempt.judgeAt = null;
latestAttempt.judgeSeq = null;
latestAttempt.outputEndSeq = preJudge.endSeq;
const eventReplay = attemptEventsForReplay(task, attempt);
const previousAttempts = attempts.filter((item) => item.index < attempt.index);
const result: CodexRunResult = {
threadId: task.codexThreadId,
turnId: null,
finalResponse: String(attempt.finalResponse ?? ""),
terminalStatus: attempt.terminalStatus,
terminalError: attempt.error,
transportClosedBeforeTerminal: attempt.transportClosedBeforeTerminal,
appServerExit: {
code: attempt.appServerExitCode,
signal: attempt.appServerSignal,
stderrTail: String(attempt.stderrTail ?? ""),
},
events: eventReplay.events,
};
const replayTask: QueueTask = {
...cloneJson(task),
status: "judging",
currentAttempt: attempt.index,
currentMode: attempt.mode,
attempts,
output: numberOrNull(attempt.outputStartSeq) === null ? hotOutput : beforeJudge,
events: eventReplay.events,
finalResponse: finalResponseForTaskBeforeJudge(attempts),
lastJudge: previousAttempts.at(-1)?.judge ?? null,
judgeFailCount: previousAttempts.filter((item) => item.judge?.decision === "fail").length,
activeTurnId: null,
finishedAt: null,
readAt: null,
nextPrompt: null,
nextMode: null,
};
return {
task: replayTask,
result,
attempt,
replay: {
sameJudgeCodePath: true,
replayExact: eventReplay.exact,
taskId: task.id,
attemptIndex: attempt.index,
attemptMode: attempt.mode,
outputStartSeq: attempt.outputStartSeq ?? null,
storedOutputEndSeq: attempt.outputEndSeq ?? null,
replayOutputEndSeq: preJudge.endSeq,
replayOutputEndSeqSource: preJudge.source,
judgeSeq: attempt.judgeSeq ?? null,
fullOutputBeforeJudgeCount: beforeJudge.length,
hotOutputCount: hotOutput.length,
currentAttemptOutputCount: currentAttemptOutputCountForReplay(attempt, beforeJudge, hotOutput, preJudge.endSeq),
eventSource: eventReplay.source,
eventReplayExact: eventReplay.exact,
eventCount: eventReplay.events.length,
note: eventReplay.exact
? "Replay uses attempt-stored event summaries plus the same judgeTask context builder and MiniMax call path."
: "Historical attempt did not store per-attempt events; replay falls back to retained task.events. Output, final response, terminal status, stderr, attempt window, prompt compaction, and MiniMax call path are reconstructed from persisted task/output archives.",
},
};
}
async function runSingleTaskJudge(task: QueueTask, url: URL): Promise<Response> {
const attemptIndex = replayAttemptIndexFromUrl(task, url);
const dryRun = truthyParam(url, "dryRun") || truthyParam(url, "noCall");
const includePrompt = truthyParam(url, "includePrompt");
const replay = buildJudgeReplay(task, attemptIndex);
const context = judgeTaskInputDiagnostics(replay.task, replay.result, includePrompt);
const startedAt = Date.now();
const judge = dryRun ? null : await judgeTask(replay.task, replay.result);
const durationMs = Date.now() - startedAt;
const storedJudge = replay.attempt.judge ?? null;
const storedFailure = storedJudge?.failureDetails ?? null;
const promptChars = Number(context.promptChars);
const payloadBytes = Number(context.payloadBytes);
return jsonResponse({
ok: true,
dryRun,
configured: config.minimaxApiKey.length > 0,
model: config.minimaxModel,
taskId: task.id,
attempt: {
index: replay.attempt.index,
mode: replay.attempt.mode,
terminalStatus: replay.attempt.terminalStatus,
finalResponseChars: replay.attempt.finalResponseChars ?? String(replay.attempt.finalResponse ?? "").length,
startedAt: replay.attempt.startedAt,
finishedAt: replay.attempt.finishedAt,
},
replay: replay.replay,
context,
judge,
durationMs,
storedJudge,
comparison: {
storedDecision: storedJudge?.decision ?? null,
storedSource: storedJudge?.source ?? null,
decisionMatchesStored: judge === null || storedJudge === null ? null : judge.decision === storedJudge.decision,
storedFailureTimedOut: storedFailure?.timedOut ?? null,
storedFailureStage: storedFailure?.stage ?? null,
storedPromptChars: storedFailure?.promptChars ?? null,
promptCharsDeltaFromStored: storedFailure?.promptChars === undefined || !Number.isFinite(promptChars) ? null : promptChars - storedFailure.promptChars,
storedPayloadBytes: storedFailure?.payloadBytes ?? null,
payloadBytesDeltaFromStored: storedFailure?.payloadBytes === undefined || !Number.isFinite(payloadBytes) ? null : payloadBytes - storedFailure.payloadBytes,
},
});
}
function attemptFromResult(task: QueueTask, mode: RunMode, startedAt: string, finishedAt: string, result: CodexRunResult, outputStartSeq: number | null = null, outputEndSeq: number | null = null, inputPrompt: string | null = null): AttemptSummary {
const finalResponse = result.finalResponse;
const attempt: AttemptSummary = {
index: task.currentAttempt,
mode,
startedAt,
finishedAt,
providerId: task.providerId,
executionMode: task.executionMode,
terminalStatus: result.terminalStatus,
transportClosedBeforeTerminal: result.transportClosedBeforeTerminal,
appServerExitCode: result.appServerExit.code,
appServerSignal: result.appServerExit.signal,
error: result.terminalError,
events: result.events,
finalResponse,
finalResponsePreview: safePreview(finalResponse, 3000),
finalResponseChars: finalResponse.length,
judge: null,
judgeAt: null,
judgeSeq: null,
stderrTail: safePreview(result.appServerExit.stderrTail, 3000),
outputStartSeq,
outputEndSeq,
};
if (inputPrompt !== null) setAttemptInputPrompt(attempt, inputPrompt);
return attempt;
}
function retryBackoffMs(completedAttempts: number): number {
const retryIndex = Math.max(1, Math.floor(completedAttempts));
const exponent = Math.min(20, retryIndex - 1);
return Math.min(retryBackoffMaxMs, retryBackoffBaseMs * (2 ** exponent));
}
async function sleepForRetryBackoff(task: QueueTask, delayMs: number): Promise<void> {
let remaining = delayMs;
while (remaining > 0 && !task.cancelRequested && !shutdownRequested) {
const chunk = Math.min(1000, remaining);
await Bun.sleep(chunk);
remaining -= chunk;
}
}
function taskHasLocalExecutionRecoveryClaim(task: QueueTask): boolean {
if (task.status === "judging") return true;
if (activeRunForTask(task) !== null) return true;
return activeRunSlotReservations.has(queueIdOf(task));
}
function queueActiveTasksForRestartRetry(reason: string, method: string, options: { onlyActiveRuns?: boolean } = {}): number {
if (!config.schedulerEnabled || !serviceRoleAllowsScheduler(config.serviceRole)) return 0;
let recovered = 0;
for (const task of state.tasks) {
if (task.status !== "running" && task.status !== "judging") continue;
if (options.onlyActiveRuns === true && !taskHasLocalExecutionRecoveryClaim(task)) continue;
task.status = "retry_wait";
task.finishedAt = null;
task.readAt = null;
task.activeTurnId = null;
task.lastError = reason;
task.nextMode = "retry";
task.nextPrompt = queueRecoveryRetryPrompt(task, reason);
if (!reserveNextAttemptBudget(task)) {
task.status = "failed";
task.finishedAt = nowIso();
task.nextMode = null;
task.nextPrompt = null;
task.lastError = `Max attempts reached (${maxTaskAttempts}) before restart recovery. ${reason}`;
appendOutput(task, "error", `${task.lastError}\n`, method);
continue;
}
setAttemptFeedbackPrompt(task.attempts.at(-1), task.nextPrompt, "queue-recovery-retry", task.attempts.length + 1);
task.updatedAt = nowIso();
appendOutput(task, "system", `${reason}; task queued for retry\n`, method);
recovered += 1;
}
if (recovered > 0) armIdleNotification();
return recovered;
}
function failTaskForFallbackRetryLimit(task: QueueTask, judge: JudgeResult | null): void {
const count = fallbackJudgeRetryCount(task);
const reason = `Fallback/non-LLM judge retry limit reached (${count}/${fallbackJudgeRetryLimit}). ${judge?.reason ?? task.lastJudge?.reason ?? "MiniMax judge was unavailable."}`;
task.status = "failed";
task.finishedAt = nowIso();
task.updatedAt = task.finishedAt;
task.activeTurnId = null;
task.nextPrompt = null;
task.nextMode = null;
task.lastError = safePreview(reason, 2000);
appendOutput(task, "error", `${reason}\n`, "queue");
persistTaskState(task);
logger("warn", "task_failed_by_fallback_retry_limit", {
taskId: task.id,
fallbackRetryCount: count,
fallbackJudgeRetryLimit,
reason: safePreview(reason, 500),
});
void notifyTaskTerminal(task);
}
async function runTask(task: QueueTask): Promise<void> {
const claimQueueId = queueIdOf(task);
logger("info", "task_processor_start", { taskId: task.id, queueId: claimQueueId, providerId: task.providerId, executionMode: task.executionMode, cwd: task.cwd, maxAttempts: task.maxAttempts, model: task.model, agentPort: codeAgentPortForModel(task.model), promptPreview: safePreview(task.prompt, 240) });
if (task.status === "retry_wait" && task.lastJudge?.source === "fallback" && task.lastJudge.decision === "retry" && fallbackJudgeRetryCount(task) >= fallbackJudgeRetryLimit) {
failTaskForFallbackRetryLimit(task, task.lastJudge);
return;
}
armIdleNotification();
task.maxAttempts = clampTaskAttempts(task.maxAttempts || config.defaultMaxAttempts);
task.startedAt ??= nowIso();
task.lastError = null;
while (task.attempts.length < task.maxAttempts && !task.cancelRequested && !shutdownRequested) {
const mode = task.nextMode ?? (task.attempts.length === 0 ? "initial" : "retry");
const rawPrompt = task.nextPrompt ?? task.prompt;
const needsFreshRecoveryPrompt = mode === "retry" && task.codexThreadId === null && task.attempts.length > 0;
const recoveryPrompt = needsFreshRecoveryPrompt
? codeAgentPortForModel(task.model) === "opencode"
? openCodeFreshRecoveryPrompt(task, rawPrompt, "retry_wait task has no persisted OpenCode session id")
: codexFreshRecoveryPrompt(task, rawPrompt, "retry_wait task has no persisted Codex thread id")
: rawPrompt;
const prompt = promptWithCodeQueueEnvironmentHint(recoveryPrompt);
if (needsFreshRecoveryPrompt) {
appendOutput(task, "system", "retry has no persisted thread/session id; starting a fresh agent thread with compact recovery context\n", "thread/recovery");
}
const releaseRunSlot = await acquireActiveRunSlot(task);
if (releaseRunSlot === null) break;
const startedAt = nowIso();
task.currentAttempt = task.attempts.length + 1;
task.currentMode = mode;
task.status = "running";
task.readAt = null;
task.finishedAt = null;
task.updatedAt = startedAt;
if (!await claimTaskInDatabase(task, claimQueueId)) {
releaseRunSlot();
return;
}
publishTaskOaEvent(task, "claim");
logger("info", "task_run_start", { taskId: task.id, queueId: queueIdOf(task), attempt: task.currentAttempt, mode, providerId: task.providerId, executionMode: task.executionMode, cwd: task.cwd, maxAttempts: task.maxAttempts, model: task.model, agentPort: codeAgentPortForModel(task.model), freshRecovery: needsFreshRecoveryPrompt });
const attemptStartOutput = appendOutput(task, "system", `attempt ${task.currentAttempt}/${task.maxAttempts} queue=${queueIdOf(task)} provider=${task.providerId} executionMode=${task.executionMode} cwd=${task.cwd} mode=${mode} model=${task.model} port=${codeAgentPortForModel(task.model)}\n`, "queue");
let result: CodexRunResult;
try {
result = await runCodeAgentTurn(task, prompt);
} finally {
releaseRunSlot();
}
const finishedAt = nowIso();
task.finalResponse = result.finalResponse || task.finalResponse;
task.attempts.push(attemptFromResult(task, mode, startedAt, finishedAt, result, attemptStartOutput?.seq ?? null, taskOutputMaxSeq(task), recoveryPrompt));
task.status = "judging";
task.activeTurnId = null;
task.updatedAt = nowIso();
persistTaskState(task);
if (task.cancelRequested) break;
const judge = await judgeTask(task, result);
task.lastJudge = judge;
const judgeOutput = appendOutput(task, judge.decision === "complete" ? "system" : judge.decision === "fail" ? "error" : "system", `judge=${judge.decision} confidence=${judge.confidence.toFixed(2)} source=${judge.source}: ${judge.reason}${judgeFailureDetailsForOutput(judge)}\n`, "judge");
const latestAttempt = task.attempts.at(-1);
if (latestAttempt !== undefined && latestAttempt.index === task.currentAttempt) {
latestAttempt.judge = judge;
latestAttempt.judgeAt = judgeOutput?.at ?? nowIso();
latestAttempt.judgeSeq = judgeOutput?.seq ?? null;
latestAttempt.outputEndSeq = judgeOutput?.seq ?? latestAttempt.outputEndSeq ?? null;
}
logger("info", "task_judged", { taskId: task.id, attempt: task.currentAttempt, decision: judge.decision, confidence: judge.confidence, source: judge.source, reason: safePreview(judge.reason, 500), failureDetails: judge.failureDetails ?? null } as unknown as JsonValue);
if (judge.decision === "complete") {
task.status = "succeeded";
task.finishedAt = nowIso();
task.activeTurnId = null;
task.nextPrompt = null;
task.nextMode = null;
persistTaskState(task);
logger("info", "task_succeeded", { taskId: task.id, attempts: task.attempts.length });
void notifyTaskTerminal(task);
return;
}
if (judge.decision === "fail") {
task.judgeFailCount += 1;
if (!explicitUserInterrupt(task, result) && task.judgeFailCount < judgeFailRetryLimit && reserveNextAttemptBudget(task)) {
task.status = "retry_wait";
task.finishedAt = null;
task.readAt = null;
const nextPrompt = judgeFailContinuationPrompt(task, judge, task.judgeFailCount);
task.nextPrompt = nextPrompt;
task.nextMode = "retry";
setAttemptFeedbackPrompt(latestAttempt, nextPrompt, "judge-fail-retry", task.attempts.length + 1);
task.updatedAt = nowIso();
appendOutput(task, "system", `judge=fail treated as retry (${task.judgeFailCount}/${judgeFailRetryLimit}); appending continuation prompt to existing session\n`, "queue");
persistTaskState(task);
logger("warn", "task_judge_fail_treated_as_retry", {
taskId: task.id,
attempt: task.currentAttempt,
judgeFailCount: task.judgeFailCount,
judgeFailRetryLimit,
reason: safePreview(judge.reason, 500),
});
const delayMs = retryBackoffMs(task.attempts.length);
appendOutput(task, "system", `retry backoff ${Math.round(delayMs / 1000)}s before appending continuation to existing session\n`, "queue");
await sleepForRetryBackoff(task, delayMs);
continue;
}
task.status = "failed";
task.finishedAt = nowIso();
task.updatedAt = task.finishedAt;
task.activeTurnId = null;
task.lastError = judge.reason;
appendOutput(task, "system", `judge=fail reached terminal threshold (${task.judgeFailCount}/${judgeFailRetryLimit}); queue will continue to the next queued task\n`, "queue");
persistTaskState(task);
logger("warn", "task_failed_by_judge_queue_continues", { taskId: task.id, judgeFailCount: task.judgeFailCount, judgeFailRetryLimit, reason: safePreview(judge.reason, 500) });
void notifyTaskTerminal(task);
return;
}
if (judge.source === "fallback" && fallbackJudgeRetryCount(task) >= fallbackJudgeRetryLimit) {
failTaskForFallbackRetryLimit(task, judge);
return;
}
if (task.attempts.length >= task.maxAttempts) break;
task.status = "retry_wait";
task.finishedAt = null;
task.readAt = null;
const nextPrompt = retryPrompt(task, judge);
task.nextPrompt = nextPrompt;
task.nextMode = "retry";
setAttemptFeedbackPrompt(latestAttempt, nextPrompt, judge.continuePrompt?.trim() ? "judge-continue-prompt" : "judge-retry-generated", task.attempts.length + 1);
task.updatedAt = nowIso();
persistTaskState(task);
const delayMs = retryBackoffMs(task.attempts.length);
appendOutput(task, "system", `retry backoff ${Math.round(delayMs / 1000)}s before appending continuation to existing session\n`, "queue");
await sleepForRetryBackoff(task, delayMs);
}
if (shutdownRequested) {
if (task.status === "running" || task.status === "judging") {
queueActiveTasksForRestartRetry("Service stopping while task was active", "shutdown");
persistState();
}
return;
}
if (task.cancelRequested) {
task.status = "canceled";
task.finishedAt = nowIso();
task.lastError = "Task canceled by request.";
} else {
task.status = "failed";
task.finishedAt = nowIso();
task.lastError = `Max attempts reached (${task.maxAttempts}).`;
}
task.activeTurnId = null;
persistTaskState(task);
logger(task.status === "canceled" ? "warn" : "error", "task_terminal", { taskId: task.id, status: task.status, attempts: task.attempts.length, error: task.lastError ?? "" });
void notifyTaskTerminal(task);
}
function updateProcessingFlag(): void {
processing = processingQueues.size > 0;
}
function activeRunSlotQueueIds(): string[] {
return Array.from(new Set([
...Array.from(activeRuns.keys()),
...Array.from(activeRunSlotReservations),
])).sort((left, right) => left.localeCompare(right));
}
function activeRunSlotCount(): number {
return activeRunSlotQueueIds().length;
}
function enqueueActiveRunSlotWaiter(task: QueueTask): ActiveRunSlotWaiter {
const waiter = {
id: nextActiveRunSlotWaiterId,
taskId: task.id,
queueId: queueIdOf(task),
enqueuedAt: nowIso(),
};
nextActiveRunSlotWaiterId += 1;
activeRunSlotWaiters.push(waiter);
return waiter;
}
function removeActiveRunSlotWaiter(waiter: ActiveRunSlotWaiter): void {
const index = activeRunSlotWaiters.findIndex((item) => item.id === waiter.id);
if (index >= 0) activeRunSlotWaiters.splice(index, 1);
}
function firstActiveRunSlotWaiter(): ActiveRunSlotWaiter | null {
return activeRunSlotWaiters[0] ?? null;
}
function activeRunSlotWaiterSummaries(): JsonValue[] {
return activeRunSlotWaiters.map((waiter, index) => ({
position: index + 1,
taskId: waiter.taskId,
queueId: waiter.queueId,
enqueuedAt: waiter.enqueuedAt,
}));
}
async function acquireActiveRunSlot(task: QueueTask): Promise<(() => void) | null> {
const queueId = queueIdOf(task);
const waiter = enqueueActiveRunSlotWaiter(task);
let lastWaitLogAt = 0;
try {
while (!shutdownRequested && !task.cancelRequested && queueTaskIsRunnable(task)) {
const memoryPressure = activeRunMemoryPressure();
const atHead = firstActiveRunSlotWaiter()?.id === waiter.id;
if (atHead && availableQueueStartSlots() > 0 && memoryPressure === null) {
removeActiveRunSlotWaiter(waiter);
activeRunSlotReservations.add(queueId);
let released = false;
return () => {
if (released) return;
released = true;
activeRunSlotReservations.delete(queueId);
if (!shutdownRequested) scheduleQueue();
};
}
if (Date.now() - lastWaitLogAt > 30_000) {
lastWaitLogAt = Date.now();
const head = firstActiveRunSlotWaiter();
logger(memoryPressure === null ? "info" : "warn", "active_run_slot_waiting", {
taskId: task.id,
queueId,
waitPosition: activeRunSlotWaiters.findIndex((item) => item.id === waiter.id) + 1,
headTaskId: head?.taskId ?? null,
headQueueId: head?.queueId ?? null,
currentBytes: memoryPressure?.currentBytes ?? null,
inactiveFileBytes: memoryPressure?.inactiveFileBytes ?? null,
workingSetBytes: memoryPressure?.workingSetBytes ?? null,
swapCurrentBytes: memoryPressure?.swapCurrentBytes ?? null,
swapMaxBytes: memoryPressure?.swapMaxBytes ?? null,
thresholdBytes: memoryPressure?.thresholdBytes ?? memoryWatchdogThreshold(),
activeRunSlotCount: activeRunSlotCount(),
});
}
await Bun.sleep(memoryPressure === null ? 500 : 2000);
}
return null;
} finally {
removeActiveRunSlotWaiter(waiter);
}
}
function taskQueueOrderMs(task: QueueTask): number {
return timestampMs(taskQueueEnteredAt(task)) ?? timestampMs(task.createdAt) ?? timestampMs(task.updatedAt) ?? 0;
}
function compareTaskQueueOrder(left: QueueTask, right: QueueTask): number {
const queueDelta = taskQueueOrderMs(left) - taskQueueOrderMs(right);
if (queueDelta !== 0) return queueDelta;
const createdDelta = (timestampMs(left.createdAt) ?? 0) - (timestampMs(right.createdAt) ?? 0);
if (createdDelta !== 0) return createdDelta;
return left.id.localeCompare(right.id);
}
function queueTaskIsRunnable(task: QueueTask): boolean {
return task.status === "queued" || task.status === "retry_wait";
}
function queueTaskBlocksFollowing(task: QueueTask): boolean {
return !terminalTask(task);
}
function queueTaskRows(queueId: string, tasks: QueueTask[] = state.tasks): QueueTask[] {
return tasks
.filter((task) => queueIdOf(task) === queueId)
.sort(compareTaskQueueOrder);
}
function queueHeadTask(queueId: string, tasks: QueueTask[] = state.tasks): QueueTask | null {
return queueTaskRows(queueId, tasks).find(queueTaskBlocksFollowing) ?? null;
}
function nextRunnableTaskFrom(queueId: string, tasks: QueueTask[] = state.tasks): QueueTask | null {
const head = queueHeadTask(queueId, tasks);
return head !== null && queueTaskIsRunnable(head) ? head : null;
}
function queueIdsForTasks(tasks: QueueTask[] = state.tasks): string[] {
const ids = new Set<string>(tasks === state.tasks ? state.queues.map((queue) => queue.id) : []);
for (const task of tasks) ids.add(queueIdOf(task));
return Array.from(ids).sort((left, right) => left.localeCompare(right));
}
function runnableQueueIds(): string[] {
return queueIdsForTasks().filter((queueId) => nextRunnableTaskFrom(queueId) !== null);
}
function availableQueueStartSlotsFor(activeSlotCount: number, maxActiveQueues = config.maxActiveQueues): number {
if (maxActiveQueues <= 0) return Number.POSITIVE_INFINITY;
return Math.max(0, maxActiveQueues - activeSlotCount);
}
function availableQueueStartSlots(): number {
return availableQueueStartSlotsFor(activeRunSlotCount());
}
function queuedReason(code: string, label: string, message: string, extra: Omit<QueuedStatusReason, "code" | "label" | "message"> = {}): QueuedStatusReason {
return { code, label, message, ...extra };
}
function memoryPressureReasonPayload(usage: CgroupMemoryUsage & { thresholdBytes: number }): JsonValue {
return {
currentBytes: usage.currentBytes,
inactiveFileBytes: usage.inactiveFileBytes,
workingSetBytes: usage.workingSetBytes,
thresholdBytes: usage.thresholdBytes,
swapCurrentBytes: usage.swapCurrentBytes,
swapMaxBytes: usage.swapMaxBytes,
};
}
function queuedStatusReason(task: QueueTask, tasks: QueueTask[] = state.tasks): QueuedStatusReason | null {
if (task.status !== "queued") return null;
const queueId = queueIdOf(task);
const sourceTasks = tasks.some((item) => item.id === task.id) ? tasks : [...tasks, task];
const rows = queueTaskRows(queueId, sourceTasks);
const head = rows.find(queueTaskBlocksFollowing) ?? null;
if (head !== null && head.id !== task.id) {
return queuedReason(
"prev_task",
"PREV TASK",
`Waiting for previous task ${head.id} in queue ${queueId} to finish first.`,
{ blockerTaskId: head.id, blockerQueueId: queueId },
);
}
if (shutdownRequested) {
return queuedReason("shutdown", "SHUTDOWN", "Code Queue is shutting down; queued work will resume after restart.");
}
if (!serviceReady) {
return queuedReason("service", "SERVICE", "Code Queue is still starting and has not enabled scheduling yet.");
}
if (!config.schedulerEnabled && config.serviceRole !== "read") {
return queuedReason("scheduler_disabled", "STANDBY", "This Code Queue instance is a k3s-managed standby and does not start queued work.");
}
if (mergingQueues.has(queueId)) {
return queuedReason("queue_merge", "MERGING", "Queue merge is rewriting queue membership; scheduling will resume immediately after it finishes.");
}
const memoryPressure = activeRunMemoryPressure();
if (memoryPressure !== null) {
return queuedReason(
"mem_limit",
"MEM LIMIT",
"Waiting for cgroup memory working set to fall below the configured start threshold.",
{ memory: memoryPressureReasonPayload(memoryPressure), activeRunSlotCount: activeRunSlotCount(), maxActiveQueues: config.maxActiveQueues },
);
}
const waitPosition = activeRunSlotWaiters.findIndex((waiter) => waiter.taskId === task.id);
if (config.maxActiveQueues > 0 && availableQueueStartSlots() <= 0) {
return queuedReason(
"active_limit",
"ACTIVE LIMIT",
`Waiting for an active run slot (${activeRunSlotCount()}/${config.maxActiveQueues} in use).`,
{ waitPosition: waitPosition >= 0 ? waitPosition + 1 : null, activeRunSlotCount: activeRunSlotCount(), maxActiveQueues: config.maxActiveQueues },
);
}
if (waitPosition > 0) {
const blocker = firstActiveRunSlotWaiter();
return queuedReason(
"slot_fifo",
"SLOT FIFO",
"Waiting behind an older runnable queue for the next active run slot.",
{ blockerTaskId: blocker?.taskId ?? null, blockerQueueId: blocker?.queueId ?? null, waitPosition: waitPosition + 1, activeRunSlotCount: activeRunSlotCount(), maxActiveQueues: config.maxActiveQueues },
);
}
if (processingQueues.has(queueId) || waitPosition === 0) {
return queuedReason(
"starting",
"STARTING",
"Queue processor has selected this task and is starting the agent run.",
{ waitPosition: waitPosition >= 0 ? waitPosition + 1 : null, activeRunSlotCount: activeRunSlotCount(), maxActiveQueues: config.maxActiveQueues },
);
}
return queuedReason("ready", "READY", "Task is the head of its queue and ready to start.");
}
function nextRunnableTask(queueId: string): QueueTask | null {
return nextRunnableTaskFrom(queueId);
}
async function processQueue(queueId: string): Promise<void> {
if (!serviceReady || !config.schedulerEnabled || processingQueues.has(queueId) || shutdownRequested) return;
processingQueues.add(queueId);
updateProcessingFlag();
try {
while (true) {
if (shutdownRequested) break;
const task = nextRunnableTask(queueId);
if (task === null) break;
try {
await runTask(task);
} catch (error) {
const message = error instanceof Error ? error.stack ?? error.message : String(error);
appendOutput(task, "error", `${message}\n`, "queue-loop");
task.status = "failed";
task.finishedAt = nowIso();
task.activeTurnId = null;
task.lastError = safePreview(message, 2000);
task.updatedAt = nowIso();
persistTaskState(task);
logger("error", "task_failed_by_queue_exception", { taskId: task.id, error: safePreview(message, 1000) });
void notifyTaskTerminal(task);
}
}
} finally {
processingQueues.delete(queueId);
updateProcessingFlag();
persistState();
if (!shutdownRequested && nextRunnableTask(queueId) !== null) scheduleQueue(queueId);
if (!shutdownRequested) scheduleQueue();
void maybeNotifyQueueIdle().catch((error) => logger("warn", "claudeqq_idle_notify_schedule_failed", { error: errorToJson(error) }));
}
}
function scheduleQueue(queueId?: string): void {
if (!serviceReady || !config.schedulerEnabled || shutdownRequested) return;
const ids = queueId === undefined ? runnableQueueIds() : [queueId];
for (const id of ids) {
if (mergingQueues.has(id)) continue;
if (processingQueues.has(id)) continue;
void processQueue(id).catch((error) => {
logger("error", "queue_loop_failed", { queueId: id, error: error instanceof Error ? error.stack ?? error.message : String(error) });
processingQueues.delete(id);
activeRunSlotReservations.delete(id);
for (let index = activeRunSlotWaiters.length - 1; index >= 0; index -= 1) {
if (activeRunSlotWaiters[index]?.queueId === id) activeRunSlotWaiters.splice(index, 1);
}
updateProcessingFlag();
const run = activeRuns.get(id);
if (run !== undefined) {
run.app.stop();
activeRuns.delete(id);
}
void maybeNotifyQueueIdle().catch((idleError) => logger("warn", "claudeqq_idle_notify_schedule_failed", { error: errorToJson(idleError) }));
});
}
}
function hasRunnableTask(): boolean {
return state.tasks.some((task) => task.status === "queued" || task.status === "retry_wait");
}
function shouldPollSchedulerDatabase(): boolean {
return config.schedulerEnabled && config.serviceRole === "scheduler";
}
function mergeSchedulerDatabaseTasks(tasks: QueueTask[]): number {
let changed = 0;
for (const task of tasks) {
if (task.status !== "queued" && task.status !== "retry_wait" && task.status !== "running" && task.status !== "judging") continue;
const existing = findTask(task.id);
if (existing === null) {
state.tasks.push(task);
changed += 1;
continue;
}
const existingUpdatedAt = timestampMs(existing.updatedAt) ?? 0;
const taskUpdatedAt = timestampMs(task.updatedAt) ?? 0;
if (taskUpdatedAt > existingUpdatedAt && !activeRunForTask(existing)) {
Object.assign(existing, task);
changed += 1;
}
}
if (changed > 0) {
state.tasks.sort((left, right) => (timestampMs(left.createdAt) ?? 0) - (timestampMs(right.createdAt) ?? 0) || left.id.localeCompare(right.id));
updateNextSeqFromTasks();
}
return changed;
}
async function refreshSchedulerTasksFromDatabase(reason: string): Promise<number> {
if (!databaseReady || !config.schedulerEnabled) return 0;
const tasks = await loadTasksFromDatabase("hot");
const changed = mergeSchedulerDatabaseTasks(tasks);
if (changed > 0) {
logger("info", "scheduler_database_hot_tasks_refreshed", { reason, changed, loaded: tasks.length });
scheduleQueue();
}
return changed;
}
function startSchedulerDatabasePoller(): void {
if (!shouldPollSchedulerDatabase()) return;
const interval = setInterval(() => {
if (!serviceReady || shutdownRequested) return;
void refreshSchedulerTasksFromDatabase("poll").catch((error) => {
databaseLastError = databaseErrorMessage(error);
logger("warn", "scheduler_database_poll_failed", { error: errorToJson(error) });
});
}, config.schedulerPollIntervalMs);
interval.unref?.();
}
function activeRunForTask(task: QueueTask): ActiveRun | null {
const queueRun = activeRuns.get(queueIdOf(task));
if (queueRun?.taskId === task.id) return queueRun;
return Array.from(activeRuns.values()).find((run) => run.taskId === task.id) ?? null;
}
function installShutdownHandlers(): void {
const stop = (signal: NodeJS.Signals): void => {
if (shutdownRequested) process.exit(0);
shutdownRequested = true;
const recovered = queueActiveTasksForRestartRetry("Service stopping while task was active", "shutdown", { onlyActiveRuns: true });
for (const run of activeRuns.values()) run.app.stop();
activeRuns.clear();
activeRunSlotReservations.clear();
activeRunSlotWaiters.splice(0, activeRunSlotWaiters.length);
processingQueues.clear();
updateProcessingFlag();
persistState();
logger("warn", "service_shutdown_requeued_active_tasks", { signal, recovered });
const forceExit = setTimeout(() => process.exit(1), 8_000);
forceExit.unref?.();
void flushDirtyTasksToDatabase(true)
.then(() => process.exit(0))
.catch((error) => {
logger("error", "service_shutdown_database_flush_failed", { signal, error: errorToJson(error) });
process.exit(1);
});
};
process.once("SIGTERM", stop);
process.once("SIGINT", stop);
}
setInterval(() => {
if (!serviceReady) return;
const pendingQueues = runnableQueueIds().filter((queueId) => !processingQueues.has(queueId));
if (pendingQueues.length > 0) {
logger("warn", "queue_watchdog_rescheduled", { runnable: state.tasks.filter((task) => task.status === "queued" || task.status === "retry_wait").length, pendingQueues });
scheduleQueue();
}
}, 5000).unref?.();
function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body, null, 2), {
status,
headers: {
"content-type": "application/json; charset=utf-8",
"access-control-allow-origin": "*",
"access-control-allow-methods": "GET,HEAD,POST,PATCH,DELETE,OPTIONS",
"access-control-allow-headers": "content-type",
},
});
}
function compactJsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: {
"content-type": "application/json; charset=utf-8",
"access-control-allow-origin": "*",
"access-control-allow-methods": "GET,HEAD,POST,PATCH,DELETE,OPTIONS",
"access-control-allow-headers": "content-type",
},
});
}
async function readJson(req: Request): Promise<unknown> {
const text = await req.text();
if (text.trim().length === 0) return {};
return JSON.parse(text) as unknown;
}
function requestErrorResponse(error: unknown): Response | null {
if (error instanceof ReferenceTaskLookupError) {
return jsonResponse({
ok: false,
error: error.message,
missingReferenceTaskIds: error.missingIds,
}, 400);
}
if (error instanceof SyntaxError && /json/iu.test(error.message)) {
return jsonResponse({
ok: false,
error: "invalid JSON request body",
detail: error.message,
}, 400);
}
if (error instanceof Error && (
error.message === "request body must be an object"
|| error.message === "prompt is required"
|| error.message === "a task cannot reference itself while editing prompt"
|| error.message === "sourceQueueId is required"
|| error.message === "source queue must be different from target queue"
|| error.message === "workdir path is required"
|| error.message === "workdir path contains an invalid character"
|| error.message.startsWith("referenceTaskIds supports at most ")
|| error.message.startsWith("queueId must match ")
|| error.message.startsWith("queue name must be ")
|| error.message.startsWith("workdir path must be ")
|| error.message.startsWith("windows-native executionMode ")
)) {
return jsonResponse({ ok: false, error: error.message }, 400);
}
return null;
}
function readOnlyRejectResponse(method: string, targetPath: string): Response {
return jsonResponse({
ok: false,
error: "Code Queue read service is read-only",
serviceRole: config.serviceRole,
method,
path: targetPath,
}, 405);
}
function databaseNotReadyResponse(method: string, targetPath: string): Response {
return jsonResponse({
ok: false,
error: "Code Queue PostgreSQL storage is not ready",
serviceRole: config.serviceRole,
method,
path: targetPath,
databaseReady,
databaseLastError,
}, 503);
}
function requireDatabaseReadyForWrite(method: string, targetPath: string): Response | null {
return databaseReady ? null : databaseNotReadyResponse(method, targetPath);
}
function schedulerOnlyRejectResponse(method: string, targetPath: string): Response {
return jsonResponse({
ok: false,
error: "Code Queue write service does not own active scheduler control for this endpoint",
serviceRole: config.serviceRole,
method,
path: targetPath,
hint: "Route active-run control to the code-queue-scheduler service.",
}, 409);
}
function findTask(id: string): QueueTask | null {
return state.tasks.find((task) => task.id === id) ?? null;
}
function parseLimit(url: URL): number {
const value = Number(url.searchParams.get("limit") ?? 100);
return Number.isInteger(value) && value > 0 ? Math.min(500, value) : 100;
}
function workdirRowsForResponse(providerIdValue: string | null, executionModeValue: string | null): WorkdirRecord[] {
const providerId = normalizeProviderId(providerIdValue) ?? null;
const executionMode = executionModeValue === null ? null : normalizeCodeExecutionMode(executionModeValue);
return sortedWorkdirRecords().filter((record) => {
if (providerId !== null && record.providerId !== providerId) return false;
if (executionMode !== null && record.executionMode !== executionMode) return false;
return true;
});
}
async function listWorkdirs(url: URL): Promise<Response> {
ensureDefaultWorkdirRecords();
return jsonResponse({
ok: true,
workdirs: workdirRowsForResponse(url.searchParams.get("providerId"), url.searchParams.get("executionMode")),
defaultProviderId: config.mainProviderId,
defaultWorkdir: config.defaultWorkdir,
remoteDefaultWorkdir: config.remoteDefaultWorkdir,
windowsNativeCodexDefaultWorkdir: config.windowsNativeCodexDefaultWorkdir,
});
}
async function createWorkdir(req: Request): Promise<Response> {
const notReady = requireDatabaseReadyForWrite(req.method, "/api/workdirs");
if (notReady !== null) return notReady;
const body = await readJson(req);
const record = typeof body === "object" && body !== null && !Array.isArray(body) ? body as Record<string, unknown> : {};
const providerId = normalizeTaskProviderId(record.providerId);
const executionMode = normalizeCodeExecutionMode(record.executionMode);
const path = normalizeWorkdirPath(record.path ?? record.cwd ?? record.workdir, providerId);
validateExecutionModeForTask(providerId, path, config.defaultModel, executionMode);
const previous = workdirRecords.get(workdirRecordKey(providerId, executionMode, path));
let ensureResult: JsonValue = { ok: false, skipped: true, reason: "remote provider workdirs are created when a task starts" };
if (providerIsMain(providerId)) {
ensureResult = ensureLocalWorkdir(path) as unknown as JsonValue;
} else if (record.ensure === true || record.createOnProvider === true) {
const command = await runCodeQueueSsh(providerId, `set -euo pipefail\nmkdir -p ${shellQuote(path)}\ntest -d ${shellQuote(path)}\nprintf 'workdir_ready path=%s\\n' ${shellQuote(path)}`, 30_000, "workdir-create");
ensureResult = {
ok: command.exitCode === 0,
exitCode: command.exitCode,
stdout: safePreview(command.stdout, 800),
stderr: safePreview(command.stderr, 800),
durationMs: command.durationMs,
} as unknown as JsonValue;
if (command.exitCode !== 0) return jsonResponse({ ok: false, error: `failed to create workdir on provider ${providerId}`, ensure: ensureResult }, 502);
}
const workdir = rememberWorkdir(providerId, executionMode, path);
await upsertWorkdirsToDatabase([workdir]);
logger("info", "workdir_saved", { providerId, executionMode, path, existed: previous !== undefined, ensure: ensureResult });
return jsonResponse({ ok: true, workdir, workdirs: sortedWorkdirRecords(), ensure: ensureResult }, previous === undefined ? 201 : 200);
}
async function deleteWorkdir(providerIdValue: string, executionModeValue: string, pathValue: string): Promise<Response> {
const notReady = requireDatabaseReadyForWrite("DELETE", `/api/workdirs/${providerIdValue}/${executionModeValue}/${pathValue}`);
if (notReady !== null) return notReady;
const providerId = normalizeTaskProviderId(providerIdValue);
const executionMode = normalizeCodeExecutionMode(executionModeValue);
const path = normalizeWorkdirPath(pathValue, providerId);
const key = workdirRecordKey(providerId, executionMode, path);
const existing = workdirRecords.get(key) ?? null;
if (existing === null) return jsonResponse({ ok: false, error: "workdir not found" }, 404);
workdirRecords.delete(key);
if (databaseReady) {
await sql`
DELETE FROM unidesk_code_queue_workdirs
WHERE provider_id = ${providerId}
AND execution_mode = ${executionMode}
AND path = ${path}
`;
}
logger("info", "workdir_deleted", { providerId, executionMode, path });
return jsonResponse({ ok: true, deleted: existing, workdirs: sortedWorkdirRecords() });
}
async function createTasks(req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, "/api/tasks");
const notReady = requireDatabaseReadyForWrite(req.method, "/api/tasks");
if (notReady !== null) return notReady;
const body = await readJson(req);
const batchRecord = typeof body === "object" && body !== null && !Array.isArray(body) ? body as Record<string, unknown> : {};
const batchQueueId = typeof batchRecord.queueId === "string" && batchRecord.queueId.trim().length > 0 ? normalizeQueueId(batchRecord.queueId) : undefined;
const records = typeof body === "object" && body !== null && !Array.isArray(body) && Array.isArray((body as Record<string, unknown>).tasks)
? (body as Record<string, unknown>).tasks as unknown[]
: [body];
const tasks = await Promise.all(records.map(async (record) => {
const normalized = normalizeRequest(record);
if (normalized.queueId === undefined && batchQueueId !== undefined) normalized.queueId = batchQueueId;
return createTask(injectCodeQueueEnvironmentHint(await injectReferencedTaskContext(normalized)));
}));
for (const task of tasks) appendOutput(task, "user", `${task.prompt}\n`, "enqueue");
state.tasks.push(...tasks);
if (tasks.length > 0) armIdleNotification();
for (const task of tasks) publishTaskOaEvent(task, "enqueue");
for (const id of new Set(tasks.map(queueIdOf))) publishQueueEvent("enqueue", id);
persistState();
logger("info", "tasks_enqueued", { count: tasks.length, ids: tasks.map((task) => task.id), queueIds: Array.from(new Set(tasks.map(queueIdOf))), providerIds: Array.from(new Set(tasks.map((task) => task.providerId))), executionModes: Array.from(new Set(tasks.map((task) => task.executionMode))) });
scheduleQueue();
await flushDirtyTasksToDatabase(true);
await upsertWorkdirsToDatabase(sortedWorkdirRecords());
return jsonResponse({ ok: true, tasks: tasks.map((task) => taskForResponse(task)), queue: await queueSummaryForResponse() }, 202);
}
async function steerTask(task: QueueTask, req: Request): Promise<Response> {
if (!serviceRoleAllowsScheduler(config.serviceRole)) return schedulerOnlyRejectResponse(req.method, `/api/tasks/${task.id}/steer`);
const body = await readJson(req);
const prompt = typeof (body as Record<string, unknown>).prompt === "string" ? String((body as Record<string, unknown>).prompt) : "";
if (prompt.trim().length === 0) return jsonResponse({ ok: false, error: "prompt is required" }, 400);
const activeRun = activeRunForTask(task);
if (activeRun === null || activeRun.threadId === null || activeRun.turnId === null || typeof activeRun.app.steer !== "function") {
return jsonResponse({ ok: false, error: "task does not have an active steerable turn", task: taskForResponse(task) }, 409);
}
const output = appendOutput(task, "user", `\n[steer] ${prompt}\n`, "turn/steer");
appendPromptHistory(task, output, "turn/steer", prompt);
await activeRun.app.steer(activeRun.threadId, activeRun.turnId, prompt);
await flushDirtyTasksToDatabase(true);
return jsonResponse({ ok: true, task: taskForResponse(task), queue: await queueSummaryForResponse() });
}
async function editQueuedTaskPrompt(task: QueueTask, req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/edit`);
const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/edit`);
if (notReady !== null) return notReady;
if (!queuedTaskPromptEditable(task)) {
return jsonResponse({
ok: false,
error: `task prompt can only be edited before first run while status=queued; current status=${task.status}`,
editable: false,
task: taskForResponse(task),
}, 409);
}
let update: QueueTaskRequest;
try {
update = await buildQueuedPromptUpdate(task, await readJson(req));
} catch (error) {
return jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 400);
}
const nextBasePrompt = update.basePrompt ?? userPromptForDisplay(update.prompt);
const nextReferenceTaskIds = update.referenceTaskIds ?? [];
if (task.prompt === update.prompt && task.basePrompt === nextBasePrompt && taskReferencesEqual(task.referenceTaskIds, nextReferenceTaskIds)) {
return jsonResponse({ ok: true, changed: false, editable: true, task: taskForResponse(task), queue: await queueSummaryForResponse(false) });
}
const previousPromptChars = task.prompt.length;
const previousBasePromptChars = task.basePrompt.length;
task.prompt = update.prompt;
task.basePrompt = nextBasePrompt;
task.referenceTaskIds = nextReferenceTaskIds;
task.referenceInjection = update.referenceInjection ?? null;
task.updatedAt = nowIso();
rewriteEnqueueOutput(task);
appendOutput(task, "system", `queued prompt edited before first run; base ${previousBasePromptChars}->${task.basePrompt.length} chars; final ${previousPromptChars}->${task.prompt.length} chars\n`, "prompt/edit");
persistState();
logger("info", "queued_task_prompt_edited", {
taskId: task.id,
queueId: queueIdOf(task),
promptChars: task.prompt.length,
basePromptChars: task.basePrompt.length,
referenceTaskIds: task.referenceTaskIds,
});
scheduleQueue(queueIdOf(task));
await flushDirtyTasksToDatabase(true);
return jsonResponse({ ok: true, changed: true, editable: true, task: taskForResponse(task), queue: await queueSummaryForResponse() });
}
async function interruptTask(task: QueueTask, method = "POST"): Promise<Response> {
if (!serviceRoleAllowsScheduler(config.serviceRole)) return schedulerOnlyRejectResponse(method, `/api/tasks/${task.id}/interrupt`);
if (task.status === "succeeded" || task.status === "failed" || task.status === "canceled") {
return jsonResponse({ ok: false, error: `task is already terminal: ${task.status}`, task: taskForResponse(task) }, 409);
}
task.cancelRequested = true;
task.updatedAt = nowIso();
appendOutput(task, "system", "interrupt requested\n", "turn/interrupt");
const activeRun = activeRunForTask(task);
if (activeRun !== null) {
if (activeRun.threadId !== null && activeRun.turnId !== null && typeof activeRun.app.interrupt === "function") {
await activeRun.app.interrupt(activeRun.threadId, activeRun.turnId).catch((error) => {
appendOutput(task, "error", `interrupt request failed: ${error instanceof Error ? error.message : String(error)}\n`, "turn/interrupt");
});
} else {
activeRun.app.stop();
}
}
if (task.status === "queued" || task.status === "retry_wait") {
task.status = "canceled";
task.finishedAt = nowIso();
}
persistTaskState(task);
if (terminalTask(task)) {
void notifyTaskTerminal(task).then(() => maybeNotifyQueueIdle(task.id)).catch((error) => logger("warn", "claudeqq_interrupt_notify_failed", { taskId: task.id, error: errorToJson(error) }));
}
await flushDirtyTasksToDatabase(true);
return jsonResponse({ ok: true, task: taskForResponse(task), queue: await queueSummaryForResponse() });
}
async function manualRetry(task: QueueTask, req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/retry`);
const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/retry`);
if (notReady !== null) return notReady;
if (task.status !== "failed" && task.status !== "canceled" && task.status !== "succeeded") {
return jsonResponse({ ok: false, error: `task is not terminal: ${task.status}`, task: taskForResponse(task) }, 409);
}
const body = await readJson(req);
const record = extractRecord(body) ?? {};
const explicitPrompt = typeof record.prompt === "string" ? record.prompt.trim() : typeof record.continuePrompt === "string" ? record.continuePrompt.trim() : "";
task.status = "queued";
task.finishedAt = null;
task.readAt = null;
task.cancelRequested = false;
task.lastError = null;
task.maxAttempts = Math.max(task.maxAttempts, task.attempts.length + 1);
task.nextMode = "retry";
task.nextPrompt = explicitPrompt.length > 0 ? explicitPrompt : retryPrompt(task, { decision: "retry", confidence: 1, reason: "Manual retry", source: "fallback" });
setAttemptFeedbackPrompt(task.attempts.at(-1), task.nextPrompt, explicitPrompt.length > 0 ? "manual-retry-explicit" : "manual-retry-generated", task.attempts.length + 1);
task.updatedAt = nowIso();
task.queueEnteredAt = task.updatedAt;
appendOutput(task, "system", explicitPrompt.length > 0 ? "manual retry queued with explicit continuation prompt\n" : "manual retry queued\n", "manual-retry");
armIdleNotification();
persistState();
scheduleQueue(queueIdOf(task));
await flushDirtyTasksToDatabase(true);
return jsonResponse({ ok: true, task: taskForResponse(task), queue: await queueSummaryForResponse() }, 202);
}
async function markTaskRead(task: QueueTask): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse("POST", `/api/tasks/${task.id}/read`);
const notReady = requireDatabaseReadyForWrite("POST", `/api/tasks/${task.id}/read`);
if (notReady !== null) return notReady;
if (!terminalTask(task)) {
return jsonResponse({ ok: false, error: `task is not terminal: ${task.status}`, task: taskForResponse(task) }, 409);
}
if (task.readAt === null) {
task.readAt = nowIso();
markTaskDirty(task.id);
persistState(false);
publishTaskOaEvent(task, "read");
logger("info", "task_marked_read", { taskId: task.id, queueId: queueIdOf(task), status: task.status });
}
await flushDirtyTasksToDatabase(true);
return compactJsonResponse({ ok: true, task: taskForListResponse(task, true), queue: await queueSummaryForResponse(false) });
}
function timestampToIso(value: Date | string | null): string | null {
if (value === null) return null;
const date = value instanceof Date ? value : new Date(value);
return Number.isNaN(date.getTime()) ? String(value) : date.toISOString();
}
async function markTaskReadById(taskId: string): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse("POST", `/api/tasks/${taskId}/read`);
const notReady = requireDatabaseReadyForWrite("POST", `/api/tasks/${taskId}/read`);
if (notReady !== null) return notReady;
if (!databaseReady) {
const task = await findTaskForMutation(taskId);
return task === null ? jsonResponse({ ok: false, error: "task not found" }, 404) : markTaskRead(task);
}
const rows = await sql<Array<{ id: string; queue_id: string; status: TaskStatus; read_at: Date | string | null }>>`
SELECT id, queue_id, status, read_at
FROM unidesk_code_queue_tasks
WHERE id = ${taskId}
LIMIT 1
`;
const row = rows[0] ?? null;
if (row === null) {
const task = findTask(taskId);
return task === null ? jsonResponse({ ok: false, error: "task not found" }, 404) : markTaskRead(task);
}
if (!(row.status === "succeeded" || row.status === "failed" || row.status === "canceled")) {
return compactJsonResponse({ ok: false, error: `task is not terminal: ${row.status}`, task: { id: taskId, queueId: safeQueueId(row.queue_id), status: row.status } }, 409);
}
const readAt = timestampToIso(row.read_at) ?? nowIso();
if (row.read_at === null) {
await sql`
UPDATE unidesk_code_queue_tasks
SET
read_at = ${readAt},
task_json = jsonb_set(task_json, '{readAt}', to_jsonb(${readAt}::text), true)
WHERE id = ${taskId}
AND read_at IS NULL
`;
const hotTask = findTask(taskId);
if (hotTask !== null) {
hotTask.readAt = readAt;
publishTaskOaEvent(hotTask, "read");
}
logger("info", "task_marked_read", { taskId, queueId: safeQueueId(row.queue_id), status: row.status });
}
return compactJsonResponse({
ok: true,
task: {
id: taskId,
queueId: safeQueueId(row.queue_id),
status: row.status,
readAt,
terminalUnread: false,
},
queue: await queueSummaryForResponse(false),
});
}
async function markTerminalTasksRead(url: URL): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse("POST", "/api/tasks/read-all");
const notReady = requireDatabaseReadyForWrite("POST", "/api/tasks/read-all");
if (notReady !== null) return notReady;
const queueFilter = url.searchParams.get("queueId");
const queueId = queueFilter === null || queueFilter.length === 0 ? null : safeQueueId(queueFilter);
const readAt = nowIso();
if (databaseReady) {
const rows = queueId === null
? await sql<Array<{ id: string }>>`
UPDATE unidesk_code_queue_tasks
SET
read_at = ${readAt},
task_json = jsonb_set(task_json, '{readAt}', to_jsonb(${readAt}::text), true)
WHERE status IN ('succeeded', 'failed', 'canceled')
AND read_at IS NULL
RETURNING id
`
: await sql<Array<{ id: string }>>`
UPDATE unidesk_code_queue_tasks
SET
read_at = ${readAt},
task_json = jsonb_set(task_json, '{readAt}', to_jsonb(${readAt}::text), true)
WHERE queue_id = ${queueId}
AND status IN ('succeeded', 'failed', 'canceled')
AND read_at IS NULL
RETURNING id
`;
const ids = new Set(rows.map((row) => row.id));
for (const task of state.tasks) {
if (!ids.has(task.id)) continue;
task.readAt = readAt;
publishTaskOaEvent(task, "read-all");
}
if (ids.size > 0) {
logger("info", "terminal_tasks_marked_read", { count: ids.size, queueId });
}
return jsonResponse({ ok: true, count: ids.size, readAt, queue: await queueSummaryForResponse(false) });
}
let count = 0;
for (const task of state.tasks) {
if (queueId !== null && queueIdOf(task) !== queueId) continue;
if (!terminalTaskUnread(task)) continue;
task.readAt = readAt;
markTaskDirty(task.id);
publishTaskOaEvent(task, "read-all");
count += 1;
}
if (count > 0) {
persistState(false);
logger("info", "terminal_tasks_marked_read", { count, queueId });
}
await flushDirtyTasksToDatabase(true);
return jsonResponse({ ok: true, count, readAt, queue: await queueSummaryForResponse(false) });
}
async function createQueue(req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, "/api/queues");
const notReady = requireDatabaseReadyForWrite(req.method, "/api/queues");
if (notReady !== null) return notReady;
const body = await readJson(req);
const record = typeof body === "object" && body !== null && !Array.isArray(body) ? body as Record<string, unknown> : {};
const queueId = normalizeQueueId(record.queueId ?? record.id);
const beforeCount = state.queues.length;
const queue = ensureQueue(queueId, record.name);
queue.updatedAt = nowIso();
markQueueDirty(queue.id);
persistState(false);
publishQueueEvent("queue-created", queue.id);
logger("info", "queue_created", { queueId, name: queue.name, existed: beforeCount === state.queues.length });
await flushDirtyTasksToDatabase(true);
const tasks = await loadAllTasksForRead();
return jsonResponse({ ok: true, queue, queues: perQueueSummaries(tasks), summary: queueSummary(false, tasks) }, beforeCount === state.queues.length ? 200 : 201);
}
async function updateQueue(queueIdValue: string, req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/queues/${queueIdValue}`);
const notReady = requireDatabaseReadyForWrite(req.method, `/api/queues/${queueIdValue}`);
if (notReady !== null) return notReady;
const queueId = normalizeQueueId(queueIdValue);
const queue = state.queues.find((item) => item.id === queueId);
if (queue === undefined) return jsonResponse({ ok: false, error: "queue not found" }, 404);
const body = await readJson(req);
const record = typeof body === "object" && body !== null && !Array.isArray(body) ? body as Record<string, unknown> : {};
if (!Object.prototype.hasOwnProperty.call(record, "name")) {
return jsonResponse({ ok: false, error: "name is required" }, 400);
}
const previousName = safeQueueName(queue.name, queue.id);
queue.name = normalizeQueueName(record.name, queue.id);
queue.updatedAt = nowIso();
markQueueDirty(queue.id);
persistState(false);
publishQueueEvent("queue-updated", queue.id);
logger("info", "queue_updated", { queueId, previousName, name: queue.name });
await flushDirtyTasksToDatabase(true);
const tasks = await loadAllTasksForRead();
return jsonResponse({ ok: true, queue, queues: perQueueSummaries(tasks), summary: queueSummary(false, tasks) });
}
function knownQueue(queueId: string): QueueRecord | null {
return state.queues.find((queue) => queue.id === queueId) ?? null;
}
function queueHasKnownTasks(queueId: string): boolean {
return state.tasks.some((task) => queueIdOf(task) === queueId);
}
function queueMergeBlocker(queueId: string): string | null {
if (processingQueues.has(queueId)) return "queue processor is currently active";
if (activeRuns.has(queueId)) return "queue has an active agent run";
if (activeRunSlotReservations.has(queueId)) return "queue is reserving an active run slot";
if (activeRunSlotWaiters.some((waiter) => waiter.queueId === queueId)) return "queue is waiting for an active run slot";
const activeTask = state.tasks.find((task) => queueIdOf(task) === queueId && (task.status === "running" || task.status === "judging"));
if (activeTask !== undefined) return `task ${activeTask.id} is ${activeTask.status}`;
const claimedPendingTask = state.tasks.find((task) => queueIdOf(task) === queueId && (task.status === "queued" || task.status === "retry_wait") && !taskIsUnclaimedMovable(task));
return claimedPendingTask === undefined ? null : `task ${claimedPendingTask.id} has already been claimed`;
}
function parseSourceQueueIds(record: Record<string, unknown>, targetQueueId: string): string[] {
const raw = record.sourceQueueIds ?? record.sources ?? record.sourceQueueId ?? record.fromQueueId ?? record.from ?? record.queueId ?? record.id;
const values = Array.isArray(raw)
? raw
: typeof raw === "string"
? raw.split(/[,\s]+/u)
: [];
const ids: string[] = [];
const seen = new Set<string>();
for (const value of values) {
const id = normalizeQueueId(value);
if (id === targetQueueId) throw new Error("source queue must be different from target queue");
if (seen.has(id)) continue;
seen.add(id);
ids.push(id);
}
if (ids.length === 0) throw new Error("sourceQueueId is required");
return ids;
}
async function mergeDatabaseQueueTasks(sourceQueueIds: string[], targetQueueId: string, mergedAt: string): Promise<{ movedTaskIds: string[]; blocker: DatabaseTaskStatusRow | null }> {
if (!databaseReady || sourceQueueIds.length === 0) return { movedTaskIds: [], blocker: null };
return await sql.begin(async (client) => {
const mergeQueueIds = Array.from(new Set([targetQueueId, ...sourceQueueIds]));
const lockedRows = await client<DatabaseTaskStatusRow[]>`
SELECT id, queue_id, status, started_at, current_attempt, codex_thread_id, active_turn_id
FROM unidesk_code_queue_tasks
WHERE queue_id IN ${client(mergeQueueIds)}
ORDER BY updated_at DESC, id DESC
FOR UPDATE
`;
const blocker = lockedRows.find((row) => {
return row.status === "running"
|| row.status === "judging"
|| (
(row.status === "queued" || row.status === "retry_wait")
&& (
row.started_at !== null
|| Number(row.current_attempt ?? 0) > 0
|| row.codex_thread_id !== null
|| row.active_turn_id !== null
)
);
}) ?? null;
if (blocker !== null) return { movedTaskIds: [], blocker };
const rows = await client<Array<{ id: string }>>`
UPDATE unidesk_code_queue_tasks
SET
queue_id = ${targetQueueId},
updated_at = ${mergedAt},
task_json = jsonb_set(
jsonb_set(
jsonb_set(
task_json,
'{queueId}',
to_jsonb(${targetQueueId}::text),
true
),
'{queueEnteredAt}',
to_jsonb(COALESCE(NULLIF(task_json->>'queueEnteredAt', ''), to_char(created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'))::text),
true
),
'{updatedAt}',
to_jsonb(${mergedAt}::text),
true
)
WHERE queue_id IN ${client(sourceQueueIds)}
AND (
status IN ('succeeded', 'failed', 'canceled')
OR (
status IN ('queued', 'retry_wait')
AND started_at IS NULL
AND current_attempt = 0
AND codex_thread_id IS NULL
AND active_turn_id IS NULL
)
)
RETURNING id
`;
return { movedTaskIds: rows.map((row) => row.id), blocker: null };
});
}
async function moveDatabaseTaskToQueue(taskId: string, targetQueueId: string, movedAt: string): Promise<{ ok: boolean; row: DatabaseTaskStatusRow | null; previousQueueId: string | null; blocker: string }> {
if (!databaseReady) return { ok: true, row: null, previousQueueId: null, blocker: "" };
return await sql.begin(async (client) => {
const rows = await client<DatabaseTaskStatusRow[]>`
SELECT id, queue_id, status, started_at, current_attempt, codex_thread_id, active_turn_id
FROM unidesk_code_queue_tasks
WHERE id = ${taskId}
LIMIT 1
FOR UPDATE
`;
const row = rows[0] ?? null;
const blocker = databaseTaskMoveBlocker(row);
if (blocker.length > 0) return { ok: false, row, previousQueueId: row === null ? null : safeQueueId(row.queue_id), blocker };
const previousQueueId = safeQueueId(row?.queue_id);
const updated = await client<DatabaseTaskStatusRow[]>`
UPDATE unidesk_code_queue_tasks
SET
queue_id = ${targetQueueId},
updated_at = ${movedAt},
task_json = jsonb_set(
jsonb_set(
jsonb_set(
task_json,
'{queueId}',
to_jsonb(${targetQueueId}::text),
true
),
'{queueEnteredAt}',
to_jsonb(${movedAt}::text),
true
),
'{updatedAt}',
to_jsonb(${movedAt}::text),
true
)
WHERE id = ${taskId}
AND status IN ('queued', 'retry_wait')
AND started_at IS NULL
AND current_attempt = 0
AND codex_thread_id IS NULL
AND active_turn_id IS NULL
RETURNING id, queue_id, status, started_at, current_attempt, codex_thread_id, active_turn_id
`;
const updatedRow = updated[0] ?? null;
return updatedRow === null
? { ok: false, row, previousQueueId, blocker: "conditional update matched no rows" }
: { ok: true, row: updatedRow, previousQueueId, blocker: "" };
});
}
async function deleteDatabaseQueues(queueIds: string[]): Promise<string[]> {
if (!databaseReady || queueIds.length === 0) return [];
const rows = await sql<Array<{ id: string }>>`
DELETE FROM unidesk_code_queue_queues
WHERE id IN ${sql(queueIds)}
RETURNING id
`;
return rows.map((row) => row.id);
}
function queueSnapshot(queueId: string, timestamp: string): QueueRecord {
const queue = knownQueue(queueId);
return queue === null
? { id: queueId, name: queueId, createdAt: timestamp, updatedAt: timestamp }
: { ...queue };
}
function deleteQueuesFromState(queueIds: string[]): QueueRecord[] {
const queueIdSet = new Set(queueIds);
const deletedQueues: QueueRecord[] = [];
const retainedQueues: QueueRecord[] = [];
for (const queue of state.queues) {
if (queueIdSet.has(queue.id)) {
deletedQueues.push({ ...queue });
dirtyDatabaseQueueIds.delete(queue.id);
} else {
retainedQueues.push(queue);
}
}
state.queues.splice(0, state.queues.length, ...retainedQueues);
for (const queueId of queueIds) dirtyDatabaseQueueIds.delete(queueId);
return deletedQueues;
}
async function mergeQueues(targetQueueIdValue: string | null, req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, targetQueueIdValue === null ? "/api/queues/merge" : `/api/queues/${targetQueueIdValue}/merge`);
const mergePath = targetQueueIdValue === null ? "/api/queues/merge" : `/api/queues/${targetQueueIdValue}/merge`;
const notReady = requireDatabaseReadyForWrite(req.method, mergePath);
if (notReady !== null) return notReady;
const body = await readJson(req);
const record = typeof body === "object" && body !== null && !Array.isArray(body) ? body as Record<string, unknown> : {};
const targetQueueId = normalizeQueueId(targetQueueIdValue ?? record.targetQueueId ?? record.intoQueueId ?? record.into);
const sourceQueueIds = parseSourceQueueIds(record, targetQueueId);
const missingSources = sourceQueueIds.filter((id) => knownQueue(id) === null && !queueHasKnownTasks(id));
if (missingSources.length > 0) return jsonResponse({ ok: false, error: `source queue not found: ${missingSources.join(", ")}` }, 404);
const mergeQueueIds = Array.from(new Set([targetQueueId, ...sourceQueueIds]));
for (const id of mergeQueueIds) mergingQueues.add(id);
try {
for (const id of mergeQueueIds) {
const blocker = queueMergeBlocker(id);
if (blocker !== null) {
return jsonResponse({ ok: false, error: `cannot merge queue ${id}: ${blocker}` }, 409);
}
}
const mergedAt = nowIso();
const databaseMerge = await mergeDatabaseQueueTasks(sourceQueueIds, targetQueueId, mergedAt);
if (databaseMerge.blocker !== null) {
const blockerQueueId = safeQueueId(databaseMerge.blocker.queue_id);
const databaseTask = await loadTaskFromDatabase(databaseMerge.blocker.id);
if (databaseTask !== null) reconcileHotTaskFromDatabase(databaseTask);
return jsonResponse({
ok: false,
error: `cannot merge queue ${blockerQueueId}: task ${databaseMerge.blocker.id} is already claimed (${databaseTaskMoveBlocker(databaseMerge.blocker) || databaseMerge.blocker.status})`,
blocker: databaseStatusRowJson(databaseMerge.blocker),
}, 409);
}
const targetQueue = ensureQueue(targetQueueId);
const sourceQueues = sourceQueueIds.map((id) => queueSnapshot(id, mergedAt));
targetQueue.updatedAt = mergedAt;
markQueueDirty(targetQueue.id);
const sourceSet = new Set(sourceQueueIds);
const hotMovedTasks: QueueTask[] = [];
for (const task of state.tasks) {
const previousQueueId = queueIdOf(task);
if (!sourceSet.has(previousQueueId)) continue;
task.queueEnteredAt = taskQueueEnteredAt(task);
task.queueId = targetQueueId;
task.updatedAt = mergedAt;
hotMovedTasks.push(task);
markTaskDirty(task.id);
publishTaskOaEvent(task, "queue-merged");
}
const deletedSourceQueues = deleteQueuesFromState(sourceQueueIds);
const databaseDeletedQueueIds = await deleteDatabaseQueues(sourceQueueIds);
persistState(false);
publishQueueEvent("queue-merged", targetQueueId);
for (const sourceQueueId of sourceQueueIds) publishQueueEvent("queue-deleted-after-merge", sourceQueueId);
if (hotMovedTasks.some((task) => task.status === "queued" || task.status === "retry_wait")) armIdleNotification();
logger("info", "queues_merged", {
targetQueueId,
sourceQueueIds,
deletedSourceQueueIds: deletedSourceQueues.map((queue) => queue.id),
hotMovedTaskCount: hotMovedTasks.length,
databaseMovedTaskCount: databaseReady ? databaseMerge.movedTaskIds.length : null,
databaseDeletedQueueIds: databaseReady ? databaseDeletedQueueIds : null,
});
for (const id of mergeQueueIds) mergingQueues.delete(id);
scheduleQueue(targetQueueId);
await flushDirtyTasksToDatabase(true);
const tasks = await loadAllTasksForRead();
const movedIdSet = new Set(databaseReady ? databaseMerge.movedTaskIds : hotMovedTasks.map((task) => task.id));
const orderedMovedTaskIds = tasks
.filter((task) => movedIdSet.has(task.id))
.sort(compareTaskQueueOrder)
.map((task) => task.id);
const targetTaskOrder = tasks
.filter((task) => queueIdOf(task) === targetQueueId)
.sort(compareTaskQueueOrder)
.map((task) => ({ id: task.id, queueEnteredAt: taskQueueEnteredAt(task), createdAt: task.createdAt, status: task.status }));
return jsonResponse({
ok: true,
targetQueueId,
sourceQueueIds,
mergedTaskCount: databaseReady ? databaseMerge.movedTaskIds.length : hotMovedTasks.length,
movedTaskIds: orderedMovedTaskIds.slice(0, 500),
targetTaskOrder: targetTaskOrder.slice(0, 500),
order: "merged tasks keep their original queueEnteredAt/createdAt ordering; source queue records are deleted after merge",
targetQueue,
sourceQueues,
deletedSourceQueues: deletedSourceQueues.length > 0 ? deletedSourceQueues : sourceQueues,
deletedSourceQueueIds: sourceQueueIds,
queues: perQueueSummaries(tasks),
summary: queueSummary(false, tasks),
}, 202);
} finally {
for (const id of mergeQueueIds) mergingQueues.delete(id);
}
}
async function moveTaskToQueue(task: QueueTask, req: Request, options: { bypassRoleCheck?: boolean } = {}): Promise<Response> {
if (options.bypassRoleCheck !== true && !serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/move`);
const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/move`);
if (notReady !== null) return notReady;
const body = await readJson(req);
const record = typeof body === "object" && body !== null && !Array.isArray(body) ? body as Record<string, unknown> : {};
const queueId = normalizeQueueId(record.queueId ?? record.id);
const movedAt = nowIso();
const hotBlocker = taskMoveBlocker(task);
if (hotBlocker.length > 0) {
const databaseTask = databaseReady ? await loadTaskFromDatabase(task.id) : null;
if (databaseTask !== null) task = reconcileHotTaskFromDatabase(databaseTask);
return jsonResponse({
ok: false,
error: `cannot move task ${task.id}: ${hotBlocker}`,
task: taskForResponse(task),
databaseTask: databaseTask === null ? null : taskForResponse(databaseTask),
}, 409);
}
const databaseMove = await moveDatabaseTaskToQueue(task.id, queueId, movedAt);
if (!databaseMove.ok) {
const databaseTask = databaseReady ? await loadTaskFromDatabase(task.id) : null;
if (databaseTask !== null) task = reconcileHotTaskFromDatabase(databaseTask);
return jsonResponse({
ok: false,
error: `cannot move task ${task.id}: ${databaseMove.blocker}`,
blocker: databaseStatusRowJson(databaseMove.row),
task: taskForResponse(task),
databaseTask: databaseTask === null ? null : taskForResponse(databaseTask),
}, databaseMove.row === null ? 404 : 409);
}
const previousQueueId = databaseMove.previousQueueId ?? queueIdOf(task);
const queue = ensureQueue(queueId);
queue.updatedAt = movedAt;
markQueueDirty(queue.id);
task.queueId = queueId;
task.queueEnteredAt = movedAt;
task.updatedAt = movedAt;
appendOutput(task, "system", `moved from queue=${previousQueueId} to queue=${queueId}\n`, "queue/move");
if (task.status === "queued" || task.status === "retry_wait") armIdleNotification();
persistState();
publishQueueEvent("task-moved", previousQueueId);
publishQueueEvent("task-moved", queueId);
logger("info", "task_moved_queue", { taskId: task.id, previousQueueId, queueId, status: task.status });
if (task.status === "queued" || task.status === "retry_wait") scheduleQueue(queueId);
await flushDirtyTasksToDatabase(true);
return jsonResponse({ ok: true, task: taskForResponse(task), queue: await queueSummaryForResponse() }, 202);
}
async function backfillOaTraceStats(url: URL): Promise<JsonValue> {
const limitRaw = Number(url.searchParams.get("limit") ?? 2000);
const limit = Number.isInteger(limitRaw) && limitRaw > 0 ? Math.min(20_000, limitRaw) : 2000;
const taskId = url.searchParams.get("taskId")?.trim() ?? "";
const tasks = taskId.length > 0
? [await findTaskForRead(taskId)].filter((task): task is QueueTask => task !== null)
: (await loadAllTasksForRead()).slice(-limit);
const includeSteps = url.searchParams.get("steps") !== "0";
let stepEventCount = 0;
for (const task of tasks) {
const queueId = queueIdOf(task);
const outputMaxSeq = taskOutputMaxSeq(task);
const output = taskFullOutput(task);
const traceStats = traceStatsFromOutputs(output);
const attemptBySeq = outputAttemptIndexMap(output);
if (includeSteps) {
for (const item of output) {
const projectionOutput = traceStepOutputForProjection(task, item);
if (outputStartsTraceStepInHistory(output, item)) {
publishCodeQueueTraceStep(task, queueId, projectionOutput, outputMaxSeq, attemptBySeq.get(item.seq) ?? null);
stepEventCount += 1;
} else if (outputUpdatesExistingTraceStep(item)) {
publishCodeQueueTraceStep(task, queueId, projectionOutput, outputMaxSeq, attemptBySeq.get(projectionOutput.seq) ?? attemptBySeq.get(item.seq) ?? null, String(item.text || "").length);
stepEventCount += 1;
}
}
}
publishCodeQueueTraceStatsSnapshot(task, queueId, "backfill", traceStats.stepCount, outputMaxSeq, traceStats);
}
logger("info", "oa_trace_stats_backfill_enqueued", { taskCount: tasks.length, limit, taskId: taskId || null, includeSteps, stepEventCount });
return { ok: true, taskCount: tasks.length, limit, taskId: taskId || null, includeSteps, stepEventCount, eventFlowBaseUrl: config.oaEventFlowBaseUrl } as unknown as JsonValue;
}
async function route(req: Request): Promise<Response> {
const url = new URL(req.url);
if (req.method === "OPTIONS") return jsonResponse({ ok: true });
try {
if (url.pathname === "/live") return jsonResponse({
ok: true,
service: "code-queue",
instanceId: config.instanceId,
role: config.serviceRole,
deploy: {
commit: config.deployCommit,
requestedCommit: config.deployRequestedCommit,
},
databaseReady,
serviceReady,
startedAt: serviceStartedAt,
});
if (url.pathname === "/" || url.pathname === "/health") {
if (!databaseReady) return jsonResponse({
ok: false,
service: "code-queue",
instanceId: config.instanceId,
role: config.serviceRole,
deploy: {
commit: config.deployCommit,
requestedCommit: config.deployRequestedCommit,
},
status: "starting",
databaseReady,
databaseLastError,
startedAt: serviceStartedAt,
}, 503);
return jsonResponse({
ok: true,
service: "code-queue",
instanceId: config.instanceId,
role: config.serviceRole,
deploy: {
commit: config.deployCommit,
requestedCommit: config.deployRequestedCommit,
},
schedulerEnabled: config.schedulerEnabled,
schedulerPollIntervalMs: config.schedulerPollIntervalMs,
queue: queueSummary(false, state.tasks),
egressProxy: await providerGatewayEgressProxyStatus(),
oaEventPublisher: oaEventPublisherStatus(),
startedAt: serviceStartedAt,
});
}
if (url.pathname === "/logs") return jsonResponse({ ok: true, logs: recentLogs.slice(-parseLimit(url)) });
if (url.pathname === "/api/events" && req.method === "GET") return jsonResponse({ ok: false, error: "Code Queue private SSE was removed; subscribe to oa-event-flow /api/events/stream with service:code-queue tags." }, 410);
if (url.pathname === "/api/dev-ready" && req.method === "GET") return jsonResponse({ ok: true, devReady: collectDevReady() });
if (url.pathname === "/api/dev-containers" && req.method === "GET") {
const plan = buildDevContainerPlan(normalizeProviderId(config.devContainerDefaultProviderId) ?? "D601", {});
return jsonResponse({
ok: true,
defaultProviderId: plan.providerId,
startEndpoint: `/api/dev-containers/${encodeURIComponent(plan.providerId)}/start`,
statusEndpoint: `/api/dev-containers/${encodeURIComponent(plan.providerId)}/status`,
masterProxyMode: "ssh-tun-nat-sealed",
defaultPlan: {
containerName: plan.containerName,
image: plan.image,
workdir: plan.workdir,
masterHost: plan.masterHost,
tunName: plan.tunName,
serverIp: plan.serverIp,
clientIp: plan.clientIp,
natChain: plan.natChain,
egressFirewallChain: plan.egressFirewallChain,
},
});
}
const devContainerStartMatch = url.pathname.match(/^\/api\/dev-containers(?:\/([^/]+))?\/start$/u);
if (devContainerStartMatch !== null && req.method === "POST") return await startDevContainer(req, devContainerStartMatch[1] === undefined ? null : decodeURIComponent(devContainerStartMatch[1]));
const devContainerStatusMatch = url.pathname.match(/^\/api\/dev-containers(?:\/([^/]+))?\/status$/u);
if (devContainerStatusMatch !== null && req.method === "GET") return await devContainerStatus(devContainerStatusMatch[1] === undefined ? null : decodeURIComponent(devContainerStatusMatch[1]));
if (url.pathname === "/api/judge/probe" && (req.method === "GET" || req.method === "POST")) return await runJudgeProbe();
if (url.pathname === "/api/judge/self-test" && (req.method === "GET" || req.method === "POST")) return jsonResponse(runJudgeInfraSelfTest());
if (url.pathname === "/api/queue-order/self-test" && (req.method === "GET" || req.method === "POST")) return jsonResponse(runQueueOrderingSelfTest());
if (url.pathname === "/api/queue-claim-move/self-test" && (req.method === "GET" || req.method === "POST")) return jsonResponse(await runQueueClaimMoveSelfTest());
if (url.pathname === "/api/reference-injection/self-test" && (req.method === "GET" || req.method === "POST")) return jsonResponse(await runReferenceInjectionSelfTest());
if (url.pathname === "/api/trace-port/self-test" && (req.method === "GET" || req.method === "POST")) return jsonResponse(runTracePortSelfTest());
if (url.pathname === "/api/oa/backfill" && (req.method === "GET" || req.method === "POST")) return jsonResponse(await backfillOaTraceStats(url));
if (url.pathname === "/api/notifications/claudeqq" && req.method === "GET") {
await loadClaudeQqNotificationOutboxFromDatabase();
const limit = parseLimit(url);
return jsonResponse({ ok: true, stats: claudeQqNotificationOutboxStats(), items: claudeQqNotificationItems(limit) });
}
if (url.pathname === "/api/notifications/claudeqq/drain" && req.method === "POST") {
return jsonResponse(await drainClaudeQqNotificationOutbox("api"));
}
if (url.pathname === "/api/notifications/claudeqq/backfill" && req.method === "POST") {
const body = await readJson(req);
const record = typeof body === "object" && body !== null && !Array.isArray(body) ? body as Record<string, unknown> : {};
const since = typeof record.since === "string" ? record.since : url.searchParams.get("since");
const dryRun = record.dryRun === true || url.searchParams.get("dryRun") === "1";
const rawLimit = Number(record.limit ?? url.searchParams.get("limit") ?? 100);
const limit = Number.isInteger(rawLimit) && rawLimit > 0 ? Math.min(500, rawLimit) : 100;
return jsonResponse(await backfillClaudeQqTaskNotifications(since, limit, dryRun));
}
if (url.pathname === "/api/queues" && req.method === "GET") {
const tasks = await loadAllTasksForRead();
const queueRecords = await loadQueuesFromDatabase();
return jsonResponse({ ok: true, queues: perQueueSummaries(tasks, queueRecords), queue: await queueSummaryForResponse(false, tasks, queueRecords) });
}
if (url.pathname === "/api/workdirs" && req.method === "GET") return await listWorkdirs(url);
if (url.pathname === "/api/workdirs" && req.method === "POST") return await createWorkdir(req);
const workdirMatch = url.pathname.match(/^\/api\/workdirs\/([^/]+)\/([^/]+)\/(.+)$/u);
if (workdirMatch !== null && req.method === "DELETE") {
return await deleteWorkdir(
decodeURIComponent(workdirMatch[1] ?? ""),
decodeURIComponent(workdirMatch[2] ?? ""),
decodeURIComponent(workdirMatch[3] ?? ""),
);
}
if (url.pathname === "/api/queues" && req.method === "POST") return await createQueue(req);
if (url.pathname === "/api/queues/merge" && req.method === "POST") return await mergeQueues(null, req);
const queueMergeMatch = url.pathname.match(/^\/api\/queues\/([^/]+)\/merge$/u);
if (queueMergeMatch !== null && req.method === "POST") return await mergeQueues(decodeURIComponent(queueMergeMatch[1] ?? ""), req);
const queueMatch = url.pathname.match(/^\/api\/queues\/([^/]+)$/u);
if (queueMatch !== null && (req.method === "PATCH" || req.method === "PUT" || req.method === "POST")) return await updateQueue(decodeURIComponent(queueMatch[1] ?? ""), req);
if (url.pathname === "/api/tasks/read-all" && req.method === "POST") return await markTerminalTasksRead(url);
if (url.pathname === "/api/tasks/stats" && req.method === "GET") {
const queueId = url.searchParams.get("queueId");
const allTasks = await loadAllTasksForRead();
const statsTasks = queueId === null ? allTasks : allTasks.filter((task) => queueIdOf(task) === safeQueueId(queueId));
return jsonResponse({ ok: true, statistics: taskStatisticsSummary(statsTasks, statsDaysFromUrl(url)), queue: queueSummary(false, allTasks) });
}
if (url.pathname === "/api/tasks/overview" && req.method === "GET") return await tasksOverviewResponse(url);
if (url.pathname === "/api/tasks" && req.method === "GET") {
const status = url.searchParams.get("status");
const queueId = url.searchParams.get("queueId");
const lite = url.searchParams.get("lite") === "1";
const includeDevReady = url.searchParams.get("devReady") !== "0" && !lite;
const searchTerms = taskSearchTerms(url);
const allTasks = await loadAllTasksForRead();
const queueFilteredTasks = queueId === null ? allTasks : allTasks.filter((task) => queueIdOf(task) === safeQueueId(queueId));
const filteredTasks = allTasks
.filter((task) => status === null || task.status === status)
.filter((task) => queueId === null || queueIdOf(task) === safeQueueId(queueId))
.filter((task) => taskMatchesSearch(task, searchTerms));
const limit = parseLimit(url);
const page = taskPageRows(filteredTasks, url, limit);
const traceStats = await readOaTraceStatsForTasks(page.rows.map((task) => task.id));
const tasks = page.rows.map((task) => applyOaTraceStatsToTaskJson(taskForListResponse(task, lite, allTasks), traceStats.get(`task:${task.id}`) ?? null));
return jsonResponse({
ok: true,
queue: queueSummary(includeDevReady, allTasks),
statistics: taskStatisticsSummary(queueFilteredTasks, statsDaysFromUrl(url)),
tasks,
pagination: {
limit,
returned: page.returned,
total: page.total,
hasMore: page.hasMore,
nextBeforeId: page.nextBeforeId,
beforeId: page.beforeId,
includeActive: page.includeActive,
},
});
}
if ((url.pathname === "/api/tasks" || url.pathname === "/api/tasks/batch") && req.method === "POST") return await createTasks(req);
const outputMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/output$/u);
if (outputMatch !== null && req.method === "GET") {
const task = await findTaskForRead(decodeURIComponent(outputMatch[1] ?? ""));
if (task === null) return jsonResponse({ ok: false, error: "task not found" }, 404);
return await outputChunkResponse(task, url);
}
const transcriptMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/transcript$/u);
if (transcriptMatch !== null && req.method === "GET") {
const task = await findTaskForRead(decodeURIComponent(transcriptMatch[1] ?? ""));
if (task === null) return jsonResponse({ ok: false, error: "task not found" }, 404);
return await transcriptChunkResponse(task, url);
}
const promptMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/prompt$/u);
if (promptMatch !== null && req.method === "GET") {
const task = await findTaskForRead(decodeURIComponent(promptMatch[1] ?? ""));
if (task === null) return jsonResponse({ ok: false, error: "task not found" }, 404);
return await taskPromptDetailResponse(task, url);
}
if (promptMatch !== null && req.method === "PATCH") {
const task = await findTaskForMutation(decodeURIComponent(promptMatch[1] ?? ""));
if (task === null) return jsonResponse({ ok: false, error: "task not found" }, 404);
return await editQueuedTaskPrompt(task, req);
}
const traceSummaryMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/trace-summary$/u);
if (traceSummaryMatch !== null && req.method === "GET") {
const task = await findTaskForRead(decodeURIComponent(traceSummaryMatch[1] ?? ""));
if (task === null) return jsonResponse({ ok: false, error: "task not found" }, 404);
const traceStats = await readOaTraceStatsForTaskAttempts(task.id, traceAttemptIndexesForTask(task));
return jsonResponse({ ok: true, summary: taskTraceSummaryResponse(task, traceStats.get(`task:${task.id}`) ?? null, traceStats) });
}
const traceStepsMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/trace-steps$/u);
if (traceStepsMatch !== null && req.method === "GET") {
const task = await findTaskForRead(decodeURIComponent(traceStepsMatch[1] ?? ""));
if (task === null) return jsonResponse({ ok: false, error: "task not found" }, 404);
return await taskTraceStepsResponse(task, url);
}
const traceStepMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/trace-step$/u);
if (traceStepMatch !== null && req.method === "GET") {
const task = await findTaskForRead(decodeURIComponent(traceStepMatch[1] ?? ""));
if (task === null) return jsonResponse({ ok: false, error: "task not found" }, 404);
return await taskTraceStepDetailResponse(task, url);
}
const judgeTaskMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/judge$/u);
if (judgeTaskMatch !== null && (req.method === "GET" || req.method === "POST")) {
const task = await findTaskForRead(decodeURIComponent(judgeTaskMatch[1] ?? ""));
if (task === null) return jsonResponse({ ok: false, error: "task not found" }, 404);
return await runSingleTaskJudge(task, url);
}
const summaryMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/summary$/u);
if (summaryMatch !== null && req.method === "GET") {
const task = await findTaskForRead(decodeURIComponent(summaryMatch[1] ?? ""));
if (task === null) return jsonResponse({ ok: false, error: "task not found" }, 404);
return jsonResponse({ ok: true, summary: taskSummaryResponse(task, url) });
}
const match = url.pathname.match(/^\/api\/tasks\/([^/]+)(?:\/(retry|steer|interrupt|move|read|edit))?$/u);
if (match !== null) {
const action = match[2];
const taskId = decodeURIComponent(match[1] ?? "");
if ((action === "retry" || action === "move" || action === "read" || action === "edit") && req.method !== "GET") {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${taskId}/${action}`);
const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${taskId}/${action}`);
if (notReady !== null) return notReady;
}
if (action === "read" && req.method === "POST") return await markTaskReadById(taskId);
const task = action === undefined && req.method === "GET"
? await findTaskForRead(taskId)
: await findTaskForMutation(taskId);
if (task === null) return jsonResponse({ ok: false, error: "task not found" }, 404);
if (action === "retry" && req.method === "POST") return await manualRetry(task, req);
if (action === "steer" && req.method === "POST") return await steerTask(task, req);
if (action === "interrupt" && req.method === "POST") return await interruptTask(task, req.method);
if (action === "move" && req.method === "POST") return await moveTaskToQueue(task, req);
if (action === "edit" && (req.method === "POST" || req.method === "PATCH")) return await editQueuedTaskPrompt(task, req);
if (action !== undefined) return jsonResponse({ ok: false, error: "not found" }, 404);
if (req.method === "GET") {
const traceStats = await readOaTraceStatsForTask(task.id);
if (url.searchParams.get("meta") === "1") return jsonResponse({ ok: true, task: applyOaTraceStatsToTaskJson(taskForMetaResponse(task), traceStats) });
const includeRaw = url.searchParams.get("raw") === "1" || url.searchParams.get("full") === "1";
return jsonResponse({ ok: true, task: applyOaTraceStatsToTaskJson(taskForResponse(task, true, includeRaw), traceStats) });
}
if (req.method === "DELETE") return await interruptTask(task, req.method);
return jsonResponse({ ok: false, error: "method not allowed" }, 405);
}
return jsonResponse({ ok: false, error: "not found", path: url.pathname }, 404);
} catch (error) {
const requestError = requestErrorResponse(error);
if (requestError !== null) {
logger("warn", "request_rejected", { path: url.pathname, error: errorToJson(error) });
return requestError;
}
logger("error", "request_failed", { path: url.pathname, error: error instanceof Error ? error.stack ?? error.message : String(error) });
return jsonResponse({ ok: false, error: error instanceof Error ? error.message : String(error) }, 500);
}
}
installShutdownHandlers();
prepareCodexHome();
prepareOpenCodeHome();
startCodexSqliteLogExporter();
startMemoryWatchdog();
Bun.serve({ hostname: config.host, port: config.port, idleTimeout: 120, fetch: route });
logger("info", "service_listening", { port: config.port, instanceId: config.instanceId, role: config.serviceRole, schedulerEnabled: config.schedulerEnabled, schedulerPollIntervalMs: config.schedulerPollIntervalMs, workdir: config.defaultWorkdir, defaultModel: config.defaultModel, judgeConfigured: config.minimaxApiKey.length > 0, storage: "postgres", databaseReady });
function startDatabaseBackedRuntime(): void {
if (serviceReady) return;
logger("info", "service_started", { port: config.port, instanceId: config.instanceId, role: config.serviceRole, schedulerEnabled: config.schedulerEnabled, schedulerPollIntervalMs: config.schedulerPollIntervalMs, workdir: config.defaultWorkdir, defaultModel: config.defaultModel, judgeConfigured: config.minimaxApiKey.length > 0, storage: "postgres" });
const devReady = collectDevReady() as Record<string, JsonValue>;
logger(devReady.ok === true ? "info" : "warn", "dev_ready_check", devReady);
const startupRecovered = config.schedulerEnabled ? queueActiveTasksForRestartRetry("Service restarted while task was active", "startup") : 0;
if (startupRecovered > 0) logger("warn", "startup_requeued_active_tasks", { recovered: startupRecovered });
persistState();
serviceReady = true;
void refreshSchedulerTasksFromDatabase("startup").catch((error) => {
databaseLastError = databaseErrorMessage(error);
logger("warn", "scheduler_database_startup_refresh_failed", { error: errorToJson(error) });
});
startSchedulerDatabasePoller();
if (config.startupOaBackfillEnabled) {
setTimeout(() => { void backfillOaTraceStats(new URL("http://code-queue.local/api/oa/backfill?limit=2000")).catch((error) => logger("warn", "oa_trace_stats_startup_backfill_failed", { error: errorToJson(error) })); }, 1000).unref?.();
}
scheduleQueue();
scheduleClaudeQqNotificationDrain(1000);
}
void initDatabasePersistenceWithRetry()
.then(() => startDatabaseBackedRuntime())
.catch((error) => {
databaseLastError = databaseErrorMessage(error);
logger("error", "database_persistence_init_failed", { error: errorToJson(error) });
});