feat: split code queue k3s services

This commit is contained in:
Codex
2026-05-16 16:47:00 +00:00
parent daf0b6d315
commit ba0b949766
12 changed files with 907 additions and 82 deletions
@@ -23,6 +23,7 @@ import type {
QueueTaskRequest,
RunMode,
RuntimeConfig,
CodeQueueServiceRole,
TaskStatus,
WorkdirRecord,
} from "./types";
@@ -266,6 +267,20 @@ function envBool(name: string, fallback: boolean): boolean {
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 envList(name: string, fallback: string[]): string[] {
const raw = process.env[name];
const source = raw === undefined || raw.length === 0 ? fallback.join(",") : raw;
@@ -317,6 +332,7 @@ function readConfig(): RuntimeConfig {
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]),
@@ -328,7 +344,9 @@ function readConfig(): RuntimeConfig {
instanceId: envString("CODE_QUEUE_INSTANCE_ID", mainProviderId),
deployCommit: envString("CODE_QUEUE_DEPLOY_COMMIT", "unknown"),
deployRequestedCommit: envString("CODE_QUEUE_DEPLOY_REQUESTED_COMMIT", ""),
schedulerEnabled: envBool("CODE_QUEUE_SCHEDULER_ENABLED", true),
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"),
@@ -1298,6 +1316,13 @@ interface DatabaseTaskRow {
task_json: unknown;
}
interface DatabaseQueueRow {
id: string;
name: string;
created_at: Date | string;
updated_at: Date | string;
}
interface DatabaseTaskIdRow {
id: string;
}
@@ -1383,6 +1408,31 @@ async function loadTasksFromDatabase(where: "all" | "hot" = "all"): Promise<Queu
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 [];
@@ -1730,22 +1780,13 @@ async function initDatabasePersistence(): Promise<void> {
inMemoryOutputRecords: config.maxInMemoryOutputRecords,
inMemoryEventRecords: config.maxInMemoryEventRecords,
});
const queueRows = await sql<Array<{ id: string; name: string; created_at: Date | string; updated_at: Date | string }>>`
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<string, QueueRecord>();
for (const row of queueRows) {
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(),
});
}
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);
@@ -1773,7 +1814,7 @@ async function initDatabasePersistence(): Promise<void> {
ensureDefaultWorkdirRecords();
await upsertWorkdirsToDatabase(sortedWorkdirRecords());
databaseReady = true;
scheduleStartupDatabaseMaintenance();
if (config.serviceRole === "combined" || config.serviceRole === "scheduler") scheduleStartupDatabaseMaintenance();
runGarbageCollection();
logger("info", "database_persistence_init_complete", {
databaseTaskCount: Number(countRows[0]?.count ?? hotTasks.length),
@@ -2320,6 +2361,7 @@ configureQueueApi({
dirtyDatabaseTaskCount: () => dirtyDatabaseTaskIds.size,
jsonResponse,
judgeFailRetryLimit,
loadQueuesFromDatabase,
loadTaskFromDatabase,
loadTasksFromDatabase,
loadTasksFromDatabaseByIds,
@@ -3223,7 +3265,7 @@ function queuedStatusReason(task: QueueTask, tasks: QueueTask[] = state.tasks):
if (!serviceReady) {
return queuedReason("service", "SERVICE", "Code Queue is still starting and has not enabled scheduling yet.");
}
if (!config.schedulerEnabled) {
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)) {
@@ -3335,6 +3377,57 @@ 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;
@@ -3440,6 +3533,27 @@ function requestErrorResponse(error: unknown): Response | null {
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 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;
}
@@ -3520,6 +3634,7 @@ async function deleteWorkdir(providerIdValue: string, executionModeValue: string
}
async function createTasks(req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, "/api/tasks");
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;
@@ -3545,6 +3660,7 @@ async function createTasks(req: Request): Promise<Response> {
}
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);
@@ -3560,6 +3676,7 @@ async function steerTask(task: QueueTask, req: Request): Promise<Response> {
}
async function editQueuedTaskPrompt(task: QueueTask, req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/edit`);
if (!queuedTaskPromptEditable(task)) {
return jsonResponse({
ok: false,
@@ -3601,7 +3718,8 @@ async function editQueuedTaskPrompt(task: QueueTask, req: Request): Promise<Resp
return jsonResponse({ ok: true, changed: true, editable: true, task: taskForResponse(task), queue: await queueSummaryForResponse() });
}
async function interruptTask(task: QueueTask): Promise<Response> {
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);
}
@@ -3631,6 +3749,7 @@ async function interruptTask(task: QueueTask): Promise<Response> {
}
async function manualRetry(task: QueueTask, req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/retry`);
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);
}
@@ -3657,6 +3776,7 @@ async function manualRetry(task: QueueTask, req: Request): Promise<Response> {
}
async function markTaskRead(task: QueueTask): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse("POST", `/api/tasks/${task.id}/read`);
if (!terminalTask(task)) {
return jsonResponse({ ok: false, error: `task is not terminal: ${task.status}`, task: taskForResponse(task) }, 409);
}
@@ -3678,6 +3798,7 @@ function timestampToIso(value: Date | string | null): string | null {
}
async function markTaskReadById(taskId: string): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse("POST", `/api/tasks/${taskId}/read`);
if (!databaseReady) {
const task = await findTaskForMutation(taskId);
return task === null ? jsonResponse({ ok: false, error: "task not found" }, 404) : markTaskRead(task);
@@ -3727,6 +3848,7 @@ async function markTaskReadById(taskId: string): Promise<Response> {
}
async function markTerminalTasksRead(url: URL): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse("POST", "/api/tasks/read-all");
const queueFilter = url.searchParams.get("queueId");
const queueId = queueFilter === null || queueFilter.length === 0 ? null : safeQueueId(queueFilter);
const readAt = nowIso();
@@ -3780,6 +3902,7 @@ async function markTerminalTasksRead(url: URL): Promise<Response> {
}
async function createQueue(req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, "/api/queues");
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);
@@ -3796,6 +3919,7 @@ async function createQueue(req: Request): Promise<Response> {
}
async function updateQueue(queueIdValue: string, req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/queues/${queueIdValue}`);
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);
@@ -3911,6 +4035,7 @@ function deleteQueuesFromState(queueIds: string[]): QueueRecord[] {
}
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 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);
@@ -3994,6 +4119,7 @@ async function mergeQueues(targetQueueIdValue: string | null, req: Request): Pro
}
async function moveTaskToQueue(task: QueueTask, req: Request): Promise<Response> {
if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/move`);
if (task.status === "running" || task.status === "judging") {
return jsonResponse({ ok: false, error: `cannot move active task ${task.id} while status=${task.status}`, task: taskForResponse(task) }, 409);
}
@@ -4055,6 +4181,7 @@ async function route(req: Request): Promise<Response> {
ok: true,
service: "code-queue",
instanceId: config.instanceId,
role: config.serviceRole,
deploy: {
commit: config.deployCommit,
requestedCommit: config.deployRequestedCommit,
@@ -4068,6 +4195,7 @@ async function route(req: Request): Promise<Response> {
ok: false,
service: "code-queue",
instanceId: config.instanceId,
role: config.serviceRole,
deploy: {
commit: config.deployCommit,
requestedCommit: config.deployRequestedCommit,
@@ -4081,11 +4209,13 @@ async function route(req: Request): Promise<Response> {
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(),
@@ -4145,7 +4275,8 @@ async function route(req: Request): Promise<Response> {
}
if (url.pathname === "/api/queues" && req.method === "GET") {
const tasks = await loadAllTasksForRead();
return jsonResponse({ ok: true, queues: perQueueSummaries(tasks), queue: queueSummary(false, tasks) });
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);
@@ -4269,7 +4400,7 @@ async function route(req: Request): Promise<Response> {
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);
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);
@@ -4279,7 +4410,7 @@ async function route(req: Request): Promise<Response> {
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);
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);
@@ -4301,7 +4432,7 @@ startCodexSqliteLogExporter();
startMemoryWatchdog();
await initDatabasePersistenceWithRetry();
Bun.serve({ hostname: config.host, port: config.port, idleTimeout: 120, fetch: route });
logger("info", "service_started", { port: config.port, instanceId: config.instanceId, schedulerEnabled: config.schedulerEnabled, workdir: config.defaultWorkdir, defaultModel: config.defaultModel, judgeConfigured: config.minimaxApiKey.length > 0, storage: "postgres" });
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);
@@ -4310,6 +4441,8 @@ const startupRecovered = config.schedulerEnabled ? queueActiveTasksForRestartRet
if (startupRecovered > 0) logger("warn", "startup_requeued_active_tasks", { recovered: startupRecovered });
persistState();
serviceReady = true;
await refreshSchedulerTasksFromDatabase("startup");
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?.();
}
@@ -25,6 +25,7 @@ export interface QueueApiContext {
dirtyDatabaseTaskCount: () => number;
jsonResponse: (body: unknown, status?: number) => Response;
judgeFailRetryLimit: number;
loadQueuesFromDatabase: () => Promise<QueueRecord[]>;
loadTaskFromDatabase: (taskId: string) => Promise<QueueTask | null>;
loadTasksFromDatabase: (where?: "all" | "hot") => Promise<QueueTask[]>;
loadTasksFromDatabaseByIds: (taskIds: string[]) => Promise<QueueTask[]>;
@@ -275,7 +276,7 @@ function taskForListResponse(task: QueueTask, lite = false, queueTasks?: QueueTa
} as unknown as JsonValue;
}
function perQueueSummaries(tasks: QueueTask[] = ctx().tasks()): JsonValue[] {
function perQueueSummaries(tasks: QueueTask[] = ctx().tasks(), queueRecords: QueueRecord[] = ctx().queues()): JsonValue[] {
const summaries = new Map<string, {
name: string;
total: number;
@@ -286,10 +287,10 @@ function perQueueSummaries(tasks: QueueTask[] = ctx().tasks()): JsonValue[] {
createdAt: string | null;
updatedAt: string | null;
}>();
for (const queue of ctx().queues()) {
queue.name = ctx().safeQueueName(queue.name, queue.id);
for (const queue of queueRecords) {
const name = ctx().safeQueueName(queue.name, queue.id);
summaries.set(queue.id, {
name: queue.name,
name,
total: 0,
counts: {},
unreadTerminal: 0,
@@ -342,7 +343,7 @@ function perQueueSummaries(tasks: QueueTask[] = ctx().tasks()): JsonValue[] {
return rows;
}
function queueSummary(includeDevReady = true, tasks: QueueTask[] = ctx().tasks()): JsonValue {
function queueSummary(includeDevReady = true, tasks: QueueTask[] = ctx().tasks(), queueRecords: QueueRecord[] = ctx().queues()): JsonValue {
const counts = tasks.reduce<Record<string, number>>((memo, task) => {
memo[task.status] = (memo[task.status] ?? 0) + 1;
return memo;
@@ -355,7 +356,7 @@ function queueSummary(includeDevReady = true, tasks: QueueTask[] = ctx().tasks()
}
const activeTaskIds = Array.from(activeTaskIdSet).sort();
const activeTaskId = activeTaskIds[0] ?? tasks.find((task) => task.status === "running" || task.status === "judging")?.id ?? null;
const queues = perQueueSummaries(tasks);
const queues = perQueueSummaries(tasks, queueRecords);
const summary: Record<string, JsonValue> = {
total: tasks.length,
defaultQueueId: ctx().defaultQueueId,
@@ -449,13 +450,14 @@ async function loadAllTasksForRead(): Promise<QueueTask[]> {
return Array.from(byId.values()).sort((left, right) => (timestampMs(left.createdAt) ?? 0) - (timestampMs(right.createdAt) ?? 0) || left.id.localeCompare(right.id));
}
async function queueSummaryForResponse(includeDevReady = true, tasks?: QueueTask[]): Promise<JsonValue> {
if (tasks !== undefined) return queueSummary(includeDevReady, tasks);
async function queueSummaryForResponse(includeDevReady = true, tasks?: QueueTask[], queueRecords?: QueueRecord[]): Promise<JsonValue> {
if (tasks !== undefined || queueRecords !== undefined) return queueSummary(includeDevReady, tasks ?? ctx().tasks(), queueRecords ?? ctx().queues());
return queueSummaryForHealth(includeDevReady);
}
async function queueSummaryForHealth(includeDevReady = true): Promise<JsonValue> {
const summary = queueSummary(includeDevReady, ctx().tasks()) as Record<string, JsonValue>;
const queueRecords = ctx().databaseReady() ? await ctx().loadQueuesFromDatabase() : ctx().queues();
const summary = queueSummary(includeDevReady, ctx().tasks(), queueRecords) as Record<string, JsonValue>;
if (!ctx().databaseReady()) return summary;
const aggregateRows = await ctx().sql<QueueSummaryAggregateRow[]>`
SELECT
@@ -501,7 +503,7 @@ async function queueSummaryForHealth(includeDevReady = true): Promise<JsonValue>
createdAt: string | null;
updatedAt: string | null;
}>();
for (const queue of ctx().queues()) {
for (const queue of queueRecords) {
summaries.set(queue.id, {
name: ctx().safeQueueName(queue.name, queue.id),
total: 0,
@@ -18,6 +18,8 @@ export interface QueuedStatusReason {
export type RunMode = "initial" | "retry";
export type CodeQueueServiceRole = "combined" | "read" | "write" | "scheduler";
export type JudgeDecision = "complete" | "retry" | "fail";
export type CodeExecutionMode = "default" | "windows-native";
@@ -35,7 +37,9 @@ export interface RuntimeConfig {
instanceId: string;
deployCommit: string;
deployRequestedCommit: string;
serviceRole: CodeQueueServiceRole;
schedulerEnabled: boolean;
schedulerPollIntervalMs: number;
startupOaBackfillEnabled: boolean;
outputArchiveDir: string;
logFile: string;
@@ -1,46 +1,159 @@
{
"apiVersion": "unidesk.ai/k3s/v1",
"kind": "ManagedKubernetesService",
"metadata": {
"name": "code-queue",
"namespace": "unidesk"
},
"spec": {
"adapterServiceId": "k3sctl-adapter",
"controlPlane": {
"type": "kubernetes",
"cluster": "unidesk-k3s",
"context": "unidesk-k3s"
[
{
"apiVersion": "unidesk.ai/k3s/v1",
"kind": "ManagedKubernetesService",
"metadata": {
"name": "code-queue",
"namespace": "unidesk"
},
"route": {
"kind": "kubernetes-service",
"serviceName": "code-queue",
"servicePort": 4222
},
"activeInstanceId": "D601",
"singleWriter": true,
"expectedNodeIds": [
"D601",
"D518"
],
"instances": [
{
"id": "D601",
"nodeId": "D601",
"role": "primary",
"baseUrl": "kubernetes://unidesk/services/code-queue:4222",
"healthPath": "/health",
"healthMode": "service-proxy"
"spec": {
"adapterServiceId": "k3sctl-adapter",
"controlPlane": {
"type": "kubernetes",
"cluster": "unidesk-k3s",
"context": "unidesk-k3s"
},
{
"id": "D518",
"nodeId": "D518",
"role": "standby",
"baseUrl": "kubernetes://unidesk/services/code-queue-d518:4222",
"healthPath": "/health",
"healthMode": "pod-ready"
}
],
"requireAllInstancesHealthy": false
"route": {
"kind": "kubernetes-service",
"serviceName": "code-queue-scheduler",
"servicePort": 4222
},
"activeInstanceId": "D601",
"singleWriter": true,
"expectedNodeIds": [
"D601",
"D518"
],
"instances": [
{
"id": "D601",
"nodeId": "D601",
"role": "primary",
"baseUrl": "kubernetes://unidesk/services/code-queue-scheduler:4222",
"healthPath": "/health",
"healthMode": "service-proxy"
},
{
"id": "D518",
"nodeId": "D518",
"role": "standby",
"baseUrl": "kubernetes://unidesk/services/code-queue-d518:4222",
"healthPath": "/health",
"healthMode": "pod-ready"
}
],
"requireAllInstancesHealthy": false
}
},
{
"apiVersion": "unidesk.ai/k3s/v1",
"kind": "ManagedKubernetesService",
"metadata": {
"name": "code-queue-read",
"namespace": "unidesk"
},
"spec": {
"adapterServiceId": "k3sctl-adapter",
"controlPlane": {
"type": "kubernetes",
"cluster": "unidesk-k3s",
"context": "unidesk-k3s"
},
"route": {
"kind": "kubernetes-service",
"serviceName": "code-queue-read",
"servicePort": 4222
},
"activeInstanceId": "D601-read",
"singleWriter": false,
"expectedNodeIds": [
"D601"
],
"instances": [
{
"id": "D601-read",
"nodeId": "D601",
"role": "standby",
"baseUrl": "kubernetes://unidesk/services/code-queue-read:4222",
"healthPath": "/live",
"healthMode": "service-proxy"
}
],
"requireAllInstancesHealthy": false
}
},
{
"apiVersion": "unidesk.ai/k3s/v1",
"kind": "ManagedKubernetesService",
"metadata": {
"name": "code-queue-write",
"namespace": "unidesk"
},
"spec": {
"adapterServiceId": "k3sctl-adapter",
"controlPlane": {
"type": "kubernetes",
"cluster": "unidesk-k3s",
"context": "unidesk-k3s"
},
"route": {
"kind": "kubernetes-service",
"serviceName": "code-queue-write",
"servicePort": 4222
},
"activeInstanceId": "D601-write",
"singleWriter": true,
"expectedNodeIds": [
"D601"
],
"instances": [
{
"id": "D601-write",
"nodeId": "D601",
"role": "primary",
"baseUrl": "kubernetes://unidesk/services/code-queue-write:4222",
"healthPath": "/health",
"healthMode": "service-proxy"
}
],
"requireAllInstancesHealthy": false
}
},
{
"apiVersion": "unidesk.ai/k3s/v1",
"kind": "ManagedKubernetesService",
"metadata": {
"name": "code-queue-scheduler",
"namespace": "unidesk"
},
"spec": {
"adapterServiceId": "k3sctl-adapter",
"controlPlane": {
"type": "kubernetes",
"cluster": "unidesk-k3s",
"context": "unidesk-k3s"
},
"route": {
"kind": "kubernetes-service",
"serviceName": "code-queue-scheduler",
"servicePort": 4222
},
"activeInstanceId": "D601-scheduler",
"singleWriter": true,
"expectedNodeIds": [
"D601"
],
"instances": [
{
"id": "D601-scheduler",
"nodeId": "D601",
"role": "primary",
"baseUrl": "kubernetes://unidesk/services/code-queue-scheduler:4222",
"healthPath": "/health",
"healthMode": "service-proxy"
}
],
"requireAllInstancesHealthy": false
}
}
}
]
@@ -6,6 +6,544 @@ metadata:
app.kubernetes.io/part-of: unidesk
unidesk.ai/k3s-cluster: unidesk-k3s
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: code-queue-read
namespace: unidesk
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: read
app.kubernetes.io/part-of: unidesk
unidesk.ai/deployment-mode: k3sctl-managed
unidesk.ai/instance-id: D601-read
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: read
unidesk.ai/instance-id: D601-read
template:
metadata:
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: read
app.kubernetes.io/part-of: unidesk
unidesk.ai/deployment-mode: k3sctl-managed
unidesk.ai/instance-id: D601-read
unidesk.ai/node-id: D601
spec:
nodeSelector:
unidesk.ai/node-id: D601
terminationGracePeriodSeconds: 30
containers:
- name: code-queue
image: unidesk-code-queue:d601
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 4222
envFrom:
- secretRef:
name: code-queue-env
optional: true
env:
- name: HOST
value: "0.0.0.0"
- name: PORT
value: "4222"
- name: DATABASE_URL
value: "postgres://unidesk:unidesk_dev_password@d601-tcp-egress-gateway.unidesk.svc.cluster.local:15432/unidesk"
- name: CODE_QUEUE_INSTANCE_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: CODE_QUEUE_SERVICE_ROLE
value: "read"
- name: CODE_QUEUE_SCHEDULER_ENABLED
value: "false"
- name: CODE_QUEUE_STARTUP_OA_BACKFILL_ENABLED
value: "false"
- name: CODE_QUEUE_DATA_DIR
value: "/var/lib/unidesk/code-queue"
- name: CODE_QUEUE_WORKDIR
value: "/workspace"
- name: CODE_QUEUE_CODEX_HOME
value: "/var/lib/unidesk/code-queue/codex-home"
- name: CODE_QUEUE_OPENCODE_XDG_DIR
value: "/var/lib/unidesk/code-queue/opencode-xdg"
- name: CODE_QUEUE_SOURCE_CODEX_CONFIG
value: "/root/.codex/config.toml"
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,minimax-m2.7"
- name: CODE_QUEUE_MODEL_REASONING_EFFORTS
value: "gpt-5.5=xhigh"
- name: CODE_QUEUE_SANDBOX
value: "danger-full-access"
- name: CODE_QUEUE_APPROVAL_POLICY
value: "never"
- name: CODE_QUEUE_MAX_ACTIVE_QUEUES
value: "0"
- name: CODE_QUEUE_DATABASE_POOL_MAX
value: "2"
- name: NODE_OPTIONS
value: "--max-old-space-size=512"
- name: GIT_CONFIG_COUNT
value: "1"
- name: GIT_CONFIG_KEY_0
value: "safe.directory"
- name: GIT_CONFIG_VALUE_0
value: "*"
- name: CODE_QUEUE_IN_MEMORY_OUTPUT_RECORDS
value: "10"
- name: CODE_QUEUE_IN_MEMORY_EVENT_RECORDS
value: "10"
- name: CODE_QUEUE_MAIN_PROVIDER_ID
value: "D601"
- name: CODE_QUEUE_REMOTE_WORKDIR
value: "/home/ubuntu"
- name: CODE_QUEUE_EXECUTION_PROVIDER_IDS
value: "D601"
- name: CODE_QUEUE_DEV_CONTAINER_MASTER_HOST
value: "74.48.78.17"
- name: CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID
value: "D601"
- name: CODE_QUEUE_DEV_CONTAINER_WORKDIR
value: "/home/ubuntu"
- name: CODE_QUEUE_EGRESS_PROXY_ENABLED
value: "false"
- name: CODE_QUEUE_EGRESS_PROXY_URL
value: ""
- name: CODE_QUEUE_EGRESS_PROXY_NO_PROXY
value: "localhost,127.0.0.1,::1,host.docker.internal,d601-tcp-egress-gateway,d601-tcp-egress-gateway.unidesk,d601-tcp-egress-gateway.unidesk.svc,d601-tcp-egress-gateway.unidesk.svc.cluster.local,backend-core,oa-event-flow,database"
- name: HTTP_PROXY
value: ""
- name: HTTPS_PROXY
value: ""
- name: ALL_PROXY
value: ""
- name: http_proxy
value: ""
- name: https_proxy
value: ""
- name: all_proxy
value: ""
- name: NO_PROXY
value: "localhost,127.0.0.1,::1,host.docker.internal,d601-tcp-egress-gateway,d601-tcp-egress-gateway.unidesk,d601-tcp-egress-gateway.unidesk.svc,d601-tcp-egress-gateway.unidesk.svc.cluster.local,backend-core,oa-event-flow,database"
- name: no_proxy
value: "localhost,127.0.0.1,::1,host.docker.internal,d601-tcp-egress-gateway,d601-tcp-egress-gateway.unidesk,d601-tcp-egress-gateway.unidesk.svc,d601-tcp-egress-gateway.unidesk.svc.cluster.local,backend-core,oa-event-flow,database"
- name: OA_EVENT_FLOW_BASE_URL
value: "http://d601-tcp-egress-gateway.unidesk.svc.cluster.local:4255"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED
value: "false"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL
value: ""
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE
value: "private"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_USER_ID
value: "645275593"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_MAX_RESPONSE_CHARS
value: "12000"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_TIMEOUT_MS
value: "15000"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_SEND_ATTEMPTS
value: "3"
- name: LOG_FILE
value: "/var/log/unidesk/code-queue-read.jsonl"
- name: UNIDESK_LOG_RETENTION_BYTES
value: "1GiB"
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
- name: workspace
mountPath: /workspace
- name: workspace
mountPath: /home/ubuntu
- name: repo
mountPath: /root/unidesk
- name: repo
mountPath: /app
- name: codex-config
mountPath: /root/.codex/config.toml
readOnly: true
- name: codex-auth
mountPath: /root/.codex/auth.json
readOnly: true
- name: ssh-dir
mountPath: /root/.ssh
readOnly: true
- name: logs
mountPath: /var/log/unidesk
- name: state
mountPath: /var/lib/unidesk/code-queue
readinessProbe:
httpGet:
path: /live
port: http
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 20
livenessProbe:
httpGet:
path: /live
port: http
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
startupProbe:
httpGet:
path: /live
port: http
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 60
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 1Gi
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: Socket
- name: workspace
hostPath:
path: /home/ubuntu
type: Directory
- name: repo
hostPath:
path: /home/ubuntu/cq-deploy
type: Directory
- name: codex-config
hostPath:
path: /home/ubuntu/.codex/config.toml
type: File
- name: codex-auth
hostPath:
path: /home/ubuntu/.codex/auth.json
type: File
- name: ssh-dir
hostPath:
path: /home/ubuntu/.ssh
type: Directory
- name: logs
hostPath:
path: /home/ubuntu/cq-deploy/.state/code-queue/logs
type: DirectoryOrCreate
- name: state
hostPath:
path: /home/ubuntu/cq-deploy/.state/code-queue
type: DirectoryOrCreate
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: code-queue-write
namespace: unidesk
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: write
app.kubernetes.io/part-of: unidesk
unidesk.ai/deployment-mode: k3sctl-managed
unidesk.ai/instance-id: D601-write
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: write
unidesk.ai/instance-id: D601-write
template:
metadata:
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: write
app.kubernetes.io/part-of: unidesk
unidesk.ai/deployment-mode: k3sctl-managed
unidesk.ai/instance-id: D601-write
unidesk.ai/node-id: D601
spec:
nodeSelector:
unidesk.ai/node-id: D601
terminationGracePeriodSeconds: 30
containers:
- name: code-queue
image: unidesk-code-queue:d601
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 4222
envFrom:
- secretRef:
name: code-queue-env
optional: true
env:
- name: HOST
value: "0.0.0.0"
- name: PORT
value: "4222"
- name: DATABASE_URL
value: "postgres://unidesk:unidesk_dev_password@d601-tcp-egress-gateway.unidesk.svc.cluster.local:15432/unidesk"
- name: CODE_QUEUE_INSTANCE_ID
value: "D601-write"
- name: CODE_QUEUE_SERVICE_ROLE
value: "write"
- name: CODE_QUEUE_SCHEDULER_ENABLED
value: "false"
- name: CODE_QUEUE_STARTUP_OA_BACKFILL_ENABLED
value: "false"
- name: CODE_QUEUE_DATA_DIR
value: "/var/lib/unidesk/code-queue"
- name: CODE_QUEUE_WORKDIR
value: "/workspace"
- name: CODE_QUEUE_CODEX_HOME
value: "/var/lib/unidesk/code-queue/codex-home"
- name: CODE_QUEUE_OPENCODE_XDG_DIR
value: "/var/lib/unidesk/code-queue/opencode-xdg"
- name: CODE_QUEUE_SOURCE_CODEX_CONFIG
value: "/root/.codex/config.toml"
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
value: "gpt-5.5,gpt-5.4-mini,gpt-5.4,minimax-m2.7"
- name: CODE_QUEUE_MODEL_REASONING_EFFORTS
value: "gpt-5.5=xhigh"
- name: CODE_QUEUE_SANDBOX
value: "danger-full-access"
- name: CODE_QUEUE_APPROVAL_POLICY
value: "never"
- name: CODE_QUEUE_MAX_ACTIVE_QUEUES
value: "0"
- name: CODE_QUEUE_DATABASE_POOL_MAX
value: "2"
- name: NODE_OPTIONS
value: "--max-old-space-size=768"
- name: GIT_CONFIG_COUNT
value: "1"
- name: GIT_CONFIG_KEY_0
value: "safe.directory"
- name: GIT_CONFIG_VALUE_0
value: "*"
- name: CODE_QUEUE_IN_MEMORY_OUTPUT_RECORDS
value: "10"
- name: CODE_QUEUE_IN_MEMORY_EVENT_RECORDS
value: "10"
- name: CODE_QUEUE_MAIN_PROVIDER_ID
value: "D601"
- name: CODE_QUEUE_REMOTE_WORKDIR
value: "/home/ubuntu"
- name: CODE_QUEUE_EXECUTION_PROVIDER_IDS
value: "D601"
- name: CODE_QUEUE_DEV_CONTAINER_MASTER_HOST
value: "74.48.78.17"
- name: CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID
value: "D601"
- name: CODE_QUEUE_DEV_CONTAINER_WORKDIR
value: "/home/ubuntu"
- name: CODE_QUEUE_EGRESS_PROXY_ENABLED
value: "false"
- name: CODE_QUEUE_EGRESS_PROXY_URL
value: ""
- name: CODE_QUEUE_EGRESS_PROXY_NO_PROXY
value: "localhost,127.0.0.1,::1,host.docker.internal,d601-tcp-egress-gateway,d601-tcp-egress-gateway.unidesk,d601-tcp-egress-gateway.unidesk.svc,d601-tcp-egress-gateway.unidesk.svc.cluster.local,backend-core,oa-event-flow,database"
- name: HTTP_PROXY
value: ""
- name: HTTPS_PROXY
value: ""
- name: ALL_PROXY
value: ""
- name: http_proxy
value: ""
- name: https_proxy
value: ""
- name: all_proxy
value: ""
- name: NO_PROXY
value: "localhost,127.0.0.1,::1,host.docker.internal,d601-tcp-egress-gateway,d601-tcp-egress-gateway.unidesk,d601-tcp-egress-gateway.unidesk.svc,d601-tcp-egress-gateway.unidesk.svc.cluster.local,backend-core,oa-event-flow,database"
- name: no_proxy
value: "localhost,127.0.0.1,::1,host.docker.internal,d601-tcp-egress-gateway,d601-tcp-egress-gateway.unidesk,d601-tcp-egress-gateway.unidesk.svc,d601-tcp-egress-gateway.unidesk.svc.cluster.local,backend-core,oa-event-flow,database"
- name: OA_EVENT_FLOW_BASE_URL
value: "http://d601-tcp-egress-gateway.unidesk.svc.cluster.local:4255"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED
value: "false"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_BASE_URL
value: ""
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_TARGET_TYPE
value: "private"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_USER_ID
value: "645275593"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_MAX_RESPONSE_CHARS
value: "12000"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_TIMEOUT_MS
value: "15000"
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_SEND_ATTEMPTS
value: "3"
- name: LOG_FILE
value: "/var/log/unidesk/code-queue-write.jsonl"
- name: UNIDESK_LOG_RETENTION_BYTES
value: "1GiB"
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
- name: workspace
mountPath: /workspace
- name: workspace
mountPath: /home/ubuntu
- name: repo
mountPath: /root/unidesk
- name: repo
mountPath: /app
- name: codex-config
mountPath: /root/.codex/config.toml
readOnly: true
- name: codex-auth
mountPath: /root/.codex/auth.json
readOnly: true
- name: ssh-dir
mountPath: /root/.ssh
readOnly: true
- name: logs
mountPath: /var/log/unidesk
- name: state
mountPath: /var/lib/unidesk/code-queue
readinessProbe:
httpGet:
path: /live
port: http
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 20
livenessProbe:
httpGet:
path: /live
port: http
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
startupProbe:
httpGet:
path: /live
port: http
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 60
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 1Gi
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: Socket
- name: workspace
hostPath:
path: /home/ubuntu
type: Directory
- name: repo
hostPath:
path: /home/ubuntu/cq-deploy
type: Directory
- name: codex-config
hostPath:
path: /home/ubuntu/.codex/config.toml
type: File
- name: codex-auth
hostPath:
path: /home/ubuntu/.codex/auth.json
type: File
- name: ssh-dir
hostPath:
path: /home/ubuntu/.ssh
type: Directory
- name: logs
hostPath:
path: /home/ubuntu/cq-deploy/.state/code-queue/logs
type: DirectoryOrCreate
- name: state
hostPath:
path: /home/ubuntu/cq-deploy/.state/code-queue
type: DirectoryOrCreate
---
apiVersion: v1
kind: Service
metadata:
name: code-queue-scheduler
namespace: unidesk
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: scheduler
app.kubernetes.io/part-of: unidesk
unidesk.ai/deployment-mode: k3sctl-managed
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: code-queue
unidesk.ai/instance-id: D601
ports:
- name: http
port: 4222
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: code-queue-read
namespace: unidesk
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: read
app.kubernetes.io/part-of: unidesk
unidesk.ai/deployment-mode: k3sctl-managed
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: read
unidesk.ai/instance-id: D601-read
ports:
- name: http
port: 4222
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: code-queue-write
namespace: unidesk
labels:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: write
app.kubernetes.io/part-of: unidesk
unidesk.ai/deployment-mode: k3sctl-managed
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: code-queue
app.kubernetes.io/component: write
unidesk.ai/instance-id: D601-write
ports:
- name: http
port: 4222
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
@@ -348,6 +886,11 @@ metadata:
unidesk.ai/instance-id: D601
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app.kubernetes.io/name: code-queue
@@ -384,8 +927,12 @@ spec:
value: "postgres://unidesk:unidesk_dev_password@d601-tcp-egress-gateway.unidesk.svc.cluster.local:15432/unidesk"
- name: CODE_QUEUE_INSTANCE_ID
value: "D601"
- name: CODE_QUEUE_SERVICE_ROLE
value: "scheduler"
- name: CODE_QUEUE_SCHEDULER_ENABLED
value: "true"
- name: CODE_QUEUE_SCHEDULER_POLL_INTERVAL_MS
value: "2000"
- name: CODE_QUEUE_STARTUP_OA_BACKFILL_ENABLED
value: "false"
- name: CODE_QUEUE_DATA_DIR
@@ -631,6 +1178,8 @@ spec:
value: "postgres://unidesk:unidesk_dev_password@d601-tcp-egress-gateway.unidesk.svc.cluster.local:15432/unidesk"
- name: CODE_QUEUE_INSTANCE_ID
value: "D518"
- name: CODE_QUEUE_SERVICE_ROLE
value: "read"
- name: CODE_QUEUE_SCHEDULER_ENABLED
value: "false"
- name: CODE_QUEUE_STARTUP_OA_BACKFILL_ENABLED