fix: stabilize code queue runtime and trace flow
This commit is contained in:
@@ -46,6 +46,7 @@ function help(): unknown {
|
||||
{ command: "schedule upsert-pgdata-backup [--time HH:MM] [--remote-base /SERVER_DATA/UNIDESK_PG_DATA]", description: "Create or update the daily PGDATA physical backup task that uploads monthly rotated archives to Baidu Netdisk." },
|
||||
{ command: "codex task <taskId> [--trace --tail|--from-start|--after-seq N|--before-seq N --limit N] [--full]", description: "Fetch a compact Code Queue task summary; trace rows are opt-in and paged with next/previous commands to avoid output explosion." },
|
||||
{ command: "codex output <taskId> [--tail|--from-start|--after-seq N|--before-seq N --limit N] [--full-text]", description: "Fetch paged raw Code Queue output records by seq when a trace row has omitted command/output text." },
|
||||
{ command: "codex judge <taskId> --attempt N [--dry-run] [--include-prompt]", description: "Replay one stored Code Queue attempt through the same judge context builder and MiniMax judge call path used by the live queue worker." },
|
||||
{ command: "codex (queues | queue create <queueId> | queue merge <sourceQueueId> --into <targetQueueId> | move <taskId> --queue <queueId>)", description: "List/create/merge Code Queue lanes and move a queued task; merge preserves task queue time order and deletes the source queue record." },
|
||||
{ command: "job list", description: "List async jobs from .state/jobs." },
|
||||
{ command: "job status <jobId|latest> [--tail-bytes N]", description: "Show job state with bounded stdout/stderr tails." },
|
||||
|
||||
@@ -27,8 +27,15 @@ interface CodexOutputOptions {
|
||||
maxTextChars: number;
|
||||
}
|
||||
|
||||
type CodexResponseFetcher = (path: string) => unknown;
|
||||
type AsyncCodexResponseFetcher = (path: string) => Promise<unknown>;
|
||||
interface CodexJudgeOptions {
|
||||
attempt: number | null;
|
||||
dryRun: boolean;
|
||||
includePrompt: boolean;
|
||||
}
|
||||
|
||||
type CodexRequestInit = { method?: string; body?: unknown };
|
||||
type CodexResponseFetcher = (path: string, init?: CodexRequestInit) => unknown;
|
||||
type AsyncCodexResponseFetcher = (path: string, init?: CodexRequestInit) => Promise<unknown>;
|
||||
|
||||
function requireTaskId(value: string | undefined, command: string): string {
|
||||
if (value === undefined || value.trim().length === 0) throw new Error(`${command} requires task id`);
|
||||
@@ -432,6 +439,21 @@ function parseOutputOptions(args: string[]): CodexOutputOptions {
|
||||
};
|
||||
}
|
||||
|
||||
function parseJudgeOptions(args: string[]): CodexJudgeOptions {
|
||||
const rawAttempt = optionValue(args, ["--attempt", "--attempt-id", "--attemptIndex"]) ?? positionalArgs(args)[0];
|
||||
let attempt: number | null = null;
|
||||
if (rawAttempt !== undefined) {
|
||||
const value = Number(rawAttempt);
|
||||
if (!Number.isInteger(value) || value <= 0) throw new Error("--attempt must be a positive integer");
|
||||
attempt = value;
|
||||
}
|
||||
return {
|
||||
attempt,
|
||||
dryRun: hasFlag(args, "--dry-run") || hasFlag(args, "--no-call"),
|
||||
includePrompt: hasFlag(args, "--include-prompt"),
|
||||
};
|
||||
}
|
||||
|
||||
function queryString(params: Record<string, string | number | boolean | null | undefined>): string {
|
||||
const search = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
@@ -506,6 +528,16 @@ function codexTaskOutput(taskId: string, options: CodexOutputOptions, fetcher: C
|
||||
return { upstream: response.upstream, outputPage: compactOutputPage(response.body, taskId, options.limit) };
|
||||
}
|
||||
|
||||
function codexTaskJudge(taskId: string, options: CodexJudgeOptions, fetcher: CodexResponseFetcher): unknown {
|
||||
const params = queryString({
|
||||
attempt: options.attempt,
|
||||
dryRun: options.dryRun ? 1 : undefined,
|
||||
includePrompt: options.includePrompt ? 1 : undefined,
|
||||
});
|
||||
const response = unwrapCodexResponse(fetcher(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/judge${params}`, { method: "POST" }));
|
||||
return { upstream: response.upstream, judgeReplay: response.body };
|
||||
}
|
||||
|
||||
export function codexTaskQuery(taskId: string, optionArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||||
return codexTaskSummary(taskId, parseTaskOptions(optionArgs), fetcher);
|
||||
}
|
||||
@@ -514,6 +546,10 @@ export function codexOutputQuery(taskId: string, optionArgs: string[], fetcher:
|
||||
return codexTaskOutput(taskId, parseOutputOptions(optionArgs), fetcher);
|
||||
}
|
||||
|
||||
export function codexJudgeQuery(taskId: string, optionArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||||
return codexTaskJudge(taskId, parseJudgeOptions(optionArgs), fetcher);
|
||||
}
|
||||
|
||||
async function codexTaskSummaryAsync(taskId: string, options: CodexTaskOptions, fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
const summaryPath = `/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: options.toolLimit })}`;
|
||||
const summaryResponse = unwrapCodexResponse(await fetcher(summaryPath));
|
||||
@@ -548,6 +584,16 @@ async function codexTaskOutputAsync(taskId: string, options: CodexOutputOptions,
|
||||
return { upstream: response.upstream, outputPage: compactOutputPage(response.body, taskId, options.limit) };
|
||||
}
|
||||
|
||||
async function codexTaskJudgeAsync(taskId: string, options: CodexJudgeOptions, fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
const params = queryString({
|
||||
attempt: options.attempt,
|
||||
dryRun: options.dryRun ? 1 : undefined,
|
||||
includePrompt: options.includePrompt ? 1 : undefined,
|
||||
});
|
||||
const response = unwrapCodexResponse(await fetcher(`/api/microservices/code-queue/proxy/api/tasks/${encodeURIComponent(taskId)}/judge${params}`, { method: "POST" }));
|
||||
return { upstream: response.upstream, judgeReplay: response.body };
|
||||
}
|
||||
|
||||
export async function codexTaskQueryAsync(taskId: string, optionArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
return codexTaskSummaryAsync(taskId, parseTaskOptions(optionArgs), fetcher);
|
||||
}
|
||||
@@ -556,6 +602,10 @@ export async function codexOutputQueryAsync(taskId: string, optionArgs: string[]
|
||||
return codexTaskOutputAsync(taskId, parseOutputOptions(optionArgs), fetcher);
|
||||
}
|
||||
|
||||
export async function codexJudgeQueryAsync(taskId: string, optionArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
return codexTaskJudgeAsync(taskId, parseJudgeOptions(optionArgs), fetcher);
|
||||
}
|
||||
|
||||
function requireQueueId(args: string[], command: string): string {
|
||||
const index = args.indexOf("--queue");
|
||||
const raw = index === -1 ? args[0] : args[index + 1];
|
||||
@@ -625,6 +675,10 @@ export async function runCodeQueueCommand(_config: UniDeskConfig, args: string[]
|
||||
const taskId = requireTaskId(taskIdArg, "codex output");
|
||||
return codexOutputQuery(taskId, args.slice(2));
|
||||
}
|
||||
if (action === "judge") {
|
||||
const taskId = requireTaskId(taskIdArg, "codex judge");
|
||||
return codexJudgeQuery(taskId, args.slice(2));
|
||||
}
|
||||
if (action === "queues") return codeQueues();
|
||||
if (action === "queue") {
|
||||
const sub = taskIdArg ?? "list";
|
||||
@@ -639,5 +693,5 @@ export async function runCodeQueueCommand(_config: UniDeskConfig, args: string[]
|
||||
const taskId = requireTaskId(taskIdArg, "codex move");
|
||||
return codexMoveTask(taskId, requireQueueId(args.slice(2), "codex move"));
|
||||
}
|
||||
throw new Error("codex command must be one of: task, summary, show, output, queues, queue list, queue create, queue merge, move");
|
||||
throw new Error("codex command must be one of: task, summary, show, output, judge, queues, queue list, queue create, queue merge, move");
|
||||
}
|
||||
|
||||
@@ -59,6 +59,12 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
|
||||
const previousRaw = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
|
||||
const previousValue = (key: string): string => previousRaw.match(new RegExp(`^${key}=(.*)$`, "m"))?.[1]?.replace(/^"|"$/g, "") ?? "";
|
||||
const runtimeSecret = (key: string): string => process.env[key] ?? previousValue(key);
|
||||
const runtimeSecretWithDefault = (key: string, defaultValue: string, legacyDefault = ""): string => {
|
||||
if (process.env[key] !== undefined) return process.env[key] ?? defaultValue;
|
||||
const previous = previousValue(key);
|
||||
if (previous.length > 0 && previous !== legacyDefault) return previous;
|
||||
return defaultValue;
|
||||
};
|
||||
let logRoot: string;
|
||||
let logDay: string;
|
||||
let logPrefix: string;
|
||||
@@ -134,7 +140,7 @@ export function writeComposeEnv(config: UniDeskConfig, freshLogPrefix: boolean):
|
||||
UNIDESK_CODE_QUEUE_MINIMAX_API_KEY: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_API_KEY") || runtimeSecret("MINIMAX_API_KEY"),
|
||||
UNIDESK_CODE_QUEUE_MINIMAX_MODEL: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_MODEL") || runtimeSecret("MINIMAX_MODEL") || "MiniMax-M2.7",
|
||||
UNIDESK_CODE_QUEUE_MINIMAX_API_BASE: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_API_BASE") || runtimeSecret("MINIMAX_API_BASE") || "https://api.minimaxi.com/v1",
|
||||
UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS: runtimeSecret("UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS") || "60000",
|
||||
UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS: runtimeSecretWithDefault("UNIDESK_CODE_QUEUE_MINIMAX_JUDGE_TIMEOUT_MS", "90000", "60000"),
|
||||
UNIDESK_CODE_QUEUE_REMOTE_WORKDIR: runtimeSecret("UNIDESK_CODE_QUEUE_REMOTE_WORKDIR") || "/home/ubuntu",
|
||||
UNIDESK_CODE_QUEUE_EXECUTION_PROVIDER_IDS: runtimeSecret("UNIDESK_CODE_QUEUE_EXECUTION_PROVIDER_IDS") || "D601",
|
||||
UNIDESK_CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID: runtimeSecret("UNIDESK_CODE_QUEUE_DEV_CONTAINER_DEFAULT_PROVIDER_ID") || "D601",
|
||||
|
||||
+15
-5
@@ -3,7 +3,7 @@ import { type UniDeskConfig } from "./config";
|
||||
import { type DebugDispatchCommand, isDebugDispatchCommand } from "./debug";
|
||||
import { summarizeMicroserviceProxyResponse } from "./microservices";
|
||||
import { isSshSkillDiscoveryArgs, parseSshArgs } from "./ssh";
|
||||
import { codexOutputQueryAsync, codexTaskQueryAsync } from "./code-queue";
|
||||
import { codexJudgeQueryAsync, codexOutputQueryAsync, codexTaskQueryAsync } from "./code-queue";
|
||||
|
||||
export interface RemoteCliOptions {
|
||||
host: string | null;
|
||||
@@ -470,16 +470,26 @@ async function remoteMicroservice(session: FrontendSession, args: string[]): Pro
|
||||
|
||||
async function remoteCodeQueue(session: FrontendSession, args: string[]): Promise<unknown> {
|
||||
const action = args[1] ?? "task";
|
||||
if (action !== "task" && action !== "summary" && action !== "show" && action !== "output") {
|
||||
throw new Error("remote codex command must be: codex task <taskId> or codex output <taskId>");
|
||||
if (action !== "task" && action !== "summary" && action !== "show" && action !== "output" && action !== "judge") {
|
||||
throw new Error("remote codex command must be: codex task <taskId>, codex output <taskId>, or codex judge <taskId> --attempt N");
|
||||
}
|
||||
const taskId = args[2];
|
||||
if (taskId === undefined || taskId.length === 0) throw new Error(`codex ${action} requires task id`);
|
||||
const fetcher = (path: string): Promise<FetchJsonResult> => frontendJson(session, path, undefined, 24_000);
|
||||
const fetcher = (path: string, init?: { method?: string; body?: unknown }): Promise<FetchJsonResult> => {
|
||||
const requestInit = init === undefined
|
||||
? undefined
|
||||
: {
|
||||
method: init.method,
|
||||
body: init.body === undefined ? undefined : JSON.stringify(init.body),
|
||||
};
|
||||
return frontendJson(session, path, requestInit, action === "judge" ? 130_000 : 24_000);
|
||||
};
|
||||
return {
|
||||
transport: "frontend",
|
||||
result: action === "output"
|
||||
? await codexOutputQueryAsync(taskId, args.slice(3), fetcher)
|
||||
: action === "judge"
|
||||
? await codexJudgeQueryAsync(taskId, args.slice(3), fetcher)
|
||||
: await codexTaskQueryAsync(taskId, args.slice(3), fetcher),
|
||||
};
|
||||
}
|
||||
@@ -538,7 +548,7 @@ async function runRemoteCliOverFrontend(options: RemoteCliOptions, config: UniDe
|
||||
emitRemoteJson(name, {
|
||||
transport: "frontend",
|
||||
baseUrl: session.baseUrl,
|
||||
commands: ["debug health", "debug dispatch", "debug task", "ssh <providerId> <command>", "ssh <providerId> skills", "microservice list", "microservice status <id>", "microservice health <id>", "microservice proxy <id> <path>", "codex task <taskId>"],
|
||||
commands: ["debug health", "debug dispatch", "debug task", "ssh <providerId> <command>", "ssh <providerId> skills", "microservice list", "microservice status <id>", "microservice health <id>", "microservice proxy <id> <path>", "codex task <taskId>", "codex judge <taskId> --attempt N"],
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user