6528 lines
291 KiB
TypeScript
6528 lines
291 KiB
TypeScript
import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||
import { runCommand } from "./command";
|
||
import { type UniDeskConfig, repoRoot, rootPath } from "./config";
|
||
import { coreInternalFetch } from "./microservices";
|
||
import { previewJson } from "./preview";
|
||
import { createSteerId, type SteerDeliveryState } from "../../src/components/microservices/code-queue/src/steer-confirmation";
|
||
import {
|
||
codeAgentPortForModel,
|
||
codeExecutionModes,
|
||
codeModelPorts as sharedCodeModelPorts,
|
||
defaultCodeModels as sharedDefaultCodeModels,
|
||
normalizeCodeExecutionMode,
|
||
normalizeRequestedCodeExecutionMode,
|
||
opencodeModels as sharedOpencodeModels,
|
||
requestedCodeExecutionModeIsRecognized,
|
||
} from "../../src/components/microservices/code-queue/src/code-agent/common";
|
||
|
||
const defaultToolLimit = 3;
|
||
const defaultTraceLimit = 80;
|
||
const maxTraceLimit = 500;
|
||
const defaultOutputLimit = 20;
|
||
const defaultOutputPreviewChars = 500;
|
||
const maxOutputPreviewChars = 1200;
|
||
const defaultTasksLimit = 20;
|
||
const defaultQueuesLimit = 8;
|
||
const maxTasksLimit = 100;
|
||
const tasksOverviewSourceFetchLimit = 200;
|
||
const supervisorSectionReturnedLimit = 3;
|
||
const supervisorRecentCompletedLimit = 3;
|
||
const supervisorPromptPreviewChars = 70;
|
||
const supervisorBodyPreviewChars = 70;
|
||
const supervisorRecentBodyPreviewChars = 50;
|
||
const unreadTriageCountLimit = 12;
|
||
const diagnosticsIdPreviewLimit = 3;
|
||
const diagnosticsReasonPreviewLimit = 2;
|
||
const mutationQueueIdPreviewLimit = 15;
|
||
const steerPromptPreviewChars = 320;
|
||
const rawCodeQueueOverviewCommand = "bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview?limit=30 --raw --full";
|
||
const sandboxLikeExecutionModes = new Set(["full-access", "danger-full-access", "workspace-write", "read-only"]);
|
||
const detailAttemptReturnedLimit = 3;
|
||
const detailInitialPromptPreviewChars = 1200;
|
||
const detailBasePromptPreviewChars = 800;
|
||
const detailLastAssistantPreviewChars = 1200;
|
||
const minimaxSubmitModel = "minimax-m2.7";
|
||
const deepseekSubmitModel = "deepseek-chat";
|
||
const gptSubmitModel = "gpt-5.5";
|
||
const submitLockWaitMs = 60_000;
|
||
const submitLockPollMs = 250;
|
||
const submitLockStaleMs = 120_000;
|
||
const submitThrottleMs = nonNegativeIntegerEnv("UNIDESK_CODEX_SUBMIT_THROTTLE_MS", 2000);
|
||
const defaultSteerRetryAttempts = 2;
|
||
const maxSteerRetryAttempts = 3;
|
||
const defaultSteerRetryDelayMs = 750;
|
||
const maxSteerRetryDelayMs = 5_000;
|
||
|
||
interface CodexTaskOptions {
|
||
detail: boolean;
|
||
trace: boolean;
|
||
traceLimit: number;
|
||
traceMode: "tail" | "after" | "before";
|
||
afterSeq: number;
|
||
beforeSeq: number | null;
|
||
toolLimit: number;
|
||
full: boolean;
|
||
rawSummary: boolean;
|
||
}
|
||
|
||
interface CodexOutputOptions {
|
||
requestedLimit: number;
|
||
limit: number;
|
||
mode: "tail" | "after" | "before";
|
||
afterSeq: number;
|
||
beforeSeq: number | null;
|
||
fullText: boolean;
|
||
maxTextChars: number;
|
||
}
|
||
|
||
interface CodexJudgeOptions {
|
||
attempt: number | null;
|
||
dryRun: boolean;
|
||
includePrompt: boolean;
|
||
}
|
||
|
||
type LiveTestAuthorizationClass = "read-only" | "live-read" | "live-mutating";
|
||
type PromptLintSeverity = "info" | "warning" | "block";
|
||
type PromptLintDisposition = "ready" | "review" | "needs-authorization";
|
||
|
||
interface CodexPromptLintOptions {
|
||
prompt: string;
|
||
}
|
||
|
||
interface PromptLintSignal {
|
||
id: string;
|
||
severity: PromptLintSeverity;
|
||
matched: boolean;
|
||
evidence: string[];
|
||
message: string;
|
||
}
|
||
|
||
interface PromptLiveAuthorizationLint {
|
||
ok: boolean;
|
||
dryRun: true;
|
||
mutation: false;
|
||
dispatchDisposition: PromptLintDisposition;
|
||
declaredClass: LiveTestAuthorizationClass | null;
|
||
effectiveClass: LiveTestAuthorizationClass;
|
||
requiredClass: LiveTestAuthorizationClass;
|
||
defaultedReadOnly: boolean;
|
||
liveMutationAuthorized: boolean;
|
||
promptShape: {
|
||
chars: number;
|
||
lines: number;
|
||
textEchoed: false;
|
||
};
|
||
requiredPromptFields: {
|
||
devTestClass: {
|
||
present: boolean;
|
||
value: LiveTestAuthorizationClass | null;
|
||
allowedValues: LiveTestAuthorizationClass[];
|
||
};
|
||
allowedLiveMutation: {
|
||
present: boolean;
|
||
nonNone: boolean;
|
||
requiredWhen: "live-mutating";
|
||
};
|
||
forbiddenActions: {
|
||
present: boolean;
|
||
};
|
||
closeoutFields: {
|
||
present: boolean;
|
||
};
|
||
};
|
||
signals: PromptLintSignal[];
|
||
missingOrContradictory: string[];
|
||
policy: {
|
||
defaultWhenUnclassified: "read-only";
|
||
promptLintOnly: true;
|
||
accessesLiveService: false;
|
||
printsPromptText: false;
|
||
reference: string;
|
||
};
|
||
commands: {
|
||
lintFile: string;
|
||
submitDryRun: string;
|
||
steerDryRun: string;
|
||
};
|
||
}
|
||
|
||
interface CodexSubmitOptions {
|
||
prompt: string;
|
||
queueId: string | undefined;
|
||
providerId: string | undefined;
|
||
cwd: string | undefined;
|
||
model: string | undefined;
|
||
reasoningEffort: string | undefined;
|
||
executionMode: string | undefined;
|
||
maxAttempts: number | undefined;
|
||
referenceTaskIds: string[];
|
||
dryRun: boolean;
|
||
}
|
||
|
||
type SubmitRoute = "minimax-opencode" | "deepseek-opencode" | "gpt-5.5-codex" | "commander-human-only";
|
||
type SubmitRouteSignalSeverity = "info" | "warning" | "block";
|
||
|
||
interface SubmitRouteSignal {
|
||
id: string;
|
||
severity: SubmitRouteSignalSeverity;
|
||
matched: boolean;
|
||
evidence: string[];
|
||
message: string;
|
||
}
|
||
|
||
interface SubmitRoutingRecommendation {
|
||
route: SubmitRoute;
|
||
recommendedRunner: "opencode" | "codex" | "commander";
|
||
recommendedModel: string | null;
|
||
confidence: "medium" | "high";
|
||
reason: string;
|
||
signals: SubmitRouteSignal[];
|
||
riskControls: {
|
||
promptSelfContained: boolean;
|
||
issueIsNotOnlySource: boolean;
|
||
noProdRestartSecretOrDbWrite: boolean;
|
||
noRuntimeCoreOrReleaseWork: boolean;
|
||
evidenceRequiredByPrompt: boolean;
|
||
mediumComplexityCandidate: boolean;
|
||
commanderMustReviewUnread: true;
|
||
};
|
||
explicitRequest: {
|
||
model: string | null;
|
||
runner: "opencode" | "codex" | null;
|
||
note: string | null;
|
||
};
|
||
routingPolicy: {
|
||
dryRunOnly: true;
|
||
doesNotChangeSubmittedPayload: true;
|
||
prodMiniMaxAssumedAvailable: false;
|
||
prodDeepSeekAssumedAvailable: false;
|
||
runtimeAdmissionUnchanged: true;
|
||
};
|
||
policyContract: {
|
||
selectionPrinciples: string[];
|
||
concurrency: {
|
||
gpt55Routine: number;
|
||
gpt55BurstMax: number;
|
||
minimaxSimpleMax: number;
|
||
deepseekMediumDefault: number;
|
||
};
|
||
modelTiers: Array<{
|
||
model: string;
|
||
runner: "opencode" | "codex";
|
||
taskRisk: string;
|
||
requiredGuards: string[];
|
||
}>;
|
||
externalProvider429: {
|
||
commanderAction: string;
|
||
interveneWhen: string[];
|
||
};
|
||
};
|
||
}
|
||
|
||
interface CodexSteerOptions {
|
||
prompt: string;
|
||
steerId: string | undefined;
|
||
dryRun: boolean;
|
||
retryAttempts: number;
|
||
retryDelayMs: number;
|
||
full: boolean;
|
||
raw: boolean;
|
||
}
|
||
|
||
interface CodexSteerConfirmOptions {
|
||
steerId: string;
|
||
raw: boolean;
|
||
}
|
||
|
||
type CodexSteerFailureReason =
|
||
| "backend-core-unreachable"
|
||
| "code-queue-microservice-unregistered"
|
||
| "proxy-unauthorized"
|
||
| "proxy-404"
|
||
| "steer-endpoint-404"
|
||
| "upstream-runtime-rejected"
|
||
| "stable-proxy-failed"
|
||
| "invalid-proxy-response";
|
||
|
||
type CodexSteerAcceptanceStatus = "accepted" | "not_accepted" | "accepted_response_timeout" | "unknown";
|
||
|
||
interface ClassifiedCodexSteerError {
|
||
reason: CodexSteerFailureReason;
|
||
scope: "backend-core" | "stable-proxy" | "code-queue-runtime" | "unknown";
|
||
status: number | null;
|
||
exitCode: number | null;
|
||
retryable: boolean;
|
||
message: string;
|
||
requestPath: string;
|
||
stableProxyPath: string;
|
||
upstreamBodyPreview: unknown;
|
||
rawProxyEquivalent: string;
|
||
recommendedCrossChecks: string[];
|
||
}
|
||
|
||
interface CodexSteerAttemptSummary {
|
||
attempt: number;
|
||
ok: boolean;
|
||
durationMs: number;
|
||
status: number | null;
|
||
exitCode: number | null;
|
||
reason: CodexSteerFailureReason | null;
|
||
scope: ClassifiedCodexSteerError["scope"] | null;
|
||
retryable: boolean;
|
||
message: string | null;
|
||
deliveryState?: CodexSteerDeliveryState | null;
|
||
steerId?: string | null;
|
||
}
|
||
|
||
type CodexSteerDeliveryState = SteerDeliveryState;
|
||
|
||
interface CompactTaskMutationResponseOptions {
|
||
fullPrompt?: boolean;
|
||
}
|
||
|
||
interface CompactSubmitQueueConfirmationOptions {
|
||
submittedTasks?: Record<string, unknown>[];
|
||
idPreviewLimit?: number;
|
||
}
|
||
|
||
interface CodexTasksOptions {
|
||
queueId: string | undefined;
|
||
requestedLimit: number;
|
||
limit: number;
|
||
beforeId: string | undefined;
|
||
unreadOnly: boolean;
|
||
statusFilter: string[] | null;
|
||
view: "supervisor" | "full";
|
||
}
|
||
|
||
type CodexUnreadAction = "summary" | "mark-read";
|
||
|
||
interface CodexUnreadOptions {
|
||
queueId: string | undefined;
|
||
requestedLimit: number;
|
||
limit: number;
|
||
beforeId: string | undefined;
|
||
statusFilter: string[] | null;
|
||
repoFilter: string | undefined;
|
||
issueFilter: string | undefined;
|
||
action: CodexUnreadAction;
|
||
confirm: boolean;
|
||
dryRun: boolean;
|
||
}
|
||
|
||
interface CodexTasksEntry {
|
||
taskId: string;
|
||
queueId: string | null;
|
||
status: string | null;
|
||
statusLabel: string | null;
|
||
awaitingTerminalJudge?: boolean;
|
||
closeoutState?: "awaiting-terminal-or-judge" | "awaiting-judge";
|
||
closeoutHint?: string;
|
||
finalResponseAt?: unknown;
|
||
currentAttempt: number | null;
|
||
updatedAt: string | null;
|
||
finishedAt: string | null;
|
||
readAt: string | null;
|
||
unread: boolean;
|
||
unreadTerminal: boolean;
|
||
promptPreview: Record<string, unknown>;
|
||
queuedReason: Record<string, unknown> | null;
|
||
lastAssistantMessage: Record<string, unknown> | null;
|
||
commands: {
|
||
show: string;
|
||
trace: string;
|
||
output: string;
|
||
steer: string;
|
||
read: string;
|
||
full: string;
|
||
detail: string;
|
||
};
|
||
}
|
||
|
||
interface SupervisorTextSummary {
|
||
text: string;
|
||
chars: number;
|
||
truncated: boolean;
|
||
}
|
||
|
||
interface SupervisorMessageSummary extends SupervisorTextSummary {
|
||
at: unknown;
|
||
source: unknown;
|
||
}
|
||
|
||
interface CodexTasksSupervisorEntry {
|
||
id: string;
|
||
queue: string | null;
|
||
status: string | null;
|
||
statusLabel?: string;
|
||
awaitingTerminalJudge?: boolean;
|
||
closeoutState?: "awaiting-terminal-or-judge" | "awaiting-judge";
|
||
closeoutHint?: string;
|
||
finalResponseAt?: unknown;
|
||
attempt: number | null;
|
||
updatedAt: string | null;
|
||
finishedAt?: string | null;
|
||
unreadTerminal?: boolean;
|
||
issues: string[];
|
||
kind: "direct-progress" | "deployment-fix" | "verification" | "management-noise" | "documentation" | "unknown";
|
||
noise?: boolean;
|
||
prompt: string;
|
||
promptChars: number;
|
||
promptTruncated?: boolean;
|
||
last?: string;
|
||
lastAt?: unknown;
|
||
lastChars?: number;
|
||
lastTruncated?: boolean;
|
||
queuedReason?: Record<string, unknown>;
|
||
read?: string;
|
||
}
|
||
|
||
interface CodexTasksSection<T = CodexTasksEntry> {
|
||
count: number;
|
||
returned: number;
|
||
truncated: boolean;
|
||
hasMore: boolean;
|
||
commands: {
|
||
next: string | null;
|
||
full: string;
|
||
showTemplate?: string;
|
||
detailTemplate?: string;
|
||
traceTemplate?: string;
|
||
outputTemplate?: string;
|
||
taskFullTemplate?: string;
|
||
readTemplate?: string;
|
||
};
|
||
items: T[];
|
||
}
|
||
|
||
interface CodexTasksDegraded {
|
||
summaryFetchFailedTaskIds: string[];
|
||
summaryFetchErrorCount: number;
|
||
summaryFetchOmittedTaskCount: number;
|
||
reason: string;
|
||
}
|
||
|
||
interface SupervisorStatusCounts {
|
||
counts: Record<string, number>;
|
||
exact: boolean;
|
||
source: "queue-summary-counts" | "overview-page-fallback";
|
||
scope: "all-queues" | "queue";
|
||
queueId: string | null;
|
||
}
|
||
|
||
interface CodexQueuesOptions {
|
||
full: boolean;
|
||
limit: number;
|
||
offset: number;
|
||
page: number;
|
||
}
|
||
|
||
interface CodexPrPreflightOptions {
|
||
remote: boolean;
|
||
pushDryRun: boolean;
|
||
pushDryRunRef: string | undefined;
|
||
prCreateDryRun: boolean;
|
||
prCreateDryRunHead: string | undefined;
|
||
issueNumber: number | null;
|
||
full: boolean;
|
||
}
|
||
|
||
interface CodexSkillsSyncOptions {
|
||
dryRun: boolean;
|
||
full: boolean;
|
||
}
|
||
|
||
type CodeQueuePrPreflightFailureKind = "auth-missing" | "runner-skills-blocker" | "github-transient" | "proxy-gap" | "git-remote-gap" | "control-plane-missing" | "target-stack-not-running";
|
||
type CodeQueueObservationGapKind = "runner-local-observation-gap" | "control-plane-observation-gap" | null;
|
||
|
||
interface CodeQueuePrPreflightTransport {
|
||
config?: UniDeskConfig | null;
|
||
coreFetch?: CodexResponseFetcher;
|
||
remoteMainServerPrPreflight?: (optionArgs: string[], config: UniDeskConfig | null) => unknown;
|
||
}
|
||
|
||
type CodexRequestInit = { method?: string; body?: unknown };
|
||
type CodexResponseFetcher = (path: string, init?: CodexRequestInit) => unknown;
|
||
type AsyncCodexResponseFetcher = (path: string, init?: CodexRequestInit) => Promise<unknown>;
|
||
|
||
const codeQueueProxyPrefix = "/api/microservices/code-queue/proxy";
|
||
|
||
function codeQueueProxyPath(path: string): string {
|
||
if (!path.startsWith("/")) throw new Error("Code Queue proxy path must start with /");
|
||
return `${codeQueueProxyPrefix}${path}`;
|
||
}
|
||
|
||
function codeQueueProxyEquivalentCommand(targetPath: string, bodyJson: string): string {
|
||
return `bun scripts/cli.ts microservice proxy code-queue ${targetPath} --method POST --body-json '${bodyJson}'`;
|
||
}
|
||
|
||
function steerConfirmationPath(taskId: string, steerId: string): string {
|
||
return `/api/tasks/${encodeURIComponent(taskId)}/steer-confirmation${queryString({ steerId })}`;
|
||
}
|
||
|
||
function steerConfirmationCommand(taskId: string, steerId: string): string {
|
||
return `bun scripts/cli.ts codex steer-confirm ${taskId} --steer-id ${steerId}`;
|
||
}
|
||
|
||
function rawSteerConfirmationCommand(taskId: string, steerId: string): string {
|
||
return `bun scripts/cli.ts microservice proxy code-queue ${steerConfirmationPath(taskId, steerId)} --raw`;
|
||
}
|
||
|
||
function nonNegativeIntegerEnv(name: string, fallback: number): number {
|
||
const raw = process.env[name];
|
||
if (raw === undefined || raw.trim().length === 0) return fallback;
|
||
const parsed = Number(raw);
|
||
if (!Number.isFinite(parsed) || parsed < 0) return fallback;
|
||
return Math.floor(parsed);
|
||
}
|
||
|
||
function sleepSync(ms: number): void {
|
||
if (ms <= 0) return;
|
||
const buffer = new SharedArrayBuffer(4);
|
||
Atomics.wait(new Int32Array(buffer), 0, 0, ms);
|
||
}
|
||
|
||
function acquireSubmitLock(): { lockPath: string; acquiredAfterMs: number; release: () => void } {
|
||
const lockRoot = rootPath(".state", "locks");
|
||
const lockPath = rootPath(".state", "locks", "codex-submit.lock.d");
|
||
mkdirSync(lockRoot, { recursive: true });
|
||
const startedAt = Date.now();
|
||
while (Date.now() - startedAt <= submitLockWaitMs) {
|
||
try {
|
||
mkdirSync(lockPath);
|
||
writeFileSync(rootPath(".state", "locks", "codex-submit.lock.d", "owner.json"), `${JSON.stringify({
|
||
pid: process.pid,
|
||
createdAt: new Date().toISOString(),
|
||
repoRoot,
|
||
}, null, 2)}\n`, "utf8");
|
||
let released = false;
|
||
return {
|
||
lockPath,
|
||
acquiredAfterMs: Date.now() - startedAt,
|
||
release: () => {
|
||
if (released) return;
|
||
released = true;
|
||
rmSync(lockPath, { recursive: true, force: true });
|
||
},
|
||
};
|
||
} catch (error) {
|
||
const code = (error as { code?: string }).code;
|
||
if (code !== "EEXIST") throw error;
|
||
let stale = false;
|
||
try {
|
||
stale = Date.now() - statSync(lockPath).mtimeMs > submitLockStaleMs;
|
||
} catch {
|
||
stale = true;
|
||
}
|
||
if (stale) {
|
||
rmSync(lockPath, { recursive: true, force: true });
|
||
continue;
|
||
}
|
||
sleepSync(submitLockPollMs);
|
||
}
|
||
}
|
||
throw new Error(`codex submit lock timed out after ${submitLockWaitMs}ms: ${lockPath}`);
|
||
}
|
||
|
||
function runWithSubmitLock<T>(operation: () => T): { result: T; lock: Record<string, unknown> } {
|
||
const lock = acquireSubmitLock();
|
||
const operationStartedAt = Date.now();
|
||
try {
|
||
sleepSync(submitThrottleMs);
|
||
const result = operation();
|
||
return {
|
||
result,
|
||
lock: {
|
||
path: lock.lockPath,
|
||
acquiredAfterMs: lock.acquiredAfterMs,
|
||
heldMs: Date.now() - operationStartedAt,
|
||
throttleMs: submitThrottleMs,
|
||
staleMs: submitLockStaleMs,
|
||
mode: "local-atomic-directory-submit-serialization",
|
||
},
|
||
};
|
||
} finally {
|
||
lock.release();
|
||
}
|
||
}
|
||
|
||
function requireTaskId(value: string | undefined, command: string): string {
|
||
if (value === undefined || value.trim().length === 0) throw new Error(`${command} requires task id`);
|
||
return value.trim();
|
||
}
|
||
|
||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
||
}
|
||
|
||
function compactObjectFields(record: Record<string, unknown> | null, keys: string[]): Record<string, unknown> | null {
|
||
if (record === null) return null;
|
||
const selected: Record<string, unknown> = {};
|
||
for (const key of keys) {
|
||
if (record[key] !== undefined) selected[key] = record[key];
|
||
}
|
||
return Object.keys(selected).length > 0 ? selected : null;
|
||
}
|
||
|
||
function asArray(value: unknown): unknown[] {
|
||
return Array.isArray(value) ? value : [];
|
||
}
|
||
|
||
function asString(value: unknown): string {
|
||
return typeof value === "string" ? value : "";
|
||
}
|
||
|
||
function asNumber(value: unknown, fallback = 0): number {
|
||
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
||
}
|
||
|
||
function finiteNumber(value: unknown): number | null {
|
||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
||
}
|
||
|
||
function asBoolean(value: unknown): boolean {
|
||
return value === true || value === 1 || value === "1" || value === "true";
|
||
}
|
||
|
||
function stringList(value: unknown): string[] {
|
||
return asArray(value).map((item) => String(item ?? "")).filter((item) => item.length > 0);
|
||
}
|
||
|
||
function textPreview(value: string, maxChars: number): Record<string, unknown> {
|
||
const truncated = value.length > maxChars;
|
||
return {
|
||
text: truncated ? value.slice(0, maxChars) : value,
|
||
chars: value.length,
|
||
truncated,
|
||
omittedChars: truncated ? value.length - maxChars : 0,
|
||
};
|
||
}
|
||
|
||
function compactInlinePreview(value: string, maxChars: number): Record<string, unknown> {
|
||
return textPreview(value.replace(/\s+/gu, " ").trim(), maxChars);
|
||
}
|
||
|
||
function supervisorTextSummary(value: string, maxChars: number): SupervisorTextSummary {
|
||
const compact = value.replace(/\s+/gu, " ").trim();
|
||
const truncated = compact.length > maxChars;
|
||
return {
|
||
text: truncated ? compact.slice(0, maxChars) : compact,
|
||
chars: compact.length,
|
||
truncated,
|
||
};
|
||
}
|
||
|
||
function fmtDuration(ms: unknown): string {
|
||
const value = Number(ms);
|
||
if (!Number.isFinite(value) || value < 0) return "--";
|
||
const totalSeconds = Math.floor(value / 1000);
|
||
const minutes = Math.floor(totalSeconds / 60);
|
||
const seconds = totalSeconds % 60;
|
||
if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
||
return `${seconds}s`;
|
||
}
|
||
|
||
function upstreamError(response: unknown): string {
|
||
const record = asRecord(response);
|
||
if (record === null) return String(response);
|
||
const body = asRecord(record.body);
|
||
const bodyError = body?.error;
|
||
if (typeof bodyError === "string") {
|
||
const requestId = typeof body?.requestId === "string" ? ` requestId=${body.requestId}` : "";
|
||
const providerId = typeof body?.providerId === "string" ? ` providerId=${body.providerId}` : "";
|
||
const observationHint = /\b(microservice|proxy|provider|tunnel|k3sctl|adapter|backend-core|offline|unavailable|timed out|timeout)\b/iu.test(bodyError)
|
||
? " The stable code-queue proxy failure is observation-path evidence only; from the supervisor or main-server CLI environment, also check `bun scripts/cli.ts codex queues`, `codex tasks`, or `codex task <taskId>` before declaring the work unevaluable or stopped."
|
||
: "";
|
||
return `${bodyError}${providerId}${requestId}${observationHint}`;
|
||
}
|
||
if (typeof record.codeQueueObservationNote === "string") {
|
||
const commands = Array.isArray(record.recommendedCommands) ? ` recommendedCommands=${JSON.stringify(record.recommendedCommands)}` : "";
|
||
return `${record.codeQueueObservationNote}${commands}`;
|
||
}
|
||
const status = typeof record.status === "number" ? `HTTP ${record.status}` : "upstream request failed";
|
||
return `${status}: ${JSON.stringify(response).slice(0, 1200)}`;
|
||
}
|
||
|
||
function parseJsonRecord(text: string): Record<string, unknown> | null {
|
||
if (text.trim().length === 0) return null;
|
||
try {
|
||
const parsed = JSON.parse(text) as unknown;
|
||
return asRecord(parsed);
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function unwrapCodexResponse(response: unknown): { upstream: { ok: unknown; status: unknown }; body: Record<string, unknown> } {
|
||
const record = asRecord(response);
|
||
if (record?.ok !== true) throw new Error(upstreamError(response));
|
||
const body = asRecord(record.body);
|
||
if (body?.ok !== true) throw new Error(upstreamError(response));
|
||
return { upstream: { ok: record.ok, status: record.status }, body };
|
||
}
|
||
|
||
function responseStatus(response: Record<string, unknown> | null): number | null {
|
||
const value = response?.status;
|
||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
||
}
|
||
|
||
function responseExitCode(response: Record<string, unknown> | null): number | null {
|
||
const value = response?.exitCode ?? response?.commandExitCode;
|
||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
||
}
|
||
|
||
function responseBody(response: Record<string, unknown> | null): Record<string, unknown> | null {
|
||
return asRecord(response?.body);
|
||
}
|
||
|
||
function responseErrorMessage(response: Record<string, unknown> | null): string {
|
||
const body = responseBody(response);
|
||
const bodyError = body?.error;
|
||
const providerError = body?.providerError;
|
||
if (typeof bodyError === "string" && bodyError.length > 0) {
|
||
return typeof providerError === "string" && providerError.length > 0 ? `${bodyError}: ${providerError}` : bodyError;
|
||
}
|
||
if (typeof response?.codeQueueObservationNote === "string") return response.codeQueueObservationNote;
|
||
if (typeof response?.stderrTail === "string" && response.stderrTail.length > 0) return response.stderrTail;
|
||
if (typeof response?.commandStderrTail === "string" && response.commandStderrTail.length > 0) return response.commandStderrTail;
|
||
return "Code Queue steer request failed";
|
||
}
|
||
|
||
function classifySteerFailure(response: unknown, targetPath: string, stableProxyPath: string, rawProxyEquivalent: string): ClassifiedCodexSteerError {
|
||
const record = asRecord(response);
|
||
const status = responseStatus(record);
|
||
const exitCode = responseExitCode(record);
|
||
const body = responseBody(record);
|
||
const message = responseErrorMessage(record);
|
||
const lowerMessage = message.toLowerCase();
|
||
const bodyPath = typeof body?.path === "string" ? body.path : "";
|
||
const bodyStage = typeof body?.stage === "string" ? body.stage : "";
|
||
const bodyServiceId = typeof body?.serviceId === "string" ? body.serviceId : "";
|
||
|
||
let reason: CodexSteerFailureReason = "invalid-proxy-response";
|
||
let scope: ClassifiedCodexSteerError["scope"] = "unknown";
|
||
let retryable = false;
|
||
|
||
if (record === null || status === null && exitCode !== null) {
|
||
reason = "backend-core-unreachable";
|
||
scope = "backend-core";
|
||
retryable = true;
|
||
} else if (status === 401 || status === 403) {
|
||
reason = "proxy-unauthorized";
|
||
scope = "stable-proxy";
|
||
retryable = false;
|
||
} else if (status === 404 && /microservice not found: code-queue/u.test(lowerMessage)) {
|
||
reason = "code-queue-microservice-unregistered";
|
||
scope = "stable-proxy";
|
||
retryable = false;
|
||
} else if (status === 404 && (lowerMessage === "task not found" || lowerMessage === "not found" || bodyPath === targetPath || bodyServiceId === "code-queue")) {
|
||
reason = "steer-endpoint-404";
|
||
scope = "code-queue-runtime";
|
||
retryable = false;
|
||
} else if (status === 404) {
|
||
reason = "proxy-404";
|
||
scope = "stable-proxy";
|
||
retryable = false;
|
||
} else if (status === 400 || status === 405 || status === 409 || /active run|steerable|scheduler-only|read-only|prompt is required/u.test(lowerMessage)) {
|
||
reason = "upstream-runtime-rejected";
|
||
scope = "code-queue-runtime";
|
||
retryable = status === 409;
|
||
} else if (status === 502 || status === 503 || status === 504 || /proxy|tunnel|provider|k3sctl|adapter|timed out|timeout|unavailable|disconnected/u.test(lowerMessage) || bodyStage.length > 0) {
|
||
reason = "stable-proxy-failed";
|
||
scope = "stable-proxy";
|
||
retryable = true;
|
||
}
|
||
|
||
return {
|
||
reason,
|
||
scope,
|
||
status,
|
||
exitCode,
|
||
retryable,
|
||
message,
|
||
requestPath: targetPath,
|
||
stableProxyPath,
|
||
upstreamBodyPreview: previewJson(body ?? record, { maxDepth: 3, maxArrayItems: 3, maxObjectKeys: 16, maxStringLength: 320 }),
|
||
rawProxyEquivalent,
|
||
recommendedCrossChecks: [
|
||
"bun scripts/cli.ts codex queues",
|
||
"bun scripts/cli.ts codex tasks --view supervisor --limit 20",
|
||
"bun scripts/cli.ts codex task <taskId>",
|
||
"bun scripts/cli.ts microservice health code-queue",
|
||
"bun scripts/cli.ts microservice diagnostics code-queue",
|
||
],
|
||
};
|
||
}
|
||
|
||
function compactSteerFailureDiagnostics(diagnostics: ClassifiedCodexSteerError, includeDetails: boolean): Record<string, unknown> {
|
||
return {
|
||
reason: diagnostics.reason,
|
||
scope: diagnostics.scope,
|
||
status: diagnostics.status,
|
||
exitCode: diagnostics.exitCode,
|
||
retryable: diagnostics.retryable,
|
||
message: diagnostics.message,
|
||
recommendedCrossChecks: diagnostics.recommendedCrossChecks,
|
||
...(includeDetails ? {
|
||
upstreamBodyPreview: diagnostics.upstreamBodyPreview,
|
||
rawProxyEquivalent: diagnostics.rawProxyEquivalent,
|
||
} : {}),
|
||
};
|
||
}
|
||
|
||
function compactSteerTraceConfirmation(value: unknown, taskId: string, steerId: string): Record<string, unknown> {
|
||
const record = asRecord(value) ?? {};
|
||
const confirmation = asRecord(record.confirmation) ?? record;
|
||
const trace = asRecord(confirmation.trace);
|
||
return {
|
||
taskId: asString(confirmation.taskId) || taskId,
|
||
steerId: asString(confirmation.steerId) || steerId,
|
||
found: confirmation.found === true,
|
||
accepted: confirmation.accepted === true,
|
||
deliveryState: asString(confirmation.deliveryState) || (confirmation.found === true ? "accepted" : "unknown"),
|
||
matchCount: asNumber(confirmation.matchCount, 0),
|
||
trace: trace === null ? null : {
|
||
seq: trace.seq ?? null,
|
||
at: trace.at ?? null,
|
||
method: trace.method ?? null,
|
||
steerId: trace.steerId ?? steerId,
|
||
promptChars: trace.promptChars ?? null,
|
||
promptHash: trace.promptHash ?? null,
|
||
promptOmitted: true,
|
||
source: trace.source ?? null,
|
||
},
|
||
duplicateSuppressionKey: confirmation.duplicateSuppressionKey ?? steerId,
|
||
promptOmitted: true,
|
||
};
|
||
}
|
||
|
||
function fetchSteerTraceConfirmation(taskId: string, steerId: string, fetcher: CodexResponseFetcher): Record<string, unknown> {
|
||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(steerConfirmationPath(taskId, steerId))));
|
||
return compactSteerTraceConfirmation(response.body, taskId, steerId);
|
||
}
|
||
|
||
function safeFetchSteerTraceConfirmation(taskId: string, steerId: string, fetcher: CodexResponseFetcher): Record<string, unknown> {
|
||
try {
|
||
return fetchSteerTraceConfirmation(taskId, steerId, fetcher);
|
||
} catch (error) {
|
||
return {
|
||
taskId,
|
||
steerId,
|
||
found: false,
|
||
accepted: false,
|
||
deliveryState: "unknown",
|
||
promptOmitted: true,
|
||
lookupError: error instanceof Error ? error.message : String(error),
|
||
commands: {
|
||
retryLookup: steerConfirmationCommand(taskId, steerId),
|
||
rawLookup: rawSteerConfirmationCommand(taskId, steerId),
|
||
},
|
||
};
|
||
}
|
||
}
|
||
|
||
function unwrapSteerResponse(response: unknown, targetPath: string, stableProxyPath: string, rawProxyEquivalent: string): { ok: true; upstream: { ok: unknown; status: unknown }; body: Record<string, unknown> } | { ok: false; diagnostics: ClassifiedCodexSteerError; rawResponse: unknown } {
|
||
const record = asRecord(response);
|
||
const body = responseBody(record);
|
||
if (record?.ok === true && body?.ok === true) return { ok: true, upstream: { ok: record.ok, status: record.status }, body };
|
||
return { ok: false, diagnostics: classifySteerFailure(response, targetPath, stableProxyPath, rawProxyEquivalent), rawResponse: response };
|
||
}
|
||
|
||
function terminalStatusFromTask(task: Record<string, unknown> | null): string {
|
||
const direct = asString(task?.terminalStatus);
|
||
if (direct.length > 0) return direct;
|
||
const attempts = asArray(task?.attempts).map((item) => asRecord(item)).filter((item): item is Record<string, unknown> => item !== null);
|
||
for (let index = attempts.length - 1; index >= 0; index -= 1) {
|
||
const status = asString(attempts[index]?.terminalStatus);
|
||
if (status.length > 0) return status;
|
||
}
|
||
return "";
|
||
}
|
||
|
||
function compactTerminalSteerRejection(taskId: string, steerId: string, response: unknown): Record<string, unknown> | null {
|
||
const record = asRecord(response);
|
||
const body = responseBody(record);
|
||
const task = asRecord(body?.task);
|
||
const status = asString(task?.status);
|
||
if (!isTerminalTaskStatus(status)) return null;
|
||
const terminalStatus = terminalStatusFromTask(task);
|
||
const lastUpdate = task?.updatedAt ?? task?.finishedAt ?? null;
|
||
return {
|
||
ok: false,
|
||
steer: {
|
||
accepted: false,
|
||
status: "not_accepted",
|
||
deliveryState: "not_accepted",
|
||
steerId,
|
||
reason: "task-already-terminal",
|
||
taskId,
|
||
taskStatus: status,
|
||
terminalStatus: terminalStatus || null,
|
||
lastUpdate,
|
||
updatedAt: task?.updatedAt ?? null,
|
||
finishedAt: task?.finishedAt ?? null,
|
||
retryable: false,
|
||
},
|
||
message: `task ${taskId} is already terminal (${status}); codex steer only applies to an active running turn`,
|
||
commands: {
|
||
show: `bun scripts/cli.ts codex task ${taskId}`,
|
||
read: `bun scripts/cli.ts codex read ${taskId}`,
|
||
followUpSubmit: `bun scripts/cli.ts codex submit --prompt-file <path> --reference-task-id ${taskId}`,
|
||
supervisor: `bun scripts/cli.ts codex tasks --view supervisor --limit ${defaultTasksLimit}`,
|
||
},
|
||
upstream: {
|
||
status: responseStatus(record),
|
||
error: asString(body?.error) || null,
|
||
},
|
||
};
|
||
}
|
||
|
||
function attachSteerDisclosure(
|
||
result: Record<string, unknown>,
|
||
disclosure: { full: boolean; raw: boolean },
|
||
response: unknown,
|
||
targetPath: string,
|
||
stableProxyPath: string,
|
||
rawProxyEquivalent: string,
|
||
): Record<string, unknown> {
|
||
if (!disclosure.full && !disclosure.raw) return result;
|
||
const record = asRecord(response);
|
||
const body = responseBody(record);
|
||
result.disclosure = {
|
||
defaultPolicy: "compact rejection; upstream body and raw response are only included with explicit --full or --raw",
|
||
full: disclosure.full,
|
||
raw: disclosure.raw,
|
||
defaultOmitted: ["request", "diagnostics.upstreamBodyPreview", "rawFailure"],
|
||
};
|
||
if (disclosure.full) {
|
||
result.request = {
|
||
path: targetPath,
|
||
stableProxyPath,
|
||
method: "POST",
|
||
bodySummary: {
|
||
promptOmitted: true,
|
||
},
|
||
};
|
||
result.diagnostics = {
|
||
upstreamBodyPreview: previewJson(body ?? record, { maxDepth: 4, maxArrayItems: 8, maxObjectKeys: 24, maxStringLength: 600 }),
|
||
rawProxyEquivalent,
|
||
};
|
||
}
|
||
if (disclosure.raw) result.rawFailure = response;
|
||
return result;
|
||
}
|
||
|
||
function steerSuccessAttempt(attempt: number, durationMs: number, upstream: { ok: unknown; status: unknown }, steerId?: string, deliveryState: CodexSteerDeliveryState = "accepted"): CodexSteerAttemptSummary {
|
||
const status = typeof upstream.status === "number" && Number.isFinite(upstream.status) ? upstream.status : null;
|
||
return {
|
||
attempt,
|
||
ok: true,
|
||
durationMs,
|
||
status,
|
||
exitCode: null,
|
||
reason: null,
|
||
scope: null,
|
||
retryable: false,
|
||
message: "steer accepted by Code Queue",
|
||
deliveryState,
|
||
steerId: steerId ?? null,
|
||
};
|
||
}
|
||
|
||
function steerFailureAttempt(attempt: number, durationMs: number, diagnostics: ClassifiedCodexSteerError): CodexSteerAttemptSummary {
|
||
return {
|
||
attempt,
|
||
ok: false,
|
||
durationMs,
|
||
status: diagnostics.status,
|
||
exitCode: diagnostics.exitCode,
|
||
reason: diagnostics.reason,
|
||
scope: diagnostics.scope,
|
||
retryable: diagnostics.retryable,
|
||
message: diagnostics.message,
|
||
deliveryState: null,
|
||
steerId: null,
|
||
};
|
||
}
|
||
|
||
function shouldRetrySteerFailure(diagnostics: ClassifiedCodexSteerError, attempt: number, maxAttempts: number): boolean {
|
||
if (attempt >= maxAttempts) return false;
|
||
return diagnostics.retryable === true && (diagnostics.reason === "stable-proxy-failed" || diagnostics.reason === "backend-core-unreachable");
|
||
}
|
||
|
||
function positiveIntegerOption(args: string[], names: string[], defaultValue: number, maxValue = Number.MAX_SAFE_INTEGER): number {
|
||
for (const name of names) {
|
||
const index = args.indexOf(name);
|
||
if (index === -1) continue;
|
||
const raw = args[index + 1];
|
||
const value = Number(raw);
|
||
if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`);
|
||
return Math.min(value, maxValue);
|
||
}
|
||
return defaultValue;
|
||
}
|
||
|
||
function nonNegativeNumberOption(args: string[], names: string[], defaultValue: number): number {
|
||
for (const name of names) {
|
||
const index = args.indexOf(name);
|
||
if (index === -1) continue;
|
||
const raw = args[index + 1];
|
||
const value = Number(raw);
|
||
if (!Number.isFinite(value) || value < 0) throw new Error(`${name} must be a non-negative number`);
|
||
return value;
|
||
}
|
||
return defaultValue;
|
||
}
|
||
|
||
function nonNegativeIntegerOption(args: string[], names: string[], defaultValue: number, maxValue = Number.MAX_SAFE_INTEGER): number {
|
||
for (const name of names) {
|
||
const index = args.indexOf(name);
|
||
if (index === -1) continue;
|
||
const raw = args[index + 1];
|
||
const value = Number(raw);
|
||
if (!Number.isInteger(value) || value < 0) throw new Error(`${name} must be a non-negative integer`);
|
||
return Math.min(value, maxValue);
|
||
}
|
||
return defaultValue;
|
||
}
|
||
|
||
function nullablePositiveNumberOption(args: string[], names: string[]): number | null {
|
||
for (const name of names) {
|
||
const index = args.indexOf(name);
|
||
if (index === -1) continue;
|
||
const raw = args[index + 1];
|
||
const value = Number(raw);
|
||
if (!Number.isFinite(value) || value <= 0) throw new Error(`${name} must be a positive number`);
|
||
return value;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function hasFlag(args: string[], name: string): boolean {
|
||
return args.includes(name);
|
||
}
|
||
|
||
function assertKnownOptions(args: string[], spec: { flags?: string[]; valueOptions?: string[] }, command: string): void {
|
||
const flags = new Set(spec.flags ?? []);
|
||
const valueOptions = new Set(spec.valueOptions ?? []);
|
||
for (let index = 0; index < args.length; index += 1) {
|
||
const arg = args[index] ?? "";
|
||
if (!arg.startsWith("--")) continue;
|
||
if (flags.has(arg)) continue;
|
||
if (valueOptions.has(arg)) {
|
||
const value = args[index + 1];
|
||
if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`);
|
||
index += 1;
|
||
continue;
|
||
}
|
||
throw new Error(`unsupported ${command} option: ${arg}`);
|
||
}
|
||
}
|
||
|
||
function textView(text: string, full: boolean, maxChars: number): Record<string, unknown> {
|
||
const truncated = !full && text.length > maxChars;
|
||
return {
|
||
text: truncated ? text.slice(0, maxChars) : text,
|
||
chars: text.length,
|
||
truncated,
|
||
omittedChars: truncated ? text.length - maxChars : 0,
|
||
};
|
||
}
|
||
|
||
function compactText(text: unknown, full: boolean, maxChars: number): Record<string, unknown> {
|
||
return textView(asString(text), full, maxChars);
|
||
}
|
||
|
||
function compactLastAssistant(value: unknown, full: boolean, maxChars = 4000): Record<string, unknown> {
|
||
const record = asRecord(value) ?? {};
|
||
return {
|
||
at: record.at ?? null,
|
||
seq: record.seq ?? null,
|
||
source: record.source ?? "none",
|
||
...textView(asString(record.text), full, maxChars),
|
||
};
|
||
}
|
||
|
||
function compactFinalResponse(value: unknown, full: boolean): Record<string, unknown> {
|
||
const record = compactLastAssistant(value, full);
|
||
return {
|
||
at: record.at,
|
||
seq: record.seq,
|
||
source: record.source,
|
||
text: record.text,
|
||
chars: record.chars,
|
||
truncated: record.truncated,
|
||
omittedChars: record.omittedChars,
|
||
};
|
||
}
|
||
|
||
function compactQueuedReason(value: unknown): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
return {
|
||
code: record.code ?? null,
|
||
label: record.label ?? null,
|
||
message: textPreview(asString(record.message), 280),
|
||
blockerTaskId: record.blockerTaskId ?? null,
|
||
blockerQueueId: record.blockerQueueId ?? null,
|
||
waitPosition: record.waitPosition ?? null,
|
||
activeRunSlotCount: record.activeRunSlotCount ?? null,
|
||
maxActiveQueues: record.maxActiveQueues ?? null,
|
||
};
|
||
}
|
||
|
||
function normalizeSubmitModel(value: string | null | undefined): string {
|
||
const raw = String(value ?? "").trim();
|
||
if (raw.length === 0) return raw;
|
||
const lower = raw.toLowerCase();
|
||
const leaf = lower.includes("/") ? lower.split("/").at(-1) ?? lower : lower;
|
||
if (leaf === minimaxSubmitModel || leaf === "m2.7") return minimaxSubmitModel;
|
||
if (leaf === deepseekSubmitModel || leaf === "deepseek") return deepseekSubmitModel;
|
||
return raw;
|
||
}
|
||
|
||
function submitModelRegistry(models: string[] = sharedDefaultCodeModels): {
|
||
codeModels: string[];
|
||
codexModels: string[];
|
||
opencodeModels: string[];
|
||
modelPorts: Record<string, "codex" | "opencode">;
|
||
} {
|
||
const codeModels = Array.from(new Set(models.map((model) => normalizeSubmitModel(model)).filter(Boolean)));
|
||
return {
|
||
codeModels,
|
||
codexModels: codeModels.filter((model) => codeAgentPortForModel(model) === "codex"),
|
||
opencodeModels: sharedOpencodeModels(codeModels),
|
||
modelPorts: sharedCodeModelPorts(codeModels),
|
||
};
|
||
}
|
||
|
||
function submitRunnerForModel(model: string | null | undefined): "opencode" | "codex" | null {
|
||
const normalized = normalizeSubmitModel(model);
|
||
if (normalized.length === 0) return null;
|
||
return normalized === minimaxSubmitModel || normalized === deepseekSubmitModel ? "opencode" : "codex";
|
||
}
|
||
|
||
function regexEvidence(text: string, patterns: RegExp[], limit = 6): string[] {
|
||
const evidence: string[] = [];
|
||
for (const pattern of patterns) {
|
||
for (const match of text.matchAll(pattern)) {
|
||
const value = String(match[0] ?? "").trim().replace(/\s+/gu, " ");
|
||
if (value.length > 0 && !evidence.includes(value)) evidence.push(value);
|
||
if (evidence.length >= limit) return evidence;
|
||
}
|
||
}
|
||
return evidence;
|
||
}
|
||
|
||
function regexEvidenceWithoutNegatedContext(text: string, patterns: RegExp[], limit = 6): string[] {
|
||
const evidence: string[] = [];
|
||
for (const pattern of patterns) {
|
||
for (const match of text.matchAll(pattern)) {
|
||
const index = match.index ?? 0;
|
||
const context = text.slice(Math.max(0, index - 36), Math.min(text.length, index + String(match[0] ?? "").length + 36));
|
||
if (/(?:禁止|不要|不得|不能|不应|不触碰|不修改|不涉及|不处理|不更改|请勿|严禁|avoid|forbid|forbidden|do not|don't|must not|no\s+)/iu.test(context)) continue;
|
||
const value = String(match[0] ?? "").trim().replace(/\s+/gu, " ");
|
||
if (value.length > 0 && !evidence.includes(value)) evidence.push(value);
|
||
if (evidence.length >= limit) return evidence;
|
||
}
|
||
}
|
||
return evidence;
|
||
}
|
||
|
||
function routeSignal(id: string, severity: SubmitRouteSignalSeverity, evidence: string[], message: string): SubmitRouteSignal {
|
||
return { id, severity, matched: evidence.length > 0, evidence, message };
|
||
}
|
||
|
||
function promptLintSignal(id: string, severity: PromptLintSeverity, evidence: string[], message: string): PromptLintSignal {
|
||
return { id, severity, matched: evidence.length > 0, evidence, message };
|
||
}
|
||
|
||
function declaredLiveTestClass(prompt: string): LiveTestAuthorizationClass | null {
|
||
const patterns: Array<[LiveTestAuthorizationClass, RegExp[]]> = [
|
||
["live-mutating", [
|
||
/\bDEV\s+test\s+class\s*[::]\s*`?live-mutating`?/iu,
|
||
/\blive\s+test\s+class\s*[::]\s*`?live-mutating`?/iu,
|
||
/\btest\s+class\s*[::]\s*`?live-mutating`?/iu,
|
||
/\bDEV\s+测试(?:授权)?分级\s*[::]\s*`?live-mutating`?/iu,
|
||
]],
|
||
["live-read", [
|
||
/\bDEV\s+test\s+class\s*[::]\s*`?live-read`?/iu,
|
||
/\blive\s+test\s+class\s*[::]\s*`?live-read`?/iu,
|
||
/\btest\s+class\s*[::]\s*`?live-read`?/iu,
|
||
/\bDEV\s+测试(?:授权)?分级\s*[::]\s*`?live-read`?/iu,
|
||
]],
|
||
["read-only", [
|
||
/\bDEV\s+test\s+class\s*[::]\s*`?read-only`?/iu,
|
||
/\blive\s+test\s+class\s*[::]\s*`?read-only`?/iu,
|
||
/\btest\s+class\s*[::]\s*`?read-only`?/iu,
|
||
/\bDEV\s+测试(?:授权)?分级\s*[::]\s*`?read-only`?/iu,
|
||
]],
|
||
];
|
||
for (const [value, valuePatterns] of patterns) {
|
||
if (valuePatterns.some((pattern) => pattern.test(prompt))) return value;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function liveClassRank(value: LiveTestAuthorizationClass): number {
|
||
if (value === "read-only") return 0;
|
||
if (value === "live-read") return 1;
|
||
return 2;
|
||
}
|
||
|
||
function hasPromptField(prompt: string, patterns: RegExp[]): boolean {
|
||
return patterns.some((pattern) => pattern.test(prompt));
|
||
}
|
||
|
||
function sanitizePromptLintEvidence(evidence: string[]): string[] {
|
||
return evidence.map((item) => item
|
||
.replace(/([?&](?:token|api[_-]?key|secret|password|credential)=)[^&\s]+/giu, "$1<redacted>")
|
||
.replace(/((?:token|api[_-]?key|secret|password|credential)\s*[:=]\s*)[^\s,;]+/giu, "$1<redacted>")
|
||
.replace(/(Bearer\s+)[A-Za-z0-9._~+/-]+=*/giu, "$1<redacted>")
|
||
.slice(0, 160));
|
||
}
|
||
|
||
function buildPromptLiveAuthorizationLint(prompt: string): PromptLiveAuthorizationLint {
|
||
const declaredClass = declaredLiveTestClass(prompt);
|
||
const effectiveClass = declaredClass ?? "read-only";
|
||
const allowedClasses: LiveTestAuthorizationClass[] = ["read-only", "live-read", "live-mutating"];
|
||
const liveReadEvidence = sanitizePromptLintEvidence(regexEvidenceWithoutNegatedContext(prompt, [
|
||
/\blive[- ]read\b/giu,
|
||
/\blive\s+(?:dev\s+)?(?:service|runtime|endpoint|health|status|logs?|metrics?)\b/giu,
|
||
/\bGET\s+\/(?:health|status|live|metrics|api\/diagnostics)\b/gu,
|
||
/\bkubectl\s+(?:get|describe|logs)\b/giu,
|
||
/\bmicroservice\s+(?:health|status|diagnostics)\b/giu,
|
||
/\bdiagnostics\b|\bstatus\b|\bmetrics\b|\blogs?\b/giu,
|
||
/只读(?:读取|观察|诊断|状态|日志)/gu,
|
||
/读取\s*(?:DEV|live|运行中|服务|日志|状态)/giu,
|
||
]));
|
||
const liveMutationEvidence = sanitizePromptLintEvidence(regexEvidenceWithoutNegatedContext(prompt, [
|
||
/\blive-mutating\b/giu,
|
||
/\bDEV\s+smoke\b|\blive\s+smoke\b|\bM3\s+smoke\b/giu,
|
||
/\bdeploy\s+apply\b|\brollout\s+restart\b|\bkubectl\s+(?:apply|delete|patch|rollout)\b/giu,
|
||
/\b(?:POST|PUT|PATCH|DELETE)\s+\/[A-Za-z0-9_./:-]*/gu,
|
||
/\bcodex\s+(?:submit|steer|interrupt|cancel)\b/giu,
|
||
/\btask\s+(?:submit|steer|retry|trigger)\b/giu,
|
||
/\btrigger\s+(?:schedule|job|task|operation|audit|evidence)\b/giu,
|
||
/\bschedule\s+(?:run|retry-run|delete)\b/giu,
|
||
/\b(?:create|write|post|put|patch)\b[^\n。]{0,40}\b(?:operation|audit|evidence)\b/giu,
|
||
/\b(?:operation|audit|evidence)\s+(?:id|record|write|create)\b/giu,
|
||
/\bDO\d+\b|\bDI\d+\b|\bres_boxsimu_\d+\b|\bhwlab-patch-panel\b/giu,
|
||
/触发|写入|部署|重启|重建|回滚|创建(?:任务|operation|audit|evidence)|硬件|虚拟硬件/gu,
|
||
]));
|
||
const prodMutationEvidence = sanitizePromptLintEvidence(regexEvidenceWithoutNegatedContext(prompt, [
|
||
/\bprod(?:uction)?\b[^\n。]*(?:deploy|restart|write|mutation|mutating|apply|rollout|delete|patch)\b/giu,
|
||
/\b(?:deploy|restart|write|mutation|mutating|apply|rollout|delete|patch)\b[^\n。]*\bprod(?:uction)?\b/giu,
|
||
/生产[^\n。]*(?:写入|部署|重启|变更|删除|回滚)/gu,
|
||
]));
|
||
const requiredClass = liveMutationEvidence.length > 0 || prodMutationEvidence.length > 0
|
||
? "live-mutating"
|
||
: liveReadEvidence.length > 0
|
||
? "live-read"
|
||
: "read-only";
|
||
const allowedLiveMutationPresent = hasPromptField(prompt, [
|
||
/\ballowed\s+live\s+mutation\s*[::]/iu,
|
||
/允许的\s*live\s*mutation\s*[::]/iu,
|
||
/允许的(?:现场|实时|运行态)?(?:写入|变更|mutation)\s*[::]/iu,
|
||
]);
|
||
const allowedLiveMutationNone = hasPromptField(prompt, [
|
||
/\ballowed\s+live\s+mutation\s*[::]\s*(?:`?none`?|无|なし)(?:\s|$|[。.;,,])/iu,
|
||
/允许的\s*live\s*mutation\s*[::]\s*(?:`?none`?|无|なし)(?:\s|$|[。.;,,])/iu,
|
||
/允许的(?:现场|实时|运行态)?(?:写入|变更|mutation)\s*[::]\s*(?:`?none`?|无|なし)(?:\s|$|[。.;,,])/iu,
|
||
]);
|
||
const forbiddenActionsPresent = hasPromptField(prompt, [
|
||
/\bforbidden\s+actions?\s*[::]/iu,
|
||
/禁止动作\s*[::]/u,
|
||
/禁止\s*[::]/u,
|
||
]);
|
||
const closeoutFieldsPresent = hasPromptField(prompt, [
|
||
/\bcloseout\s+fields?\s*[::]/iu,
|
||
/\bfinal\s+response\b[^\n。]*(?:must|include|report)/iu,
|
||
/\b收口字段\s*[::]/u,
|
||
/\bfinal\s+response\b[^\n。]*报告/iu,
|
||
]);
|
||
const effectiveInsufficient = liveClassRank(effectiveClass) < liveClassRank(requiredClass);
|
||
const liveMutationAuthorized = effectiveClass === "live-mutating" && allowedLiveMutationPresent && !allowedLiveMutationNone;
|
||
const contradictionEvidence = [
|
||
...(effectiveClass === "read-only" && liveMutationEvidence.length > 0 ? ["declares/read-only but prompt contains live mutation signals"] : []),
|
||
...(effectiveClass === "live-read" && liveMutationEvidence.length > 0 ? ["declares/live-read but prompt contains live mutation signals"] : []),
|
||
...(effectiveClass === "live-mutating" && allowedLiveMutationNone ? ["declares/live-mutating but allowed live mutation is none"] : []),
|
||
...(prodMutationEvidence.length > 0 ? prodMutationEvidence.map((item) => `prod mutation signal: ${item}`) : []),
|
||
];
|
||
const missingOrContradictory = [
|
||
...(declaredClass === null ? ["missing DEV test class; defaulting to read-only"] : []),
|
||
...(effectiveInsufficient ? [`effective class ${effectiveClass} is below required ${requiredClass}`] : []),
|
||
...(requiredClass === "live-mutating" && !allowedLiveMutationPresent ? ["live-mutating prompt must include allowed live mutation"] : []),
|
||
...(requiredClass === "live-mutating" && allowedLiveMutationNone ? ["live-mutating prompt cannot set allowed live mutation to none"] : []),
|
||
...(!forbiddenActionsPresent ? ["missing forbidden actions"] : []),
|
||
...(!closeoutFieldsPresent ? ["missing closeout fields"] : []),
|
||
...contradictionEvidence,
|
||
];
|
||
const signals = [
|
||
promptLintSignal("declared-dev-test-class", "info", declaredClass === null ? [] : [declaredClass], "Prompt explicitly declares DEV test class."),
|
||
promptLintSignal("live-read-signal", "warning", liveReadEvidence, "Prompt appears to read live DEV service state, logs, health, status, metrics, or Kubernetes objects."),
|
||
promptLintSignal("live-mutation-signal", "block", liveMutationEvidence, "Prompt appears to trigger runtime writes, deployment, task control, operation/audit/evidence creation, or HWLAB DO/DI activity."),
|
||
promptLintSignal("prod-mutation-signal", "block", prodMutationEvidence, "Prompt appears to mention production mutation; Code Queue runner prompts must not implicitly authorize this."),
|
||
promptLintSignal("allowed-live-mutation-field", requiredClass === "live-mutating" ? "block" : "info", allowedLiveMutationPresent && !allowedLiveMutationNone ? ["present"] : [], "live-mutating prompts must enumerate allowed live mutation commands and target state changes."),
|
||
promptLintSignal("forbidden-actions-field", "warning", forbiddenActionsPresent ? ["present"] : [], "Prompt should list forbidden high-risk actions."),
|
||
promptLintSignal("closeout-fields-field", "warning", closeoutFieldsPresent ? ["present"] : [], "Prompt should require final closeout fields for class, mutation, commands, targets, evidence, and residual risk."),
|
||
];
|
||
const ok = missingOrContradictory.length === 0;
|
||
const dispatchDisposition: PromptLintDisposition = ok
|
||
? "ready"
|
||
: requiredClass === "live-mutating" || effectiveInsufficient || contradictionEvidence.length > 0
|
||
? "needs-authorization"
|
||
: "review";
|
||
return {
|
||
ok,
|
||
dryRun: true,
|
||
mutation: false,
|
||
dispatchDisposition,
|
||
declaredClass,
|
||
effectiveClass,
|
||
requiredClass,
|
||
defaultedReadOnly: declaredClass === null,
|
||
liveMutationAuthorized,
|
||
promptShape: {
|
||
chars: prompt.length,
|
||
lines: prompt.split(/\r\n|\r|\n/u).length,
|
||
textEchoed: false,
|
||
},
|
||
requiredPromptFields: {
|
||
devTestClass: {
|
||
present: declaredClass !== null,
|
||
value: declaredClass,
|
||
allowedValues: allowedClasses,
|
||
},
|
||
allowedLiveMutation: {
|
||
present: allowedLiveMutationPresent,
|
||
nonNone: allowedLiveMutationPresent && !allowedLiveMutationNone,
|
||
requiredWhen: "live-mutating",
|
||
},
|
||
forbiddenActions: {
|
||
present: forbiddenActionsPresent,
|
||
},
|
||
closeoutFields: {
|
||
present: closeoutFieldsPresent,
|
||
},
|
||
},
|
||
signals,
|
||
missingOrContradictory,
|
||
policy: {
|
||
defaultWhenUnclassified: "read-only",
|
||
promptLintOnly: true,
|
||
accessesLiveService: false,
|
||
printsPromptText: false,
|
||
reference: "docs/reference/code-queue-supervision.md#dev-测试授权分级",
|
||
},
|
||
commands: {
|
||
lintFile: "bun scripts/cli.ts codex prompt-lint --prompt-file <path>",
|
||
submitDryRun: "bun scripts/cli.ts codex submit --prompt-file <path> --dry-run",
|
||
steerDryRun: "bun scripts/cli.ts codex steer <taskId> --prompt-file <path> --dry-run",
|
||
},
|
||
};
|
||
}
|
||
|
||
function submitPolicyContract(): SubmitRoutingRecommendation["policyContract"] {
|
||
return {
|
||
selectionPrinciples: [
|
||
"Use GPT-5.5 for high-risk, runtime/core, security, CI/CD, deploy, release, and final quality calls.",
|
||
"Use DeepSeek/OpenCode for self-contained medium-complexity work with limited write scope and verifiable tests.",
|
||
"Use MiniMax only for simple, low-risk, self-contained work with external evidence and commander review.",
|
||
"Keep prod restart, secret access, DB writes, destructive Git, and running-task control with the commander or human.",
|
||
],
|
||
concurrency: {
|
||
gpt55Routine: 5,
|
||
gpt55BurstMax: 10,
|
||
minimaxSimpleMax: 10,
|
||
deepseekMediumDefault: 5,
|
||
},
|
||
modelTiers: [
|
||
{
|
||
model: gptSubmitModel,
|
||
runner: "codex",
|
||
taskRisk: "high-risk-or-complex",
|
||
requiredGuards: ["bounded ownership", "multi-signal verification", "no implicit prod rollout"],
|
||
},
|
||
{
|
||
model: deepseekSubmitModel,
|
||
runner: "opencode",
|
||
taskRisk: "medium-complexity",
|
||
requiredGuards: ["self-contained prompt", "limited write scope", "contract/unit verification", "commander review"],
|
||
},
|
||
{
|
||
model: minimaxSubmitModel,
|
||
runner: "opencode",
|
||
taskRisk: "simple-low-risk",
|
||
requiredGuards: ["issue is auxiliary only", "evidence required", "no prod/secrets/DB writes", "diff and test review"],
|
||
},
|
||
],
|
||
externalProvider429: {
|
||
commanderAction: "wait-while-exponential-backoff-is-healthy",
|
||
interveneWhen: ["heartbeat expired", "retry state machine stuck", "task lost", "retry attempts exhausted"],
|
||
},
|
||
};
|
||
}
|
||
|
||
function submitRoutingRecommendation(options: CodexSubmitOptions): SubmitRoutingRecommendation {
|
||
const prompt = options.prompt;
|
||
const lower = prompt.toLowerCase();
|
||
const prodOrStateMutation = regexEvidenceWithoutNegatedContext(lower, [
|
||
/\bprod(?:uction)?\b/gu,
|
||
/\brestart(?:ing|ed)?\b/gu,
|
||
/\brebuild(?:ing|ed)?\b/gu,
|
||
/\bdeploy(?:ment|ing|ed)?\b/gu,
|
||
/\bserver\s+rebuild\b/gu,
|
||
/\bdeploy\s+apply\b/gu,
|
||
/\binterrupt\b|\bcancel\b/gu,
|
||
/\bsecret\b|\btoken\b|\bapi[_-]?key\b|\bcredential\b/gu,
|
||
/\bpostgres(?:ql)?\b|\bpsql\b|\bdatabase\b|\bdb\b|\bsql\b|\bmigration\b/gu,
|
||
]);
|
||
const runtimeCore = regexEvidenceWithoutNegatedContext(lower, [
|
||
/\bcode[- ]queue\s+(?:runtime|scheduler|backend|execution|runner)\b/gu,
|
||
/\bbackend-core\b/gu,
|
||
/\bprovider-gateway\b/gu,
|
||
/\bk3sctl-adapter\b/gu,
|
||
/\bruntime-preflight\b/gu,
|
||
/\bactive\s+run\b/gu,
|
||
]);
|
||
const issueReference = regexEvidence(lower, [
|
||
/\bgithub\s+issue\b/gu,
|
||
/\bissue\s+#?\d+\b/gu,
|
||
/#\d+/gu,
|
||
/\bgh\s+issue\s+view\b/gu,
|
||
]);
|
||
const issueOnly = regexEvidence(lower, [
|
||
/\bread\s+(?:the\s+)?issue\b/gu,
|
||
/\bsee\s+(?:github\s+)?issue\b/gu,
|
||
/\bfrom\s+issue\s+#?\d+\b/gu,
|
||
/\bissue\s+(?:has|contains)\s+(?:the\s+)?(?:full|complete)\s+(?:context|requirements?)\b/gu,
|
||
/读取\s*(?:github\s*)?issue/gu,
|
||
/查看\s*(?:github\s*)?issue/gu,
|
||
]);
|
||
const issueAuxiliaryGuard = regexEvidence(prompt, [
|
||
/issue[^。\n]*辅助引用/giu,
|
||
/issue[^。\n]*不能作为唯一来源/giu,
|
||
/issue[^。\n]*not[^。\n]*only[^。\n]*source/giu,
|
||
/issue[^。\n]*auxiliary[^。\n]*reference/giu,
|
||
]);
|
||
const lowRiskEvidence = regexEvidence(lower, [
|
||
/\bdry-run\b/gu,
|
||
/\bpreflight\b/gu,
|
||
/\bcontract\s+test\b/gu,
|
||
/\btypecheck\b/gu,
|
||
/\bdocs?\b|\bdocumentation\b/gu,
|
||
/\bread[- ]?only\b/gu,
|
||
/只读/gu,
|
||
/文档/gu,
|
||
/轻量/gu,
|
||
]);
|
||
const mediumComplexityEvidence = regexEvidence(lower, [
|
||
/\bfront[- ]?end\b|\bfrontend\b|\breact\b|\btsx\b|\bcss\b|\bui\b|\bcomponent\b/gu,
|
||
/\buser[- ]service\b|\bservice\s+module\b/gu,
|
||
/\blocal\s+(?:module|helper|cli|tool)\b/gu,
|
||
/\bbounded\s+(?:bug\s*)?fix\b|\bsmall\s+(?:bug\s*)?fix\b/gu,
|
||
/\bunit\s+test\b|\bcontract\s+guard\b/gu,
|
||
/中等复杂|中等风险|前端|组件|样式|局部(?:模块|修复)|用户服务|契约守卫/gu,
|
||
]);
|
||
const evidenceRequest = regexEvidence(lower, [
|
||
/\bevidence\b/gu,
|
||
/\bverification\b|\bverified\b|\bvalidate\b|\bvalidation\b/gu,
|
||
/\btest(?:s|ed|ing)?\b/gu,
|
||
/\bdry-run\b/gu,
|
||
/\bcommit\b/gu,
|
||
/验证/gu,
|
||
/证据/gu,
|
||
/自测/gu,
|
||
]);
|
||
const selfContainedHints = regexEvidence(prompt, [
|
||
/目标[::]/gu,
|
||
/范围[::]/gu,
|
||
/禁止[::]/gu,
|
||
/验证[::]/gu,
|
||
/final response/giu,
|
||
/完整需求/gu,
|
||
/本 prompt/gu,
|
||
]);
|
||
const destructiveWords = regexEvidenceWithoutNegatedContext(lower, [
|
||
/\brm\s+-rf\b/gu,
|
||
/\bgit\s+reset\s+--hard\b/gu,
|
||
/\bgit\s+checkout\s+--\b/gu,
|
||
/\bdrop\s+table\b/gu,
|
||
/\btruncate\s+table\b/gu,
|
||
/\bdelete\s+from\b/gu,
|
||
]);
|
||
const crossModule = regexEvidenceWithoutNegatedContext(lower, [
|
||
/\bcross[- ]module\b/gu,
|
||
/\barchitecture\b|\barchitectural\b/gu,
|
||
/\brelease\/v1\b/gu,
|
||
/\bci\/cd\b/gu,
|
||
/\brollout\b|\brollback\b/gu,
|
||
/跨模块/gu,
|
||
/架构/gu,
|
||
/回滚方案|复杂回滚/gu,
|
||
]);
|
||
const model = normalizeSubmitModel(options.model);
|
||
const explicitRunner = submitRunnerForModel(model);
|
||
const promptSelfContained = prompt.length >= 700 || selfContainedHints.length >= 3;
|
||
const issueIsNotOnlySource = issueReference.length === 0 || issueAuxiliaryGuard.length > 0 || issueOnly.length === 0 && prompt.length >= 500;
|
||
const noProdRestartSecretOrDbWrite = prodOrStateMutation.length === 0 && destructiveWords.length === 0;
|
||
const noRuntimeCoreOrReleaseWork = runtimeCore.length === 0 && crossModule.length === 0;
|
||
const evidenceRequiredByPrompt = evidenceRequest.length > 0;
|
||
const mediumComplexityCandidate = promptSelfContained
|
||
&& issueIsNotOnlySource
|
||
&& noProdRestartSecretOrDbWrite
|
||
&& noRuntimeCoreOrReleaseWork
|
||
&& evidenceRequiredByPrompt
|
||
&& mediumComplexityEvidence.length > 0;
|
||
const signals = [
|
||
routeSignal("prod-state-secret-db-write", "block", [...prodOrStateMutation, ...destructiveWords], "Mentions production/state mutation, restart, secrets, DB writes, or destructive commands."),
|
||
routeSignal("runtime-core", "warning", runtimeCore, "Touches Code Queue runtime, backend-core, provider-gateway, k3s adapter, or active run behavior."),
|
||
routeSignal("issue-source-risk", "warning", issueOnly, "Prompt appears to rely on GitHub issue reading as task context."),
|
||
routeSignal("issue-auxiliary-source-guard", "info", issueAuxiliaryGuard, "Prompt explicitly says GitHub issue is auxiliary and not the only source."),
|
||
routeSignal("cross-module-release", "warning", crossModule, "Mentions cross-module architecture, CI/CD rollout, release line, or rollback work."),
|
||
routeSignal("medium-complexity-verifiable", "info", mediumComplexityEvidence, "Mentions bounded medium-complexity work such as frontend, local CLI/helper, user-service module, or contract guard changes."),
|
||
routeSignal("low-risk-verifiable", "info", lowRiskEvidence, "Mentions low-risk or verifiable work such as docs, read-only checks, dry-run, preflight, or contract tests."),
|
||
routeSignal("evidence-requested", "info", evidenceRequest, "Prompt asks for tests, validation, commit, or evidence."),
|
||
routeSignal("self-contained-hints", "info", selfContainedHints, "Prompt includes explicit task sections that make it easier to verify without reading an issue."),
|
||
];
|
||
|
||
let route: SubmitRoute = "gpt-5.5-codex";
|
||
let recommendedRunner: SubmitRoutingRecommendation["recommendedRunner"] = "codex";
|
||
let recommendedModel: string | null = gptSubmitModel;
|
||
let confidence: SubmitRoutingRecommendation["confidence"] = "medium";
|
||
let reason = "Default to GPT-5.5 when the prompt is not clearly low-risk and self-contained.";
|
||
|
||
if (prodOrStateMutation.length > 0 || destructiveWords.length > 0) {
|
||
route = "commander-human-only";
|
||
recommendedRunner = "commander";
|
||
recommendedModel = null;
|
||
confidence = "high";
|
||
reason = "This task mentions production/state mutation, restart, secrets, DB writes, or destructive operations; keep it with the commander or a human.";
|
||
} else if (runtimeCore.length > 0 || crossModule.length > 0) {
|
||
route = "gpt-5.5-codex";
|
||
confidence = "high";
|
||
reason = "This task touches runtime/core/cross-module or release-governance surfaces, so it should stay on GPT-5.5.";
|
||
} else if (mediumComplexityCandidate) {
|
||
route = "deepseek-opencode";
|
||
recommendedRunner = "opencode";
|
||
recommendedModel = deepseekSubmitModel;
|
||
confidence = "high";
|
||
reason = "The prompt looks self-contained, medium-complexity, and verifiable without production/state privileges; it is a DeepSeek/OpenCode candidate after commander review.";
|
||
} else if (mediumComplexityEvidence.length > 0 && issueIsNotOnlySource && noProdRestartSecretOrDbWrite && noRuntimeCoreOrReleaseWork) {
|
||
route = "deepseek-opencode";
|
||
recommendedRunner = "opencode";
|
||
recommendedModel = deepseekSubmitModel;
|
||
confidence = "medium";
|
||
reason = "The prompt has medium-complexity signals, but the commander should tighten self-contained context, write scope, and verification requirements before relying on DeepSeek/OpenCode.";
|
||
} else if (promptSelfContained && issueIsNotOnlySource && evidenceRequiredByPrompt && lowRiskEvidence.length > 0) {
|
||
route = "minimax-opencode";
|
||
recommendedRunner = "opencode";
|
||
recommendedModel = minimaxSubmitModel;
|
||
confidence = "high";
|
||
reason = "The prompt looks self-contained, low-risk, and asks for verifiable evidence; it is a MiniMax/OpenCode candidate if the runner smoke is currently green.";
|
||
} else if (lowRiskEvidence.length > 0 && issueIsNotOnlySource && noProdRestartSecretOrDbWrite) {
|
||
route = "minimax-opencode";
|
||
recommendedRunner = "opencode";
|
||
recommendedModel = minimaxSubmitModel;
|
||
confidence = "medium";
|
||
reason = "The prompt has low-risk signals, but the commander should tighten self-contained context and evidence requirements before relying on MiniMax.";
|
||
}
|
||
|
||
const explicitNote = model.length === 0
|
||
? null
|
||
: explicitRunner === recommendedRunner
|
||
? "Explicit --model matches the dry-run recommendation."
|
||
: "Explicit --model differs from the dry-run recommendation; this dry-run does not rewrite the payload.";
|
||
|
||
return {
|
||
route,
|
||
recommendedRunner,
|
||
recommendedModel,
|
||
confidence,
|
||
reason,
|
||
signals,
|
||
riskControls: {
|
||
promptSelfContained,
|
||
issueIsNotOnlySource,
|
||
noProdRestartSecretOrDbWrite,
|
||
noRuntimeCoreOrReleaseWork,
|
||
evidenceRequiredByPrompt,
|
||
mediumComplexityCandidate,
|
||
commanderMustReviewUnread: true,
|
||
},
|
||
explicitRequest: {
|
||
model: model.length === 0 ? null : model,
|
||
runner: explicitRunner,
|
||
note: explicitNote,
|
||
},
|
||
routingPolicy: {
|
||
dryRunOnly: true,
|
||
doesNotChangeSubmittedPayload: true,
|
||
prodMiniMaxAssumedAvailable: false,
|
||
prodDeepSeekAssumedAvailable: false,
|
||
runtimeAdmissionUnchanged: true,
|
||
},
|
||
policyContract: submitPolicyContract(),
|
||
};
|
||
}
|
||
|
||
function compactSchedulerHeartbeat(value: unknown): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
return {
|
||
taskId: record.taskId ?? null,
|
||
attempt: record.attempt ?? null,
|
||
owner: record.owner ?? null,
|
||
schedulerInstance: record.schedulerInstance ?? null,
|
||
agentPort: record.agentPort ?? null,
|
||
activeTurnId: record.activeTurnId ?? null,
|
||
codexThreadId: record.codexThreadId ?? null,
|
||
lastLocalHeartbeatAt: record.lastLocalHeartbeatAt ?? null,
|
||
lastObservedAgentEventAt: record.lastObservedAgentEventAt ?? null,
|
||
lastPersistedTraceAt: record.lastPersistedTraceAt ?? null,
|
||
outputMaxSeq: record.outputMaxSeq ?? null,
|
||
};
|
||
}
|
||
|
||
function splitBrainLiveFromDiagnostics(record: Record<string, unknown>): boolean {
|
||
if (typeof record.splitBrainLive === "boolean") return record.splitBrainLive;
|
||
const state = String(record.state ?? record.health ?? "").toLowerCase();
|
||
const riskTaskIds = Array.from(new Set([
|
||
...stringList(record.heartbeatRiskTaskIds),
|
||
...stringList(record.heartbeatExpiredTaskIds),
|
||
...stringList(record.heartbeatMissingTaskIds),
|
||
...stringList(record.staleRecoveryCandidateTaskIds),
|
||
]));
|
||
return state === "split-brain" && stringList(record.heartbeatFreshTaskIds).length > 0 && riskTaskIds.length === 0;
|
||
}
|
||
|
||
function effectiveLivenessFromDiagnostics(record: Record<string, unknown>): string {
|
||
if (typeof record.effectiveLiveness === "string" && record.effectiveLiveness.length > 0) return record.effectiveLiveness;
|
||
if (stringList(record.heartbeatRiskTaskIds).length > 0
|
||
|| stringList(record.heartbeatExpiredTaskIds).length > 0
|
||
|| stringList(record.heartbeatMissingTaskIds).length > 0
|
||
|| stringList(record.staleRecoveryCandidateTaskIds).length > 0) {
|
||
return "at-risk";
|
||
}
|
||
if (splitBrainLiveFromDiagnostics(record)) return "live";
|
||
return String(record.state ?? record.health ?? "unknown") === "healthy" ? "healthy" : "degraded";
|
||
}
|
||
|
||
function recommendedActionFromDiagnostics(record: Record<string, unknown>): string {
|
||
if (typeof record.recommendedAction === "string" && record.recommendedAction.length > 0) return record.recommendedAction;
|
||
const effectiveLiveness = effectiveLivenessFromDiagnostics(record);
|
||
if (effectiveLiveness === "at-risk") return "investigate-heartbeat-risk";
|
||
if (effectiveLiveness === "live") return "continue-supervision";
|
||
if (effectiveLiveness === "degraded") return "observe-degraded";
|
||
return "none";
|
||
}
|
||
|
||
function activeHeartbeatCountFromDiagnostics(record: Record<string, unknown>, activeHeartbeatTaskIds: { count: number }, heartbeatFreshTaskIds: { count: number }): number {
|
||
const explicit = asNumber(record.activeHeartbeatCount, Number.NaN);
|
||
return Number.isFinite(explicit) ? explicit : Math.max(activeHeartbeatTaskIds.count, heartbeatFreshTaskIds.count);
|
||
}
|
||
|
||
function compactLivenessDecision(record: Record<string, unknown>, lists: {
|
||
activeHeartbeatTaskIds: { items: string[]; count: number; truncated: boolean; omitted: number };
|
||
heartbeatFreshTaskIds: { items: string[]; count: number; truncated: boolean; omitted: number };
|
||
heartbeatRiskTaskIds: { items: string[]; count: number };
|
||
databaseActiveTaskIds: { count: number };
|
||
}): Record<string, unknown> {
|
||
const splitBrainLive = splitBrainLiveFromDiagnostics(record);
|
||
const effectiveLiveness = effectiveLivenessFromDiagnostics(record);
|
||
const recommendedAction = recommendedActionFromDiagnostics(record);
|
||
const activeHeartbeatCount = activeHeartbeatCountFromDiagnostics(record, lists.activeHeartbeatTaskIds, lists.heartbeatFreshTaskIds);
|
||
return {
|
||
effectiveLiveness,
|
||
recommendedAction,
|
||
splitBrainLive,
|
||
activeHeartbeatCount,
|
||
heartbeatFreshTaskCount: lists.heartbeatFreshTaskIds.count,
|
||
heartbeatFreshTaskIds: lists.heartbeatFreshTaskIds.items,
|
||
heartbeatFreshTaskIdsTruncated: lists.heartbeatFreshTaskIds.truncated,
|
||
databaseActiveTaskCount: asNumber(record.databaseActiveTaskCount, lists.databaseActiveTaskIds.count),
|
||
schedulerActiveRunSlotCount: record.schedulerActiveRunSlotCount ?? null,
|
||
heartbeatRiskTaskCount: lists.heartbeatRiskTaskIds.count,
|
||
interpretation: splitBrainLive
|
||
? "scheduler heartbeat is fresh; treat active task count from heartbeat as live and continue supervision"
|
||
: effectiveLiveness === "at-risk"
|
||
? "heartbeat risk is present; investigate heartbeat freshness before recovery"
|
||
: effectiveLiveness === "degraded"
|
||
? "diagnostics are degraded; cross-check heartbeat, trace and control-plane sources"
|
||
: "diagnostics indicate healthy liveness",
|
||
};
|
||
}
|
||
|
||
function boundedUniqueStringList(value: unknown, limit = diagnosticsIdPreviewLimit): { items: string[]; count: number; omitted: number; truncated: boolean } {
|
||
const all = Array.from(new Set(stringList(value))).sort();
|
||
const items = all.slice(0, limit);
|
||
return {
|
||
items,
|
||
count: all.length,
|
||
omitted: Math.max(0, all.length - items.length),
|
||
truncated: all.length > items.length,
|
||
};
|
||
}
|
||
|
||
function boundedInlineString(value: unknown, maxChars: number): { text: string | null; chars: number; truncated: boolean; omittedChars: number } {
|
||
const text = asString(value).replace(/\s+/gu, " ").trim();
|
||
const truncated = text.length > maxChars;
|
||
return {
|
||
text: text.length === 0 ? null : truncated ? text.slice(0, maxChars) : text,
|
||
chars: text.length,
|
||
truncated,
|
||
omittedChars: truncated ? text.length - maxChars : 0,
|
||
};
|
||
}
|
||
|
||
function compactExecutionDiagnostics(value: unknown): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
const fullHeartbeatRiskTaskIds = Array.from(new Set([
|
||
...stringList(record.heartbeatRiskTaskIds),
|
||
...stringList(record.heartbeatExpiredTaskIds),
|
||
...stringList(record.heartbeatMissingTaskIds),
|
||
...stringList(record.staleRecoveryCandidateTaskIds),
|
||
])).sort();
|
||
const databaseActiveTaskIds = boundedUniqueStringList(record.databaseActiveTaskIds);
|
||
const schedulerActiveTaskIds = boundedUniqueStringList(record.schedulerActiveTaskIds);
|
||
const activeHeartbeatTaskIds = boundedUniqueStringList(record.activeHeartbeatTaskIds);
|
||
const heartbeatFreshTaskIds = boundedUniqueStringList(record.heartbeatFreshTaskIds);
|
||
const heartbeatExpiredTaskIds = boundedUniqueStringList(record.heartbeatExpiredTaskIds);
|
||
const heartbeatMissingTaskIds = boundedUniqueStringList(record.heartbeatMissingTaskIds);
|
||
const staleRecoveryCandidateTaskIds = boundedUniqueStringList(record.staleRecoveryCandidateTaskIds);
|
||
const heartbeatRiskTaskIds = boundedUniqueStringList(fullHeartbeatRiskTaskIds);
|
||
const traceGapTaskIds = boundedUniqueStringList(record.traceGapTaskIds);
|
||
const traceGapNotStaleTaskIds = boundedUniqueStringList(record.traceGapNotStaleTaskIds);
|
||
const allReasons = stringList(record.reasons);
|
||
const reasons = allReasons.slice(0, diagnosticsReasonPreviewLimit).map((reason) => boundedInlineString(reason, 240).text).filter((reason): reason is string => reason !== null);
|
||
const livenessSummary = boundedInlineString(record.livenessSummary, 420);
|
||
const liveness = compactLivenessDecision(record, {
|
||
activeHeartbeatTaskIds,
|
||
heartbeatFreshTaskIds,
|
||
heartbeatRiskTaskIds,
|
||
databaseActiveTaskIds,
|
||
});
|
||
const omittedCounts = {
|
||
databaseActiveTaskIds: databaseActiveTaskIds.omitted,
|
||
schedulerActiveTaskIds: schedulerActiveTaskIds.omitted,
|
||
activeHeartbeatTaskIds: activeHeartbeatTaskIds.omitted,
|
||
heartbeatFreshTaskIds: heartbeatFreshTaskIds.omitted,
|
||
heartbeatExpiredTaskIds: heartbeatExpiredTaskIds.omitted,
|
||
heartbeatMissingTaskIds: heartbeatMissingTaskIds.omitted,
|
||
staleRecoveryCandidateTaskIds: staleRecoveryCandidateTaskIds.omitted,
|
||
heartbeatRiskTaskIds: heartbeatRiskTaskIds.omitted,
|
||
traceGapTaskIds: traceGapTaskIds.omitted,
|
||
traceGapNotStaleTaskIds: traceGapNotStaleTaskIds.omitted,
|
||
reasons: Math.max(0, allReasons.length - reasons.length),
|
||
livenessSummaryChars: livenessSummary.omittedChars,
|
||
};
|
||
return {
|
||
state: record.state ?? record.health ?? null,
|
||
degraded: record.degraded ?? null,
|
||
splitBrain: record.splitBrain ?? null,
|
||
splitBrainLive: splitBrainLiveFromDiagnostics(record),
|
||
effectiveLiveness: liveness.effectiveLiveness,
|
||
recommendedAction: liveness.recommendedAction,
|
||
liveness,
|
||
livenessSummary: livenessSummary.text,
|
||
livenessSummaryChars: livenessSummary.chars,
|
||
livenessSummaryTruncated: livenessSummary.truncated,
|
||
executionStateSource: record.executionStateSource ?? null,
|
||
controlPlane: boundedInlineString(record.controlPlane, 120).text,
|
||
databaseActiveTaskCount: record.databaseActiveTaskCount ?? databaseActiveTaskIds.count,
|
||
databaseActiveTaskIds: databaseActiveTaskIds.items,
|
||
schedulerActiveRunSlotCount: record.schedulerActiveRunSlotCount ?? null,
|
||
schedulerActiveTaskIds: schedulerActiveTaskIds.items,
|
||
activeHeartbeatCount: liveness.activeHeartbeatCount,
|
||
activeHeartbeatTaskIds: activeHeartbeatTaskIds.items,
|
||
heartbeatFreshTaskIds: heartbeatFreshTaskIds.items,
|
||
heartbeatExpiredTaskIds: heartbeatExpiredTaskIds.items,
|
||
heartbeatMissingTaskIds: heartbeatMissingTaskIds.items,
|
||
staleRecoveryCandidateTaskIds: staleRecoveryCandidateTaskIds.items,
|
||
heartbeatRiskTaskIds: heartbeatRiskTaskIds.items,
|
||
traceGapTaskIds: traceGapTaskIds.items,
|
||
traceGapNotStaleTaskIds: traceGapNotStaleTaskIds.items,
|
||
lastSchedulerHeartbeatAt: record.lastSchedulerHeartbeatAt ?? null,
|
||
lastObservedAgentEventAt: record.lastObservedAgentEventAt ?? null,
|
||
lastPersistedTraceAt: record.lastPersistedTraceAt ?? null,
|
||
oaPublisher: previewJson(record.oaPublisher ?? null, { maxDepth: 3, maxArrayItems: 4, maxObjectKeys: 12, maxStringLength: 240 }),
|
||
reasons,
|
||
listBudget: {
|
||
idPreviewLimit: diagnosticsIdPreviewLimit,
|
||
reasonPreviewLimit: diagnosticsReasonPreviewLimit,
|
||
truncated: Object.values(omittedCounts).some((count) => count > 0),
|
||
omittedCounts,
|
||
rawCommand: "bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview?limit=30 --raw --full",
|
||
},
|
||
};
|
||
}
|
||
|
||
function compactQueueExecutionDiagnostics(value: unknown): Record<string, unknown> | null {
|
||
const diagnostics = compactExecutionDiagnostics(value);
|
||
if (diagnostics === null) return null;
|
||
const listBudget = asRecord(diagnostics.listBudget) ?? {};
|
||
const omittedCounts = asRecord(listBudget.omittedCounts) ?? {};
|
||
return {
|
||
state: diagnostics.state ?? null,
|
||
degraded: diagnostics.degraded ?? null,
|
||
splitBrain: diagnostics.splitBrain ?? null,
|
||
splitBrainLive: diagnostics.splitBrainLive ?? null,
|
||
effectiveLiveness: diagnostics.effectiveLiveness ?? null,
|
||
recommendedAction: diagnostics.recommendedAction ?? null,
|
||
liveness: diagnostics.liveness ?? null,
|
||
executionStateSource: diagnostics.executionStateSource ?? null,
|
||
controlPlane: diagnostics.controlPlane ?? null,
|
||
databaseActiveTaskCount: diagnostics.databaseActiveTaskCount ?? null,
|
||
schedulerActiveRunSlotCount: diagnostics.schedulerActiveRunSlotCount ?? null,
|
||
activeHeartbeatCount: diagnostics.activeHeartbeatCount ?? null,
|
||
lastSchedulerHeartbeatAt: diagnostics.lastSchedulerHeartbeatAt ?? null,
|
||
lastObservedAgentEventAt: diagnostics.lastObservedAgentEventAt ?? null,
|
||
lastPersistedTraceAt: diagnostics.lastPersistedTraceAt ?? null,
|
||
reasons: diagnostics.reasons ?? [],
|
||
listBudget: {
|
||
truncated: listBudget.truncated ?? false,
|
||
omittedCounts: {
|
||
databaseActiveTaskIds: omittedCounts.databaseActiveTaskIds ?? 0,
|
||
activeHeartbeatTaskIds: omittedCounts.activeHeartbeatTaskIds ?? 0,
|
||
heartbeatFreshTaskIds: omittedCounts.heartbeatFreshTaskIds ?? 0,
|
||
heartbeatRiskTaskIds: omittedCounts.heartbeatRiskTaskIds ?? 0,
|
||
reasons: omittedCounts.reasons ?? 0,
|
||
},
|
||
rawCommand: listBudget.rawCommand ?? "bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview?limit=30 --raw --full",
|
||
},
|
||
};
|
||
}
|
||
|
||
function firstFiniteNumber(...values: unknown[]): number | null {
|
||
for (const value of values) {
|
||
const number = finiteNumber(value);
|
||
if (number !== null) return number;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function stringListCount(value: unknown): number | null {
|
||
return Array.isArray(value) ? stringList(value).length : null;
|
||
}
|
||
|
||
function compactCodeQueueActivity(
|
||
queue: Record<string, unknown>,
|
||
diagnostics: Record<string, unknown> | null,
|
||
options: { schedulerLocalActiveQueueIds?: string[]; runnableQueueCount?: number | null } = {},
|
||
): Record<string, unknown> {
|
||
const rawDiagnostics = asRecord(queue.executionDiagnostics) ?? {};
|
||
const compactDiagnostics = diagnostics ?? {};
|
||
const rawLiveness = asRecord(rawDiagnostics.liveness) ?? {};
|
||
const compactLiveness = asRecord(compactDiagnostics.liveness) ?? {};
|
||
const counts = asRecord(queue.counts) ?? {};
|
||
const schedulerLocalActiveQueueIds = options.schedulerLocalActiveQueueIds ?? stringList(queue.activeQueueIds);
|
||
const databaseRunningTaskCount = firstFiniteNumber(counts.running) ?? 0;
|
||
const databaseJudgingTaskCount = firstFiniteNumber(counts.judging) ?? 0;
|
||
const databaseActiveTaskCount = firstFiniteNumber(
|
||
rawDiagnostics.databaseActiveTaskCount,
|
||
queue.databaseActiveTaskCount,
|
||
compactDiagnostics.databaseActiveTaskCount,
|
||
stringListCount(rawDiagnostics.databaseActiveTaskIds),
|
||
stringListCount(queue.databaseActiveTaskIds),
|
||
databaseRunningTaskCount + databaseJudgingTaskCount,
|
||
) ?? 0;
|
||
const heartbeatFreshActiveTaskCount = firstFiniteNumber(
|
||
rawLiveness.heartbeatFreshTaskCount,
|
||
compactLiveness.heartbeatFreshTaskCount,
|
||
stringListCount(rawDiagnostics.heartbeatFreshTaskIds),
|
||
stringListCount(compactDiagnostics.heartbeatFreshTaskIds),
|
||
) ?? 0;
|
||
const activeHeartbeatTaskCount = firstFiniteNumber(
|
||
rawDiagnostics.activeHeartbeatCount,
|
||
compactDiagnostics.activeHeartbeatCount,
|
||
stringListCount(rawDiagnostics.activeHeartbeatTaskIds),
|
||
heartbeatFreshActiveTaskCount,
|
||
) ?? 0;
|
||
const heartbeatRiskTaskCount = firstFiniteNumber(
|
||
rawLiveness.heartbeatRiskTaskCount,
|
||
compactLiveness.heartbeatRiskTaskCount,
|
||
stringListCount(rawDiagnostics.heartbeatRiskTaskIds),
|
||
stringListCount(compactDiagnostics.heartbeatRiskTaskIds),
|
||
) ?? 0;
|
||
const staleRecoveryCandidateTaskCount = firstFiniteNumber(
|
||
rawLiveness.staleRecoveryCandidateTaskCount,
|
||
compactLiveness.staleRecoveryCandidateTaskCount,
|
||
stringListCount(rawDiagnostics.staleRecoveryCandidateTaskIds),
|
||
stringListCount(compactDiagnostics.staleRecoveryCandidateTaskIds),
|
||
) ?? 0;
|
||
const schedulerLocalActiveRunSlotCount = firstFiniteNumber(
|
||
rawDiagnostics.schedulerActiveRunSlotCount,
|
||
queue.schedulerActiveRunSlotCount,
|
||
queue.activeRunSlotCount,
|
||
compactDiagnostics.schedulerActiveRunSlotCount,
|
||
);
|
||
const runnableQueueCount = firstFiniteNumber(options.runnableQueueCount, queue.runnableQueueCount);
|
||
const effectiveActiveTaskCount = Math.max(databaseActiveTaskCount, databaseRunningTaskCount, heartbeatFreshActiveTaskCount);
|
||
const executionState = asString(rawDiagnostics.state ?? compactDiagnostics.state);
|
||
const effectiveLiveness = asString(rawDiagnostics.effectiveLiveness ?? compactDiagnostics.effectiveLiveness ?? rawLiveness.effectiveLiveness ?? compactLiveness.effectiveLiveness);
|
||
const recommendedAction = asString(rawDiagnostics.recommendedAction ?? compactDiagnostics.recommendedAction ?? rawLiveness.recommendedAction ?? compactLiveness.recommendedAction);
|
||
const splitBrain = asBoolean(rawDiagnostics.splitBrain) || asBoolean(compactDiagnostics.splitBrain) || executionState === "split-brain";
|
||
const splitBrainLive = splitBrainLiveFromDiagnostics(rawDiagnostics) || splitBrainLiveFromDiagnostics(compactDiagnostics);
|
||
const effectiveActiveSource = heartbeatFreshActiveTaskCount > 0 && heartbeatFreshActiveTaskCount >= databaseActiveTaskCount
|
||
? "heartbeat-fresh"
|
||
: databaseActiveTaskCount > 0
|
||
? "database-active"
|
||
: schedulerLocalActiveQueueIds.length > 0 || (schedulerLocalActiveRunSlotCount ?? 0) > 0
|
||
? "scheduler-local"
|
||
: "none";
|
||
const activeQueueIdsNote = schedulerLocalActiveQueueIds.length === 0 && effectiveActiveTaskCount > 0
|
||
? "activeQueueIds are scheduler-local only; zero local queue ids does not mean zero active runners when database or heartbeat counts are nonzero."
|
||
: "activeQueueIds are scheduler-local active-run slots; use effectiveActiveTaskCount for commander concurrency decisions.";
|
||
const recommendedActionIntervenes = recommendedAction.length > 0 && recommendedAction !== "continue-supervision";
|
||
const interventionRequired = heartbeatRiskTaskCount > 0 || staleRecoveryCandidateTaskCount > 0 || recommendedActionIntervenes || (splitBrain && !splitBrainLive);
|
||
const splitBrainDisposition = splitBrainLive
|
||
? "live-count-as-active"
|
||
: splitBrain
|
||
? "risk-investigate-before-new-work"
|
||
: "not-split-brain";
|
||
const splitBrainReason = splitBrainLive
|
||
? "database active/running tasks have fresh heartbeat evidence and no heartbeat-risk candidates in the compact summary."
|
||
: splitBrain
|
||
? "split-brain without fresh-heartbeat live evidence is risky; inspect heartbeat risk, stale recovery candidates, and raw diagnostics before changing capacity."
|
||
: "control-plane and execution-plane activity signals are not split-brain.";
|
||
const interventionReason = splitBrainLive
|
||
? "fresh heartbeat makes split-brain live; count these runners as active and continue supervision."
|
||
: heartbeatRiskTaskCount > 0
|
||
? "heartbeat risk is present; inspect before adding replacement work or recovering tasks."
|
||
: staleRecoveryCandidateTaskCount > 0
|
||
? "stale recovery candidates are present; follow the recovery runbook before changing concurrency."
|
||
: recommendedActionIntervenes
|
||
? `execution diagnostics recommend ${recommendedAction}; intervene before adding work.`
|
||
: splitBrain
|
||
? "split-brain is not proven live; inspect raw diagnostics before treating capacity as available."
|
||
: "no intervention signal in compact activity summary.";
|
||
const commanderConcurrency = {
|
||
activeRunnerCount: effectiveActiveTaskCount,
|
||
activeRunnerCountField: "activity.effectiveActiveTaskCount",
|
||
activeRunnerCountSource: effectiveActiveSource,
|
||
decisionRule: "subtract activeRunnerCount from the commander concurrency target; for a 15-runner policy, remaining slots = 15 - activeRunnerCount.",
|
||
splitBrainDisposition,
|
||
splitBrainReason,
|
||
interventionRequired,
|
||
interventionReason,
|
||
};
|
||
return {
|
||
effectiveActiveTaskCount,
|
||
effectiveActiveSource,
|
||
databaseRunningTaskCount,
|
||
databaseActiveTaskCount,
|
||
heartbeatFreshActiveTaskCount,
|
||
activeHeartbeatTaskCount,
|
||
heartbeatRiskTaskCount,
|
||
staleRecoveryCandidateTaskCount,
|
||
schedulerLocalActiveQueueCount: schedulerLocalActiveQueueIds.length,
|
||
schedulerLocalActiveRunSlotCount,
|
||
runnableQueueCount,
|
||
effectiveLiveness: effectiveLiveness || null,
|
||
recommendedAction: recommendedAction || null,
|
||
splitBrainLive,
|
||
splitBrainDisposition,
|
||
splitBrainReason,
|
||
commanderConcurrency,
|
||
activeQueueIdsScope: "scheduler-local-active-run-slots",
|
||
activeQueueIdsNote,
|
||
interpretation: splitBrainLive
|
||
? "split-brain live: database-active tasks have fresh scheduler heartbeat; continue supervision."
|
||
: heartbeatRiskTaskCount > 0
|
||
? "heartbeat risk is present; investigate before retry or recovery."
|
||
: effectiveActiveTaskCount > 0
|
||
? "active work is present; compare database, heartbeat, and scheduler-local counts before changing concurrency."
|
||
: "no active work observed by database, heartbeat, or scheduler-local signals.",
|
||
};
|
||
}
|
||
|
||
function supervisorExecutionDiagnostics(value: unknown): Record<string, unknown> | null {
|
||
const diagnostics = compactExecutionDiagnostics(value);
|
||
if (diagnostics === null) return null;
|
||
const liveness = asRecord(diagnostics.liveness) ?? {};
|
||
const listBudget = asRecord(diagnostics.listBudget) ?? {};
|
||
return {
|
||
state: diagnostics.state ?? null,
|
||
effectiveLiveness: diagnostics.effectiveLiveness ?? null,
|
||
recommendedAction: diagnostics.recommendedAction ?? null,
|
||
splitBrainLive: diagnostics.splitBrainLive ?? null,
|
||
activeHeartbeatCount: diagnostics.activeHeartbeatCount ?? null,
|
||
databaseActiveTaskCount: diagnostics.databaseActiveTaskCount ?? null,
|
||
schedulerActiveRunSlotCount: diagnostics.schedulerActiveRunSlotCount ?? null,
|
||
lastObservedAgentEventAt: diagnostics.lastObservedAgentEventAt ?? null,
|
||
heartbeatFreshTaskIds: diagnostics.heartbeatFreshTaskIds ?? [],
|
||
heartbeatRiskTaskIds: diagnostics.heartbeatRiskTaskIds ?? [],
|
||
traceGapTaskIds: diagnostics.traceGapTaskIds ?? [],
|
||
reasons: diagnostics.reasons ?? [],
|
||
interpretation: liveness.interpretation ?? null,
|
||
listBudget: {
|
||
idPreviewLimit: listBudget.idPreviewLimit ?? diagnosticsIdPreviewLimit,
|
||
reasonPreviewLimit: listBudget.reasonPreviewLimit ?? diagnosticsReasonPreviewLimit,
|
||
truncated: listBudget.truncated ?? false,
|
||
omittedCounts: listBudget.omittedCounts ?? {},
|
||
rawCommand: listBudget.rawCommand ?? "bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview?limit=30 --raw --full",
|
||
},
|
||
};
|
||
}
|
||
|
||
function compactToolSummary(value: unknown, full: boolean, limit = defaultToolLimit): Record<string, unknown> {
|
||
const record = asRecord(value) ?? {};
|
||
const allItems = asArray(record.items);
|
||
const sourceItems = full ? allItems : allItems.slice(0, limit);
|
||
const items = sourceItems.map((item) => {
|
||
const line = asRecord(item) ?? {};
|
||
return {
|
||
seq: line.seq ?? null,
|
||
at: line.at ?? null,
|
||
kind: line.kind ?? null,
|
||
title: line.title ?? "",
|
||
status: line.status ?? null,
|
||
commandPreview: compactText(line.commandPreview, full, 360),
|
||
commandOmittedLines: line.commandOmittedLines ?? 0,
|
||
outputPreview: compactText(line.outputPreview, full, 360),
|
||
outputOmittedLines: line.outputOmittedLines ?? 0,
|
||
rawSeqs: line.rawSeqs ?? [],
|
||
};
|
||
});
|
||
return {
|
||
count: record.count ?? allItems.length,
|
||
returned: items.length,
|
||
limit,
|
||
truncated: record.truncated === true || (!full && allItems.length > items.length),
|
||
items,
|
||
};
|
||
}
|
||
|
||
function compactSummary(summary: unknown, options: CodexTaskOptions, taskId: string): Record<string, unknown> {
|
||
const record = asRecord(summary) ?? {};
|
||
const transcriptCount = asNumber(record.transcriptCount, 0);
|
||
const transcriptMaxSeq = transcriptCount > 0 ? record.transcriptMaxSeq ?? null : null;
|
||
const initialPrompt = asString(record.initialPrompt ?? record.prompt);
|
||
const initialPromptView = textView(initialPrompt, options.full, detailInitialPromptPreviewChars);
|
||
const basePromptView = textView(asString(record.basePrompt), options.full, detailBasePromptPreviewChars);
|
||
const attemptRecordsSource = asArray(record.attempts);
|
||
const attemptRecords = (options.full ? attemptRecordsSource : attemptRecordsSource.slice(0, detailAttemptReturnedLimit))
|
||
.map((attempt) => compactAttemptCycle(attempt, options.full));
|
||
return {
|
||
id: record.id ?? taskId,
|
||
queueId: record.queueId ?? null,
|
||
status: record.status ?? null,
|
||
providerId: record.providerId ?? null,
|
||
model: record.model ?? null,
|
||
agentPort: record.agentPort ?? null,
|
||
agentPortInfo: record.agentPortInfo ?? null,
|
||
reasoningEffort: record.reasoningEffort ?? null,
|
||
cwd: record.cwd ?? null,
|
||
attempts: {
|
||
currentAttempt: record.currentAttempt ?? null,
|
||
maxAttempts: record.maxAttempts ?? null,
|
||
currentMode: record.currentMode ?? null,
|
||
judgeFailCount: record.judgeFailCount ?? null,
|
||
judgeFailRetryLimit: record.judgeFailRetryLimit ?? null,
|
||
attemptRecordCount: attemptRecordsSource.length,
|
||
attemptRecordsReturned: attemptRecords.length,
|
||
attemptRecordsTruncated: !options.full && attemptRecordsSource.length > attemptRecords.length,
|
||
attemptRecords,
|
||
},
|
||
thread: {
|
||
codexThreadId: record.codexThreadId ?? null,
|
||
activeTurnId: record.activeTurnId ?? null,
|
||
cancelRequested: record.cancelRequested ?? null,
|
||
schedulerHeartbeat: compactSchedulerHeartbeat(record.schedulerHeartbeat),
|
||
},
|
||
timing: record.timing ?? null,
|
||
createdAt: record.createdAt ?? null,
|
||
startedAt: record.startedAt ?? null,
|
||
updatedAt: record.updatedAt ?? null,
|
||
finishedAt: record.finishedAt ?? null,
|
||
...(options.full
|
||
? { initialPrompt: initialPromptView, basePrompt: basePromptView }
|
||
: { initialPromptPreview: initialPromptView, basePromptPreview: basePromptView }),
|
||
referenceTaskIds: record.referenceTaskIds ?? [],
|
||
referenceInjection: record.referenceInjection ?? null,
|
||
lastAssistantMessage: compactLastAssistant(record.lastAssistantMessage, options.full, detailLastAssistantPreviewChars),
|
||
lastJudge: record.lastJudge ?? null,
|
||
lastError: record.lastError ?? null,
|
||
toolSummary: compactToolSummary(record.toolSummary, options.full, options.toolLimit),
|
||
counts: {
|
||
transcript: record.transcriptCount ?? null,
|
||
output: record.outputCount ?? null,
|
||
events: record.eventCount ?? null,
|
||
},
|
||
traceDisclosure: {
|
||
included: options.trace,
|
||
renderer: "shared trace-summary/trace-steps progressive abstraction; CLI and WebUI diverge only at final rendering",
|
||
total: record.transcriptCount ?? null,
|
||
maxSeq: transcriptMaxSeq,
|
||
detailOutputPolicy: "bounded detail by default; use --full, --trace, --tool-limit, or codex output for progressive disclosure",
|
||
defaultPage: `bun scripts/cli.ts codex task ${taskId} --trace --limit ${defaultTraceLimit}`,
|
||
firstPage: `bun scripts/cli.ts codex task ${taskId} --trace --from-start --limit ${defaultTraceLimit}`,
|
||
nextPageTemplate: `bun scripts/cli.ts codex task ${taskId} --trace --after-seq <nextAfterSeq> --limit ${defaultTraceLimit}`,
|
||
previousPageTemplate: `bun scripts/cli.ts codex task ${taskId} --trace --before-seq <previousBeforeSeq> --limit ${defaultTraceLimit}`,
|
||
rawOutputTemplate: `bun scripts/cli.ts codex output ${taskId} --after-seq <rawSeq> --limit ${defaultOutputLimit}`,
|
||
fullTextSummary: `bun scripts/cli.ts codex task ${taskId} --detail --full --tool-limit ${Math.max(options.toolLimit, defaultToolLimit)}`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function compactReviewSummary(summary: unknown, options: CodexTaskOptions, taskId: string): Record<string, unknown> {
|
||
const record = asRecord(summary) ?? {};
|
||
const initialPrompt = asString(record.basePrompt || record.initialPrompt || record.prompt);
|
||
const transcriptCount = asNumber(record.transcriptCount, 0);
|
||
return {
|
||
id: record.id ?? taskId,
|
||
queueId: record.queueId ?? null,
|
||
status: record.status ?? null,
|
||
providerId: record.providerId ?? null,
|
||
model: record.model ?? null,
|
||
agentPort: record.agentPort ?? null,
|
||
agentPortInfo: record.agentPortInfo ?? null,
|
||
cwd: record.cwd ?? null,
|
||
attempts: {
|
||
currentAttempt: record.currentAttempt ?? null,
|
||
maxAttempts: record.maxAttempts ?? null,
|
||
currentMode: record.currentMode ?? null,
|
||
},
|
||
timing: record.timing ?? null,
|
||
createdAt: record.createdAt ?? null,
|
||
startedAt: record.startedAt ?? null,
|
||
updatedAt: record.updatedAt ?? null,
|
||
finishedAt: record.finishedAt ?? null,
|
||
originalPrompt: textView(initialPrompt, options.full, 3000),
|
||
finalResponse: compactFinalResponse(record.lastAssistantMessage, options.full),
|
||
lastError: record.lastError ?? null,
|
||
counts: {
|
||
transcript: record.transcriptCount ?? null,
|
||
output: record.outputCount ?? null,
|
||
events: record.eventCount ?? null,
|
||
},
|
||
disclosure: {
|
||
mode: "review",
|
||
defaultScope: "original prompt and final response only; use detail/trace/output commands for progressive disclosure",
|
||
fullPromptAndResponse: `bun scripts/cli.ts codex task ${taskId} --full`,
|
||
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
|
||
fullDetail: `bun scripts/cli.ts codex task ${taskId} --detail --full --tool-limit ${Math.max(options.toolLimit, defaultToolLimit)}`,
|
||
traceTail: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
|
||
outputTail: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`,
|
||
rawOutputTemplate: `bun scripts/cli.ts codex output ${taskId} --after-seq <rawSeq> --limit ${defaultOutputLimit}`,
|
||
transcriptTotal: transcriptCount,
|
||
transcriptMaxSeq: transcriptCount > 0 ? record.transcriptMaxSeq ?? null : null,
|
||
},
|
||
};
|
||
}
|
||
|
||
function traceKindLabel(kind: unknown): string {
|
||
const value = String(kind || "");
|
||
if (value === "ran") return "Ran";
|
||
if (value === "explored") return "Explored";
|
||
if (value === "edited") return "Edited";
|
||
if (value === "error") return "Error";
|
||
if (value === "system") return "System";
|
||
return "Message";
|
||
}
|
||
|
||
function transcriptLineToStep(line: unknown): Record<string, unknown> {
|
||
const record = asRecord(line) ?? {};
|
||
const summaryLines = stringList(record.summaryLines);
|
||
const fallbackSummary = [record.commandPreview, record.bodyPreview, record.title].map((value) => asString(value).trim()).filter(Boolean);
|
||
return {
|
||
seq: record.seq ?? null,
|
||
at: record.at ?? null,
|
||
kind: record.kind ?? "message",
|
||
title: record.title ?? "",
|
||
status: record.status ?? null,
|
||
durationMs: record.durationMs ?? null,
|
||
rawSeqs: record.rawSeqs ?? [],
|
||
summaryLines: summaryLines.length > 0 ? summaryLines : fallbackSummary.slice(0, 4),
|
||
hasDetail: true,
|
||
};
|
||
}
|
||
|
||
function compactTraceStep(step: unknown, taskId: string): Record<string, unknown> {
|
||
const record = asRecord(step) ?? {};
|
||
const seq = record.seq ?? null;
|
||
return {
|
||
seq,
|
||
at: record.at ?? null,
|
||
kind: record.kind ?? "message",
|
||
label: traceKindLabel(record.kind),
|
||
title: record.title ?? "",
|
||
status: record.status ?? null,
|
||
durationMs: record.durationMs ?? null,
|
||
duration: fmtDuration(record.durationMs),
|
||
rawSeqs: record.rawSeqs ?? [],
|
||
summaryLines: stringList(record.summaryLines),
|
||
hasDetail: asBoolean(record.hasDetail),
|
||
detailCommand: seq === null ? null : `bun scripts/cli.ts microservice proxy code-queue /api/tasks/${encodeURIComponent(taskId)}/trace-step?seq=${encodeURIComponent(String(seq))} --raw`,
|
||
};
|
||
}
|
||
|
||
function compactAttemptCycle(value: unknown, full: boolean): Record<string, unknown> {
|
||
const record = asRecord(value) ?? {};
|
||
return {
|
||
index: record.index ?? null,
|
||
synthetic: record.synthetic ?? false,
|
||
label: record.label ?? null,
|
||
mode: record.mode ?? null,
|
||
terminalStatus: record.terminalStatus ?? null,
|
||
appServerExitCode: record.appServerExitCode ?? null,
|
||
appServerSignal: record.appServerSignal ?? null,
|
||
error: record.error ?? null,
|
||
stderrTail: textView(asString(record.stderrTail), full, 600),
|
||
startedAt: record.startedAt ?? null,
|
||
finishedAt: record.finishedAt ?? null,
|
||
startSeq: record.startSeq ?? null,
|
||
endSeq: record.endSeq ?? null,
|
||
execution: record.execution ?? null,
|
||
finalResponse: textView(asString(record.finalResponsePreview ?? record.finalResponse), full, 1200),
|
||
judge: record.judge ?? null,
|
||
runnerErrorClassification: record.runnerErrorClassification ?? null,
|
||
feedbackPrompt: textView(asString(record.feedbackPromptPreview), full, 800),
|
||
};
|
||
}
|
||
|
||
function renderTraceConsoleRows(summary: Record<string, unknown>, steps: Record<string, unknown>[]): string[] {
|
||
const rows: string[] = [];
|
||
const execution = asRecord(summary.execution) ?? {};
|
||
const stats = asRecord(execution.traceStats) ?? asRecord(summary.traceStats);
|
||
const statsSource = String(execution.statsSource || summary.statsSource || "");
|
||
const statsUsable = stats !== null && (statsSource === "oa-event-flow" || statsSource === "raw-trace-fallback" || stats.source === "oa-event-flow");
|
||
const stat = (key: string): string | number => {
|
||
if (!statsUsable) return "--";
|
||
const value = Number(stats[key]);
|
||
return Number.isFinite(value) && value >= 0 ? Math.floor(value) : "--";
|
||
};
|
||
rows.push([
|
||
`task=${summary.id ?? ""}`,
|
||
`status=${summary.status ?? ""}`,
|
||
`port=${summary.agentPort ?? "codex"}`,
|
||
`model=${summary.model ?? ""}`,
|
||
`updated=${summary.updatedAt ?? ""}`,
|
||
].filter((item) => !item.endsWith("=")).join(" "));
|
||
const read = stat("readCount");
|
||
const edit = stat("editCount");
|
||
const run = stat("runCount");
|
||
const toolCount = typeof read === "number" || typeof edit === "number" || typeof run === "number" ? Number(read === "--" ? 0 : read) + Number(edit === "--" ? 0 : edit) + Number(run === "--" ? 0 : run) : "--";
|
||
rows.push(`progressive steps=${steps.length} tools=${toolCount} read=${read} edit=${edit} run=${run} errors=${stat("errorCount")}`);
|
||
for (const step of steps) {
|
||
const head = [`#${step.seq ?? "?"}`, `[${step.label ?? traceKindLabel(step.kind)}]`, step.title ?? ""]
|
||
.filter((item) => String(item).length > 0)
|
||
.join(" ");
|
||
rows.push(`${head}${step.status ? ` (${step.status})` : ""}${step.duration && step.duration !== "--" ? ` ${step.duration}` : ""}`);
|
||
for (const line of stringList(step.summaryLines).slice(0, 4)) rows.push(` ${line}`);
|
||
}
|
||
return rows;
|
||
}
|
||
|
||
function compactProgressiveTrace(summaryBody: Record<string, unknown>, steps: Record<string, unknown>[], taskId: string, full: boolean): Record<string, unknown> {
|
||
const summary = asRecord(summaryBody.summary) ?? summaryBody;
|
||
return {
|
||
taskId: summary.id ?? taskId,
|
||
queueId: summary.queueId ?? null,
|
||
status: summary.status ?? null,
|
||
updatedAt: summary.updatedAt ?? null,
|
||
model: summary.model ?? null,
|
||
agentPort: summary.agentPort ?? null,
|
||
agentPortInfo: summary.agentPortInfo ?? null,
|
||
prompt: summary.prompt ?? null,
|
||
execution: summary.execution ?? null,
|
||
attempts: asArray(summary.attempts).map((attempt) => compactAttemptCycle(attempt, full)),
|
||
displayedStepSeqs: steps.map((step) => step.seq ?? null),
|
||
renderedRows: renderTraceConsoleRows(summary, steps),
|
||
};
|
||
}
|
||
|
||
function compactTracePage(body: Record<string, unknown>, taskId: string, limit: number, summaryBody: Record<string, unknown> | null, options: CodexTaskOptions): Record<string, unknown> {
|
||
const stepsSource = asArray(body.steps).length > 0 ? asArray(body.steps) : asArray(body.transcript).map(transcriptLineToStep);
|
||
const steps = stepsSource.map((step) => compactTraceStep(step, taskId));
|
||
const summary = summaryBody === null ? null : asRecord(summaryBody.summary) ?? summaryBody;
|
||
const nextAfterSeq = body.nextAfterSeq ?? null;
|
||
const previousBeforeSeq = body.previousBeforeSeq ?? null;
|
||
const omittedInPage = steps.some((item) => {
|
||
const line = asRecord(item) ?? {};
|
||
return asNumber(line.bodyOmittedLines, 0) > 0 || asNumber(line.commandOmittedLines, 0) > 0;
|
||
});
|
||
const hasRawSeqs = steps.some((item) => asArray(item.rawSeqs).length > 0);
|
||
return {
|
||
taskId: body.taskId ?? taskId,
|
||
queueId: body.queueId ?? null,
|
||
status: body.status ?? null,
|
||
updatedAt: body.updatedAt ?? null,
|
||
agentPort: body.agentPort ?? summary?.agentPort ?? null,
|
||
agentPortInfo: body.agentPortInfo ?? summary?.agentPortInfo ?? null,
|
||
mode: body.mode ?? null,
|
||
limit,
|
||
returned: steps.length,
|
||
total: body.total ?? null,
|
||
maxSeq: body.maxSeq ?? null,
|
||
afterSeq: body.afterSeq ?? null,
|
||
nextAfterSeq,
|
||
beforeSeq: body.beforeSeq ?? null,
|
||
previousBeforeSeq,
|
||
hasMore: body.hasMore ?? false,
|
||
hasBefore: body.hasBefore ?? false,
|
||
steps,
|
||
progressive: summaryBody === null ? null : compactProgressiveTrace(summaryBody, steps, taskId, options.full),
|
||
commands: {
|
||
next: body.hasMore === true && nextAfterSeq !== null ? `bun scripts/cli.ts codex task ${taskId} --trace --after-seq ${nextAfterSeq} --limit ${limit}` : null,
|
||
previous: body.hasBefore === true && previousBeforeSeq !== null ? `bun scripts/cli.ts codex task ${taskId} --trace --before-seq ${previousBeforeSeq} --limit ${limit}` : null,
|
||
tail: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${limit}`,
|
||
first: `bun scripts/cli.ts codex task ${taskId} --trace --from-start --limit ${limit}`,
|
||
stepDetailTemplate: `bun scripts/cli.ts microservice proxy code-queue /api/tasks/${taskId}/trace-step?seq=<seq> --raw`,
|
||
rawOutput: omittedInPage || hasRawSeqs ? `Use rawSeqs on each trace step, e.g. bun scripts/cli.ts codex output ${taskId} --after-seq <rawSeqBefore> --limit ${defaultOutputLimit}` : null,
|
||
},
|
||
};
|
||
}
|
||
|
||
function parseTaskOptions(args: string[]): CodexTaskOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--detail", "--trace", "--tail", "--from-start", "--first", "--full", "--raw-summary"],
|
||
valueOptions: ["--before-seq", "--beforeSeq", "--after-seq", "--afterSeq", "--trace-limit", "--limit", "--tool-limit"],
|
||
}, "codex task");
|
||
const beforeSeq = nullablePositiveNumberOption(args, ["--before-seq", "--beforeSeq"]);
|
||
const afterSeq = nonNegativeNumberOption(args, ["--after-seq", "--afterSeq"], 0);
|
||
const fromStart = hasFlag(args, "--from-start") || hasFlag(args, "--first");
|
||
const trace = hasFlag(args, "--trace") || beforeSeq !== null || args.some((arg) => arg === "--after-seq" || arg === "--afterSeq") || fromStart || hasFlag(args, "--tail");
|
||
const traceMode = beforeSeq !== null ? "before" : fromStart || args.some((arg) => arg === "--after-seq" || arg === "--afterSeq") ? "after" : "tail";
|
||
const rawSummary = hasFlag(args, "--raw-summary");
|
||
return {
|
||
detail: hasFlag(args, "--detail") || rawSummary,
|
||
trace,
|
||
traceLimit: positiveIntegerOption(args, ["--trace-limit", "--limit"], defaultTraceLimit, maxTraceLimit),
|
||
traceMode,
|
||
afterSeq,
|
||
beforeSeq,
|
||
toolLimit: positiveIntegerOption(args, ["--tool-limit"], defaultToolLimit, 500),
|
||
full: hasFlag(args, "--full") || hasFlag(args, "--raw-summary"),
|
||
rawSummary,
|
||
};
|
||
}
|
||
|
||
function parseOutputOptions(args: string[]): CodexOutputOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--tail", "--from-start", "--first", "--full-text", "--raw"],
|
||
valueOptions: ["--before-seq", "--beforeSeq", "--after-seq", "--afterSeq", "--limit", "--max-text-chars"],
|
||
}, "codex output");
|
||
const beforeSeq = nullablePositiveNumberOption(args, ["--before-seq", "--beforeSeq"]);
|
||
const afterSeq = nonNegativeNumberOption(args, ["--after-seq", "--afterSeq"], 0);
|
||
const fromStart = hasFlag(args, "--from-start") || hasFlag(args, "--first");
|
||
const mode = beforeSeq !== null ? "before" : fromStart || args.some((arg) => arg === "--after-seq" || arg === "--afterSeq") ? "after" : "tail";
|
||
const fullText = hasFlag(args, "--full-text") || hasFlag(args, "--raw");
|
||
const requestedLimit = positiveIntegerOption(args, ["--limit"], defaultOutputLimit, maxTraceLimit);
|
||
return {
|
||
requestedLimit,
|
||
limit: fullText ? requestedLimit : Math.min(requestedLimit, defaultOutputLimit),
|
||
mode,
|
||
afterSeq,
|
||
beforeSeq,
|
||
fullText,
|
||
maxTextChars: positiveIntegerOption(args, ["--max-text-chars"], defaultOutputPreviewChars, fullText ? 500_000 : maxOutputPreviewChars),
|
||
};
|
||
}
|
||
|
||
function parseTasksOptions(args: string[]): CodexTasksOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--unread-only", "--unread", "--full", "--all"],
|
||
valueOptions: ["--queue", "--queue-id", "--limit", "--status", "--view", "--before-id", "--beforeId"],
|
||
}, "codex tasks");
|
||
const viewValue = optionValue(args, ["--view"]) ?? "supervisor";
|
||
if (viewValue !== "supervisor" && viewValue !== "full") throw new Error(`--view must be supervisor or full; got ${viewValue}`);
|
||
const statusRaw = optionValue(args, ["--status"]);
|
||
const statusFilter = statusRaw === undefined
|
||
? null
|
||
: statusRaw.split(/[,\s]+/u).map((item) => item.trim()).filter(Boolean);
|
||
if (statusFilter !== null) {
|
||
const supported = new Set(["queued", "running", "judging", "retry_wait", "succeeded", "failed", "canceled"]);
|
||
const unsupported = statusFilter.filter((status) => !supported.has(status));
|
||
if (unsupported.length > 0) throw new Error(`unsupported --status value: ${unsupported.join(", ")}; supported: ${Array.from(supported).join(", ")}`);
|
||
}
|
||
const requestedLimit = positiveIntegerOption(args, ["--limit"], defaultTasksLimit);
|
||
return {
|
||
queueId: optionValue(args, ["--queue", "--queue-id"]),
|
||
requestedLimit,
|
||
limit: Math.min(requestedLimit, maxTasksLimit),
|
||
beforeId: optionValue(args, ["--before-id", "--beforeId"]),
|
||
unreadOnly: hasFlag(args, "--unread-only") || hasFlag(args, "--unread"),
|
||
statusFilter,
|
||
view: hasFlag(args, "--full") || hasFlag(args, "--all") ? "full" : viewValue,
|
||
};
|
||
}
|
||
|
||
function normalizeRepoFilter(value: string | undefined): string | undefined {
|
||
if (value === undefined) return undefined;
|
||
const trimmed = value.trim()
|
||
.replace(/^https?:\/\/github\.com\//iu, "")
|
||
.replace(/\.git$/iu, "")
|
||
.replace(/^\/+|\/+$/gu, "");
|
||
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(trimmed)) {
|
||
throw new Error(`--repo must be owner/name; got ${value}`);
|
||
}
|
||
return trimmed;
|
||
}
|
||
|
||
function normalizeIssueFilter(value: string | undefined): string | undefined {
|
||
if (value === undefined) return undefined;
|
||
const trimmed = value.trim().replace(/^#/u, "");
|
||
const number = Number(trimmed);
|
||
if (!Number.isInteger(number) || number <= 0) throw new Error(`--issue must be a positive issue number; got ${value}`);
|
||
return `#${number}`;
|
||
}
|
||
|
||
function parseUnreadOptions(args: string[]): CodexUnreadOptions {
|
||
const first = args[0];
|
||
const hasSubcommand = first !== undefined && !first.startsWith("--");
|
||
const subcommand = hasSubcommand ? first : "summary";
|
||
if (subcommand !== "summary" && subcommand !== "list" && subcommand !== "mark-read") {
|
||
throw new Error(`codex unread subcommand must be summary, list, or mark-read; got ${subcommand}`);
|
||
}
|
||
const optionArgs = hasSubcommand ? args.slice(1) : args;
|
||
assertKnownOptions(optionArgs, {
|
||
flags: ["--mark-read", "--confirm", "--dry-run"],
|
||
valueOptions: ["--queue", "--queue-id", "--limit", "--status", "--repo", "--issue", "--before-id", "--beforeId"],
|
||
}, "codex unread");
|
||
const statusRaw = optionValue(optionArgs, ["--status"]);
|
||
const statusFilter = statusRaw === undefined
|
||
? null
|
||
: statusRaw.split(/[,\s]+/u).map((item) => item.trim()).filter(Boolean);
|
||
if (statusFilter !== null) {
|
||
const supported = new Set(["succeeded", "failed", "canceled"]);
|
||
const unsupported = statusFilter.filter((status) => !supported.has(status));
|
||
if (unsupported.length > 0) throw new Error(`unsupported --status value for codex unread: ${unsupported.join(", ")}; supported terminal statuses: ${Array.from(supported).join(", ")}`);
|
||
}
|
||
const requestedLimit = positiveIntegerOption(optionArgs, ["--limit"], defaultTasksLimit);
|
||
const action: CodexUnreadAction = subcommand === "mark-read" || hasFlag(optionArgs, "--mark-read") ? "mark-read" : "summary";
|
||
const explicitDryRun = hasFlag(optionArgs, "--dry-run");
|
||
return {
|
||
queueId: optionValue(optionArgs, ["--queue", "--queue-id"]),
|
||
requestedLimit,
|
||
limit: Math.min(requestedLimit, maxTasksLimit),
|
||
beforeId: optionValue(optionArgs, ["--before-id", "--beforeId"]),
|
||
statusFilter,
|
||
repoFilter: normalizeRepoFilter(optionValue(optionArgs, ["--repo"])),
|
||
issueFilter: normalizeIssueFilter(optionValue(optionArgs, ["--issue"])),
|
||
action,
|
||
confirm: hasFlag(optionArgs, "--confirm"),
|
||
dryRun: explicitDryRun || action === "summary",
|
||
};
|
||
}
|
||
|
||
function parseQueuesOptions(args: string[]): CodexQueuesOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--full", "--all"],
|
||
valueOptions: ["--limit", "--offset", "--page"],
|
||
}, "codex queues");
|
||
const limit = positiveIntegerOption(args, ["--limit"], defaultQueuesLimit, maxTasksLimit);
|
||
const page = positiveIntegerOption(args, ["--page"], 1);
|
||
const offsetExplicit = args.includes("--offset");
|
||
const offset = offsetExplicit ? nonNegativeIntegerOption(args, ["--offset"], 0) : (page - 1) * limit;
|
||
return {
|
||
full: hasFlag(args, "--full") || hasFlag(args, "--all"),
|
||
limit,
|
||
offset,
|
||
page: Math.floor(offset / limit) + 1,
|
||
};
|
||
}
|
||
|
||
function parsePrPreflightOptions(args: string[]): CodexPrPreflightOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--remote", "--push-dry-run", "--pushDryRun", "--pr-create-dry-run", "--prCreateDryRun", "--full", "--raw"],
|
||
valueOptions: ["--push-dry-run-ref", "--pushDryRunRef", "--pr-create-dry-run-head", "--prCreateDryRunHead", "--issue", "--issue-number", "--issueNumber"],
|
||
}, "codex pr-preflight");
|
||
return {
|
||
remote: hasFlag(args, "--remote"),
|
||
pushDryRun: hasFlag(args, "--push-dry-run") || hasFlag(args, "--pushDryRun"),
|
||
pushDryRunRef: optionValue(args, ["--push-dry-run-ref", "--pushDryRunRef"]),
|
||
prCreateDryRun: hasFlag(args, "--pr-create-dry-run") || hasFlag(args, "--prCreateDryRun"),
|
||
prCreateDryRunHead: optionValue(args, ["--pr-create-dry-run-head", "--prCreateDryRunHead"]),
|
||
issueNumber: nullablePositiveNumberOption(args, ["--issue", "--issue-number", "--issueNumber"]),
|
||
full: hasFlag(args, "--full") || hasFlag(args, "--raw"),
|
||
};
|
||
}
|
||
|
||
function parseSkillsSyncOptions(args: string[]): CodexSkillsSyncOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--dry-run", "--dryRun", "--full", "--raw"],
|
||
}, "codex skills-sync");
|
||
const dryRun = hasFlag(args, "--dry-run") || hasFlag(args, "--dryRun");
|
||
return {
|
||
dryRun,
|
||
full: hasFlag(args, "--full") || hasFlag(args, "--raw"),
|
||
};
|
||
}
|
||
|
||
function parseJudgeOptions(args: string[]): CodexJudgeOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--dry-run", "--no-call", "--include-prompt"],
|
||
valueOptions: ["--attempt", "--attempt-id", "--attemptIndex"],
|
||
}, "codex judge");
|
||
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)) {
|
||
if (value !== undefined && value !== null) search.set(key, String(value));
|
||
}
|
||
const text = search.toString();
|
||
return text.length > 0 ? `?${text}` : "";
|
||
}
|
||
|
||
function codexTaskSummary(taskId: string, options: CodexTaskOptions, fetcher: CodexResponseFetcher): unknown {
|
||
const summaryPath = codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: options.toolLimit })}`);
|
||
const summaryResponse = unwrapCodexResponse(fetcher(summaryPath));
|
||
const summary = summaryResponse.body.summary;
|
||
const result: Record<string, unknown> = {
|
||
upstream: summaryResponse.upstream,
|
||
summary: options.detail ? compactSummary(summary, options, taskId) : compactReviewSummary(summary, options, taskId),
|
||
};
|
||
if (options.rawSummary) result.rawSummary = summary;
|
||
if (options.trace) {
|
||
const traceParams: Record<string, string | number | boolean | null> = { limit: options.traceLimit };
|
||
if (options.traceMode === "tail") traceParams.tail = 1;
|
||
if (options.traceMode === "after") traceParams.afterSeq = options.afterSeq;
|
||
if (options.traceMode === "before") traceParams.beforeSeq = options.beforeSeq;
|
||
const traceSummaryResponse = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/trace-summary`)));
|
||
const traceResponse = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/trace-steps${queryString(traceParams)}`)));
|
||
result.trace = compactTracePage(traceResponse.body, taskId, options.traceLimit, traceSummaryResponse.body, options);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
function compactOutputRecord(item: unknown, options: CodexOutputOptions): Record<string, unknown> {
|
||
const record = asRecord(item) ?? {};
|
||
const text = textView(asString(record.text), options.fullText, options.maxTextChars);
|
||
return {
|
||
seq: record.seq ?? null,
|
||
at: record.at ?? null,
|
||
channel: record.channel ?? null,
|
||
method: record.method ?? null,
|
||
itemId: record.itemId ?? null,
|
||
text: text.text,
|
||
textChars: text.chars,
|
||
...(text.truncated ? { textTruncated: true, textOmittedChars: text.omittedChars } : {}),
|
||
};
|
||
}
|
||
|
||
function compactOutputPage(body: Record<string, unknown>, taskId: string, options: CodexOutputOptions): Record<string, unknown> {
|
||
const output = asArray(body.output).map((item) => compactOutputRecord(item, options));
|
||
const nextAfterSeq = body.nextAfterSeq ?? null;
|
||
const previousBeforeSeq = body.previousBeforeSeq ?? null;
|
||
return {
|
||
taskId: body.taskId ?? taskId,
|
||
queueId: body.queueId ?? null,
|
||
status: body.status ?? null,
|
||
updatedAt: body.updatedAt ?? null,
|
||
mode: body.mode ?? null,
|
||
requestedLimit: options.requestedLimit,
|
||
limit: options.limit,
|
||
returned: output.length,
|
||
total: body.total ?? null,
|
||
maxSeq: body.maxSeq ?? null,
|
||
afterSeq: body.afterSeq ?? null,
|
||
nextAfterSeq,
|
||
beforeSeq: body.beforeSeq ?? null,
|
||
previousBeforeSeq,
|
||
hasMore: body.hasMore ?? false,
|
||
hasBefore: body.hasBefore ?? false,
|
||
disclosure: {
|
||
defaultPolicy: "bounded output rows and bounded text previews; use --full-text with an explicit seq window only when raw text is required",
|
||
limitCapped: !options.fullText && options.limit < options.requestedLimit,
|
||
fullText: options.fullText,
|
||
textPreviewChars: options.maxTextChars,
|
||
requestedLimit: options.requestedLimit,
|
||
effectiveLimit: options.limit,
|
||
},
|
||
output,
|
||
commands: {
|
||
next: body.hasMore === true && nextAfterSeq !== null ? `bun scripts/cli.ts codex output ${taskId} --after-seq ${nextAfterSeq} --limit ${options.limit}` : null,
|
||
previous: body.hasBefore === true && previousBeforeSeq !== null ? `bun scripts/cli.ts codex output ${taskId} --before-seq ${previousBeforeSeq} --limit ${options.limit}` : null,
|
||
tail: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${options.limit}`,
|
||
first: `bun scripts/cli.ts codex output ${taskId} --from-start --limit ${options.limit}`,
|
||
fullText: `bun scripts/cli.ts codex output ${taskId} --after-seq <rawSeqBefore> --limit ${Math.min(options.requestedLimit, defaultOutputLimit)} --full-text`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function codexTaskOutput(taskId: string, options: CodexOutputOptions, fetcher: CodexResponseFetcher): unknown {
|
||
const params: Record<string, string | number | boolean | null> = {
|
||
limit: options.limit,
|
||
fullText: options.fullText ? 1 : 0,
|
||
maxTextChars: options.maxTextChars,
|
||
};
|
||
if (options.mode === "tail") params.tail = 1;
|
||
if (options.mode === "after") params.afterSeq = options.afterSeq;
|
||
if (options.mode === "before") params.beforeSeq = options.beforeSeq;
|
||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/output${queryString(params)}`)));
|
||
return { upstream: response.upstream, outputPage: compactOutputPage(response.body, taskId, options) };
|
||
}
|
||
|
||
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(codeQueueProxyPath(`/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);
|
||
}
|
||
|
||
export function codexOutputQuery(taskId: string, optionArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||
return codexTaskOutput(taskId, parseOutputOptions(optionArgs), fetcher);
|
||
}
|
||
|
||
export function codexJudgeQuery(taskId: string, optionArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||
return codexTaskJudge(taskId, parseJudgeOptions(optionArgs), fetcher);
|
||
}
|
||
|
||
export function codexSteerTaskForTest(taskId: string, optionArgs: string[], fetcher: CodexResponseFetcher): unknown {
|
||
return codexSteerTask(taskId, optionArgs, fetcher);
|
||
}
|
||
|
||
export function codexSteerTraceConfirmForTest(taskId: string, optionArgs: string[], fetcher: CodexResponseFetcher): unknown {
|
||
return codexSteerTraceConfirm(taskId, optionArgs, fetcher);
|
||
}
|
||
|
||
function isTerminalTaskStatus(status: unknown): boolean {
|
||
return status === "succeeded" || status === "failed" || status === "canceled";
|
||
}
|
||
|
||
function isActiveTaskStatus(status: unknown): boolean {
|
||
return status === "running" || status === "judging";
|
||
}
|
||
|
||
function taskStatusRank(status: unknown): number {
|
||
if (status === "running") return 0;
|
||
if (status === "judging") return 1;
|
||
if (status === "retry_wait") return 2;
|
||
if (status === "queued") return 3;
|
||
if (status === "succeeded") return 9;
|
||
if (status === "failed") return 10;
|
||
if (status === "canceled") return 11;
|
||
return 99;
|
||
}
|
||
|
||
function taskTimelineMs(task: Record<string, unknown>): number {
|
||
const value = asString(task.finishedAt ?? task.updatedAt ?? task.createdAt);
|
||
const timestamp = Date.parse(value);
|
||
return Number.isFinite(timestamp) ? timestamp : 0;
|
||
}
|
||
|
||
function taskOverviewCandidateKey(task: Record<string, unknown>): string {
|
||
return asString(task.id);
|
||
}
|
||
|
||
function taskUnreadTerminal(task: Record<string, unknown>): boolean {
|
||
const directUnread = task.terminalUnread ?? task.unreadTerminal ?? task.unread;
|
||
if (typeof directUnread === "boolean") return directUnread;
|
||
const status = asString(task.status);
|
||
const readAt = task.readAt;
|
||
return isTerminalTaskStatus(status) && (readAt === null || readAt === undefined || readAt === "");
|
||
}
|
||
|
||
function taskQueuedRunnable(task: Record<string, unknown>): boolean {
|
||
const status = asString(task.status);
|
||
if (status === "queued" || status === "retry_wait") return true;
|
||
return asRecord(task.queuedReason)?.code === "ready";
|
||
}
|
||
|
||
function taskIssueRefs(task: Record<string, unknown>, summary: Record<string, unknown> | null): string[] {
|
||
const text = [
|
||
asString(task.displayPrompt),
|
||
asString(task.basePrompt),
|
||
asString(task.prompt),
|
||
asString(summary?.initialPrompt),
|
||
asString(summary?.basePrompt),
|
||
asString(summary?.prompt),
|
||
asString(summary?.lastError),
|
||
asString(task.lastError),
|
||
].join("\n");
|
||
return Array.from(new Set(Array.from(text.matchAll(/#(\d{1,6})\b/gu)).map((match) => `#${match[1]}`))).slice(0, 8);
|
||
}
|
||
|
||
function taskClassification(task: Record<string, unknown>, summary: Record<string, unknown> | null): {
|
||
kind: CodexTasksSupervisorEntry["kind"];
|
||
labels: string[];
|
||
managementNoise: boolean;
|
||
reason: string;
|
||
} {
|
||
const text = [
|
||
asString(task.displayPrompt),
|
||
asString(task.basePrompt),
|
||
asString(task.prompt),
|
||
asString(task.lastError),
|
||
asString(summary?.lastError),
|
||
asString(asRecord(summary?.lastAssistantMessage)?.text),
|
||
].join("\n").toLowerCase();
|
||
const matches = (pattern: RegExp): boolean => pattern.test(text);
|
||
const labels: string[] = [];
|
||
if (matches(/\b(?:gate|report|aggregator|runbook|contract|audit|review|brief|evidence|diagnostic|observability|visibility|preflight|smoke)\b|报告|审查|汇总|观测|诊断|预检|门禁/iu)) labels.push("management-or-verification");
|
||
if (matches(/\b(?:deploy|deployment|prod|dev|release|artifact|ci|cd)\b|部署|发布|上线/iu)) labels.push("deployment");
|
||
if (matches(/\b(?:fix|bug|repair|implement|feature|ui|frontend|backend|api|database|db|workbench|patch-panel|box-simu|gateway-simu)\b|修复|实现|用户|工作台|接线|仿真|数据库/iu)) labels.push("direct-work");
|
||
if (matches(/\b(?:doc|docs|reference|markdown)\b|文档|参考/iu)) labels.push("documentation");
|
||
if (labels.length === 0) labels.push("uncategorized");
|
||
|
||
if (labels.includes("management-or-verification") && !labels.includes("direct-work") && !labels.includes("deployment")) {
|
||
return { kind: "management-noise", labels, managementNoise: true, reason: "matched report/gate/review/diagnostic terms without direct implementation or deployment terms" };
|
||
}
|
||
if (labels.includes("deployment")) {
|
||
return { kind: "deployment-fix", labels, managementNoise: labels.includes("management-or-verification") && !labels.includes("direct-work"), reason: "matched deployment or artifact terms" };
|
||
}
|
||
if (labels.includes("direct-work")) {
|
||
return { kind: "direct-progress", labels, managementNoise: false, reason: "matched implementation, user-visible, runtime, or repair terms" };
|
||
}
|
||
if (labels.includes("management-or-verification")) {
|
||
return { kind: "verification", labels, managementNoise: true, reason: "matched verification/report terms; keep folded unless it blocks real work" };
|
||
}
|
||
if (labels.includes("documentation")) {
|
||
return { kind: "documentation", labels, managementNoise: false, reason: "matched documentation terms" };
|
||
}
|
||
return { kind: "unknown", labels, managementNoise: false, reason: "no strong classifier term matched" };
|
||
}
|
||
|
||
function supervisorLastMessage(summaryLastAssistant: unknown, maxChars: number): SupervisorMessageSummary | null {
|
||
if (summaryLastAssistant === undefined || summaryLastAssistant === null) return null;
|
||
const record = asRecord(summaryLastAssistant) ?? {};
|
||
const text = asString(record.text);
|
||
return {
|
||
at: record.at ?? null,
|
||
source: record.source ?? "none",
|
||
...supervisorTextSummary(text, maxChars),
|
||
};
|
||
}
|
||
|
||
const awaitingTerminalJudgeHint = "finalResponse is visible while task status is non-terminal; wait for terminal status and judge before closeout.";
|
||
|
||
function finalResponseAwaitingTerminalStatus(status: string | null, summaryLastAssistant: unknown): {
|
||
label: string;
|
||
state: "awaiting-terminal-or-judge" | "awaiting-judge";
|
||
finalResponseAt: unknown;
|
||
} | null {
|
||
if (status !== "running" && status !== "judging") return null;
|
||
const record = asRecord(summaryLastAssistant);
|
||
if (record === null) return null;
|
||
if (asString(record.source) !== "finalResponse") return null;
|
||
if (asString(record.text).trim().length === 0) return null;
|
||
return {
|
||
label: status === "judging" ? "judging (awaiting judge)" : "running (awaiting terminal/judge)",
|
||
state: status === "judging" ? "awaiting-judge" : "awaiting-terminal-or-judge",
|
||
finalResponseAt: record.at ?? null,
|
||
};
|
||
}
|
||
|
||
function taskWatchEntry(task: Record<string, unknown>, summary: Record<string, unknown> | null): CodexTasksEntry {
|
||
const taskId = asString(task.id);
|
||
const summaryCommands = summary === null ? null : asRecord(summary.commands);
|
||
const summaryLastAssistant = summary?.lastAssistantMessage ?? task.lastAssistantMessage;
|
||
const status = asString(task.status) || null;
|
||
const awaitingStatus = finalResponseAwaitingTerminalStatus(status, summaryLastAssistant);
|
||
const promptPreview = textPreview(asString(task.displayPrompt ?? task.basePrompt ?? task.prompt), 360);
|
||
const showCommand = typeof summary?.cliHint === "string" && summary.cliHint.length > 0
|
||
? summary.cliHint
|
||
: `bun scripts/cli.ts codex task ${taskId}`;
|
||
const traceCommand = typeof summary?.traceHint === "string" && summary.traceHint.length > 0
|
||
? summary.traceHint
|
||
: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`;
|
||
const outputCommand = `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`;
|
||
const steerCommand = `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path>`;
|
||
return {
|
||
taskId,
|
||
queueId: asString(task.queueId) || null,
|
||
status,
|
||
statusLabel: awaitingStatus?.label ?? status,
|
||
...(awaitingStatus === null ? {} : {
|
||
awaitingTerminalJudge: true,
|
||
closeoutState: awaitingStatus.state,
|
||
closeoutHint: awaitingTerminalJudgeHint,
|
||
finalResponseAt: awaitingStatus.finalResponseAt,
|
||
}),
|
||
currentAttempt: typeof task.currentAttempt === "number" && Number.isFinite(task.currentAttempt) ? task.currentAttempt : null,
|
||
updatedAt: asString(task.updatedAt) || null,
|
||
finishedAt: asString(task.finishedAt) || null,
|
||
readAt: asString(task.readAt) || null,
|
||
unread: taskUnreadTerminal(task),
|
||
unreadTerminal: taskUnreadTerminal(task),
|
||
promptPreview,
|
||
queuedReason: compactQueuedReason(task.queuedReason),
|
||
lastAssistantMessage: summaryLastAssistant === undefined || summaryLastAssistant === null ? null : compactLastAssistant(summaryLastAssistant, false),
|
||
commands: {
|
||
show: typeof summaryCommands?.show === "string" && summaryCommands.show.length > 0 ? summaryCommands.show : showCommand,
|
||
trace: typeof summaryCommands?.trace === "string" && summaryCommands.trace.length > 0 ? summaryCommands.trace : traceCommand,
|
||
output: outputCommand,
|
||
steer: steerCommand,
|
||
read: `bun scripts/cli.ts codex read ${taskId}`,
|
||
full: `bun scripts/cli.ts codex task ${taskId} --full`,
|
||
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function taskSupervisorEntry(task: Record<string, unknown>, summary: Record<string, unknown> | null, bodyPreviewChars = supervisorBodyPreviewChars): CodexTasksSupervisorEntry {
|
||
const taskId = asString(task.id);
|
||
const summaryLastAssistant = summary?.lastAssistantMessage ?? task.lastAssistantMessage;
|
||
const status = asString(task.status) || null;
|
||
const awaitingStatus = finalResponseAwaitingTerminalStatus(status, summaryLastAssistant);
|
||
const unreadTerminal = taskUnreadTerminal(task);
|
||
const classification = taskClassification(task, summary);
|
||
const queuedReason = compactQueuedReason(task.queuedReason);
|
||
const prompt = supervisorTextSummary(asString(task.displayPrompt ?? task.basePrompt ?? task.prompt), supervisorPromptPreviewChars);
|
||
const lastMessage = supervisorLastMessage(summaryLastAssistant, bodyPreviewChars);
|
||
return {
|
||
id: taskId,
|
||
queue: asString(task.queueId) || null,
|
||
status,
|
||
...(awaitingStatus === null ? {} : {
|
||
statusLabel: awaitingStatus.label,
|
||
awaitingTerminalJudge: true,
|
||
closeoutState: awaitingStatus.state,
|
||
closeoutHint: awaitingTerminalJudgeHint,
|
||
finalResponseAt: awaitingStatus.finalResponseAt,
|
||
}),
|
||
attempt: typeof task.currentAttempt === "number" && Number.isFinite(task.currentAttempt) ? task.currentAttempt : null,
|
||
updatedAt: asString(task.updatedAt) || null,
|
||
...(isTerminalTaskStatus(status) ? { finishedAt: asString(task.finishedAt) || null, unreadTerminal } : {}),
|
||
issues: taskIssueRefs(task, summary),
|
||
kind: classification.kind,
|
||
...(classification.managementNoise ? { noise: true } : {}),
|
||
prompt: prompt.text,
|
||
promptChars: prompt.chars,
|
||
...(prompt.truncated ? { promptTruncated: true } : {}),
|
||
...(lastMessage === null ? {} : {
|
||
last: lastMessage.text,
|
||
lastAt: lastMessage.at,
|
||
lastChars: lastMessage.chars,
|
||
...(lastMessage.truncated ? { lastTruncated: true } : {}),
|
||
}),
|
||
...(queuedReason === null ? {} : { queuedReason }),
|
||
...(unreadTerminal ? { read: `bun scripts/cli.ts codex read ${taskId}` } : {}),
|
||
};
|
||
}
|
||
|
||
function buildTaskWatchSection(
|
||
tasks: Record<string, unknown>[],
|
||
summaries: Map<string, Record<string, unknown>>,
|
||
limit: number,
|
||
nextCommand: string | null,
|
||
fullCommand: string,
|
||
): CodexTasksSection {
|
||
const visibleTasks = tasks.slice(0, limit);
|
||
const items = visibleTasks.map((task) => taskWatchEntry(task, summaries.get(taskOverviewCandidateKey(task)) ?? null));
|
||
const truncated = tasks.length > limit;
|
||
return {
|
||
count: tasks.length,
|
||
returned: items.length,
|
||
truncated,
|
||
hasMore: truncated,
|
||
commands: {
|
||
next: truncated ? nextCommand : null,
|
||
full: fullCommand,
|
||
},
|
||
items,
|
||
};
|
||
}
|
||
|
||
function buildSupervisorTaskSection(
|
||
tasks: Record<string, unknown>[],
|
||
summaries: Map<string, Record<string, unknown>>,
|
||
limit: number,
|
||
nextCommand: string | null,
|
||
fullCommand: string,
|
||
bodyPreviewChars = supervisorBodyPreviewChars,
|
||
): CodexTasksSection<CodexTasksSupervisorEntry> {
|
||
const visibleTasks = tasks.slice(0, limit);
|
||
const items = visibleTasks.map((task) => taskSupervisorEntry(task, summaries.get(taskOverviewCandidateKey(task)) ?? null, bodyPreviewChars));
|
||
const truncated = tasks.length > limit;
|
||
const commands = {
|
||
next: truncated || nextCommand !== null ? nextCommand : null,
|
||
full: fullCommand,
|
||
...(items.length === 0 ? {} : {
|
||
showTemplate: "bun scripts/cli.ts codex task <taskId>",
|
||
detailTemplate: "bun scripts/cli.ts codex task <taskId> --detail",
|
||
traceTemplate: `bun scripts/cli.ts codex task <taskId> --trace --tail --limit ${defaultTraceLimit}`,
|
||
outputTemplate: `bun scripts/cli.ts codex output <taskId> --tail --limit ${defaultOutputLimit}`,
|
||
taskFullTemplate: "bun scripts/cli.ts codex task <taskId> --full",
|
||
readTemplate: "bun scripts/cli.ts codex read <taskId>",
|
||
}),
|
||
};
|
||
return {
|
||
count: tasks.length,
|
||
returned: items.length,
|
||
truncated,
|
||
hasMore: truncated || nextCommand !== null,
|
||
commands,
|
||
items,
|
||
};
|
||
}
|
||
|
||
function collectTaskWatchDegraded(summaryErrors: Array<{ taskId: string; message: string }>, omittedTaskCount = 0): CodexTasksDegraded | null {
|
||
if (summaryErrors.length === 0 && omittedTaskCount === 0) return null;
|
||
return {
|
||
summaryFetchFailedTaskIds: summaryErrors.map((error) => error.taskId),
|
||
summaryFetchErrorCount: summaryErrors.length,
|
||
summaryFetchOmittedTaskCount: omittedTaskCount,
|
||
reason: summaryErrors.length > 0
|
||
? "task summary fetch failed for one or more entries; unread state still comes from task-level overview data"
|
||
: "task summary fetch was intentionally omitted for bounded full view; use commands.show for per-task detail",
|
||
};
|
||
}
|
||
|
||
function sortRunningWatchTasks(tasks: Record<string, unknown>[]): Record<string, unknown>[] {
|
||
return [...tasks]
|
||
.filter((task) => isActiveTaskStatus(asString(task.status)))
|
||
.sort((left, right) => {
|
||
const rankDelta = taskStatusRank(asString(left.status)) - taskStatusRank(asString(right.status));
|
||
if (rankDelta !== 0) return rankDelta;
|
||
const timeDelta = taskTimelineMs(right) - taskTimelineMs(left);
|
||
if (timeDelta !== 0) return timeDelta;
|
||
return asString(left.id).localeCompare(asString(right.id));
|
||
});
|
||
}
|
||
|
||
function sortCompletedWatchTasks(tasks: Record<string, unknown>[]): Record<string, unknown>[] {
|
||
return [...tasks]
|
||
.filter((task) => isTerminalTaskStatus(asString(task.status)))
|
||
.sort((left, right) => {
|
||
const timeDelta = taskTimelineMs(right) - taskTimelineMs(left);
|
||
if (timeDelta !== 0) return timeDelta;
|
||
return asString(left.id).localeCompare(asString(right.id));
|
||
});
|
||
}
|
||
|
||
function sortQueuedWatchTasks(tasks: Record<string, unknown>[]): Record<string, unknown>[] {
|
||
return [...tasks]
|
||
.filter((task) => taskQueuedRunnable(task))
|
||
.sort((left, right) => {
|
||
const rankDelta = taskStatusRank(asString(left.status)) - taskStatusRank(asString(right.status));
|
||
if (rankDelta !== 0) return rankDelta;
|
||
const timeDelta = taskTimelineMs(left) - taskTimelineMs(right);
|
||
if (timeDelta !== 0) return timeDelta;
|
||
return asString(left.id).localeCompare(asString(right.id));
|
||
});
|
||
}
|
||
|
||
function taskMatchesStatusFilter(task: Record<string, unknown>, statusFilter: string[] | null): boolean {
|
||
return statusFilter === null || statusFilter.includes(asString(task.status));
|
||
}
|
||
|
||
function filterTasksForOptions(tasks: Record<string, unknown>[], options: CodexTasksOptions): Record<string, unknown>[] {
|
||
return tasks
|
||
.filter((task) => options.queueId === undefined || asString(task.queueId) === options.queueId)
|
||
.filter((task) => taskMatchesStatusFilter(task, options.statusFilter))
|
||
.filter((task) => !options.unreadOnly || taskUnreadTerminal(task));
|
||
}
|
||
|
||
function baseTaskListOptions(options: CodexTasksOptions): CodexTasksOptions {
|
||
return {
|
||
queueId: options.queueId,
|
||
requestedLimit: options.requestedLimit,
|
||
limit: options.limit,
|
||
beforeId: options.beforeId,
|
||
unreadOnly: options.unreadOnly,
|
||
statusFilter: options.statusFilter,
|
||
view: options.view,
|
||
};
|
||
}
|
||
|
||
function taskListCommand(options: CodexTasksOptions, extra: string[] = []): string {
|
||
const args = ["codex", "tasks"];
|
||
if (options.view !== "supervisor") args.push("--view", options.view);
|
||
if (options.queueId !== undefined) args.push("--queue", options.queueId);
|
||
if (options.unreadOnly) args.push("--unread");
|
||
if (options.statusFilter !== null) args.push("--status", options.statusFilter.join(","));
|
||
if (options.requestedLimit !== defaultTasksLimit) args.push("--limit", String(options.requestedLimit));
|
||
if (options.beforeId !== undefined) args.push("--before-id", options.beforeId);
|
||
args.push(...extra);
|
||
return `bun scripts/cli.ts ${args.join(" ")}`;
|
||
}
|
||
|
||
function codexTasksLimitDisclosure(options: CodexTasksOptions): Record<string, unknown> {
|
||
return {
|
||
requestedLimit: options.requestedLimit,
|
||
effectiveLimit: options.limit,
|
||
maxLimit: maxTasksLimit,
|
||
limitCapped: options.requestedLimit > options.limit,
|
||
};
|
||
}
|
||
|
||
function codexTasksSourceDisclosure(pagination: Record<string, unknown>): Record<string, unknown> {
|
||
const effectiveLimit = asNumber(pagination.limit, 0) || null;
|
||
return {
|
||
endpoint: "/api/tasks/overview",
|
||
requestedLimit: tasksOverviewSourceFetchLimit,
|
||
fetchLimit: tasksOverviewSourceFetchLimit,
|
||
limit: effectiveLimit,
|
||
effectiveLimit,
|
||
limitCapped: effectiveLimit !== null && effectiveLimit < tasksOverviewSourceFetchLimit,
|
||
returned: asNumber(pagination.returned, 0) || null,
|
||
total: asNumber(pagination.total, 0) || null,
|
||
hasMore: asBoolean(pagination.hasMore),
|
||
nextBeforeId: asString(pagination.nextBeforeId) || null,
|
||
includeActive: asBoolean(pagination.includeActive),
|
||
};
|
||
}
|
||
|
||
function taskSectionLimit(options: CodexTasksOptions, maxReturned = supervisorSectionReturnedLimit): number {
|
||
return Math.min(options.limit, maxReturned);
|
||
}
|
||
|
||
function sectionNextCommand(
|
||
tasks: Record<string, unknown>[],
|
||
limit: number,
|
||
options: CodexTasksOptions,
|
||
sourceNextCommand: string | null,
|
||
): string | null {
|
||
if (tasks.length > limit) {
|
||
const lastVisibleTask = tasks[limit - 1];
|
||
const beforeId = lastVisibleTask === undefined ? "" : taskOverviewCandidateKey(lastVisibleTask);
|
||
if (beforeId.length > 0) return taskListCommand({ ...options, beforeId });
|
||
}
|
||
return sourceNextCommand;
|
||
}
|
||
|
||
function countRecordValues(value: unknown): Record<string, number> {
|
||
const record = asRecord(value);
|
||
const counts: Record<string, number> = {};
|
||
if (record === null) return counts;
|
||
for (const [status, rawCount] of Object.entries(record)) {
|
||
const count = typeof rawCount === "number" ? rawCount : typeof rawCount === "string" ? Number(rawCount) : Number.NaN;
|
||
if (Number.isFinite(count) && count >= 0) counts[status] = Math.floor(count);
|
||
}
|
||
return counts;
|
||
}
|
||
|
||
function queueSummaryCountsForOptions(taskPage: CodexTasksTaskPage, options: CodexTasksOptions): SupervisorStatusCounts | null {
|
||
const queue = taskPage.queue;
|
||
if (queue === null) return null;
|
||
if (options.queueId !== undefined) {
|
||
for (const row of asArray(queue.queues)) {
|
||
const record = asRecord(row);
|
||
if (record === null || asString(record.id) !== options.queueId) continue;
|
||
if (asRecord(record.counts) === null) return null;
|
||
return {
|
||
counts: countRecordValues(record.counts),
|
||
exact: true,
|
||
source: "queue-summary-counts",
|
||
scope: "queue",
|
||
queueId: options.queueId,
|
||
};
|
||
}
|
||
return null;
|
||
}
|
||
if (asRecord(queue.counts) === null) return null;
|
||
return {
|
||
counts: countRecordValues(queue.counts),
|
||
exact: true,
|
||
source: "queue-summary-counts",
|
||
scope: "all-queues",
|
||
queueId: null,
|
||
};
|
||
}
|
||
|
||
function fallbackStatusCountsForOptions(taskPage: CodexTasksTaskPage, options: CodexTasksOptions): SupervisorStatusCounts {
|
||
const counts: Record<string, number> = {};
|
||
for (const task of taskPage.tasks) {
|
||
if (options.queueId !== undefined && asString(task.queueId) !== options.queueId) continue;
|
||
const status = asString(task.status);
|
||
if (status.length === 0) continue;
|
||
counts[status] = (counts[status] ?? 0) + 1;
|
||
}
|
||
return {
|
||
counts,
|
||
exact: false,
|
||
source: "overview-page-fallback",
|
||
scope: options.queueId === undefined ? "all-queues" : "queue",
|
||
queueId: options.queueId ?? null,
|
||
};
|
||
}
|
||
|
||
function statusCountsForOptions(taskPage: CodexTasksTaskPage, options: CodexTasksOptions): SupervisorStatusCounts {
|
||
return queueSummaryCountsForOptions(taskPage, options) ?? fallbackStatusCountsForOptions(taskPage, options);
|
||
}
|
||
|
||
function positiveCount(value: unknown): number | null {
|
||
const count = asNumber(value, Number.NaN);
|
||
return Number.isFinite(count) && count >= 0 ? Math.floor(count) : null;
|
||
}
|
||
|
||
function runtimeMaxActiveQueues(taskPage: CodexTasksTaskPage): number | null {
|
||
const value = positiveCount(taskPage.queue?.maxActiveQueues);
|
||
return value !== null && value > 0 ? value : null;
|
||
}
|
||
|
||
function activeRunningRedlineState(activeCount: number, hardRedline: number, burstRedline: number, routineTarget: number): string {
|
||
if (activeCount >= hardRedline) return "at-or-over-hard-redline";
|
||
if (activeCount >= burstRedline) return "burst-redline";
|
||
if (activeCount >= routineTarget) return "above-routine-target";
|
||
return "below-routine-target";
|
||
}
|
||
|
||
function supervisorActiveRunningSummary(
|
||
taskPage: CodexTasksTaskPage,
|
||
options: CodexTasksOptions,
|
||
runningSection: CodexTasksSection<CodexTasksSupervisorEntry>,
|
||
diagnostics: Record<string, unknown> | null,
|
||
): Record<string, unknown> {
|
||
const statusCounts = statusCountsForOptions(taskPage, options);
|
||
const runningCount = statusCounts.counts.running ?? 0;
|
||
const judgingCount = statusCounts.counts.judging ?? 0;
|
||
const activeStatusCount = runningCount + judgingCount;
|
||
const heartbeatActiveCount = positiveCount(diagnostics?.activeHeartbeatCount);
|
||
const schedulerLocalActiveRunSlotCount = positiveCount(diagnostics?.schedulerActiveRunSlotCount);
|
||
const effectiveActiveRunnerCount = Math.max(activeStatusCount, heartbeatActiveCount ?? 0, schedulerLocalActiveRunSlotCount ?? 0);
|
||
const policy = submitPolicyContract().concurrency;
|
||
const routineTarget = policy.gpt55Routine;
|
||
const burstRedline = Math.max(policy.gpt55BurstMax, policy.minimaxSimpleMax);
|
||
const hardRedline = 15;
|
||
const runtimeLimit = runtimeMaxActiveQueues(taskPage);
|
||
const redlineState = activeRunningRedlineState(activeStatusCount, hardRedline, burstRedline, routineTarget);
|
||
const exactInterpretation = statusCounts.exact
|
||
? "activeRunning.count is exact from queue status counts; row pagination does not change this count"
|
||
: "activeRunning.count is page-scoped because queue status counts were absent; do not use it for commander redline decisions without a queue summary/raw cross-check";
|
||
const baseOptions = baseTaskListOptions({ ...options, beforeId: undefined, unreadOnly: false, statusFilter: null });
|
||
const runningOptions: CodexTasksOptions = { ...baseOptions, statusFilter: ["running", "judging"] };
|
||
const queuedOptions: CodexTasksOptions = { ...baseOptions, statusFilter: ["queued", "retry_wait"] };
|
||
return {
|
||
count: activeStatusCount,
|
||
exact: statusCounts.exact,
|
||
source: statusCounts.source,
|
||
scope: statusCounts.scope,
|
||
queueId: statusCounts.queueId,
|
||
statuses: {
|
||
running: runningCount,
|
||
judging: judgingCount,
|
||
},
|
||
effectiveActiveRunnerCount,
|
||
effectiveActiveRunnerCountSources: {
|
||
activeStatusCount,
|
||
heartbeatActiveCount,
|
||
schedulerLocalActiveRunSlotCount,
|
||
},
|
||
rowPage: {
|
||
returned: runningSection.returned,
|
||
availableInCurrentOverview: runningSection.count,
|
||
returnedLimit: taskSectionLimit(options),
|
||
truncated: runningSection.truncated,
|
||
hasMore: runningSection.hasMore,
|
||
distinction: "returned is the compact row page; count is the active running total used for supervision",
|
||
},
|
||
redline: {
|
||
countField: "supervisor.activeRunning.count",
|
||
routineTarget,
|
||
burstRedline,
|
||
hardRedline,
|
||
runtimeMaxActiveQueues: runtimeLimit,
|
||
state: redlineState,
|
||
remainingToRoutineTarget: Math.max(0, routineTarget - activeStatusCount),
|
||
remainingToBurstRedline: Math.max(0, burstRedline - activeStatusCount),
|
||
remainingToHardRedline: Math.max(0, hardRedline - activeStatusCount),
|
||
decisionReady: statusCounts.exact,
|
||
interpretation: exactInterpretation,
|
||
},
|
||
commands: {
|
||
running: taskListCommand(runningOptions),
|
||
runningFull: taskListCommand({ ...runningOptions, view: "full" }),
|
||
queued: taskListCommand(queuedOptions),
|
||
queues: "bun scripts/cli.ts codex queues",
|
||
nextRunningPage: runningSection.commands.next,
|
||
},
|
||
};
|
||
}
|
||
|
||
function fetchTaskSummaries(taskIds: string[], fetcher: CodexResponseFetcher): { summaries: Map<string, Record<string, unknown>>; degraded: CodexTasksDegraded | null } {
|
||
const boundedIds = taskIds.slice(0, maxTasksLimit);
|
||
const summaries = new Map<string, Record<string, unknown>>();
|
||
const errors: Array<{ taskId: string; message: string }> = [];
|
||
for (const taskId of boundedIds) {
|
||
try {
|
||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: 1 })}`)));
|
||
const summary = asRecord(response.body.summary) ?? {};
|
||
summaries.set(taskId, summary);
|
||
} catch (error) {
|
||
errors.push({ taskId, message: error instanceof Error ? error.message : String(error) });
|
||
}
|
||
}
|
||
return { summaries, degraded: collectTaskWatchDegraded(errors, Math.max(0, taskIds.length - boundedIds.length)) };
|
||
}
|
||
|
||
async function fetchTaskSummariesAsync(taskIds: string[], fetcher: AsyncCodexResponseFetcher): Promise<{ summaries: Map<string, Record<string, unknown>>; degraded: CodexTasksDegraded | null }> {
|
||
const boundedIds = taskIds.slice(0, maxTasksLimit);
|
||
const summaries = new Map<string, Record<string, unknown>>();
|
||
const errors: Array<{ taskId: string; message: string }> = [];
|
||
await Promise.all(boundedIds.map(async (taskId) => {
|
||
try {
|
||
const response = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: 1 })}`)));
|
||
const summary = asRecord(response.body.summary) ?? {};
|
||
summaries.set(taskId, summary);
|
||
} catch (error) {
|
||
errors.push({ taskId, message: error instanceof Error ? error.message : String(error) });
|
||
}
|
||
}));
|
||
return { summaries, degraded: collectTaskWatchDegraded(errors, Math.max(0, taskIds.length - boundedIds.length)) };
|
||
}
|
||
|
||
type CodexTasksTaskPage = {
|
||
queue: Record<string, unknown> | null;
|
||
pagination: Record<string, unknown>;
|
||
tasks: Record<string, unknown>[];
|
||
};
|
||
|
||
function tasksListQueryString(options: CodexTasksOptions): string {
|
||
return queryString({
|
||
limit: tasksOverviewSourceFetchLimit,
|
||
priorityLimit: tasksOverviewSourceFetchLimit,
|
||
queueId: options.queueId,
|
||
beforeId: options.beforeId,
|
||
includeActive: 1,
|
||
selected: 0,
|
||
compact: 1,
|
||
stats: 0,
|
||
skipTrace: 1,
|
||
});
|
||
}
|
||
|
||
function loadCodexTasks(taskArgs: CodexTasksOptions, fetcher: CodexResponseFetcher): { upstream: { ok: unknown; status: unknown }; page: CodexTasksTaskPage } {
|
||
const byId = new Map<string, Record<string, unknown>>();
|
||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/overview${tasksListQueryString(taskArgs)}`)));
|
||
const pageTasks = asArray(response.body.tasks).map((task) => asRecord(task)).filter((task): task is Record<string, unknown> => task !== null);
|
||
for (const task of pageTasks) {
|
||
const taskId = taskOverviewCandidateKey(task);
|
||
if (taskId.length === 0) continue;
|
||
const existing = byId.get(taskId);
|
||
if (existing === undefined || taskTimelineMs(task) >= taskTimelineMs(existing)) byId.set(taskId, task);
|
||
}
|
||
const tasks = Array.from(byId.values()).sort((left, right) => {
|
||
const leftTime = taskTimelineMs(left);
|
||
const rightTime = taskTimelineMs(right);
|
||
if (leftTime !== rightTime) return rightTime - leftTime;
|
||
return asString(left.id).localeCompare(asString(right.id));
|
||
});
|
||
return { upstream: response.upstream, page: { queue: asRecord(response.body.queue), pagination: asRecord(response.body.pagination) ?? {}, tasks } };
|
||
}
|
||
|
||
function unreadLoadOptions(options: CodexUnreadOptions): CodexTasksOptions {
|
||
return {
|
||
queueId: options.queueId,
|
||
requestedLimit: maxTasksLimit,
|
||
limit: maxTasksLimit,
|
||
beforeId: undefined,
|
||
unreadOnly: true,
|
||
statusFilter: null,
|
||
view: "full",
|
||
};
|
||
}
|
||
|
||
function unreadTriageCommand(options: CodexUnreadOptions, action: CodexUnreadAction = "summary", extra: string[] = []): string {
|
||
const args = ["codex", "unread"];
|
||
if (action === "mark-read") args.push("mark-read");
|
||
if (options.queueId !== undefined) args.push("--queue", options.queueId);
|
||
if (options.repoFilter !== undefined) args.push("--repo", options.repoFilter);
|
||
if (options.issueFilter !== undefined) args.push("--issue", options.issueFilter.replace(/^#/u, ""));
|
||
if (options.statusFilter !== null) args.push("--status", options.statusFilter.join(","));
|
||
if (options.requestedLimit !== defaultTasksLimit) args.push("--limit", String(options.requestedLimit));
|
||
if (options.beforeId !== undefined) args.push("--before-id", options.beforeId);
|
||
args.push(...extra);
|
||
return `bun scripts/cli.ts ${args.join(" ")}`;
|
||
}
|
||
|
||
function taskTriageSearchText(task: Record<string, unknown>): string {
|
||
return [
|
||
asString(task.displayPrompt),
|
||
asString(task.basePrompt),
|
||
asString(task.prompt),
|
||
asString(task.cwd),
|
||
asString(task.lastError),
|
||
...stringList(task.referenceTaskIds),
|
||
].join("\n");
|
||
}
|
||
|
||
function taskRepoRefs(task: Record<string, unknown>): string[] {
|
||
const text = taskTriageSearchText(task);
|
||
const repos = new Set<string>();
|
||
for (const match of text.matchAll(/\b([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#\d{1,6}\b/gu)) {
|
||
const repo = match[1] ?? "";
|
||
if (repo.length > 0) repos.add(repo);
|
||
}
|
||
for (const match of text.matchAll(/\bgithub\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?(?:[#/\s?]|$)/giu)) {
|
||
const owner = match[1] ?? "";
|
||
const name = (match[2] ?? "").replace(/\.git$/iu, "");
|
||
if (owner.length > 0 && name.length > 0) repos.add(`${owner}/${name}`);
|
||
}
|
||
return Array.from(repos).sort((left, right) => left.localeCompare(right));
|
||
}
|
||
|
||
function taskIssueRefsForTriage(task: Record<string, unknown>): string[] {
|
||
const text = taskTriageSearchText(task);
|
||
const issues = new Set<string>();
|
||
for (const match of text.matchAll(/#(\d{1,6})\b/gu)) issues.add(`#${match[1]}`);
|
||
for (const match of text.matchAll(/\/issues\/(\d{1,6})\b/gu)) issues.add(`#${match[1]}`);
|
||
return Array.from(issues)
|
||
.sort((left, right) => Number(left.slice(1)) - Number(right.slice(1)));
|
||
}
|
||
|
||
function lowerSet(values: string[]): Set<string> {
|
||
return new Set(values.map((value) => value.toLowerCase()));
|
||
}
|
||
|
||
function taskMatchesUnreadFilters(task: Record<string, unknown>, options: CodexUnreadOptions): boolean {
|
||
if (!taskUnreadTerminal(task)) return false;
|
||
if (!taskMatchesStatusFilter(task, options.statusFilter)) return false;
|
||
if (options.queueId !== undefined && asString(task.queueId) !== options.queueId) return false;
|
||
if (options.repoFilter !== undefined && !lowerSet(taskRepoRefs(task)).has(options.repoFilter.toLowerCase())) return false;
|
||
if (options.issueFilter !== undefined && !taskIssueRefsForTriage(task).includes(options.issueFilter)) return false;
|
||
return true;
|
||
}
|
||
|
||
function unreadSortedCandidates(tasks: Record<string, unknown>[], options: CodexUnreadOptions): Record<string, unknown>[] {
|
||
return sortCompletedWatchTasks(tasks.filter((task) => taskMatchesUnreadFilters(task, options)));
|
||
}
|
||
|
||
function unreadPageCandidates(candidates: Record<string, unknown>[], options: CodexUnreadOptions): Record<string, unknown>[] {
|
||
if (options.beforeId === undefined) return candidates;
|
||
const beforeIndex = candidates.findIndex((task) => taskOverviewCandidateKey(task) === options.beforeId);
|
||
return beforeIndex < 0 ? candidates : candidates.slice(beforeIndex + 1);
|
||
}
|
||
|
||
function incrementCount(map: Map<string, number>, key: string): void {
|
||
map.set(key, (map.get(key) ?? 0) + 1);
|
||
}
|
||
|
||
function countBucket(map: Map<string, number>, limit = unreadTriageCountLimit): Record<string, unknown> {
|
||
const rows = Array.from(map.entries())
|
||
.map(([key, count]) => ({ key, count }))
|
||
.sort((left, right) => right.count - left.count || left.key.localeCompare(right.key));
|
||
return {
|
||
totalDistinct: rows.length,
|
||
returned: Math.min(rows.length, limit),
|
||
truncated: rows.length > limit,
|
||
items: rows.slice(0, limit),
|
||
};
|
||
}
|
||
|
||
function unreadTriageCounts(candidates: Record<string, unknown>[]): Record<string, unknown> {
|
||
const byRepo = new Map<string, number>();
|
||
const byIssue = new Map<string, number>();
|
||
const byStatus = new Map<string, number>();
|
||
const byQueue = new Map<string, number>();
|
||
for (const task of candidates) {
|
||
incrementCount(byStatus, asString(task.status) || "unknown");
|
||
incrementCount(byQueue, asString(task.queueId) || "unknown");
|
||
const repos = taskRepoRefs(task);
|
||
const issues = taskIssueRefsForTriage(task);
|
||
if (repos.length === 0) incrementCount(byRepo, "unknown");
|
||
for (const repo of repos) incrementCount(byRepo, repo);
|
||
if (issues.length === 0) incrementCount(byIssue, "unknown");
|
||
for (const issue of issues) incrementCount(byIssue, issue);
|
||
}
|
||
return {
|
||
totalUnreadTerminal: candidates.length,
|
||
byRepo: countBucket(byRepo),
|
||
byIssue: countBucket(byIssue),
|
||
byStatus: countBucket(byStatus),
|
||
byQueue: countBucket(byQueue),
|
||
};
|
||
}
|
||
|
||
function unreadTriageItem(task: Record<string, unknown>): Record<string, unknown> {
|
||
const taskId = taskOverviewCandidateKey(task);
|
||
return {
|
||
id: taskId,
|
||
queue: asString(task.queueId) || null,
|
||
status: asString(task.status) || null,
|
||
repos: taskRepoRefs(task),
|
||
issues: taskIssueRefsForTriage(task),
|
||
updatedAt: asString(task.updatedAt) || null,
|
||
finishedAt: asString(task.finishedAt) || null,
|
||
commands: {
|
||
show: `bun scripts/cli.ts codex task ${taskId}`,
|
||
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
|
||
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
|
||
output: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`,
|
||
read: `bun scripts/cli.ts codex read ${taskId}`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function unreadTriageSummary(upstream: { ok: unknown; status: unknown }, page: CodexTasksTaskPage, options: CodexUnreadOptions): {
|
||
result: Record<string, unknown>;
|
||
visibleCandidates: Record<string, unknown>[];
|
||
} {
|
||
const allCandidates = unreadSortedCandidates(page.tasks, options);
|
||
const pagedCandidates = unreadPageCandidates(allCandidates, options);
|
||
const visibleCandidates = pagedCandidates.slice(0, options.limit);
|
||
const hasMore = pagedCandidates.length > visibleCandidates.length;
|
||
const nextBeforeId = hasMore ? taskOverviewCandidateKey(visibleCandidates.at(-1) ?? {}) || null : null;
|
||
const nextCommand = nextBeforeId === null ? null : unreadTriageCommand({ ...options, beforeId: nextBeforeId });
|
||
const readCommand = unreadTriageCommand(options, "mark-read", ["--confirm"]);
|
||
return {
|
||
visibleCandidates,
|
||
result: {
|
||
ok: true,
|
||
upstream,
|
||
unreadTriage: {
|
||
filters: {
|
||
queueId: options.queueId ?? null,
|
||
repo: options.repoFilter ?? null,
|
||
issue: options.issueFilter ?? null,
|
||
status: options.statusFilter,
|
||
requestedLimit: options.requestedLimit,
|
||
limit: options.limit,
|
||
limitCapped: options.requestedLimit > options.limit,
|
||
beforeId: options.beforeId ?? null,
|
||
},
|
||
readOnly: options.action === "summary" || options.dryRun,
|
||
bounded: true,
|
||
disclosure: {
|
||
policy: "summary counts plus bounded task ids only; no raw prompt, final response, trace, or output is included by default",
|
||
countBucketLimit: unreadTriageCountLimit,
|
||
itemLimit: options.limit,
|
||
mutationPolicy: "batch mark-read requires codex unread mark-read --confirm; per-task read remains codex read <taskId>",
|
||
},
|
||
counts: unreadTriageCounts(allCandidates),
|
||
newest: {
|
||
count: allCandidates.length,
|
||
returned: visibleCandidates.length,
|
||
hasMore,
|
||
nextBeforeId,
|
||
commands: {
|
||
next: nextCommand,
|
||
showTemplate: "bun scripts/cli.ts codex task <taskId>",
|
||
detailTemplate: "bun scripts/cli.ts codex task <taskId> --detail",
|
||
traceTemplate: `bun scripts/cli.ts codex task <taskId> --trace --tail --limit ${defaultTraceLimit}`,
|
||
outputTemplate: `bun scripts/cli.ts codex output <taskId> --tail --limit ${defaultOutputLimit}`,
|
||
readTemplate: "bun scripts/cli.ts codex read <taskId>",
|
||
},
|
||
items: visibleCandidates.map(unreadTriageItem),
|
||
},
|
||
commands: {
|
||
refresh: unreadTriageCommand(options),
|
||
tasksUnread: taskListCommand({
|
||
queueId: options.queueId,
|
||
requestedLimit: Math.min(options.requestedLimit, defaultTasksLimit),
|
||
limit: Math.min(options.limit, defaultTasksLimit),
|
||
beforeId: undefined,
|
||
unreadOnly: true,
|
||
statusFilter: options.statusFilter,
|
||
view: "supervisor",
|
||
}),
|
||
byRepo: "bun scripts/cli.ts codex unread --repo owner/name",
|
||
byIssue: "bun scripts/cli.ts codex unread --issue <number>",
|
||
byStatus: "bun scripts/cli.ts codex unread --status succeeded|failed|canceled",
|
||
byQueue: "bun scripts/cli.ts codex unread --queue <queueId>",
|
||
perTaskRead: "bun scripts/cli.ts codex read <taskId>",
|
||
batchReadDryRun: unreadTriageCommand(options, "mark-read", ["--dry-run"]),
|
||
batchReadConfirm: readCommand,
|
||
},
|
||
},
|
||
},
|
||
};
|
||
}
|
||
|
||
function codexUnreadMutationGuard(result: Record<string, unknown>, visibleCandidates: Record<string, unknown>[], options: CodexUnreadOptions): Record<string, unknown> {
|
||
const triage = asRecord(result.unreadTriage) ?? {};
|
||
return {
|
||
...result,
|
||
ok: false,
|
||
unreadTriage: {
|
||
...triage,
|
||
readOnly: true,
|
||
mutation: {
|
||
requested: true,
|
||
confirmed: false,
|
||
blocked: true,
|
||
reason: "batch mark-read requires --confirm; default codex unread output is read-only",
|
||
candidateTaskIds: visibleCandidates.map(taskOverviewCandidateKey),
|
||
command: unreadTriageCommand(options, "mark-read", ["--confirm"]),
|
||
},
|
||
},
|
||
};
|
||
}
|
||
|
||
function codexUnreadMutationResult(result: Record<string, unknown>, options: CodexUnreadOptions, results: Record<string, unknown>[]): Record<string, unknown> {
|
||
const triage = asRecord(result.unreadTriage) ?? {};
|
||
const failed = results.filter((item) => item.ok === false);
|
||
return {
|
||
...result,
|
||
ok: failed.length === 0,
|
||
unreadTriage: {
|
||
...triage,
|
||
readOnly: false,
|
||
mutation: {
|
||
requested: true,
|
||
confirmed: true,
|
||
action: "mark-read",
|
||
limit: options.limit,
|
||
attempted: results.length,
|
||
succeeded: results.length - failed.length,
|
||
failed: failed.length,
|
||
results,
|
||
commands: {
|
||
refresh: unreadTriageCommand({ ...options, action: "summary", confirm: false, dryRun: true }),
|
||
tasksUnread: `bun scripts/cli.ts codex unread --limit ${defaultTasksLimit}`,
|
||
},
|
||
},
|
||
},
|
||
};
|
||
}
|
||
|
||
function codexUnreadTriage(taskArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||
const options = parseUnreadOptions(taskArgs);
|
||
const { upstream, page } = loadCodexTasks(unreadLoadOptions(options), fetcher);
|
||
const { result, visibleCandidates } = unreadTriageSummary(upstream, page, options);
|
||
if (options.action !== "mark-read" || options.dryRun) return result;
|
||
if (!options.confirm) return codexUnreadMutationGuard(result, visibleCandidates, options);
|
||
const results: Record<string, unknown>[] = [];
|
||
for (const task of visibleCandidates) {
|
||
const taskId = taskOverviewCandidateKey(task);
|
||
try {
|
||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
|
||
results.push({ taskId, ok: true, upstream: response.upstream });
|
||
} catch (error) {
|
||
results.push({ taskId, ok: false, error: error instanceof Error ? error.message : String(error) });
|
||
}
|
||
}
|
||
return codexUnreadMutationResult(result, options, results);
|
||
}
|
||
|
||
function codexTasksOverviewResult(
|
||
taskPage: CodexTasksTaskPage,
|
||
upstream: { ok: unknown; status: unknown },
|
||
options: CodexTasksOptions,
|
||
summaries: Map<string, Record<string, unknown>>,
|
||
degraded: CodexTasksDegraded | null,
|
||
): Record<string, unknown> {
|
||
if (options.view === "full") return codexTasksFullResult(taskPage, upstream, options, summaries, degraded);
|
||
const allTasks = filterTasksForOptions(taskPage.tasks, options);
|
||
const runningTasks = sortRunningWatchTasks(allTasks);
|
||
const unreadCompletedTasks = sortCompletedWatchTasks(allTasks).filter((task) => taskUnreadTerminal(task));
|
||
const recentCompletedTasks = options.unreadOnly ? [] : sortCompletedWatchTasks(allTasks).filter((task) => !taskUnreadTerminal(task));
|
||
const queuedTasks = options.unreadOnly ? [] : sortQueuedWatchTasks(allTasks);
|
||
const nextBeforeId = asString(taskPage.pagination.nextBeforeId) || null;
|
||
const sourceHasMore = asBoolean(taskPage.pagination.hasMore);
|
||
const nextCommand = sourceHasMore && nextBeforeId !== null ? taskListCommand({ ...options, beforeId: nextBeforeId }) : null;
|
||
const fullCommand = taskListCommand({ ...options, view: "full" });
|
||
const sectionLimit = taskSectionLimit(options);
|
||
const recentLimit = Math.min(options.limit, supervisorRecentCompletedLimit);
|
||
const runningSection = buildSupervisorTaskSection(runningTasks, summaries, sectionLimit, sectionNextCommand(runningTasks, sectionLimit, options, nextCommand), fullCommand);
|
||
const unreadSection = buildSupervisorTaskSection(unreadCompletedTasks, summaries, sectionLimit, sectionNextCommand(unreadCompletedTasks, sectionLimit, options, nextCommand), fullCommand);
|
||
const recentSection = buildSupervisorTaskSection(recentCompletedTasks, summaries, recentLimit, sectionNextCommand(recentCompletedTasks, recentLimit, options, nextCommand), fullCommand, supervisorRecentBodyPreviewChars);
|
||
const queuedSection = buildSupervisorTaskSection(queuedTasks, summaries, sectionLimit, sectionNextCommand(queuedTasks, sectionLimit, options, nextCommand), fullCommand);
|
||
const pagination = taskPage.pagination;
|
||
const diagnostics = supervisorExecutionDiagnostics(asRecord(taskPage.queue)?.executionDiagnostics);
|
||
const activity = compactCodeQueueActivity(asRecord(taskPage.queue) ?? {}, diagnostics);
|
||
const commanderConcurrency = asRecord(activity.commanderConcurrency) ?? {};
|
||
const activeRunning = supervisorActiveRunningSummary(taskPage, options, runningSection, diagnostics);
|
||
const visibleSupervisorItems = [...runningSection.items, ...unreadSection.items, ...recentSection.items, ...queuedSection.items];
|
||
const classifierCounts = visibleSupervisorItems.reduce((counts, item) => {
|
||
const key = item.kind;
|
||
counts[key] = (counts[key] ?? 0) + 1;
|
||
if (item.noise) counts.managementNoise = (counts.managementNoise ?? 0) + 1;
|
||
return counts;
|
||
}, {} as Record<string, number>);
|
||
return {
|
||
upstream,
|
||
supervisor: {
|
||
filters: {
|
||
view: options.view,
|
||
queueId: options.queueId ?? null,
|
||
requestedLimit: options.requestedLimit,
|
||
limit: options.limit,
|
||
...codexTasksLimitDisclosure(options),
|
||
unreadOnly: options.unreadOnly,
|
||
status: options.statusFilter,
|
||
beforeId: options.beforeId ?? null,
|
||
},
|
||
source: { queueId: options.queueId ?? null, ...codexTasksSourceDisclosure(pagination) },
|
||
bounded: true,
|
||
disclosure: {
|
||
defaultView: "supervisor",
|
||
policy: "bounded summary rows only; prompt/body are short previews and raw detail requires explicit task/full/trace/output commands",
|
||
limitSemantics: "--limit is request/scanning/page budget; supervisor sections still return compact bounded row pages and expose counts separately",
|
||
limitPolicy: {
|
||
...codexTasksLimitDisclosure(options),
|
||
sourceFetchLimit: tasksOverviewSourceFetchLimit,
|
||
sourceEffectiveLimit: asNumber(pagination.limit, 0) || null,
|
||
sourceReturned: asNumber(pagination.returned, 0) || null,
|
||
note: "codex tasks --limit is capped for CLI output safety; supervisor sections also keep small row caps and expose commands.next for pagination",
|
||
},
|
||
outputBudget: {
|
||
promptPreviewChars: supervisorPromptPreviewChars,
|
||
bodyPreviewChars: supervisorBodyPreviewChars,
|
||
requestedLimit: options.requestedLimit,
|
||
effectiveLimit: options.limit,
|
||
limitCapped: options.limit < options.requestedLimit,
|
||
sectionReturnedLimit: sectionLimit,
|
||
recentCompletedReturnedLimit: recentLimit,
|
||
recentCompletedBodyPreviewChars: supervisorRecentBodyPreviewChars,
|
||
rowCommands: "task id is the row drill-down key; show/detail/trace/output/full/read are section templates to avoid repeated noise",
|
||
},
|
||
fullCommand,
|
||
next: nextCommand,
|
||
rawOverview: `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview${tasksListQueryString(options)} --raw`,
|
||
},
|
||
counts: {
|
||
scanned: allTasks.length,
|
||
running: runningSection.count,
|
||
activeRunningCount: activeRunning.count,
|
||
activeRunningExact: activeRunning.exact,
|
||
activeRunningRowsReturned: runningSection.returned,
|
||
completedUnread: unreadSection.count,
|
||
recentCompleted: recentSection.count,
|
||
queued: queuedSection.count,
|
||
commanderActiveRunnerCount: asNumber(commanderConcurrency.activeRunnerCount, 0),
|
||
effectiveActive: asNumber(activity.effectiveActiveTaskCount, 0),
|
||
databaseRunning: asNumber(activity.databaseRunningTaskCount, 0),
|
||
heartbeatFreshActive: asNumber(activity.heartbeatFreshActiveTaskCount, 0),
|
||
schedulerLocalActiveQueues: asNumber(activity.schedulerLocalActiveQueueCount, 0),
|
||
},
|
||
classifierCounts,
|
||
commanderConcurrency,
|
||
activity,
|
||
activeRunning,
|
||
executionDiagnostics: diagnostics,
|
||
degraded,
|
||
commands: {
|
||
refresh: taskListCommand(options),
|
||
unread: taskListCommand({ ...options, unreadOnly: true }),
|
||
byStatus: `bun scripts/cli.ts codex tasks --status <status>${options.queueId === undefined ? "" : ` --queue ${options.queueId}`}${options.requestedLimit === defaultTasksLimit ? "" : ` --limit ${options.requestedLimit}`}`,
|
||
full: fullCommand,
|
||
next: nextCommand,
|
||
},
|
||
running: runningSection,
|
||
completedUnread: unreadSection,
|
||
recentCompleted: recentSection,
|
||
queued: queuedSection,
|
||
},
|
||
};
|
||
}
|
||
|
||
function codexTasksFullResult(
|
||
taskPage: CodexTasksTaskPage,
|
||
upstream: { ok: unknown; status: unknown },
|
||
options: CodexTasksOptions,
|
||
summaries: Map<string, Record<string, unknown>>,
|
||
degraded: CodexTasksDegraded | null,
|
||
): Record<string, unknown> {
|
||
const filtered = filterTasksForOptions(taskPage.tasks, options);
|
||
const visible = filtered.slice(0, options.limit);
|
||
const pagination = taskPage.pagination;
|
||
const nextBeforeId = asString(pagination.nextBeforeId) || null;
|
||
const sourceHasMore = asBoolean(pagination.hasMore);
|
||
const nextCommand = sourceHasMore && nextBeforeId !== null ? taskListCommand({ ...options, beforeId: nextBeforeId }) : null;
|
||
return {
|
||
upstream,
|
||
tasks: {
|
||
filters: {
|
||
view: "full",
|
||
queueId: options.queueId ?? null,
|
||
requestedLimit: options.requestedLimit,
|
||
limit: options.limit,
|
||
...codexTasksLimitDisclosure(options),
|
||
unreadOnly: options.unreadOnly,
|
||
status: options.statusFilter,
|
||
beforeId: options.beforeId ?? null,
|
||
},
|
||
source: codexTasksSourceDisclosure(pagination),
|
||
count: filtered.length,
|
||
returned: visible.length,
|
||
hasMore: filtered.length > visible.length || sourceHasMore,
|
||
degraded,
|
||
commands: {
|
||
supervisor: taskListCommand({ ...options, view: "supervisor" }),
|
||
next: nextCommand,
|
||
rawOverview: `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview${tasksListQueryString(options)} --raw`,
|
||
},
|
||
items: visible.map((task) => taskWatchEntry(task, summaries.get(taskOverviewCandidateKey(task)) ?? null)),
|
||
},
|
||
};
|
||
}
|
||
|
||
function visibleTaskIdsForOverview(tasks: Record<string, unknown>[], options: CodexTasksOptions): string[] {
|
||
const filtered = filterTasksForOptions(tasks, options);
|
||
if (options.view === "full") {
|
||
return filtered.slice(0, options.limit).map((task) => taskOverviewCandidateKey(task)).filter((taskId) => taskId.length > 0);
|
||
}
|
||
const sectionLimit = taskSectionLimit(options);
|
||
return Array.from(new Set([
|
||
...sortRunningWatchTasks(filtered).slice(0, sectionLimit),
|
||
...sortCompletedWatchTasks(filtered).filter((task) => taskUnreadTerminal(task)).slice(0, sectionLimit),
|
||
...sortCompletedWatchTasks(filtered).filter((task) => !taskUnreadTerminal(task)).slice(0, Math.min(options.limit, supervisorRecentCompletedLimit)),
|
||
...sortQueuedWatchTasks(filtered).slice(0, sectionLimit),
|
||
].map((task) => taskOverviewCandidateKey(task))))
|
||
.filter((taskId) => taskId.length > 0);
|
||
}
|
||
|
||
function codexTasksQuery(taskArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||
const options = parseTasksOptions(taskArgs);
|
||
const { upstream, page } = loadCodexTasks(options, fetcher);
|
||
const sectionTaskIds = visibleTaskIdsForOverview(page.tasks, options);
|
||
const { summaries, degraded } = fetchTaskSummaries(sectionTaskIds, fetcher);
|
||
return codexTasksOverviewResult(page, upstream, options, summaries, degraded);
|
||
}
|
||
|
||
export function codexTasksQueryForTest(taskArgs: string[], fetcher: CodexResponseFetcher): unknown {
|
||
return codexTasksQuery(taskArgs, fetcher);
|
||
}
|
||
|
||
export function codexUnreadTriageForTest(taskArgs: string[], fetcher: CodexResponseFetcher): unknown {
|
||
return codexUnreadTriage(taskArgs, fetcher);
|
||
}
|
||
|
||
async function codexTasksQueryAsync(taskArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||
const options = parseTasksOptions(taskArgs);
|
||
const byId = new Map<string, Record<string, unknown>>();
|
||
const response = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/overview${tasksListQueryString(options)}`)));
|
||
const pageTasks = asArray(response.body.tasks).map((task) => asRecord(task)).filter((task): task is Record<string, unknown> => task !== null);
|
||
for (const task of pageTasks) {
|
||
const taskId = taskOverviewCandidateKey(task);
|
||
if (taskId.length === 0) continue;
|
||
const existing = byId.get(taskId);
|
||
if (existing === undefined || taskTimelineMs(task) >= taskTimelineMs(existing)) byId.set(taskId, task);
|
||
}
|
||
const tasks = Array.from(byId.values()).sort((left, right) => {
|
||
const leftTime = taskTimelineMs(left);
|
||
const rightTime = taskTimelineMs(right);
|
||
if (leftTime !== rightTime) return rightTime - leftTime;
|
||
return asString(left.id).localeCompare(asString(right.id));
|
||
});
|
||
const page: CodexTasksTaskPage = { queue: asRecord(response.body.queue), pagination: asRecord(response.body.pagination) ?? {}, tasks };
|
||
const visibleTaskIds = visibleTaskIdsForOverview(page.tasks, options);
|
||
const { summaries, degraded } = await fetchTaskSummariesAsync(visibleTaskIds, fetcher);
|
||
return codexTasksOverviewResult(page, response.upstream, options, summaries, degraded);
|
||
}
|
||
|
||
async function codexUnreadTriageAsync(taskArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||
const options = parseUnreadOptions(taskArgs);
|
||
const loadOptions = unreadLoadOptions(options);
|
||
const byId = new Map<string, Record<string, unknown>>();
|
||
const response = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/overview${tasksListQueryString(loadOptions)}`)));
|
||
const pageTasks = asArray(response.body.tasks).map((task) => asRecord(task)).filter((task): task is Record<string, unknown> => task !== null);
|
||
for (const task of pageTasks) {
|
||
const taskId = taskOverviewCandidateKey(task);
|
||
if (taskId.length === 0) continue;
|
||
const existing = byId.get(taskId);
|
||
if (existing === undefined || taskTimelineMs(task) >= taskTimelineMs(existing)) byId.set(taskId, task);
|
||
}
|
||
const tasks = Array.from(byId.values()).sort((left, right) => {
|
||
const leftTime = taskTimelineMs(left);
|
||
const rightTime = taskTimelineMs(right);
|
||
if (leftTime !== rightTime) return rightTime - leftTime;
|
||
return asString(left.id).localeCompare(asString(right.id));
|
||
});
|
||
const page: CodexTasksTaskPage = { queue: asRecord(response.body.queue), pagination: asRecord(response.body.pagination) ?? {}, tasks };
|
||
const { result, visibleCandidates } = unreadTriageSummary(response.upstream, page, options);
|
||
if (options.action !== "mark-read" || options.dryRun) return result;
|
||
if (!options.confirm) return codexUnreadMutationGuard(result, visibleCandidates, options);
|
||
const results: Record<string, unknown>[] = [];
|
||
for (const task of visibleCandidates) {
|
||
const taskId = taskOverviewCandidateKey(task);
|
||
try {
|
||
const readResponse = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
|
||
results.push({ taskId, ok: true, upstream: readResponse.upstream });
|
||
} catch (error) {
|
||
results.push({ taskId, ok: false, error: error instanceof Error ? error.message : String(error) });
|
||
}
|
||
}
|
||
return codexUnreadMutationResult(result, options, results);
|
||
}
|
||
|
||
async function codexTaskSummaryAsync(taskId: string, options: CodexTaskOptions, fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||
const summaryPath = codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: options.toolLimit })}`);
|
||
const summaryResponse = unwrapCodexResponse(await fetcher(summaryPath));
|
||
const summary = summaryResponse.body.summary;
|
||
const result: Record<string, unknown> = {
|
||
upstream: summaryResponse.upstream,
|
||
summary: options.detail ? compactSummary(summary, options, taskId) : compactReviewSummary(summary, options, taskId),
|
||
};
|
||
if (options.rawSummary) result.rawSummary = summary;
|
||
if (options.trace) {
|
||
const traceParams: Record<string, string | number | boolean | null> = { limit: options.traceLimit };
|
||
if (options.traceMode === "tail") traceParams.tail = 1;
|
||
if (options.traceMode === "after") traceParams.afterSeq = options.afterSeq;
|
||
if (options.traceMode === "before") traceParams.beforeSeq = options.beforeSeq;
|
||
const traceSummaryResponse = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/trace-summary`)));
|
||
const traceResponse = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/trace-steps${queryString(traceParams)}`)));
|
||
result.trace = compactTracePage(traceResponse.body, taskId, options.traceLimit, traceSummaryResponse.body, options);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
async function codexTaskOutputAsync(taskId: string, options: CodexOutputOptions, fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||
const params: Record<string, string | number | boolean | null> = {
|
||
limit: options.limit,
|
||
fullText: options.fullText ? 1 : 0,
|
||
maxTextChars: options.maxTextChars,
|
||
};
|
||
if (options.mode === "tail") params.tail = 1;
|
||
if (options.mode === "after") params.afterSeq = options.afterSeq;
|
||
if (options.mode === "before") params.beforeSeq = options.beforeSeq;
|
||
const response = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/output${queryString(params)}`)));
|
||
return { upstream: response.upstream, outputPage: compactOutputPage(response.body, taskId, options) };
|
||
}
|
||
|
||
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(codeQueueProxyPath(`/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);
|
||
}
|
||
|
||
export async function codexOutputQueryAsync(taskId: string, optionArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||
return codexTaskOutputAsync(taskId, parseOutputOptions(optionArgs), fetcher);
|
||
}
|
||
|
||
export async function codexJudgeQueryAsync(taskId: string, optionArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||
return codexTaskJudgeAsync(taskId, parseJudgeOptions(optionArgs), fetcher);
|
||
}
|
||
|
||
export { codexQueuesQueryAsync, codexTasksQueryAsync, codexUnreadTriageAsync };
|
||
|
||
function requireQueueId(args: string[], command: string): string {
|
||
const index = args.indexOf("--queue");
|
||
const raw = index === -1 ? args[0] : args[index + 1];
|
||
if (raw === undefined || raw.trim().length === 0) throw new Error(`${command} requires queue id, for example --queue default`);
|
||
return raw.trim();
|
||
}
|
||
|
||
function optionValue(args: string[], names: string[]): string | undefined {
|
||
for (const name of names) {
|
||
const index = args.indexOf(name);
|
||
if (index === -1) continue;
|
||
const raw = args[index + 1];
|
||
if (raw === undefined || raw.trim().length === 0) throw new Error(`${name} requires a non-empty value`);
|
||
return raw.trim();
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
function optionValues(args: string[], names: string[]): string[] {
|
||
const values: string[] = [];
|
||
for (let index = 0; index < args.length; index += 1) {
|
||
const name = args[index] ?? "";
|
||
if (!names.includes(name)) continue;
|
||
const raw = args[index + 1];
|
||
if (raw === undefined || raw.trim().length === 0) throw new Error(`${name} requires a non-empty value`);
|
||
values.push(raw.trim());
|
||
index += 1;
|
||
}
|
||
return values;
|
||
}
|
||
|
||
function positionalArgs(args: string[]): string[] {
|
||
const positions: string[] = [];
|
||
for (let index = 0; index < args.length; index += 1) {
|
||
const value = args[index] ?? "";
|
||
if (value.startsWith("--")) {
|
||
index += 1;
|
||
continue;
|
||
}
|
||
positions.push(value);
|
||
}
|
||
return positions;
|
||
}
|
||
|
||
function positionalArgsWithValueOptions(args: string[], valueOptions: Set<string>): string[] {
|
||
const positions: string[] = [];
|
||
for (let index = 0; index < args.length; index += 1) {
|
||
const value = args[index] ?? "";
|
||
if (value.startsWith("--")) {
|
||
if (valueOptions.has(value)) index += 1;
|
||
continue;
|
||
}
|
||
positions.push(value);
|
||
}
|
||
return positions;
|
||
}
|
||
|
||
function requireMergeSourceQueueId(args: string[], command: string): string {
|
||
const raw = optionValue(args, ["--source", "--from", "--queue"]) ?? positionalArgs(args)[0];
|
||
if (raw === undefined || raw.trim().length === 0) throw new Error(`${command} requires source queue id, for example: codex queue merge old --into default`);
|
||
return raw.trim();
|
||
}
|
||
|
||
function requireMergeTargetQueueId(args: string[], command: string): string {
|
||
const raw = optionValue(args, ["--into", "--target", "--to"]) ?? positionalArgs(args)[1];
|
||
if (raw === undefined || raw.trim().length === 0) throw new Error(`${command} requires target queue id, for example: codex queue merge old --into default`);
|
||
return raw.trim();
|
||
}
|
||
|
||
function compactCounts(value: unknown): Record<string, unknown> {
|
||
const counts = asRecord(value) ?? {};
|
||
const compact: Record<string, unknown> = {};
|
||
for (const [key, count] of Object.entries(counts)) {
|
||
if (typeof count === "number" && count === 0) continue;
|
||
compact[key] = count;
|
||
}
|
||
return compact;
|
||
}
|
||
|
||
function compactQueueRow(value: unknown): Record<string, unknown> {
|
||
const record = asRecord(value) ?? {};
|
||
return {
|
||
id: record.id ?? null,
|
||
name: record.name ?? null,
|
||
total: record.total ?? null,
|
||
counts: compactCounts(record.counts),
|
||
unreadTerminal: record.unreadTerminal ?? 0,
|
||
activeTaskId: record.activeTaskId ?? null,
|
||
runnableTaskId: record.runnableTaskId ?? null,
|
||
processing: record.processing ?? null,
|
||
updatedAt: record.updatedAt ?? null,
|
||
commands: {
|
||
tasks: record.id === undefined || record.id === null ? null : `bun scripts/cli.ts codex tasks --queue ${String(record.id)} --limit ${defaultTasksLimit}`,
|
||
unread: record.id === undefined || record.id === null ? null : `bun scripts/cli.ts codex unread --queue ${String(record.id)} --limit ${defaultTasksLimit}`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function queueListCommand(options: Partial<CodexQueuesOptions> = {}): string {
|
||
const full = options.full === true;
|
||
const limit = options.limit ?? defaultQueuesLimit;
|
||
const offset = options.offset ?? 0;
|
||
return [
|
||
"bun scripts/cli.ts codex queues",
|
||
full ? "--full" : "",
|
||
limit === defaultQueuesLimit ? "" : `--limit ${limit}`,
|
||
offset > 0 ? `--offset ${offset}` : "",
|
||
].filter(Boolean).join(" ");
|
||
}
|
||
|
||
function compactQueuesResponse(body: Record<string, unknown>, options: CodexQueuesOptions, upstream: { ok: unknown; status: unknown }): Record<string, unknown> {
|
||
const queue = asRecord(body.queue) ?? asRecord(body.summary) ?? {};
|
||
const queues = asArray(body.queues).map(compactQueueRow);
|
||
const activeIds = stringList(queue.activeQueueIds);
|
||
const nonemptyQueues = queues.filter((row) => asNumber(row.total, 0) > 0);
|
||
const unreadQueues = queues.filter((row) => asNumber(row.unreadTerminal, 0) > 0);
|
||
const runnableQueues = queues.filter((row) => row.runnableTaskId !== null && row.runnableTaskId !== undefined);
|
||
const activeQueues = queues.filter((row) => typeof row.id === "string" && activeIds.includes(row.id));
|
||
const selected = options.full ? queues : Array.from(new Map([...activeQueues, ...unreadQueues, ...runnableQueues, ...nonemptyQueues].map((row) => [String(row.id), row])).values());
|
||
const visible = selected.slice(options.offset, options.offset + options.limit);
|
||
const diagnostics = compactQueueExecutionDiagnostics(queue.executionDiagnostics);
|
||
const activity = compactCodeQueueActivity(queue, diagnostics, { schedulerLocalActiveQueueIds: activeIds, runnableQueueCount: runnableQueues.length });
|
||
const commanderConcurrency = asRecord(activity.commanderConcurrency) ?? {};
|
||
const activeTaskIds = boundedUniqueStringList(queue.activeTaskIds, Math.min(options.limit, maxTasksLimit));
|
||
const queuedTaskIds = boundedUniqueStringList(queue.queuedTaskIds, Math.min(options.limit, maxTasksLimit));
|
||
const nextOffset = options.offset + visible.length;
|
||
const previousOffset = Math.max(0, options.offset - options.limit);
|
||
const hasMore = nextOffset < selected.length;
|
||
const hasPrevious = options.offset > 0;
|
||
return {
|
||
upstream,
|
||
queues: {
|
||
view: options.full ? "full" : "summary",
|
||
bounded: true,
|
||
outputPolicy: {
|
||
default: "paged-low-noise",
|
||
stableItemsPath: "data.queues.items[]",
|
||
rawFullCommand: "bun scripts/cli.ts microservice proxy code-queue /api/queues --raw --full",
|
||
},
|
||
count: selected.length,
|
||
returned: visible.length,
|
||
limit: options.limit,
|
||
offset: options.offset,
|
||
page: options.page,
|
||
hasMore,
|
||
hasPrevious,
|
||
totals: {
|
||
totalTasks: queue.total ?? null,
|
||
queueCount: queue.queueCount ?? queues.length,
|
||
activeQueueCount: activeIds.length,
|
||
activeQueueCountScope: "scheduler-local-active-run-slots",
|
||
schedulerLocalActiveQueueCount: activeIds.length,
|
||
commanderActiveRunnerCount: commanderConcurrency.activeRunnerCount,
|
||
effectiveActiveTaskCount: activity.effectiveActiveTaskCount,
|
||
databaseRunningTaskCount: activity.databaseRunningTaskCount,
|
||
databaseActiveTaskCount: activity.databaseActiveTaskCount,
|
||
heartbeatFreshActiveTaskCount: activity.heartbeatFreshActiveTaskCount,
|
||
nonemptyQueueCount: nonemptyQueues.length,
|
||
unreadQueueCount: unreadQueues.length,
|
||
runnableQueueCount: runnableQueues.length,
|
||
},
|
||
commanderConcurrency,
|
||
activity,
|
||
activeQueueIds: queue.activeQueueIds ?? [],
|
||
activeQueueIdsScope: "scheduler-local-active-run-slots",
|
||
activeQueueIdsNote: activity.activeQueueIdsNote,
|
||
activeTaskIds: activeTaskIds.items,
|
||
activeTaskIdsCount: activeTaskIds.count,
|
||
activeTaskIdsTruncated: activeTaskIds.truncated,
|
||
queuedTaskIds: queuedTaskIds.items,
|
||
queuedTaskIdsCount: queuedTaskIds.count,
|
||
queuedTaskIdsTruncated: queuedTaskIds.truncated,
|
||
counts: queue.counts ?? {},
|
||
unreadTerminal: queue.unreadTerminal ?? 0,
|
||
executionDiagnostics: diagnostics,
|
||
items: visible,
|
||
...(options.full
|
||
? {
|
||
compatibility: {
|
||
deprecated: true,
|
||
deprecatedPath: "data.queues.deprecatedFullArray[]",
|
||
stablePath: "data.queues.items[]",
|
||
deprecatedFullArrayOmitted: true,
|
||
message: "Use data.queues.items[] for both codex queues and codex queues --full; raw full upstream is available only through the explicit raw command.",
|
||
},
|
||
}
|
||
: {}),
|
||
commands: {
|
||
refresh: queueListCommand({ full: options.full, limit: options.limit, offset: options.offset }),
|
||
next: hasMore ? queueListCommand({ full: options.full, limit: options.limit, offset: nextOffset }) : null,
|
||
previous: hasPrevious ? queueListCommand({ full: options.full, limit: options.limit, offset: previousOffset }) : null,
|
||
first: queueListCommand({ full: options.full, limit: options.limit, offset: 0 }),
|
||
full: queueListCommand({ full: true, limit: options.limit, offset: 0 }),
|
||
tasks: `bun scripts/cli.ts codex tasks --view supervisor --limit ${Math.min(options.limit, defaultTasksLimit)}`,
|
||
unread: `bun scripts/cli.ts codex unread --limit ${Math.min(options.limit, defaultTasksLimit)}`,
|
||
raw: "bun scripts/cli.ts microservice proxy code-queue /api/queues --raw --full",
|
||
},
|
||
},
|
||
};
|
||
}
|
||
|
||
export function codexQueuesQueryForTest(optionArgs: string[], fetcher: CodexResponseFetcher): unknown {
|
||
const options = parseQueuesOptions(optionArgs);
|
||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath("/api/queues")));
|
||
return compactQueuesResponse(response.body, options, response.upstream);
|
||
}
|
||
|
||
async function codexQueuesQueryAsync(optionArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||
const options = parseQueuesOptions(optionArgs);
|
||
const response = unwrapCodexResponse(await fetcher(codeQueueProxyPath("/api/queues")));
|
||
return compactQueuesResponse(response.body, options, response.upstream);
|
||
}
|
||
|
||
function codeQueues(optionArgs: string[] = []): unknown {
|
||
return codexQueuesQueryForTest(optionArgs, coreInternalFetch);
|
||
}
|
||
|
||
function codexCreateQueue(queueId: string): unknown {
|
||
return unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath("/api/queues"), { method: "POST", body: { queueId } }));
|
||
}
|
||
|
||
function codexMergeQueue(sourceQueueId: string, targetQueueId: string): unknown {
|
||
return unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath(`/api/queues/${encodeURIComponent(targetQueueId)}/merge`), { method: "POST", body: { sourceQueueId } }));
|
||
}
|
||
|
||
function codexMoveTask(taskId: string, queueId: string): unknown {
|
||
return unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/move`), { method: "POST", body: { queueId } }));
|
||
}
|
||
|
||
function promptFromArgs(args: string[], command: string, valueOptions: Set<string>): string {
|
||
const promptFile = optionValue(args, ["--prompt-file", "--file"]);
|
||
const promptStdin = hasFlag(args, "--prompt-stdin") || hasFlag(args, "--stdin");
|
||
const promptArgs = positionalArgsWithValueOptions(args, valueOptions);
|
||
const sources = [promptFile !== undefined, promptStdin, promptArgs.length > 0].filter(Boolean).length;
|
||
if (sources !== 1) throw new Error(`${command} requires exactly one prompt source: positional prompt, --prompt-file, or --prompt-stdin`);
|
||
const text = promptFile !== undefined
|
||
? (promptFile === "-" ? readFileSync(0, "utf8") : readFileSync(promptFile, "utf8"))
|
||
: promptStdin
|
||
? readFileSync(0, "utf8")
|
||
: promptArgs.join(" ");
|
||
if (text.trim().length === 0) throw new Error(`${command} prompt must not be empty`);
|
||
return text;
|
||
}
|
||
|
||
const submitPromptValueOptions = new Set([
|
||
"--prompt-file",
|
||
"--file",
|
||
"--queue",
|
||
"--queue-id",
|
||
"--provider",
|
||
"--provider-id",
|
||
"--cwd",
|
||
"--workdir",
|
||
"--model",
|
||
"--reasoning-effort",
|
||
"--execution-mode",
|
||
"--mode",
|
||
"--max-attempts",
|
||
"--reference-task-id",
|
||
"--reference",
|
||
"--ref",
|
||
]);
|
||
|
||
const steerPromptValueOptions = new Set([
|
||
"--prompt-file",
|
||
"--file",
|
||
"--retry-attempts",
|
||
"--retry-delay-ms",
|
||
"--steer-id",
|
||
"--steerId",
|
||
]);
|
||
|
||
function referenceTaskIdsFromOptions(args: string[]): string[] {
|
||
const values = optionValues(args, ["--reference-task-id", "--reference", "--ref"]);
|
||
const ids: string[] = [];
|
||
for (const value of values.flatMap((item) => item.split(/[,\s]+/u))) {
|
||
const id = value.trim();
|
||
if (id.length > 0 && !ids.includes(id)) ids.push(id);
|
||
}
|
||
return ids;
|
||
}
|
||
|
||
function parseRequestedExecutionMode(value: string | undefined): string | undefined {
|
||
if (value === undefined) return undefined;
|
||
const requested = normalizeRequestedCodeExecutionMode(value);
|
||
if (requested === null) throw new Error("--execution-mode must be a short mode identifier");
|
||
return requested;
|
||
}
|
||
|
||
function executionModeSummary(requestedValue: string | null | undefined, effectiveValue?: unknown): Record<string, unknown> {
|
||
const requested = normalizeRequestedCodeExecutionMode(requestedValue);
|
||
const effective = typeof effectiveValue === "string" && effectiveValue.trim().length > 0
|
||
? normalizeCodeExecutionMode(effectiveValue)
|
||
: normalizeCodeExecutionMode(requested);
|
||
const requestedLooksLikeSandbox = requested !== null && sandboxLikeExecutionModes.has(requested);
|
||
const recognized = requestedCodeExecutionModeIsRecognized(requested);
|
||
return {
|
||
requested,
|
||
effective,
|
||
recognized,
|
||
normalized: requested !== null && requested !== effective,
|
||
availableModes: codeExecutionModes,
|
||
requestedLooksLikeSandbox,
|
||
permissionBoundary: "--execution-mode selects the Code Queue runtime mode; Codex sandbox permissions come from runnerPermissions.sandbox.",
|
||
...(requestedLooksLikeSandbox ? { warning: `${requested} is not a Code Queue execution mode and is not applied as a per-task sandbox override.` } : {}),
|
||
};
|
||
}
|
||
|
||
function dryRunRunnerPermissionsSummary(): Record<string, unknown> {
|
||
return {
|
||
observed: false,
|
||
scope: "code-queue-service-config",
|
||
sandbox: null,
|
||
approvalPolicy: null,
|
||
perTaskOverrideSupported: false,
|
||
note: "Dry-run does not contact Code Queue; real submit responses include observed runnerPermissions from the service.",
|
||
secretsPrinted: false,
|
||
};
|
||
}
|
||
|
||
function compactRunnerPermissions(value: unknown): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
return {
|
||
observed: record.observed ?? true,
|
||
scope: record.scope ?? "code-queue-service-config",
|
||
sandbox: record.sandbox ?? null,
|
||
approvalPolicy: record.approvalPolicy ?? null,
|
||
perTaskOverrideSupported: record.perTaskOverrideSupported ?? false,
|
||
secretsPrinted: false,
|
||
};
|
||
}
|
||
|
||
function compactTaskExecutionModeRequest(record: Record<string, unknown>): Record<string, unknown> {
|
||
return executionModeSummary(asString(record.requestedExecutionMode) || null, record.executionMode);
|
||
}
|
||
|
||
function parseSubmitOptions(args: string[]): CodexSubmitOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--prompt-stdin", "--stdin", "--dry-run"],
|
||
valueOptions: [
|
||
"--prompt-file",
|
||
"--file",
|
||
"--queue",
|
||
"--queue-id",
|
||
"--provider-id",
|
||
"--provider",
|
||
"--cwd",
|
||
"--workdir",
|
||
"--model",
|
||
"--reasoning-effort",
|
||
"--execution-mode",
|
||
"--mode",
|
||
"--max-attempts",
|
||
"--reference-task-id",
|
||
"--reference",
|
||
"--ref",
|
||
],
|
||
}, "codex submit");
|
||
const maxAttempts = args.some((arg) => arg === "--max-attempts")
|
||
? positiveIntegerOption(args, ["--max-attempts"], 99, 99)
|
||
: undefined;
|
||
return {
|
||
prompt: promptFromArgs(args, "codex submit", submitPromptValueOptions),
|
||
queueId: optionValue(args, ["--queue", "--queue-id"]),
|
||
providerId: optionValue(args, ["--provider-id", "--provider"]),
|
||
cwd: optionValue(args, ["--cwd", "--workdir"]),
|
||
model: optionValue(args, ["--model"]),
|
||
reasoningEffort: optionValue(args, ["--reasoning-effort"]),
|
||
executionMode: parseRequestedExecutionMode(optionValue(args, ["--execution-mode", "--mode"])),
|
||
maxAttempts,
|
||
referenceTaskIds: referenceTaskIdsFromOptions(args),
|
||
dryRun: hasFlag(args, "--dry-run"),
|
||
};
|
||
}
|
||
|
||
function parseSteerOptions(args: string[]): CodexSteerOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--prompt-stdin", "--stdin", "--dry-run", "--no-retry", "--full", "--raw"],
|
||
valueOptions: ["--prompt-file", "--file", "--retry-attempts", "--retry-delay-ms", "--steer-id", "--steerId"],
|
||
}, "codex steer");
|
||
const retryAttempts = hasFlag(args, "--no-retry")
|
||
? 1
|
||
: positiveIntegerOption(args, ["--retry-attempts"], defaultSteerRetryAttempts, maxSteerRetryAttempts);
|
||
const steerId = optionValue(args, ["--steer-id", "--steerId"]);
|
||
if (steerId !== undefined && !/^[A-Za-z0-9._:-]{8,128}$/u.test(steerId)) throw new Error("--steer-id must be 8-128 chars using letters, numbers, dot, underscore, colon, or dash");
|
||
return {
|
||
prompt: promptFromArgs(args, "codex steer", steerPromptValueOptions),
|
||
steerId,
|
||
dryRun: hasFlag(args, "--dry-run"),
|
||
retryAttempts,
|
||
retryDelayMs: nonNegativeIntegerOption(args, ["--retry-delay-ms"], defaultSteerRetryDelayMs, maxSteerRetryDelayMs),
|
||
full: hasFlag(args, "--full") || hasFlag(args, "--raw"),
|
||
raw: hasFlag(args, "--raw"),
|
||
};
|
||
}
|
||
|
||
function parseSteerConfirmOptions(args: string[]): CodexSteerConfirmOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--raw"],
|
||
valueOptions: ["--steer-id", "--steerId"],
|
||
}, "codex steer-confirm");
|
||
const steerId = optionValue(args, ["--steer-id", "--steerId"]);
|
||
if (steerId === undefined) throw new Error("codex steer-confirm requires --steer-id <id>");
|
||
if (!/^[A-Za-z0-9._:-]{8,128}$/u.test(steerId)) throw new Error("--steer-id must be 8-128 chars using letters, numbers, dot, underscore, colon, or dash");
|
||
return { steerId, raw: hasFlag(args, "--raw") };
|
||
}
|
||
|
||
function parsePromptLintOptions(args: string[]): CodexPromptLintOptions {
|
||
assertKnownOptions(args, {
|
||
flags: ["--prompt-stdin", "--stdin"],
|
||
valueOptions: ["--prompt-file", "--file"],
|
||
}, "codex prompt-lint");
|
||
return {
|
||
prompt: promptFromArgs(args, "codex prompt-lint", steerPromptValueOptions),
|
||
};
|
||
}
|
||
|
||
function submitPayload(options: CodexSubmitOptions): Record<string, unknown> {
|
||
return {
|
||
prompt: options.prompt,
|
||
...(options.queueId === undefined ? {} : { queueId: options.queueId }),
|
||
...(options.providerId === undefined ? {} : { providerId: options.providerId }),
|
||
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
||
...(options.model === undefined ? {} : { model: options.model }),
|
||
...(options.reasoningEffort === undefined ? {} : { reasoningEffort: options.reasoningEffort }),
|
||
...(options.executionMode === undefined ? {} : { executionMode: options.executionMode }),
|
||
...(options.maxAttempts === undefined ? {} : { maxAttempts: options.maxAttempts }),
|
||
...(options.referenceTaskIds.length === 0 ? {} : { referenceTaskIds: options.referenceTaskIds }),
|
||
};
|
||
}
|
||
|
||
function compactTaskMutationResponse(task: unknown, options: CompactTaskMutationResponseOptions = {}): Record<string, unknown> {
|
||
const record = asRecord(task) ?? {};
|
||
const taskId = asString(record.id);
|
||
const prompt = asString(record.displayPrompt ?? record.basePrompt ?? record.prompt);
|
||
return {
|
||
id: taskId || null,
|
||
queueId: record.queueId ?? null,
|
||
status: record.status ?? null,
|
||
queuedReason: record.queuedReason ?? null,
|
||
providerId: record.providerId ?? null,
|
||
model: record.model ?? null,
|
||
reasoningEffort: record.reasoningEffort ?? null,
|
||
cwd: record.cwd ?? null,
|
||
executionMode: record.executionMode ?? null,
|
||
requestedExecutionMode: record.requestedExecutionMode ?? null,
|
||
executionModeRequest: compactTaskExecutionModeRequest(record),
|
||
maxAttempts: record.maxAttempts ?? null,
|
||
currentAttempt: record.currentAttempt ?? null,
|
||
cancelRequested: record.cancelRequested ?? null,
|
||
schedulerHeartbeat: compactSchedulerHeartbeat(record.schedulerHeartbeat),
|
||
createdAt: record.createdAt ?? null,
|
||
startedAt: record.startedAt ?? null,
|
||
updatedAt: record.updatedAt ?? null,
|
||
finishedAt: record.finishedAt ?? null,
|
||
...(options.fullPrompt === true ? { prompt: textView(prompt, true, 1200) } : { promptPreview: textView(prompt, false, 1200) }),
|
||
commands: taskId.length === 0 ? null : {
|
||
show: `bun scripts/cli.ts codex task ${taskId}`,
|
||
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
|
||
output: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`,
|
||
steer: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path>`,
|
||
interrupt: `bun scripts/cli.ts codex interrupt ${taskId}`,
|
||
move: `bun scripts/cli.ts codex move ${taskId} --queue <queueId>`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function compactSubmitTaskConfirmation(task: unknown): Record<string, unknown> {
|
||
const record = asRecord(task) ?? {};
|
||
const taskId = asString(record.id);
|
||
const prompt = asString(record.displayPrompt ?? record.basePrompt ?? record.prompt);
|
||
return {
|
||
id: taskId || null,
|
||
queueId: record.queueId ?? null,
|
||
status: record.status ?? null,
|
||
queuedReason: record.queuedReason ?? null,
|
||
providerId: record.providerId ?? null,
|
||
model: record.model ?? null,
|
||
reasoningEffort: record.reasoningEffort ?? null,
|
||
cwd: record.cwd ?? null,
|
||
executionMode: record.executionMode ?? null,
|
||
requestedExecutionMode: record.requestedExecutionMode ?? null,
|
||
executionModeRequest: compactTaskExecutionModeRequest(record),
|
||
maxAttempts: record.maxAttempts ?? null,
|
||
createdAt: record.createdAt ?? null,
|
||
updatedAt: record.updatedAt ?? null,
|
||
promptChars: prompt.length,
|
||
promptOmitted: true,
|
||
commands: taskId.length === 0 ? null : {
|
||
show: `bun scripts/cli.ts codex task ${taskId}`,
|
||
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
|
||
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function compactSteerTaskConfirmation(task: unknown, steerId: string): Record<string, unknown> {
|
||
const compact = compactSubmitTaskConfirmation(task);
|
||
const commands = asRecord(compact.commands) ?? {};
|
||
return {
|
||
...compact,
|
||
commands: {
|
||
...commands,
|
||
traceConfirm: steerConfirmationCommand(asString(compact.id) || "<taskId>", steerId),
|
||
},
|
||
};
|
||
}
|
||
|
||
function orderedUniqueStringList(values: string[]): string[] {
|
||
const seen = new Set<string>();
|
||
const items: string[] = [];
|
||
for (const value of values) {
|
||
if (seen.has(value)) continue;
|
||
seen.add(value);
|
||
items.push(value);
|
||
}
|
||
return items;
|
||
}
|
||
|
||
function compactIdPreview(knownIds: string[], totalCount: number, limit: number, source: string, note: string | null = null): Record<string, unknown> {
|
||
const all = orderedUniqueStringList(knownIds);
|
||
const count = Math.max(0, totalCount, all.length);
|
||
const items = all.slice(0, limit);
|
||
const omitted = Math.max(0, count - items.length);
|
||
const idsUnavailable = count > 0 && items.length === 0;
|
||
const result: Record<string, unknown> = {
|
||
count,
|
||
returned: items.length,
|
||
omitted,
|
||
truncated: omitted > 0 || all.length > items.length,
|
||
source,
|
||
countsAreAuthoritative: true,
|
||
idsUnavailable,
|
||
itemsMeaning: idsUnavailable
|
||
? "not-enumerated-in-default-submit-output"
|
||
: count === 0
|
||
? "authoritative-empty"
|
||
: omitted > 0 || all.length > items.length
|
||
? "bounded-preview"
|
||
: "complete-known-list",
|
||
rawCommand: rawCodeQueueOverviewCommand,
|
||
...(note === null ? {} : { note }),
|
||
};
|
||
if (idsUnavailable) {
|
||
result.itemsOmitted = true;
|
||
result.note = note ?? "Count is nonzero, but the default submit response did not enumerate ids for this list; use rawCommand for drill-down.";
|
||
} else {
|
||
result.items = items;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
function idPreviewInputItems(value: unknown): string[] {
|
||
const record = asRecord(value);
|
||
return stringList(record?.items ?? value);
|
||
}
|
||
|
||
function idPreviewInputCount(value: unknown): number {
|
||
const record = asRecord(value);
|
||
const explicit = asNumber(record?.count, Number.NaN);
|
||
if (Number.isFinite(explicit)) return explicit;
|
||
return idPreviewInputItems(value).length;
|
||
}
|
||
|
||
function countForStatus(counts: Record<string, unknown>, status: string): number {
|
||
return asNumber(counts[status], 0);
|
||
}
|
||
|
||
function maxFiniteNumber(values: number[]): number {
|
||
const finite = values.filter((value) => Number.isFinite(value));
|
||
return finite.length === 0 ? 0 : Math.max(...finite);
|
||
}
|
||
|
||
function taskIdsForStatuses(tasks: Record<string, unknown>[], statuses: Set<string> | null): string[] {
|
||
return tasks.flatMap((task) => {
|
||
const id = asString(task.id);
|
||
if (id.length === 0) return [];
|
||
if (statuses !== null && !statuses.has(asString(task.status))) return [];
|
||
return [id];
|
||
});
|
||
}
|
||
|
||
function submittedTaskState(task: Record<string, unknown>): Record<string, unknown> {
|
||
const id = asString(task.id);
|
||
const status = asString(task.status) || "unknown";
|
||
const queueId = asString(task.queueId) || null;
|
||
const state = status === "queued" || status === "retry_wait"
|
||
? "queued"
|
||
: status === "running" || status === "judging"
|
||
? "running"
|
||
: isTerminalTaskStatus(status)
|
||
? "terminal"
|
||
: "unknown";
|
||
return {
|
||
id: id || null,
|
||
queueId,
|
||
status,
|
||
state,
|
||
source: "response.tasks[].status",
|
||
};
|
||
}
|
||
|
||
function submittedTaskStatusCounts(tasks: Record<string, unknown>[]): Record<string, number> {
|
||
const counts: Record<string, number> = {};
|
||
for (const task of tasks) {
|
||
const status = asString(task.status) || "unknown";
|
||
counts[status] = (counts[status] ?? 0) + 1;
|
||
}
|
||
return counts;
|
||
}
|
||
|
||
function previewSource(parts: string[], fallback: string): string {
|
||
const unique = orderedUniqueStringList(parts.filter((part) => part.length > 0));
|
||
return unique.length > 0 ? unique.join("+") : fallback;
|
||
}
|
||
|
||
function compactSubmitQueueConfirmation(value: unknown, options: CompactSubmitQueueConfirmationOptions = {}): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
const counts = asRecord(record.counts) ?? {};
|
||
const diagnosticsRecord = asRecord(record.executionDiagnostics) ?? {};
|
||
const submittedTasks = options.submittedTasks ?? [];
|
||
const idPreviewLimit = Math.max(1, Math.min(options.idPreviewLimit ?? mutationQueueIdPreviewLimit, maxTasksLimit));
|
||
const submittedTaskIds = taskIdsForStatuses(submittedTasks, null);
|
||
const submittedQueuedTaskIds = taskIdsForStatuses(submittedTasks, new Set(["queued", "retry_wait"]));
|
||
const submittedActiveTaskIds = taskIdsForStatuses(submittedTasks, new Set(["running", "judging"]));
|
||
|
||
const upstreamQueuedTaskIds = idPreviewInputItems(record.queuedTaskIds);
|
||
const queuedKnownIds = orderedUniqueStringList([...submittedQueuedTaskIds, ...upstreamQueuedTaskIds]);
|
||
const queuedStatusCount = countForStatus(counts, "queued") + countForStatus(counts, "retry_wait");
|
||
const queuedCount = Math.max(queuedKnownIds.length, idPreviewInputCount(record.queuedTaskIds), queuedStatusCount);
|
||
const queuedPreview: Record<string, unknown> = {
|
||
...compactIdPreview(
|
||
queuedKnownIds,
|
||
queuedCount,
|
||
idPreviewLimit,
|
||
previewSource([
|
||
submittedQueuedTaskIds.length > 0 ? "submittedTaskIds" : "",
|
||
upstreamQueuedTaskIds.length > 0 ? "upstreamQueuedTaskIds" : "",
|
||
queuedKnownIds.length === 0 && queuedCount > 0 ? "aggregateCountsOnly" : "",
|
||
], "none"),
|
||
queuedCount > queuedKnownIds.length ? "Upstream did not enumerate every queued id in this low-noise mutation response; count remains authoritative." : null,
|
||
),
|
||
sourceCounts: {
|
||
queued: countForStatus(counts, "queued"),
|
||
retryWait: countForStatus(counts, "retry_wait"),
|
||
upstreamQueuedTaskIds: idPreviewInputCount(record.queuedTaskIds),
|
||
submittedQueuedTaskIds: submittedQueuedTaskIds.length,
|
||
},
|
||
};
|
||
|
||
const upstreamActiveTaskIds = idPreviewInputItems(record.activeTaskIds);
|
||
const databaseActiveTaskIds = idPreviewInputItems(record.databaseActiveTaskIds);
|
||
const diagnosticsDatabaseActiveTaskIds = idPreviewInputItems(diagnosticsRecord.databaseActiveTaskIds);
|
||
const diagnosticsHeartbeatTaskIds = idPreviewInputItems(diagnosticsRecord.heartbeatFreshTaskIds);
|
||
const activeKnownIds = orderedUniqueStringList([
|
||
...submittedActiveTaskIds,
|
||
...upstreamActiveTaskIds,
|
||
...databaseActiveTaskIds,
|
||
...diagnosticsDatabaseActiveTaskIds,
|
||
...diagnosticsHeartbeatTaskIds,
|
||
]);
|
||
const statusActiveCount = countForStatus(counts, "running") + countForStatus(counts, "judging");
|
||
const databaseActiveTaskCount = maxFiniteNumber([
|
||
databaseActiveTaskIds.length,
|
||
diagnosticsDatabaseActiveTaskIds.length,
|
||
asNumber(record.databaseActiveTaskCount, Number.NaN),
|
||
asNumber(diagnosticsRecord.databaseActiveTaskCount, Number.NaN),
|
||
]);
|
||
const activeCount = maxFiniteNumber([
|
||
activeKnownIds.length,
|
||
idPreviewInputCount(record.activeTaskIds),
|
||
databaseActiveTaskCount,
|
||
statusActiveCount,
|
||
]);
|
||
const activePreview: Record<string, unknown> = {
|
||
...compactIdPreview(
|
||
activeKnownIds,
|
||
activeCount,
|
||
idPreviewLimit,
|
||
previewSource([
|
||
submittedActiveTaskIds.length > 0 ? "submittedTaskIds" : "",
|
||
upstreamActiveTaskIds.length > 0 ? "upstreamActiveTaskIds" : "",
|
||
databaseActiveTaskIds.length > 0 ? "databaseActiveTaskIds" : "",
|
||
diagnosticsDatabaseActiveTaskIds.length > 0 ? "executionDiagnostics.databaseActiveTaskIds" : "",
|
||
diagnosticsHeartbeatTaskIds.length > 0 ? "executionDiagnostics.heartbeatFreshTaskIds" : "",
|
||
activeKnownIds.length === 0 && activeCount > 0 ? "aggregateCountsOnly" : "",
|
||
], "none"),
|
||
activeCount > activeKnownIds.length ? "Upstream only exposed aggregate active counts for part of the running set; count remains authoritative." : null,
|
||
),
|
||
sourceCounts: {
|
||
running: countForStatus(counts, "running"),
|
||
judging: countForStatus(counts, "judging"),
|
||
upstreamActiveTaskIds: idPreviewInputCount(record.activeTaskIds),
|
||
databaseActiveTaskCount,
|
||
diagnosticsDatabaseActiveTaskIds: idPreviewInputCount(diagnosticsRecord.databaseActiveTaskIds),
|
||
diagnosticsHeartbeatFreshTaskIds: idPreviewInputCount(diagnosticsRecord.heartbeatFreshTaskIds),
|
||
submittedActiveTaskIds: submittedActiveTaskIds.length,
|
||
},
|
||
};
|
||
const databaseActivePreview: Record<string, unknown> = {
|
||
...compactIdPreview(
|
||
orderedUniqueStringList([...databaseActiveTaskIds, ...diagnosticsDatabaseActiveTaskIds]),
|
||
maxFiniteNumber([databaseActiveTaskCount, idPreviewInputCount(record.databaseActiveTaskIds), idPreviewInputCount(diagnosticsRecord.databaseActiveTaskIds)]),
|
||
idPreviewLimit,
|
||
previewSource([
|
||
databaseActiveTaskIds.length > 0 ? "databaseActiveTaskIds" : "",
|
||
diagnosticsDatabaseActiveTaskIds.length > 0 ? "executionDiagnostics.databaseActiveTaskIds" : "",
|
||
databaseActiveTaskCount > 0 && databaseActiveTaskIds.length === 0 && diagnosticsDatabaseActiveTaskIds.length === 0 ? "aggregateCountsOnly" : "",
|
||
], "none"),
|
||
),
|
||
sourceCounts: {
|
||
queueDatabaseActiveTaskCount: asNumber(record.databaseActiveTaskCount, Number.NaN),
|
||
diagnosticsDatabaseActiveTaskCount: asNumber(diagnosticsRecord.databaseActiveTaskCount, Number.NaN),
|
||
queueDatabaseActiveTaskIds: idPreviewInputCount(record.databaseActiveTaskIds),
|
||
diagnosticsDatabaseActiveTaskIds: idPreviewInputCount(diagnosticsRecord.databaseActiveTaskIds),
|
||
},
|
||
};
|
||
const submittedPreview = compactIdPreview(submittedTaskIds, submittedTaskIds.length, idPreviewLimit, "response.tasks");
|
||
const omittedCounts = {
|
||
activeTaskIds: asNumber(activePreview.omitted, 0),
|
||
databaseActiveTaskIds: asNumber(databaseActivePreview.omitted, 0),
|
||
queuedTaskIds: asNumber(queuedPreview.omitted, 0),
|
||
submittedTaskIds: asNumber(submittedPreview.omitted, 0),
|
||
};
|
||
const listsTruncated = Object.values(omittedCounts).some((count) => count > 0)
|
||
|| activePreview.truncated === true
|
||
|| databaseActivePreview.truncated === true
|
||
|| queuedPreview.truncated === true
|
||
|| submittedPreview.truncated === true;
|
||
const diagnostics = compactQueueExecutionDiagnostics(record.executionDiagnostics);
|
||
const activity = compactCodeQueueActivity(record, diagnostics);
|
||
const commanderConcurrency = asRecord(activity.commanderConcurrency) ?? {};
|
||
const unavailableIdLists = {
|
||
activeTaskIds: activePreview.idsUnavailable === true,
|
||
databaseActiveTaskIds: databaseActivePreview.idsUnavailable === true,
|
||
queuedTaskIds: queuedPreview.idsUnavailable === true,
|
||
submittedTaskIds: submittedPreview.idsUnavailable === true,
|
||
};
|
||
return {
|
||
total: record.total ?? null,
|
||
queueCount: record.queueCount ?? null,
|
||
counts: record.counts ?? null,
|
||
activeQueueIds: boundedUniqueStringList(record.activeQueueIds, 8),
|
||
activeTaskIds: activePreview,
|
||
databaseActiveTaskCount,
|
||
databaseActiveTaskIds: databaseActivePreview,
|
||
queuedTaskIds: queuedPreview,
|
||
activity,
|
||
commanderConcurrency,
|
||
executionDiagnostics: diagnostics,
|
||
runnerPermissions: compactRunnerPermissions(record.runnerPermissions),
|
||
...(submittedTasks.length === 0 ? {} : { submittedTaskIds: submittedPreview }),
|
||
countContext: {
|
||
queued: countForStatus(counts, "queued"),
|
||
retryWait: countForStatus(counts, "retry_wait"),
|
||
running: countForStatus(counts, "running"),
|
||
judging: countForStatus(counts, "judging"),
|
||
active: statusActiveCount,
|
||
databaseActive: databaseActiveTaskCount,
|
||
submitted: submittedTaskIds.length,
|
||
},
|
||
listPreviewPolicy: {
|
||
bounded: true,
|
||
idPreviewLimit,
|
||
countsAreAuthoritative: true,
|
||
truncated: listsTruncated,
|
||
omittedCounts,
|
||
unavailableIdLists,
|
||
emptyItemsSemantics: "items=[] is only emitted for an authoritative empty list or a nonempty bounded preview result; if count is nonzero and ids were not enumerated, the preview omits items and sets idsUnavailable=true.",
|
||
note: listsTruncated
|
||
? "Low-noise mutation output omits additional task ids from one or more previews; use the raw command for full upstream queue detail."
|
||
: "Low-noise mutation output includes all known task ids returned for these previews.",
|
||
rawCommand: rawCodeQueueOverviewCommand,
|
||
},
|
||
stateDisclosure: {
|
||
submittedStatusSource: "response.tasks[].status",
|
||
queueCountsSource: "response.queue.counts",
|
||
activeCountField: "queue.countContext.active",
|
||
commanderActiveRunnerCountField: "queue.activity.effectiveActiveTaskCount",
|
||
splitBrainLive: diagnostics?.splitBrainLive ?? false,
|
||
splitBrainDisposition: diagnostics?.splitBrainLive === true ? "live-counts-remain-active; continue supervision unless commanderConcurrency.interventionRequired=true" : "not-split-brain-live",
|
||
idsUnavailableMeaning: "A nonzero count with idsUnavailable=true means ids were omitted or unavailable in the bounded submit summary, not that there are no tasks.",
|
||
rawCommand: rawCodeQueueOverviewCommand,
|
||
},
|
||
byQueue: Array.isArray(record.byQueue) ? record.byQueue : undefined,
|
||
};
|
||
}
|
||
|
||
function compactSubmitConcurrencyGuard(value: Record<string, unknown>): Record<string, unknown> {
|
||
return {
|
||
mode: value.mode ?? null,
|
||
acquiredAfterMs: value.acquiredAfterMs ?? null,
|
||
heldMs: value.heldMs ?? null,
|
||
throttleMs: value.throttleMs ?? null,
|
||
staleMs: value.staleMs ?? null,
|
||
};
|
||
}
|
||
|
||
function compactSubmitSuccessResponse(body: Record<string, unknown>, upstream: Record<string, unknown>, lock: Record<string, unknown>): Record<string, unknown> {
|
||
const rawTasks = asArray(body.tasks);
|
||
const submittedTasks = rawTasks.map((task) => asRecord(task)).filter((task): task is Record<string, unknown> => task !== null);
|
||
const allTasks = rawTasks.map(compactSubmitTaskConfirmation);
|
||
const tasks = allTasks.slice(0, defaultTasksLimit);
|
||
const allTaskStates = submittedTasks.map(submittedTaskState);
|
||
const taskStates = allTaskStates.slice(0, defaultTasksLimit);
|
||
const allTaskIds = allTasks.map((task) => asString(task.id)).filter(Boolean);
|
||
const taskIds = allTaskIds.slice(0, defaultTasksLimit);
|
||
const queueIds = Array.from(new Set(tasks.map((task) => asString(task.queueId)).filter(Boolean))).sort();
|
||
const firstTaskId = taskIds[0] ?? null;
|
||
const firstQueueId = queueIds[0] ?? null;
|
||
const firstSubmittedTask = submittedTasks[0] ?? {};
|
||
const queueRecord = asRecord(body.queue);
|
||
const runnerPermissions = compactRunnerPermissions(queueRecord?.runnerPermissions);
|
||
return {
|
||
ok: true,
|
||
upstream,
|
||
executionMode: executionModeSummary(asString(firstSubmittedTask.requestedExecutionMode) || null, firstSubmittedTask.executionMode),
|
||
runnerPermissions,
|
||
submitted: {
|
||
accepted: true,
|
||
taskCount: allTasks.length,
|
||
returnedTaskCount: tasks.length,
|
||
taskIds,
|
||
taskIdsCount: allTaskIds.length,
|
||
taskIdsTruncated: allTaskIds.length > taskIds.length,
|
||
queueIds,
|
||
statusCounts: submittedTaskStatusCounts(submittedTasks),
|
||
taskStates,
|
||
taskStatesTruncated: allTaskStates.length > taskStates.length,
|
||
stateSource: "response.tasks[].status is authoritative for the submitted task state at submit-confirmation time.",
|
||
tasks,
|
||
tasksTruncated: allTasks.length > tasks.length,
|
||
promptOmitted: true,
|
||
outputPolicy: {
|
||
default: "write-confirmation",
|
||
promptEchoed: false,
|
||
reason: "codex submit is a write operation; default output confirms persistence and provides drill-down commands without echoing prompt text.",
|
||
},
|
||
},
|
||
queue: compactSubmitQueueConfirmation(body.queue, { submittedTasks }),
|
||
submitConcurrencyGuard: compactSubmitConcurrencyGuard(lock),
|
||
commands: {
|
||
firstTask: firstTaskId === null ? null : `bun scripts/cli.ts codex task ${firstTaskId}`,
|
||
firstTaskDetail: firstTaskId === null ? null : `bun scripts/cli.ts codex task ${firstTaskId} --detail`,
|
||
queue: firstQueueId === null ? null : `bun scripts/cli.ts codex tasks --queue ${firstQueueId} --limit ${defaultTasksLimit}`,
|
||
supervisor: `bun scripts/cli.ts codex tasks --view supervisor --limit ${defaultTasksLimit}`,
|
||
queues: "bun scripts/cli.ts codex queues",
|
||
},
|
||
};
|
||
}
|
||
|
||
export function compactSubmitSuccessResponseForTest(body: Record<string, unknown>, upstream: Record<string, unknown> = { ok: true, status: 200 }, lock: Record<string, unknown> = {}): Record<string, unknown> {
|
||
return compactSubmitSuccessResponse(body, upstream, lock);
|
||
}
|
||
|
||
function compactTerminalReadAttempt(value: unknown): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
return {
|
||
index: record.index ?? null,
|
||
synthetic: record.synthetic ?? false,
|
||
label: record.label ?? null,
|
||
mode: record.mode ?? null,
|
||
terminalStatus: record.terminalStatus ?? null,
|
||
appServerExitCode: record.appServerExitCode ?? null,
|
||
appServerSignal: record.appServerSignal ?? null,
|
||
transportClosedBeforeTerminal: record.transportClosedBeforeTerminal ?? null,
|
||
error: record.error ?? null,
|
||
stderrTail: textView(asString(record.stderrTail), false, 1200),
|
||
startedAt: record.startedAt ?? null,
|
||
finishedAt: record.finishedAt ?? null,
|
||
startSeq: record.startSeq ?? record.outputStartSeq ?? null,
|
||
endSeq: record.endSeq ?? record.outputEndSeq ?? null,
|
||
execution: record.execution ?? null,
|
||
finalResponse: textView(asString(record.finalResponsePreview ?? record.finalResponse), false, 3000),
|
||
judge: record.judge ?? null,
|
||
runnerErrorClassification: record.runnerErrorClassification ?? null,
|
||
};
|
||
}
|
||
|
||
function compactTerminalReadFinalResponse(summary: Record<string, unknown>, readTask: Record<string, unknown>): Record<string, unknown> {
|
||
const lastAssistantMessage = asRecord(summary.lastAssistantMessage);
|
||
const text = asString(lastAssistantMessage?.text ?? summary.finalResponse ?? readTask.finalResponse);
|
||
return {
|
||
at: lastAssistantMessage?.at ?? summary.finishedAt ?? readTask.finishedAt ?? null,
|
||
seq: lastAssistantMessage?.seq ?? null,
|
||
source: lastAssistantMessage?.source ?? (text.trim().length > 0 ? "finalResponse" : "none"),
|
||
...textView(text, false, 6000),
|
||
};
|
||
}
|
||
|
||
function compactReadReferenceInjection(value: unknown): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
const items = asArray(record.items);
|
||
const returnedItems = items.slice(0, 12);
|
||
return {
|
||
version: record.version ?? null,
|
||
injectedAt: record.injectedAt ?? null,
|
||
itemCount: record.itemCount ?? null,
|
||
directReferenceTaskIds: record.directReferenceTaskIds ?? [],
|
||
maxRounds: record.maxRounds ?? null,
|
||
truncated: record.truncated ?? null,
|
||
itemsReturned: returnedItems.length,
|
||
itemsTruncated: items.length > returnedItems.length,
|
||
items: returnedItems.map((item) => {
|
||
const itemRecord = asRecord(item) ?? {};
|
||
return {
|
||
round: itemRecord.round ?? null,
|
||
roundIndex: itemRecord.roundIndex ?? null,
|
||
taskId: itemRecord.taskId ?? null,
|
||
viaTaskId: itemRecord.viaTaskId ?? null,
|
||
status: itemRecord.status ?? null,
|
||
providerId: itemRecord.providerId ?? null,
|
||
executionMode: itemRecord.executionMode ?? null,
|
||
model: itemRecord.model ?? null,
|
||
cwd: itemRecord.cwd ?? null,
|
||
createdAt: itemRecord.createdAt ?? null,
|
||
updatedAt: itemRecord.updatedAt ?? null,
|
||
promptChars: itemRecord.promptChars ?? null,
|
||
finalResponseChars: itemRecord.finalResponseChars ?? null,
|
||
finalResponseAt: itemRecord.finalResponseAt ?? null,
|
||
finalResponseSource: itemRecord.finalResponseSource ?? null,
|
||
referenceTaskIds: itemRecord.referenceTaskIds ?? [],
|
||
cliHint: itemRecord.cliHint ?? null,
|
||
};
|
||
}),
|
||
};
|
||
}
|
||
|
||
function compactTerminalReadTask(summary: unknown, readTask: unknown, taskId: string): Record<string, unknown> {
|
||
const summaryRecord = asRecord(summary) ?? {};
|
||
const readRecord = asRecord(readTask) ?? {};
|
||
const id = asString(summaryRecord.id ?? readRecord.id) || taskId;
|
||
const attempts = asArray(summaryRecord.attempts);
|
||
const lastAttempt = compactTerminalReadAttempt(attempts.at(-1));
|
||
const toolSummary = asRecord(summaryRecord.toolSummary) ?? {};
|
||
const readAt = readRecord.readAt ?? summaryRecord.readAt ?? null;
|
||
const referenceInjection = compactReadReferenceInjection(summaryRecord.referenceInjection ?? readRecord.referenceInjectionSummary);
|
||
return {
|
||
id,
|
||
queueId: summaryRecord.queueId ?? readRecord.queueId ?? null,
|
||
status: summaryRecord.status ?? readRecord.status ?? null,
|
||
terminalUnread: false,
|
||
readAt,
|
||
providerId: summaryRecord.providerId ?? readRecord.providerId ?? null,
|
||
executionMode: summaryRecord.executionMode ?? readRecord.executionMode ?? null,
|
||
requestedExecutionMode: summaryRecord.requestedExecutionMode ?? readRecord.requestedExecutionMode ?? null,
|
||
executionModeRequest: compactTaskExecutionModeRequest({
|
||
requestedExecutionMode: summaryRecord.requestedExecutionMode ?? readRecord.requestedExecutionMode ?? null,
|
||
executionMode: summaryRecord.executionMode ?? readRecord.executionMode ?? null,
|
||
}),
|
||
executionModeInfo: summaryRecord.executionModeInfo ?? readRecord.executionModeInfo ?? null,
|
||
model: summaryRecord.model ?? readRecord.model ?? null,
|
||
agentPort: summaryRecord.agentPort ?? readRecord.agentPort ?? null,
|
||
agentPortInfo: summaryRecord.agentPortInfo ?? readRecord.agentPortInfo ?? null,
|
||
cwd: summaryRecord.cwd ?? readRecord.cwd ?? null,
|
||
reasoningEffort: summaryRecord.reasoningEffort ?? readRecord.reasoningEffort ?? null,
|
||
maxAttempts: summaryRecord.maxAttempts ?? readRecord.maxAttempts ?? null,
|
||
attempts: {
|
||
currentAttempt: summaryRecord.currentAttempt ?? readRecord.currentAttempt ?? null,
|
||
maxAttempts: summaryRecord.maxAttempts ?? readRecord.maxAttempts ?? null,
|
||
currentMode: summaryRecord.currentMode ?? readRecord.currentMode ?? null,
|
||
judgeFailCount: summaryRecord.judgeFailCount ?? readRecord.judgeFailCount ?? null,
|
||
judgeFailRetryLimit: summaryRecord.judgeFailRetryLimit ?? readRecord.judgeFailRetryLimit ?? null,
|
||
count: attempts.length,
|
||
lastAttempt,
|
||
},
|
||
thread: {
|
||
codexThreadId: summaryRecord.codexThreadId ?? readRecord.codexThreadId ?? null,
|
||
activeTurnId: summaryRecord.activeTurnId ?? readRecord.activeTurnId ?? null,
|
||
cancelRequested: summaryRecord.cancelRequested ?? readRecord.cancelRequested ?? null,
|
||
},
|
||
timing: summaryRecord.timing ?? readRecord.timing ?? null,
|
||
createdAt: summaryRecord.createdAt ?? readRecord.createdAt ?? null,
|
||
startedAt: summaryRecord.startedAt ?? readRecord.startedAt ?? null,
|
||
updatedAt: summaryRecord.updatedAt ?? readRecord.updatedAt ?? null,
|
||
finishedAt: summaryRecord.finishedAt ?? readRecord.finishedAt ?? null,
|
||
referenceTaskIds: summaryRecord.referenceTaskIds ?? readRecord.referenceTaskIds ?? [],
|
||
referenceInjection,
|
||
finalResponse: compactTerminalReadFinalResponse(summaryRecord, readRecord),
|
||
lastError: summaryRecord.lastError ?? readRecord.lastError ?? lastAttempt?.error ?? null,
|
||
lastJudge: summaryRecord.lastJudge ?? readRecord.lastJudge ?? null,
|
||
counts: {
|
||
transcript: summaryRecord.transcriptCount ?? readRecord.transcriptCount ?? null,
|
||
transcriptMaxSeq: summaryRecord.transcriptMaxSeq ?? readRecord.transcriptMaxSeq ?? null,
|
||
output: summaryRecord.outputCount ?? readRecord.outputCount ?? null,
|
||
retainedOutput: summaryRecord.retainedOutputCount ?? readRecord.retainedOutputCount ?? null,
|
||
outputMaxSeq: summaryRecord.outputMaxSeq ?? readRecord.outputMaxSeq ?? null,
|
||
events: summaryRecord.eventCount ?? readRecord.eventCount ?? null,
|
||
tools: toolSummary.count ?? null,
|
||
},
|
||
disclosure: {
|
||
mode: "terminal-read",
|
||
promptIncluded: false,
|
||
toolLogsIncluded: false,
|
||
finalResponseIncluded: true,
|
||
promptChars: asString(summaryRecord.initialPrompt ?? summaryRecord.prompt).length || (summaryRecord.promptChars ?? null),
|
||
toolCount: toolSummary.count ?? null,
|
||
policy: "read returns terminal metadata and final response; prompt and tool logs remain behind explicit progressive drill-down commands",
|
||
},
|
||
commands: {
|
||
show: `bun scripts/cli.ts codex task ${id}`,
|
||
detail: `bun scripts/cli.ts codex task ${id} --detail`,
|
||
full: `bun scripts/cli.ts codex task ${id} --full`,
|
||
trace: `bun scripts/cli.ts codex task ${id} --trace --tail --limit ${defaultTraceLimit}`,
|
||
output: `bun scripts/cli.ts codex output ${id} --tail --limit ${defaultOutputLimit}`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function codexReadTaskWithFetcher(taskId: string, fetcher: CodexResponseFetcher): unknown {
|
||
const summaryResponse = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: defaultToolLimit })}`)));
|
||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
|
||
const readTask = asRecord(response.body.task) ?? {};
|
||
const terminalUnread = readTask.terminalUnread ?? readTask.unreadTerminal ?? false;
|
||
return {
|
||
upstream: {
|
||
summary: summaryResponse.upstream,
|
||
read: response.upstream,
|
||
},
|
||
read: {
|
||
marked: true,
|
||
readAt: readTask.readAt ?? null,
|
||
terminalUnread,
|
||
},
|
||
task: compactTerminalReadTask(summaryResponse.body.summary, response.body.task, taskId),
|
||
queue: compactQueueMutationSummary(response.body.queue),
|
||
commands: {
|
||
show: `bun scripts/cli.ts codex task ${taskId}`,
|
||
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
|
||
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
|
||
output: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`,
|
||
unread: `bun scripts/cli.ts codex unread --limit ${defaultTasksLimit}`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function codexReadTask(taskId: string): unknown {
|
||
return codexReadTaskWithFetcher(taskId, coreInternalFetch);
|
||
}
|
||
|
||
export function codexReadTaskForTest(taskId: string, fetcher: CodexResponseFetcher): unknown {
|
||
return codexReadTaskWithFetcher(taskId, fetcher);
|
||
}
|
||
|
||
function compactQueueMutationSummary(value: unknown): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
return {
|
||
activeQueueIds: record.activeQueueIds ?? null,
|
||
activeTaskIds: record.activeTaskIds ?? null,
|
||
databaseActiveTaskCount: record.databaseActiveTaskCount ?? null,
|
||
databaseActiveTaskIds: record.databaseActiveTaskIds ?? null,
|
||
schedulerHeartbeatStaleMs: record.schedulerHeartbeatStaleMs ?? null,
|
||
executionDiagnostics: compactExecutionDiagnostics(record.executionDiagnostics),
|
||
queuedTaskIds: record.queuedTaskIds ?? null,
|
||
counts: record.counts ?? null,
|
||
byQueue: Array.isArray(record.byQueue) ? record.byQueue : undefined,
|
||
};
|
||
}
|
||
|
||
function compactSkillsStatus(value: unknown): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
return {
|
||
ok: record.ok ?? false,
|
||
path: record.path ?? null,
|
||
source: record.source ?? null,
|
||
target: record.target ?? null,
|
||
mountPoint: record.mountPoint ?? null,
|
||
exists: record.exists ?? false,
|
||
available: record.available ?? false,
|
||
degraded: record.degraded ?? true,
|
||
blocker: record.blocker ?? null,
|
||
readonly: record.readonly ?? false,
|
||
skillCount: record.skillCount ?? 0,
|
||
requiredSkills: Array.isArray(record.requiredSkills) ? record.requiredSkills : [],
|
||
missingSkills: Array.isArray(record.missingSkills) ? record.missingSkills : [],
|
||
valuesPrinted: record.valuesPrinted ?? false,
|
||
pathSpelling: compactObjectFields(asRecord(record.pathSpelling), [
|
||
"expectedTarget",
|
||
"forbiddenPathChecked",
|
||
"forbiddenPathExists",
|
||
"forbiddenPathConfigured",
|
||
"forbiddenPathRoles",
|
||
"forbiddenPathMustNotBeUsed",
|
||
]),
|
||
repairHint: record.repairHint ?? null,
|
||
};
|
||
}
|
||
|
||
function compactSkillPathReport(value: unknown): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
return {
|
||
path: record.path ?? null,
|
||
approved: record.approved ?? false,
|
||
exists: record.exists ?? false,
|
||
directory: record.directory ?? false,
|
||
readable: record.readable ?? false,
|
||
writable: record.writable ?? false,
|
||
readonly: record.readonly ?? false,
|
||
mountPoint: record.mountPoint ?? null,
|
||
skillCount: record.skillCount ?? 0,
|
||
requiredSkills: Array.isArray(record.requiredSkills) ? record.requiredSkills.map(String) : [],
|
||
missingSkills: Array.isArray(record.missingSkills) ? record.missingSkills.map(String) : [],
|
||
error: record.error ?? null,
|
||
};
|
||
}
|
||
|
||
function compactSkillsSyncStatus(value: unknown, full = false): Record<string, unknown> | null {
|
||
const record = asRecord(value);
|
||
if (record === null) return null;
|
||
const source = compactSkillPathReport(record.source);
|
||
const target = compactSkillPathReport(record.target);
|
||
const compact: Record<string, unknown> = {
|
||
ok: record.ok ?? false,
|
||
degraded: record.degraded ?? true,
|
||
blocker: record.blocker ?? null,
|
||
checkedAt: record.checkedAt ?? null,
|
||
mode: record.mode ?? "dry-run",
|
||
dryRun: record.dryRun ?? true,
|
||
mutation: record.mutation ?? false,
|
||
syncMode: record.syncMode ?? null,
|
||
source,
|
||
target,
|
||
expected: record.expected ?? null,
|
||
counts: record.counts ?? null,
|
||
missing: record.missing ?? null,
|
||
permissionFailures: Array.isArray(record.permissionFailures) ? record.permissionFailures.slice(0, full ? undefined : 4) : [],
|
||
permissionFailureCount: Array.isArray(record.permissionFailures) ? record.permissionFailures.length : 0,
|
||
pathSpelling: record.pathSpelling ?? null,
|
||
plannedActions: record.plannedActions ?? null,
|
||
instructions: Array.isArray(record.instructions) ? record.instructions.map(String).slice(0, full ? undefined : 4) : [],
|
||
commands: record.commands ?? null,
|
||
valuesPrinted: record.valuesPrinted ?? false,
|
||
};
|
||
if (full) compact.rawSkillsSync = record;
|
||
return compact;
|
||
}
|
||
|
||
function codeQueueDevReady(): unknown {
|
||
const response = unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath("/api/dev-ready")));
|
||
const devReady = asRecord(response.body.devReady) ?? {};
|
||
return {
|
||
upstream: response.upstream,
|
||
devReady: {
|
||
ok: devReady.ok ?? false,
|
||
missingTools: devReady.missingTools ?? [],
|
||
workdir: devReady.workdir ?? null,
|
||
docker: devReady.docker ?? null,
|
||
codexConfig: devReady.codexConfig ?? null,
|
||
ssh: devReady.ssh ?? null,
|
||
skills: compactSkillsStatus(devReady.skills),
|
||
skillsSync: compactSkillsSyncStatus(devReady.skillsSync),
|
||
},
|
||
commands: {
|
||
health: "bun scripts/cli.ts codex dev-ready",
|
||
raw: "bun scripts/cli.ts microservice proxy code-queue /api/dev-ready --raw",
|
||
skillsSync: "bun scripts/cli.ts codex skills-sync --dry-run",
|
||
},
|
||
};
|
||
}
|
||
|
||
function codeQueueSkillsSync(args: string[]): unknown {
|
||
const options = parseSkillsSyncOptions(args);
|
||
if (!options.dryRun) {
|
||
return {
|
||
ok: false,
|
||
failureKind: "dry-run-required",
|
||
runnerDisposition: "business-failed",
|
||
message: "codex skills-sync is dry-run only; pass --dry-run",
|
||
mutation: false,
|
||
commands: {
|
||
dryRun: "bun scripts/cli.ts codex skills-sync --dry-run",
|
||
full: "bun scripts/cli.ts codex skills-sync --dry-run --full",
|
||
},
|
||
};
|
||
}
|
||
const rawResponse = coreInternalFetch(codeQueueProxyPath("/api/skills-sync?dryRun=1"));
|
||
const response = asRecord(rawResponse);
|
||
if (response?.ok !== true) {
|
||
const targetStack = asRecord(response?.targetStack);
|
||
const observed = asRecord(response?.observed);
|
||
const missingContainers = Array.isArray(targetStack?.missingContainers) ? targetStack.missingContainers.map(String) : [];
|
||
const relatedContainers = Array.isArray(targetStack?.relatedContainers) ? targetStack.relatedContainers : [];
|
||
return {
|
||
ok: false,
|
||
dryRun: true,
|
||
mutation: false,
|
||
runnerDisposition: response?.runnerDisposition ?? "infra-blocked",
|
||
failureKind: response?.failureKind ?? "control-plane-missing",
|
||
degradedReason: response?.degradedReason ?? "backend-core-proxy-unavailable",
|
||
message: response?.message ?? response?.stderrTail ?? response?.stdoutTail ?? "Code Queue skills sync dry-run could not reach the control plane",
|
||
controlPlane: {
|
||
mode: "local-backend-core",
|
||
localBackendCoreMissing: response?.failureKind === "target-stack-not-running",
|
||
schedulerStateChanged: false,
|
||
liveRunnerHostPathMutated: false,
|
||
},
|
||
targetStackSummary: targetStack === null ? null : {
|
||
missingContainers,
|
||
missingContainerCount: missingContainers.length,
|
||
relatedContainerCount: relatedContainers.length,
|
||
verifyOnlyObserved: targetStack.verifyOnlyObserved ?? false,
|
||
},
|
||
observed: observed === null ? null : {
|
||
commandExitCode: observed.commandExitCode ?? null,
|
||
stderrTail: typeof observed.stderrTail === "string" ? textView(observed.stderrTail, false, 600) : null,
|
||
},
|
||
outputPolicy: {
|
||
default: "compact-skills-sync-control-plane-failure",
|
||
unrelatedDiagnosticsOmitted: true,
|
||
full: "use microservice health/proxy commands only when control-plane diagnostics are needed",
|
||
},
|
||
commands: {
|
||
retry: "bun scripts/cli.ts codex skills-sync --dry-run",
|
||
full: "bun scripts/cli.ts codex skills-sync --dry-run --full",
|
||
health: "bun scripts/cli.ts microservice health code-queue",
|
||
rawProxy: "bun scripts/cli.ts microservice proxy code-queue /api/skills-sync?dryRun=1 --raw --full",
|
||
},
|
||
};
|
||
}
|
||
const body = asRecord(response.body);
|
||
if (body?.ok !== true) {
|
||
return {
|
||
ok: false,
|
||
dryRun: true,
|
||
mutation: false,
|
||
runnerDisposition: "infra-blocked",
|
||
failureKind: "runtime-skills-sync-missing",
|
||
degradedReason: "runtime-skills-sync-response-invalid",
|
||
message: "Code Queue skills sync dry-run response did not include a valid body",
|
||
upstream: { ok: response.ok, status: response.status ?? null },
|
||
bodyPreview: body,
|
||
commands: {
|
||
retry: "bun scripts/cli.ts codex skills-sync --dry-run",
|
||
rawProxy: "bun scripts/cli.ts microservice proxy code-queue /api/skills-sync?dryRun=1 --raw --full",
|
||
},
|
||
};
|
||
}
|
||
const skillsSync = asRecord(body.skillsSync);
|
||
return {
|
||
upstream: { ok: response.ok, status: response.status ?? null },
|
||
skillsSync: compactSkillsSyncStatus(skillsSync, options.full),
|
||
outputPolicy: {
|
||
default: "compact-skills-sync-dry-run",
|
||
full: "bun scripts/cli.ts codex skills-sync --dry-run --full",
|
||
mutation: false,
|
||
},
|
||
};
|
||
}
|
||
|
||
function compactCommandProbe(value: unknown): Record<string, unknown> | null {
|
||
const probe = asRecord(value);
|
||
if (probe === null) return null;
|
||
return {
|
||
command: probe.command ?? null,
|
||
args: Array.isArray(probe.args) ? probe.args : [],
|
||
ok: probe.ok ?? false,
|
||
exitCode: probe.exitCode ?? null,
|
||
signal: probe.signal ?? null,
|
||
error: probe.error ?? null,
|
||
stdout: typeof probe.stdout === "string" ? textView(probe.stdout, false, 1200) : null,
|
||
stderr: typeof probe.stderr === "string" ? textView(probe.stderr, false, 1200) : null,
|
||
};
|
||
}
|
||
|
||
function commandProbeFailureText(value: unknown): string {
|
||
const probe = asRecord(value);
|
||
if (probe === null || probe.ok === true) return "";
|
||
const args = Array.isArray(probe.args) ? probe.args.map(String).join(" ") : "";
|
||
return [
|
||
typeof probe.command === "string" ? probe.command : "",
|
||
args,
|
||
typeof probe.error === "string" ? probe.error : "",
|
||
typeof probe.stderr === "string" ? probe.stderr : "",
|
||
typeof probe.stdout === "string" ? probe.stdout : "",
|
||
].filter((item) => item.length > 0).join("\n");
|
||
}
|
||
|
||
function commandProbeLooksLikeGithubTransient(value: unknown): boolean {
|
||
const text = commandProbeFailureText(value);
|
||
if (text.length === 0) return false;
|
||
if (/\bproxy\b/iu.test(text) && /could not resolve|connect|tunnel|proxy/iu.test(text)) return false;
|
||
return /temporary failure in name resolution|error connecting to api\.github\.com|getaddrinfo|could not resolve host:? (github\.com|api\.github\.com)|name or service not known|eai_again|enotfound|etimedout|econnreset|ehostunreach|enetunreach|failed to connect to (github\.com|api\.github\.com)|network is unreachable/iu.test(text);
|
||
}
|
||
|
||
function githubTransientEvidence(pull: Record<string, unknown>): Record<string, unknown> | null {
|
||
const egress = asRecord(pull.egress) ?? {};
|
||
const remote = asRecord(pull.remote);
|
||
const probes: Array<[string, unknown]> = [
|
||
["egress.githubDefault", egress.githubDefault],
|
||
["egress.apiDefault", egress.apiDefault],
|
||
["egress.issueApi", egress.issueApi],
|
||
];
|
||
if (remote !== null) {
|
||
probes.push(
|
||
["remote.gitLsRemote", remote.gitLsRemote],
|
||
["remote.gitHttpsLsRemote", remote.gitHttpsLsRemote],
|
||
["remote.ghAuthStatus", remote.ghAuthStatus],
|
||
["remote.ghRepoView", remote.ghRepoView],
|
||
["remote.ghIssueView", remote.ghIssueView],
|
||
["remote.ghPrReadOnly", remote.ghPrReadOnly],
|
||
);
|
||
}
|
||
const failedProbes = probes
|
||
.map(([name, probe]) => ({ name, probe, text: commandProbeFailureText(probe) }))
|
||
.filter((item) => commandProbeLooksLikeGithubTransient(item.probe))
|
||
.slice(0, 4);
|
||
if (failedProbes.length === 0) return null;
|
||
return {
|
||
kind: "github-transient",
|
||
retryable: true,
|
||
scope: "github-dns-api",
|
||
failedProbes: failedProbes.map((item) => ({
|
||
name: item.name,
|
||
preview: compactInlinePreview(item.text, 240),
|
||
})),
|
||
commanderAction: "retry/backoff; if Code Queue heartbeat or trace is fresh, keep the task running and continue supervision",
|
||
notAuthMissing: true,
|
||
notPrSemanticFailure: true,
|
||
valuesPrinted: false,
|
||
};
|
||
}
|
||
|
||
function compactToolStatus(value: unknown): Record<string, unknown> {
|
||
const tool = asRecord(value) ?? {};
|
||
return {
|
||
ok: tool.ok ?? false,
|
||
path: tool.path ?? null,
|
||
version: tool.version ?? null,
|
||
};
|
||
}
|
||
|
||
function compactUniDeskGhCliStatus(value: unknown): Record<string, unknown> {
|
||
const cli = asRecord(value);
|
||
const observed = cli !== null;
|
||
const ok = observed ? cli.ok === true : null;
|
||
return {
|
||
ok,
|
||
observed,
|
||
path: observed ? cli.path ?? null : null,
|
||
present: observed ? cli.present ?? false : null,
|
||
role: "repo-native REST GitHub CLI used by bun scripts/cli.ts gh",
|
||
requiresSystemGhBinary: false,
|
||
unavailableReason: ok === true
|
||
? null
|
||
: observed
|
||
? "scripts-cli-missing"
|
||
: "runtime-preflight-did-not-report-unidesk-gh-cli",
|
||
};
|
||
}
|
||
|
||
function compactAuthBrokerRuntimeStatus(value: unknown): Record<string, unknown> {
|
||
const broker = asRecord(value);
|
||
const observed = broker !== null;
|
||
const capability = asRecord(broker?.capability);
|
||
const configured = broker?.configured === true || broker?.ok === true && broker?.source === "auth-broker";
|
||
const operations = Array.isArray(capability?.operations)
|
||
? capability.operations.map(String)
|
||
: ["github.auth.status", "github.issue.read", "github.pr.read", "github.pr.create"];
|
||
return {
|
||
ok: configured,
|
||
observed,
|
||
configured,
|
||
source: typeof broker?.source === "string" ? broker.source : configured ? "auth-broker" : "broker/auth-broker-needed",
|
||
endpointEnvKey: typeof broker?.endpointEnvKey === "string" ? broker.endpointEnvKey : null,
|
||
runnerEnvTokenRequired: broker?.runnerEnvTokenRequired === true,
|
||
credentialSource: typeof broker?.credentialSource === "string" ? broker.credentialSource : configured ? "broker-held-github-credential" : null,
|
||
failureKind: configured ? null : broker?.failureKind ?? "auth-missing",
|
||
degradedReason: configured ? null : broker?.degradedReason ?? "auth-broker-needed",
|
||
capability: {
|
||
source: typeof capability?.source === "string" ? capability.source : configured ? "broker-issued-token" : "missing-token",
|
||
githubRestAuth: capability?.githubRestAuth === true || configured,
|
||
operations,
|
||
systemGhBinaryRequiredForWrites: false,
|
||
preflightWritesRemote: false,
|
||
preflightCreatesPr: false,
|
||
preflightMergesPr: false,
|
||
realPrCreateRequiresCommanderAuthorization: true,
|
||
valuesPrinted: false,
|
||
},
|
||
nextAction: typeof broker?.nextAction === "string"
|
||
? broker.nextAction
|
||
: configured
|
||
? "use-auth-broker"
|
||
: "configure-auth-broker-or-env-token",
|
||
next: Array.isArray(broker?.next) ? broker.next.map(String) : configured
|
||
? ["keep PR preflight read-only; create a real PR only after commander authorization"]
|
||
: [
|
||
"configure UNIDESK_AUTH_BROKER_URL or AUTH_BROKER_URL for broker-backed runner auth",
|
||
"or inject GH_TOKEN/GITHUB_TOKEN into the Code Queue scheduler runtime secret until broker mode is live",
|
||
],
|
||
valuesRead: false,
|
||
valuesPrinted: false,
|
||
};
|
||
}
|
||
|
||
function compactAgentPortStatus(value: unknown): Record<string, unknown> {
|
||
const port = asRecord(value) ?? {};
|
||
return {
|
||
ok: port.ok ?? false,
|
||
commandPath: port.commandPath ?? null,
|
||
version: port.version ?? null,
|
||
errors: Array.isArray(port.errors) ? port.errors : [],
|
||
};
|
||
}
|
||
|
||
function tokenCoverageStatus(credentials: Record<string, unknown>, authBroker: Record<string, unknown>): Record<string, unknown> {
|
||
const ghTokenPresent = credentials.ghTokenPresent === true;
|
||
const githubTokenPresent = credentials.githubTokenPresent === true;
|
||
const anyEnvToken = ghTokenPresent || githubTokenPresent;
|
||
const anyGhCredentialStore = credentials.ghHostsConfigPresent === true || credentials.gitCredentialsPresent === true;
|
||
const authBrokerOk = authBroker.ok === true;
|
||
const effective = authBrokerOk || anyEnvToken;
|
||
const envSource = ghTokenPresent ? "GH_TOKEN" : githubTokenPresent ? "GITHUB_TOKEN" : null;
|
||
return {
|
||
ok: effective,
|
||
source: authBrokerOk ? "auth-broker" : envSource,
|
||
credentialSource: authBrokerOk ? "broker-issued-token" : anyEnvToken ? "env-token" : null,
|
||
ghTokenPresent,
|
||
githubTokenPresent,
|
||
ghCredentialStorePresent: anyGhCredentialStore,
|
||
authBrokerPresent: authBrokerOk,
|
||
authBrokerSource: authBroker.source ?? null,
|
||
runnerEnvTokenRequired: !authBrokerOk,
|
||
runnerDisposition: effective ? "ready" : "infra-blocked",
|
||
missing: effective ? [] : ["GH_TOKEN", "GITHUB_TOKEN"],
|
||
envMissing: anyEnvToken ? [] : ["GH_TOKEN", "GITHUB_TOKEN"],
|
||
scope: authBrokerOk ? "broker-held-github-credential" : "scheduler-runner-env",
|
||
note: authBrokerOk
|
||
? "scheduler can use auth-broker for GitHub REST PR preflight without exposing GH_TOKEN/GITHUB_TOKEN to runner env"
|
||
: anyEnvToken
|
||
? "scheduler has a GitHub env token that can be forwarded to provider dev containers through CODE_QUEUE_REMOTE_CODEX_ENV_KEYS"
|
||
: "scheduler is missing GH_TOKEN/GITHUB_TOKEN and auth-broker is not configured; provider dev containers cannot receive a GitHub token even though CODE_QUEUE_REMOTE_CODEX_ENV_KEYS includes those keys",
|
||
};
|
||
}
|
||
|
||
function authBrokerNeededStatus(tokenCoverage: Record<string, unknown>, runtimeAuthBroker: Record<string, unknown>, systemGhBinary: Record<string, unknown>, unideskGhCli: Record<string, unknown>): Record<string, unknown> {
|
||
const missing = Array.isArray(tokenCoverage.missing) ? tokenCoverage.missing.map(String) : [];
|
||
const ready = tokenCoverage.ok === true;
|
||
const authBrokerReady = tokenCoverage.source === "auth-broker";
|
||
const needed = !ready;
|
||
const capability = asRecord(runtimeAuthBroker.capability) ?? {};
|
||
const capabilitySource = tokenCoverage.credentialSource ?? "missing-token";
|
||
const nextAction = authBrokerReady
|
||
? "use-auth-broker"
|
||
: ready
|
||
? "use-env-token-until-auth-broker-live"
|
||
: "configure-auth-broker-or-env-token";
|
||
return {
|
||
ok: ready,
|
||
source: authBrokerReady ? "auth-broker" : needed ? "broker/auth-broker-needed" : tokenCoverage.source ?? "runner-env-token",
|
||
needed,
|
||
configured: runtimeAuthBroker.configured === true,
|
||
runnerDisposition: needed ? "infra-blocked" : "ready",
|
||
failureKind: needed ? "auth-missing" : null,
|
||
degradedReason: needed ? "auth-broker-needed" : null,
|
||
runnerEnvTokenRequiredWithoutBroker: !authBrokerReady,
|
||
brokerCredentialSource: authBrokerReady ? runtimeAuthBroker.credentialSource ?? "broker-held-github-credential" : null,
|
||
capability: {
|
||
source: capabilitySource,
|
||
githubRestAuth: ready,
|
||
operations: Array.isArray(capability.operations)
|
||
? capability.operations.map(String)
|
||
: ["github.auth.status", "github.issue.read", "github.pr.read", "github.pr.create"],
|
||
systemGhBinaryRequiredForWrites: false,
|
||
unideskGhCliRequired: true,
|
||
preflightWritesRemote: false,
|
||
preflightCreatesPr: false,
|
||
preflightMergesPr: false,
|
||
realPrCreateRequiresCommanderAuthorization: true,
|
||
valuesPrinted: false,
|
||
},
|
||
nextAction,
|
||
valuesPrinted: false,
|
||
evidence: {
|
||
envTokenMissing: needed,
|
||
missing,
|
||
authBrokerObserved: runtimeAuthBroker.observed ?? false,
|
||
authBrokerConfigured: runtimeAuthBroker.configured ?? false,
|
||
authBrokerSource: runtimeAuthBroker.source ?? null,
|
||
systemGhBinaryOk: systemGhBinary.ok === true,
|
||
systemGhBinaryRequiredForWrites: false,
|
||
unideskGhCliObserved: unideskGhCli.observed ?? false,
|
||
unideskGhCliOk: unideskGhCli.ok ?? null,
|
||
unideskGhCliRequiresSystemGhBinary: false,
|
||
systemGhMissingMisclassifiedAsUniDeskCliMissing: false,
|
||
},
|
||
next: needed
|
||
? [
|
||
"configure UNIDESK_AUTH_BROKER_URL or AUTH_BROKER_URL so PR preflight can use a broker-held GitHub credential without exposing runner env tokens",
|
||
"or inject GH_TOKEN/GITHUB_TOKEN into the Code Queue scheduler runtime secret until broker mode is live",
|
||
]
|
||
: authBrokerReady
|
||
? ["keep PR preflight read-only; create a real PR only after commander authorization"]
|
||
: ["env token path is ready; Auth Broker is still needed before removing runner env GitHub credentials"],
|
||
reference: "docs/reference/auth-broker.md#post-v1githubpr-preflight",
|
||
};
|
||
}
|
||
|
||
function activeRunnerDevContainerCapability(): Record<string, unknown> {
|
||
const ghTokenPresent = typeof process.env.GH_TOKEN === "string" && process.env.GH_TOKEN.length > 0;
|
||
const githubTokenPresent = typeof process.env.GITHUB_TOKEN === "string" && process.env.GITHUB_TOKEN.length > 0;
|
||
const anyToken = ghTokenPresent || githubTokenPresent;
|
||
return {
|
||
scope: "current-cli-process",
|
||
applicableWhen: "this command is running inside the active Code Queue runner/dev container",
|
||
observed: true,
|
||
ok: anyToken,
|
||
ghTokenPresent,
|
||
githubTokenPresent,
|
||
credentialSource: ghTokenPresent ? "GH_TOKEN" : githubTokenPresent ? "GITHUB_TOKEN" : null,
|
||
notEquivalentToSchedulerEnv: true,
|
||
relationToRemotePreflight: "independent-scope; scheduler-runner-env auth-missing does not prove the active runner/dev container lacks GitHub PR capability",
|
||
valuesPrinted: false,
|
||
commands: {
|
||
authStatus: "bun scripts/cli.ts gh auth status --repo pikasTech/unidesk",
|
||
prCreateDryRun: "bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --title <title> --body-file <file> --base master --head <head> --dry-run",
|
||
prCommentDryRun: "bun scripts/cli.ts gh pr comment create <number> --repo pikasTech/unidesk --body-file <file> --dry-run",
|
||
},
|
||
interpretation: anyToken
|
||
? "current CLI process has a GitHub token candidate; validate with gh auth status and PR dry-run before declaring this runner unable to create/comment PRs"
|
||
: "current CLI process did not expose GH_TOKEN/GITHUB_TOKEN; this still does not prove another active runner/dev container lacks token coverage",
|
||
};
|
||
}
|
||
|
||
function prPreflightScopeBoundary(tokenCoverage: Record<string, unknown> | null): Record<string, unknown> {
|
||
const schedulerScope = typeof tokenCoverage?.scope === "string" ? tokenCoverage.scope : "scheduler-runner-env";
|
||
return {
|
||
headline: "scheduler-runner-env auth-missing is not active runner/dev container PR incapability",
|
||
schedulerPreflightScope: schedulerScope,
|
||
activeRunnerDevContainerScope: "current-cli-process",
|
||
scopesAreIndependent: true,
|
||
schedulerAuthMissingDoesNotMeanActiveRunnerCannotCreatePr: true,
|
||
authMissingInterpretation: "auth-missing from codex pr-preflight --remote is scoped to the scheduler/runtime preflight surface; do not simplify it to 'the active runner cannot create PRs'",
|
||
currentRunnerCheck: "use activeRunnerDevContainer plus bun scripts/cli.ts gh auth status --repo pikasTech/unidesk and gh pr create --dry-run for the current dev container",
|
||
valuesPrinted: false,
|
||
};
|
||
}
|
||
|
||
function prPreflightRecommendedActions(tokenCoverage: Record<string, unknown> | null): Record<string, unknown>[] {
|
||
const schedulerReady = tokenCoverage?.ok === true;
|
||
return [
|
||
{
|
||
scope: "active-runner-dev-container",
|
||
priority: "first",
|
||
action: "verify-current-runner-auth",
|
||
command: "bun scripts/cli.ts gh auth status --repo pikasTech/unidesk",
|
||
writesRemote: false,
|
||
},
|
||
{
|
||
scope: "active-runner-dev-container",
|
||
priority: "first",
|
||
action: "verify-current-runner-pr-create-plan",
|
||
command: "bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --title <title> --body-file <file> --base master --head <head> --dry-run",
|
||
writesRemote: false,
|
||
},
|
||
{
|
||
scope: "scheduler-runtime",
|
||
priority: schedulerReady ? "long-term" : "required-for-scheduler-preflight",
|
||
action: "configure-scheduler-auth-source",
|
||
command: "configure auth-broker or inject GH_TOKEN/GITHUB_TOKEN via the scheduler runtime secret",
|
||
writesRemote: false,
|
||
},
|
||
];
|
||
}
|
||
|
||
function prPreflightAuthScopeSummary(tokenCoverage: Record<string, unknown> | null, activeRunnerDevContainer: Record<string, unknown>): Record<string, unknown> {
|
||
const schedulerReady = tokenCoverage?.ok === true;
|
||
return {
|
||
schedulerPreflightScope: typeof tokenCoverage?.scope === "string" ? tokenCoverage.scope : "scheduler-runner-env",
|
||
schedulerAuthReady: schedulerReady,
|
||
schedulerAuthSource: tokenCoverage?.source ?? null,
|
||
schedulerAuthMissingIsScoped: !schedulerReady,
|
||
activeRunnerDevContainerScope: activeRunnerDevContainer.scope ?? "current-cli-process",
|
||
activeRunnerTokenCandidatePresent: activeRunnerDevContainer.ok === true,
|
||
interpretation: schedulerReady
|
||
? "scheduler preflight has GitHub auth coverage; active runner PR creation still needs repo-native dry-run verification before real writes"
|
||
: "scheduler-runner-env auth-missing does not prove the active runner/dev container lacks PR create/comment capability",
|
||
valuesPrinted: false,
|
||
};
|
||
}
|
||
|
||
function prPreflightTokenCoverage(record: Record<string, unknown>): Record<string, unknown> | null {
|
||
const preflight = asRecord(record.preflight);
|
||
const schedulerPreflight = asRecord(record.schedulerPreflight);
|
||
const schedulerAuth = asRecord(schedulerPreflight?.auth);
|
||
const schedulerSummary = schedulerPreflight === null ? null : {
|
||
ok: schedulerPreflight.authReady ?? null,
|
||
source: schedulerPreflight.authSource ?? null,
|
||
credentialSource: schedulerPreflight.credentialSource ?? null,
|
||
scope: schedulerPreflight.scope ?? null,
|
||
missing: Array.isArray(schedulerPreflight.missing) && schedulerPreflight.missing.length > 0
|
||
? schedulerPreflight.missing.map(String)
|
||
: schedulerPreflight.authReady === false
|
||
? ["GH_TOKEN", "GITHUB_TOKEN"]
|
||
: [],
|
||
runnerDisposition: schedulerPreflight.authReady === true ? "ready" : "infra-blocked",
|
||
};
|
||
return asRecord(record.tokenCoverage)
|
||
?? asRecord(preflight?.tokenCoverage)
|
||
?? schedulerAuth
|
||
?? schedulerSummary
|
||
?? null;
|
||
}
|
||
|
||
function prPreflightAuthBroker(record: Record<string, unknown>): Record<string, unknown> | null {
|
||
const preflight = asRecord(record.preflight);
|
||
const schedulerPreflight = asRecord(record.schedulerPreflight);
|
||
return asRecord(record.authBroker)
|
||
?? asRecord(preflight?.authBroker)
|
||
?? asRecord(schedulerPreflight?.authBroker)
|
||
?? null;
|
||
}
|
||
|
||
function prPreflightCapabilityContract(record: Record<string, unknown>): Record<string, unknown> | null {
|
||
const preflight = asRecord(record.preflight);
|
||
const prCapability = asRecord(record.prCapability);
|
||
return asRecord(record.prCapabilityContract)
|
||
?? asRecord(preflight?.prCapabilityContract)
|
||
?? prCapability
|
||
?? null;
|
||
}
|
||
|
||
function prPreflightCommandSet(record: Record<string, unknown>, options: CodexPrPreflightOptions): Record<string, unknown> {
|
||
const preflight = asRecord(record.preflight);
|
||
const commands = asRecord(record.commands) ?? asRecord(preflight?.commands) ?? {};
|
||
const activeRunnerDevContainer = asRecord(record.activeRunnerDevContainer) ?? activeRunnerDevContainerCapability();
|
||
const activeCommands = asRecord(activeRunnerDevContainer.commands) ?? {};
|
||
const remoteFlag = options.remote ? " --remote" : "";
|
||
const fullDetail = `bun scripts/cli.ts codex pr-preflight${remoteFlag} --full`;
|
||
return {
|
||
verifyActiveRunnerAuth: activeCommands.authStatus ?? commands.local ?? "bun scripts/cli.ts gh auth status --repo pikasTech/unidesk",
|
||
verifyActiveRunnerPrCreateDryRun: activeCommands.prCreateDryRun ?? "bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --title <title> --body-file <file> --base master --head <head> --dry-run",
|
||
rerunSchedulerPreflight: commands.runner ?? `bun scripts/cli.ts codex pr-preflight${remoteFlag}`,
|
||
fullDetail,
|
||
rawProxy: commands.rawProxy ?? "bun scripts/cli.ts microservice proxy code-queue /api/runtime-preflight?remote=1 --raw --full",
|
||
schedulerAuthSource: "configure auth-broker or inject GH_TOKEN/GITHUB_TOKEN into the scheduler runtime secret",
|
||
};
|
||
}
|
||
|
||
function compactRecommendedActions(record: Record<string, unknown>): Record<string, unknown>[] {
|
||
const actions = Array.isArray(record.recommendedActions) ? record.recommendedActions : [];
|
||
if (actions.length > 0) {
|
||
return actions.slice(0, 3).map((item) => {
|
||
const action = asRecord(item) ?? {};
|
||
return {
|
||
scope: action.scope ?? null,
|
||
priority: action.priority ?? null,
|
||
action: action.action ?? null,
|
||
command: action.command ?? null,
|
||
writesRemote: action.writesRemote ?? false,
|
||
};
|
||
});
|
||
}
|
||
const tokenCoverage = prPreflightTokenCoverage(record);
|
||
return prPreflightRecommendedActions(tokenCoverage);
|
||
}
|
||
|
||
function compactPrPreflightCommanderView(record: Record<string, unknown>, options: CodexPrPreflightOptions): Record<string, unknown> {
|
||
if (options.full) return record;
|
||
|
||
const tokenCoverage = prPreflightTokenCoverage(record);
|
||
const authBroker = prPreflightAuthBroker(record);
|
||
const capability = prPreflightCapabilityContract(record);
|
||
const activeRunnerDevContainer = asRecord(record.activeRunnerDevContainer) ?? activeRunnerDevContainerCapability();
|
||
const activeCommands = asRecord(activeRunnerDevContainer.commands) ?? {};
|
||
const authBrokerCapability = asRecord(authBroker?.capability);
|
||
const expectedPrHandoff = asRecord(capability?.expectedPrHandoff);
|
||
const unideskGhCli = asRecord(capability?.unideskGhCli);
|
||
const pushDryRun = asRecord(capability?.pushDryRun);
|
||
const prCreateDryRun = asRecord(capability?.prCreateDryRun);
|
||
const unsupportedMergeBoundary = asRecord(capability?.unsupportedMergeBoundary);
|
||
const commands = prPreflightCommandSet(record, options);
|
||
const schedulerAuthReady = tokenCoverage?.ok === true;
|
||
const activeRunnerTokenCandidatePresent = activeRunnerDevContainer.ok === true;
|
||
const failureKind = record.failureKind ?? (schedulerAuthReady ? null : "auth-missing");
|
||
const degradedReason = record.degradedReason ?? (schedulerAuthReady ? null : "auth-broker-needed");
|
||
const githubTransient = asRecord(record.githubTransient);
|
||
|
||
return {
|
||
ok: record.ok === true,
|
||
runnerDisposition: record.runnerDisposition ?? (record.ok === true ? "ready" : "infra-blocked"),
|
||
failureKind,
|
||
degradedReason,
|
||
...(record.retryable === true ? { retryable: true } : {}),
|
||
...(typeof record.commanderAction === "string" ? { commanderAction: record.commanderAction } : {}),
|
||
...(githubTransient === null ? {} : { githubTransient }),
|
||
summary: {
|
||
status: record.ok === true ? "ready" : "blocked",
|
||
schedulerPreflightAuthReady: schedulerAuthReady,
|
||
schedulerPreflightScope: tokenCoverage?.scope ?? "scheduler-runner-env",
|
||
activeRunnerTokenCandidatePresent,
|
||
activeRunnerScope: activeRunnerDevContainer.scope ?? "current-cli-process",
|
||
interpretation: schedulerAuthReady
|
||
? "scheduler preflight auth is ready; still use active-runner dry-runs before writes"
|
||
: "scheduler auth is missing for preflight admission only; this does not prove the active runner/dev container cannot create or comment PRs",
|
||
},
|
||
schedulerPreflight: {
|
||
scope: tokenCoverage?.scope ?? "scheduler-runner-env",
|
||
authReady: schedulerAuthReady,
|
||
authSource: tokenCoverage?.source ?? null,
|
||
credentialSource: tokenCoverage?.credentialSource ?? null,
|
||
missing: Array.isArray(tokenCoverage?.missing) ? tokenCoverage.missing.map(String) : [],
|
||
failureKind: schedulerAuthReady ? null : failureKind,
|
||
degradedReason: schedulerAuthReady ? null : degradedReason,
|
||
authBroker: authBroker === null ? null : {
|
||
ok: authBroker.ok ?? schedulerAuthReady,
|
||
source: authBroker.source ?? null,
|
||
configured: authBroker.configured ?? null,
|
||
needed: authBroker.needed ?? !schedulerAuthReady,
|
||
nextAction: authBroker.nextAction ?? null,
|
||
capabilitySource: authBrokerCapability?.source ?? tokenCoverage?.credentialSource ?? null,
|
||
},
|
||
},
|
||
activeRunnerPrCapability: {
|
||
scope: activeRunnerDevContainer.scope ?? "current-cli-process",
|
||
tokenCandidatePresent: activeRunnerTokenCandidatePresent,
|
||
credentialSource: activeRunnerDevContainer.credentialSource ?? null,
|
||
ghTokenPresent: activeRunnerDevContainer.ghTokenPresent ?? false,
|
||
githubTokenPresent: activeRunnerDevContainer.githubTokenPresent ?? false,
|
||
independentOfSchedulerPreflight: activeRunnerDevContainer.notEquivalentToSchedulerEnv ?? true,
|
||
verifyCommands: {
|
||
authStatus: activeCommands.authStatus ?? commands.verifyActiveRunnerAuth,
|
||
prCreateDryRun: activeCommands.prCreateDryRun ?? commands.verifyActiveRunnerPrCreateDryRun,
|
||
},
|
||
},
|
||
prCapability: capability === null ? null : {
|
||
targetBranch: capability.targetBranch ?? "master",
|
||
sourceBranch: expectedPrHandoff?.sourceBranch ?? prCreateDryRun?.headBranch ?? null,
|
||
unideskGhCliOk: unideskGhCli?.ok ?? null,
|
||
systemGhBinaryRequiredForWrites: capability.systemGhBinaryRequiredForWrites ?? false,
|
||
realPrCreateRequiresCommanderAuthorization: capability.realPrCreateRequiresCommanderAuthorization ?? true,
|
||
pushDryRun: pushDryRun === null ? null : {
|
||
requested: pushDryRun.requested ?? false,
|
||
writesRemote: pushDryRun.writesRemote ?? false,
|
||
commandShape: pushDryRun.commandShape ?? null,
|
||
},
|
||
prCreateDryRun: prCreateDryRun === null ? null : {
|
||
requested: prCreateDryRun.requested ?? false,
|
||
writesRemote: prCreateDryRun.writesRemote ?? false,
|
||
commandShape: prCreateDryRun.commandShape ?? null,
|
||
},
|
||
mergeSupported: unsupportedMergeBoundary?.supported ?? false,
|
||
mergeCommand: unsupportedMergeBoundary?.command ?? "bun scripts/cli.ts gh pr merge <number> --repo pikasTech/unidesk",
|
||
},
|
||
authScopeSummary: record.authScopeSummary ?? prPreflightAuthScopeSummary(tokenCoverage, activeRunnerDevContainer),
|
||
scopeBoundary: record.scopeBoundary ?? prPreflightScopeBoundary(tokenCoverage),
|
||
recommendedActions: compactRecommendedActions(record),
|
||
upstream: record.upstream ?? null,
|
||
controlPlane: record.controlPlane ?? null,
|
||
observationGap: record.observationGap ?? undefined,
|
||
localObservationGap: record.localObservationGap ?? undefined,
|
||
localObservationSummary: record.localObservationSummary ?? undefined,
|
||
remoteObservationSummary: record.remoteObservationSummary ?? undefined,
|
||
commands,
|
||
disclosure: {
|
||
defaultView: "commander-compact",
|
||
fullDetailOmitted: true,
|
||
fullObservationsOmitted: asRecord(record.disclosure)?.fullObservationsOmitted ?? true,
|
||
expandWith: commands.fullDetail,
|
||
rawProxy: commands.rawProxy,
|
||
detailFieldsOmitted: ["preflight", "tools", "agentPorts", "git", "egress", "remote", "limitations", "risks", "rawRuntimePreflight"],
|
||
},
|
||
};
|
||
}
|
||
|
||
function decoratePrPreflightScopeBoundary(record: Record<string, unknown>): Record<string, unknown> {
|
||
const preflight = asRecord(record.preflight);
|
||
const tokenCoverage = prPreflightTokenCoverage(record);
|
||
const scopeBoundary = prPreflightScopeBoundary(tokenCoverage);
|
||
const activeRunnerDevContainer = activeRunnerDevContainerCapability();
|
||
const authScopeSummary = prPreflightAuthScopeSummary(tokenCoverage, activeRunnerDevContainer);
|
||
const recommendedActions = prPreflightRecommendedActions(tokenCoverage);
|
||
return {
|
||
...record,
|
||
authScopeSummary,
|
||
scopeBoundary,
|
||
activeRunnerDevContainer,
|
||
recommendedActions,
|
||
...(preflight === null ? {} : { preflight: { ...preflight, authScopeSummary, scopeBoundary, recommendedActions } }),
|
||
};
|
||
}
|
||
|
||
function prPreflightObservationGap(kind: Exclude<CodeQueueObservationGapKind, null>, detail: {
|
||
reason: string;
|
||
localBackendCoreMissing?: boolean;
|
||
remoteFallbackUsed?: boolean;
|
||
remoteControlPlaneReachable?: boolean | null;
|
||
}): Record<string, unknown> {
|
||
const controlPlaneGap = kind === "control-plane-observation-gap";
|
||
return {
|
||
kind,
|
||
blockingDisposition: kind,
|
||
scope: controlPlaneGap ? "control-plane" : "runner-local",
|
||
reason: detail.reason,
|
||
localBackendCoreMissing: detail.localBackendCoreMissing === true,
|
||
remoteFallbackUsed: detail.remoteFallbackUsed === true,
|
||
remoteControlPlaneReachable: detail.remoteControlPlaneReachable ?? null,
|
||
schedulerStoppage: false,
|
||
schedulerStateMachineChanged: false,
|
||
recommendedAction: controlPlaneGap ? "cross-check-control-plane" : "retry-from-control-plane-or-remote-fallback",
|
||
note: controlPlaneGap
|
||
? "The control-plane observation path is unavailable; this is not evidence that the scheduler stopped executing active tasks."
|
||
: "The current runner/local backend-core observation path is unavailable; this is not evidence that active Code Queue execution stopped.",
|
||
};
|
||
}
|
||
|
||
function prPreflightObservationSummary(record: Record<string, unknown> | null): Record<string, unknown> | null {
|
||
if (record === null) return null;
|
||
const preflight = asRecord(record.preflight);
|
||
const schedulerPreflight = asRecord(record.schedulerPreflight);
|
||
const tokenCoverage = asRecord(record.tokenCoverage)
|
||
?? asRecord(preflight?.tokenCoverage)
|
||
?? (schedulerPreflight === null ? null : {
|
||
ok: schedulerPreflight.authReady ?? null,
|
||
source: schedulerPreflight.authSource ?? null,
|
||
credentialSource: schedulerPreflight.credentialSource ?? null,
|
||
scope: schedulerPreflight.scope ?? null,
|
||
missing: Array.isArray(schedulerPreflight.missing) && schedulerPreflight.missing.length > 0
|
||
? schedulerPreflight.missing.map(String)
|
||
: schedulerPreflight.authReady === false
|
||
? ["GH_TOKEN", "GITHUB_TOKEN"]
|
||
: [],
|
||
});
|
||
const controlPlane = asRecord(record.controlPlane);
|
||
const targetStack = asRecord(record.targetStack);
|
||
return {
|
||
ok: record.ok ?? null,
|
||
runnerDisposition: record.runnerDisposition ?? null,
|
||
failureKind: record.failureKind ?? null,
|
||
degradedReason: record.degradedReason ?? null,
|
||
controlPlane: controlPlane === null ? null : {
|
||
mode: controlPlane.mode ?? null,
|
||
localBackendCoreMissing: controlPlane.localBackendCoreMissing ?? null,
|
||
remoteFallbackUsed: controlPlane.remoteFallbackUsed ?? null,
|
||
},
|
||
targetStack: targetStack === null ? null : {
|
||
missingContainers: Array.isArray(targetStack.missingContainers) ? targetStack.missingContainers.map(String) : [],
|
||
verifyOnlyObserved: targetStack.verifyOnlyObserved ?? false,
|
||
},
|
||
tokenCoverage: tokenCoverage === null ? null : {
|
||
ok: tokenCoverage.ok ?? null,
|
||
source: tokenCoverage.source ?? null,
|
||
credentialSource: tokenCoverage.credentialSource ?? null,
|
||
scope: tokenCoverage.scope ?? null,
|
||
missing: Array.isArray(tokenCoverage.missing) ? tokenCoverage.missing.map(String) : [],
|
||
},
|
||
};
|
||
}
|
||
|
||
function maybeFullPrPreflightObservations(options: CodexPrPreflightOptions, localRecord: Record<string, unknown>, remoteRecord: Record<string, unknown>): Record<string, unknown> {
|
||
if (options.full) {
|
||
return {
|
||
localObservation: localRecord,
|
||
remoteObservation: remoteRecord,
|
||
};
|
||
}
|
||
return {
|
||
localObservationSummary: prPreflightObservationSummary(localRecord),
|
||
remoteObservationSummary: prPreflightObservationSummary(remoteRecord),
|
||
disclosure: {
|
||
fullObservationsOmitted: true,
|
||
expandWith: "bun scripts/cli.ts codex pr-preflight --remote --full",
|
||
},
|
||
};
|
||
}
|
||
|
||
function compactPrRuntimePreflight(preflight: Record<string, unknown>, options: CodexPrPreflightOptions): Record<string, unknown> {
|
||
const pull = asRecord(preflight.pullRequestDelivery) ?? {};
|
||
const skills = compactSkillsStatus(preflight.skills);
|
||
const skillsSync = compactSkillsSyncStatus(preflight.skillsSync, options.full);
|
||
const tools = asRecord(pull.tools) ?? {};
|
||
const unideskGhCli = compactUniDeskGhCliStatus(pull.unideskGhCli);
|
||
const authBrokerRuntime = compactAuthBrokerRuntimeStatus(pull.authBroker);
|
||
const systemGhBinary = compactToolStatus(tools.gh);
|
||
const credentials = asRecord(pull.credentials) ?? {};
|
||
const git = asRecord(pull.git) ?? {};
|
||
const githubContext = asRecord(pull.githubContext) ?? {};
|
||
const egress = asRecord(pull.egress) ?? {};
|
||
const proxy = asRecord(egress.proxy) ?? {};
|
||
const remote = asRecord(pull.remote);
|
||
const ports = asRecord(preflight.ports) ?? {};
|
||
const tokenCoverage = tokenCoverageStatus(credentials, authBrokerRuntime);
|
||
const limitations = Array.isArray(pull.limitations) ? pull.limitations.map(String) : [];
|
||
const risks = Array.isArray(pull.risks) ? pull.risks.map(String) : [];
|
||
const ok = preflight.ok === true && tokenCoverage.ok === true;
|
||
const githubTransient = githubTransientEvidence(pull);
|
||
const skillsBlocked = skills !== null && skills.ok !== true;
|
||
const skillsSyncBlocked = skillsSync !== null && skillsSync.ok !== true;
|
||
const failureKind = !tokenCoverage.ok
|
||
? "auth-missing"
|
||
: skillsBlocked || skillsSyncBlocked
|
||
? "runner-skills-blocker"
|
||
: githubTransient !== null
|
||
? "github-transient"
|
||
: limitations.some((item) => item.includes("git ls-remote") || item.includes("git push --dry-run failed"))
|
||
? "git-remote-gap"
|
||
: !preflight.ok
|
||
? "proxy-gap"
|
||
: null;
|
||
const degradedReason = failureKind === "auth-missing"
|
||
? "auth-broker-needed"
|
||
: failureKind === "runner-skills-blocker"
|
||
? typeof skillsSync?.blocker === "string" ? skillsSync.blocker : typeof skills?.blocker === "string" ? skills.blocker : "runner-skills-degraded"
|
||
: failureKind === "github-transient"
|
||
? "github-dns-api-transient"
|
||
: failureKind === "git-remote-gap"
|
||
? "git remote probe failed"
|
||
: failureKind === "proxy-gap"
|
||
? limitations.find((item) => item.includes("proxy") || item.includes("auth") || item.includes("egress") || item.includes("reachable")) ?? null
|
||
: null;
|
||
const defaultPushDryRunRef = "refs/heads/probe/code-queue-pr-capability-dryrun";
|
||
const pushDryRunRef = options.pushDryRunRef ?? defaultPushDryRunRef;
|
||
const targetBranch = "master";
|
||
const expectedHeadBranch = options.prCreateDryRunHead ?? (typeof git.branch === "string" && git.branch.length > 0 ? git.branch : "<head-branch>");
|
||
const scopeBoundary = prPreflightScopeBoundary(tokenCoverage);
|
||
const activeRunnerDevContainer = activeRunnerDevContainerCapability();
|
||
const authScopeSummary = prPreflightAuthScopeSummary(tokenCoverage, activeRunnerDevContainer);
|
||
const recommendedActions = prPreflightRecommendedActions(tokenCoverage);
|
||
const result: Record<string, unknown> = {
|
||
ok,
|
||
failureKind,
|
||
degradedReason,
|
||
checkedAt: preflight.checkedAt ?? pull.checkedAt ?? null,
|
||
runner: {
|
||
serviceId: "code-queue",
|
||
plane: "D601 k3s scheduler/runner",
|
||
queueScope: "all queues executed by the scheduler, including default",
|
||
cwd: preflight.cwd ?? null,
|
||
pid: preflight.pid ?? null,
|
||
},
|
||
skills,
|
||
skillsSync,
|
||
tokenCoverage,
|
||
authBroker: authBrokerNeededStatus(tokenCoverage, authBrokerRuntime, systemGhBinary, unideskGhCli),
|
||
authScopeSummary,
|
||
scopeBoundary,
|
||
recommendedActions,
|
||
prCapabilityContract: {
|
||
targetBranch,
|
||
tokenSource: tokenCoverage.source,
|
||
authSource: tokenCoverage.credentialSource,
|
||
realPrCreateRequiresCommanderAuthorization: true,
|
||
systemGhBinaryRequiredForWrites: false,
|
||
unideskGhCli,
|
||
authBroker: authBrokerRuntime,
|
||
pushDryRun: {
|
||
requested: options.pushDryRun,
|
||
ref: pushDryRunRef,
|
||
writesRemote: false,
|
||
commandShape: `git push --dry-run origin HEAD:${pushDryRunRef}`,
|
||
},
|
||
prCreateDryRun: {
|
||
requested: options.prCreateDryRun,
|
||
headBranch: expectedHeadBranch,
|
||
writesRemote: false,
|
||
commandShape: `bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --base ${targetBranch} --head ${expectedHeadBranch} --dry-run`,
|
||
},
|
||
expectedPrHandoff: {
|
||
sourceBranch: expectedHeadBranch,
|
||
targetBranch,
|
||
runnerCreatesPrAfterAuthorization: true,
|
||
commanderReviewsAndMerges: true,
|
||
preflightCreatesPr: false,
|
||
preflightMergesPr: false,
|
||
},
|
||
unsupportedMergeBoundary: {
|
||
supported: false,
|
||
command: "bun scripts/cli.ts gh pr merge <number> --repo pikasTech/unidesk",
|
||
degradedReason: "unsupported-command",
|
||
runnerDisposition: "business-failed",
|
||
note: "UniDesk CLI intentionally does not merge PRs in this phase; runner handoff stops at PR creation and evidence.",
|
||
},
|
||
},
|
||
controlPlane: {
|
||
mode: "local-backend-core",
|
||
localBackendCoreMissing: false,
|
||
remoteFallbackUsed: false,
|
||
},
|
||
tools: {
|
||
git: compactToolStatus(tools.git),
|
||
gh: systemGhBinary,
|
||
systemGhBinary,
|
||
hub: compactToolStatus(tools.hub),
|
||
jq: compactToolStatus(tools.jq),
|
||
ssh: compactToolStatus(tools.ssh),
|
||
curl: compactToolStatus(tools.curl),
|
||
unideskGhCli,
|
||
},
|
||
agentPorts: {
|
||
codex: compactAgentPortStatus(ports.codex),
|
||
opencode: compactAgentPortStatus(ports.opencode),
|
||
},
|
||
git: {
|
||
insideWorktree: git.insideWorktree ?? false,
|
||
branch: git.branch ?? null,
|
||
head: git.head ?? null,
|
||
originMaster: git.originMaster ?? null,
|
||
remoteOrigin: git.remoteOrigin ?? null,
|
||
home: git.home ?? null,
|
||
homeWritable: git.homeWritable ?? false,
|
||
knownHostsPresent: git.knownHostsPresent ?? false,
|
||
privateKeyPresent: git.privateKeyPresent ?? false,
|
||
},
|
||
githubContext: {
|
||
host: githubContext.host ?? null,
|
||
apiBaseUrl: githubContext.apiBaseUrl ?? null,
|
||
repo: githubContext.repo ?? null,
|
||
issueProbeNumber: githubContext.issueProbeNumber ?? null,
|
||
},
|
||
egress: {
|
||
proxy: {
|
||
selectedProxyHost: proxy.selectedProxyHost ?? null,
|
||
selectedProxyPort: proxy.selectedProxyPort ?? null,
|
||
selectedProxyHostResolvable: proxy.selectedProxyHostResolvable ?? null,
|
||
},
|
||
githubDefault: compactCommandProbe(egress.githubDefault),
|
||
apiDefault: compactCommandProbe(egress.apiDefault),
|
||
issueApi: compactCommandProbe(egress.issueApi),
|
||
},
|
||
remote: remote === null ? null : {
|
||
gitLsRemote: compactCommandProbe(remote.gitLsRemote),
|
||
gitHttpsLsRemote: compactCommandProbe(remote.gitHttpsLsRemote),
|
||
githubSshAuthenticated: remote.githubSshAuthenticated ?? false,
|
||
ghAuthStatus: compactCommandProbe(remote.ghAuthStatus),
|
||
ghRepoView: compactCommandProbe(remote.ghRepoView),
|
||
ghIssueView: compactCommandProbe(remote.ghIssueView),
|
||
ghPrReadOnly: compactCommandProbe(remote.ghPrReadOnly),
|
||
},
|
||
pushDryRun: compactCommandProbe(pull.pushDryRun),
|
||
prCreateDryRun: compactCommandProbe(pull.prCreateDryRun),
|
||
limitations,
|
||
risks,
|
||
runnerDisposition: ok ? "ready" : "infra-blocked",
|
||
...(githubTransient === null ? {} : {
|
||
retryable: true,
|
||
commanderAction: "retry-backoff-or-keep-running-if-heartbeat-fresh",
|
||
githubTransient,
|
||
}),
|
||
activeRunnerDevContainer,
|
||
recoveryHint: ok
|
||
? tokenCoverage.source === "auth-broker"
|
||
? "Runner PR workflow has auth-broker coverage for GitHub REST preflight; real PR creation still requires commander authorization."
|
||
: "Runner PR workflow has env-token coverage for the scheduler."
|
||
: githubTransient !== null
|
||
? "GitHub DNS/API connectivity failed transiently. Retry with backoff; if the task heartbeat or trace is fresh, keep supervising the running task instead of closing or requeueing business work."
|
||
: "Scheduler preflight lacks GitHub auth coverage for scheduler-scoped admission only. First verify the active task with repo-native gh auth status and PR create dry-run; long-term configure auth-broker or inject GH_TOKEN/GITHUB_TOKEN into the scheduler runtime secret.",
|
||
commands: {
|
||
local: "bun scripts/cli.ts gh auth status --repo pikasTech/unidesk",
|
||
runner: "bun scripts/cli.ts codex pr-preflight --remote",
|
||
runnerPushDryRun: "bun scripts/cli.ts codex pr-preflight --remote --push-dry-run --push-dry-run-ref refs/heads/probe/code-queue-pr-capability",
|
||
runnerPrCreateDryRun: "bun scripts/cli.ts codex pr-preflight --remote --pr-create-dry-run --pr-create-dry-run-head <head-branch>",
|
||
rawProxy: "bun scripts/cli.ts microservice proxy code-queue /api/runtime-preflight?remote=1 --raw",
|
||
},
|
||
};
|
||
if (options.full) result.rawRuntimePreflight = preflight;
|
||
return result;
|
||
}
|
||
|
||
function queryRemoteMainServerPrPreflight(optionArgs: string[], config: UniDeskConfig): unknown {
|
||
const command = ["bun", "scripts/cli.ts", "--main-server-ip", config.network.publicHost, "codex", "pr-preflight", ...optionArgs];
|
||
const result = runCommand(command, repoRoot, { timeoutMs: 120_000 });
|
||
const parsed = parseJsonRecord(result.stdout);
|
||
if (parsed !== null) {
|
||
const data = asRecord(parsed.data);
|
||
const dataResult = asRecord(data?.result);
|
||
if (dataResult !== null) return dataResult;
|
||
if (data !== null) return data;
|
||
const error = asRecord(parsed.error);
|
||
if (error !== null) {
|
||
const message = typeof error.message === "string" && error.message.length > 0
|
||
? error.message
|
||
: result.stderr.trim() || result.stdout.trim() || "remote control plane unreachable";
|
||
return {
|
||
ok: false,
|
||
runnerDisposition: "infra-blocked",
|
||
blockingDisposition: "control-plane-observation-gap",
|
||
failureKind: "control-plane-missing",
|
||
degradedReason: "remote-control-plane-unreachable",
|
||
message,
|
||
observationGap: prPreflightObservationGap("control-plane-observation-gap", {
|
||
reason: "remote control plane CLI returned a structured error",
|
||
localBackendCoreMissing: false,
|
||
remoteFallbackUsed: true,
|
||
remoteControlPlaneReachable: false,
|
||
}),
|
||
controlPlane: {
|
||
mode: "remote-frontend",
|
||
host: config.network.publicHost,
|
||
frontendUrl: `http://${config.network.publicHost}:${config.network.frontend.port}`,
|
||
localBackendCoreMissing: false,
|
||
remoteFallbackUsed: true,
|
||
},
|
||
observed: {
|
||
exitCode: result.exitCode,
|
||
stdoutTail: result.stdout.slice(-2000),
|
||
stderrTail: result.stderr.slice(-2000),
|
||
},
|
||
commands: {
|
||
retry: `bun scripts/cli.ts --main-server-ip ${config.network.publicHost} codex pr-preflight --remote`,
|
||
},
|
||
};
|
||
}
|
||
}
|
||
const message = result.stderr.trim() || result.stdout.trim() || `remote control plane unreachable: exitCode=${result.exitCode ?? "null"}`;
|
||
return {
|
||
ok: false,
|
||
runnerDisposition: "infra-blocked",
|
||
blockingDisposition: "control-plane-observation-gap",
|
||
failureKind: "control-plane-missing",
|
||
degradedReason: "remote-control-plane-unreachable",
|
||
message,
|
||
observationGap: prPreflightObservationGap("control-plane-observation-gap", {
|
||
reason: "remote control plane CLI could not be reached or returned non-JSON output",
|
||
localBackendCoreMissing: false,
|
||
remoteFallbackUsed: true,
|
||
remoteControlPlaneReachable: false,
|
||
}),
|
||
controlPlane: {
|
||
mode: "remote-frontend",
|
||
host: config.network.publicHost,
|
||
frontendUrl: `http://${config.network.publicHost}:${config.network.frontend.port}`,
|
||
localBackendCoreMissing: false,
|
||
remoteFallbackUsed: true,
|
||
},
|
||
observed: {
|
||
exitCode: result.exitCode,
|
||
stdoutTail: result.stdout.slice(-2000),
|
||
stderrTail: result.stderr.slice(-2000),
|
||
},
|
||
commands: {
|
||
retry: `bun scripts/cli.ts --main-server-ip ${config.network.publicHost} codex pr-preflight --remote`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function codeQueuePrPreflight(optionArgs: string[] = [], transport: CodeQueuePrPreflightTransport = {}): unknown {
|
||
const options = parsePrPreflightOptions(optionArgs);
|
||
const config = transport.config ?? null;
|
||
const fetcher = transport.coreFetch ?? coreInternalFetch;
|
||
const path = codeQueueProxyPath(`/api/runtime-preflight${queryString({
|
||
remote: options.remote ? 1 : undefined,
|
||
pushDryRun: options.pushDryRun ? 1 : undefined,
|
||
pushDryRunRef: options.pushDryRunRef,
|
||
prCreateDryRun: options.prCreateDryRun ? 1 : undefined,
|
||
prCreateDryRunHead: options.prCreateDryRunHead,
|
||
issue: options.issueNumber,
|
||
})}`);
|
||
const localResponse = fetcher(path);
|
||
const localRecord = asRecord(localResponse);
|
||
const localTargetStackMissing = localRecord?.ok === false
|
||
&& localRecord.failureKind === "target-stack-not-running"
|
||
&& localRecord.degradedReason === "backend-core-container-missing";
|
||
const remoteMainServerPrPreflight = transport.remoteMainServerPrPreflight
|
||
?? (config === null ? null : (args: string[], _config: UniDeskConfig | null) => queryRemoteMainServerPrPreflight(args, config));
|
||
if (options.remote && localTargetStackMissing && remoteMainServerPrPreflight !== null) {
|
||
const remoteResponse = remoteMainServerPrPreflight(optionArgs, config);
|
||
const remoteRecord = asRecord(remoteResponse);
|
||
if (remoteRecord !== null) {
|
||
if (remoteRecord.ok === false) {
|
||
return compactPrPreflightCommanderView(decoratePrPreflightScopeBoundary({
|
||
...remoteRecord,
|
||
observationGap: prPreflightObservationGap(
|
||
remoteRecord.failureKind === "control-plane-missing" ? "control-plane-observation-gap" : "runner-local-observation-gap",
|
||
{
|
||
reason: remoteRecord.failureKind === "control-plane-missing"
|
||
? "remote control plane could not be observed after local backend-core target-stack absence"
|
||
: "local backend-core target-stack absence was bypassed by remote fallback, but the remote result still failed",
|
||
localBackendCoreMissing: true,
|
||
remoteFallbackUsed: true,
|
||
remoteControlPlaneReachable: remoteRecord.failureKind === "control-plane-missing" ? false : true,
|
||
},
|
||
),
|
||
blockingDisposition: remoteRecord.failureKind === "control-plane-missing" ? "control-plane-observation-gap" : remoteRecord.blockingDisposition ?? remoteRecord.runnerDisposition ?? "infra-blocked",
|
||
controlPlane: {
|
||
...(asRecord(remoteRecord.controlPlane) ?? {}),
|
||
mode: "remote-frontend",
|
||
host: config?.network.publicHost ?? null,
|
||
frontendUrl: config === null ? null : `http://${config.network.publicHost}:${config.network.frontend.port}`,
|
||
localBackendCoreMissing: true,
|
||
remoteFallbackUsed: true,
|
||
},
|
||
...maybeFullPrPreflightObservations(options, localRecord, remoteRecord),
|
||
}), options);
|
||
}
|
||
return compactPrPreflightCommanderView(decoratePrPreflightScopeBoundary({
|
||
...remoteRecord,
|
||
localObservationGap: prPreflightObservationGap("runner-local-observation-gap", {
|
||
reason: "local backend-core target-stack absence was bypassed by healthy remote control-plane fallback",
|
||
localBackendCoreMissing: true,
|
||
remoteFallbackUsed: true,
|
||
remoteControlPlaneReachable: true,
|
||
}),
|
||
controlPlane: {
|
||
...(asRecord(remoteRecord.controlPlane) ?? {}),
|
||
mode: "remote-frontend",
|
||
host: config?.network.publicHost ?? null,
|
||
frontendUrl: config === null ? null : `http://${config.network.publicHost}:${config.network.frontend.port}`,
|
||
localBackendCoreMissing: true,
|
||
remoteFallbackUsed: true,
|
||
},
|
||
...maybeFullPrPreflightObservations(options, localRecord, remoteRecord),
|
||
}), options);
|
||
}
|
||
}
|
||
if (localRecord?.ok !== true) {
|
||
if (options.remote && localTargetStackMissing) {
|
||
const failureKind: CodeQueuePrPreflightFailureKind = "control-plane-missing";
|
||
const degradedReason = "remote-control-plane-unreachable";
|
||
return {
|
||
...(localRecord ?? {}),
|
||
ok: false,
|
||
runnerDisposition: "infra-blocked",
|
||
blockingDisposition: "control-plane-observation-gap",
|
||
failureKind,
|
||
degradedReason,
|
||
message: "remote control plane unreachable; local backend-core target-stack absence is observation-gap evidence only",
|
||
observationGap: prPreflightObservationGap("control-plane-observation-gap", {
|
||
reason: "local backend-core target stack is missing and no remote control plane could be observed",
|
||
localBackendCoreMissing: true,
|
||
remoteFallbackUsed: false,
|
||
remoteControlPlaneReachable: false,
|
||
}),
|
||
controlPlane: {
|
||
mode: "local-backend-core",
|
||
localBackendCoreMissing: true,
|
||
remoteFallbackUsed: false,
|
||
},
|
||
commands: {
|
||
retry: config !== null
|
||
? `bun scripts/cli.ts --main-server-ip ${config.network.publicHost} codex pr-preflight --remote`
|
||
: "bun scripts/cli.ts codex pr-preflight --remote",
|
||
local: "bun scripts/cli.ts microservice proxy code-queue /api/runtime-preflight --raw",
|
||
},
|
||
};
|
||
}
|
||
return {
|
||
...(localRecord ?? {}),
|
||
ok: false,
|
||
runnerDisposition: localRecord?.runnerDisposition ?? "infra-blocked",
|
||
blockingDisposition: localTargetStackMissing ? "runner-local-observation-gap" : localRecord?.blockingDisposition ?? localRecord?.runnerDisposition ?? "infra-blocked",
|
||
failureKind: (localRecord?.failureKind as CodeQueuePrPreflightFailureKind | undefined) ?? "proxy-gap",
|
||
degradedReason: localRecord?.degradedReason ?? "backend-core-proxy-unavailable",
|
||
message: localRecord?.message ?? localRecord?.stderrTail ?? localRecord?.stdoutTail ?? "Code Queue runtime preflight could not be observed",
|
||
...(localTargetStackMissing
|
||
? {
|
||
observationGap: prPreflightObservationGap("runner-local-observation-gap", {
|
||
reason: "local backend-core target stack is missing in this runner observation path",
|
||
localBackendCoreMissing: true,
|
||
remoteFallbackUsed: false,
|
||
remoteControlPlaneReachable: null,
|
||
}),
|
||
}
|
||
: {}),
|
||
controlPlane: {
|
||
mode: "local-backend-core",
|
||
localBackendCoreMissing: localTargetStackMissing,
|
||
remoteFallbackUsed: false,
|
||
},
|
||
commands: {
|
||
retry: config !== null
|
||
? `bun scripts/cli.ts --main-server-ip ${config.network.publicHost} codex pr-preflight --remote`
|
||
: "bun scripts/cli.ts codex pr-preflight --remote",
|
||
local: "bun scripts/cli.ts microservice proxy code-queue /api/runtime-preflight --raw",
|
||
},
|
||
};
|
||
}
|
||
const response = asRecord(localRecord);
|
||
if (response?.ok !== true) {
|
||
throw new Error(upstreamError(localRecord));
|
||
}
|
||
const body = asRecord(response.body);
|
||
const preflight = asRecord(body?.runtimePreflight);
|
||
if (preflight === null) {
|
||
return {
|
||
ok: false,
|
||
runnerDisposition: "infra-blocked",
|
||
failureKind: "proxy-gap",
|
||
degradedReason: "runtime-preflight-missing",
|
||
message: "Code Queue runtime-preflight response did not include runtimePreflight",
|
||
controlPlane: {
|
||
mode: "local-backend-core",
|
||
localBackendCoreMissing: false,
|
||
remoteFallbackUsed: false,
|
||
},
|
||
upstream: { ok: response.ok, status: response.status },
|
||
commands: {
|
||
retry: "bun scripts/cli.ts codex pr-preflight --remote",
|
||
},
|
||
};
|
||
}
|
||
const compact = compactPrRuntimePreflight(preflight, options);
|
||
return compactPrPreflightCommanderView({
|
||
ok: compact.ok,
|
||
runnerDisposition: compact.runnerDisposition,
|
||
failureKind: compact.failureKind ?? null,
|
||
degradedReason: compact.degradedReason ?? null,
|
||
...(compact.retryable === true ? { retryable: true } : {}),
|
||
...(typeof compact.commanderAction === "string" ? { commanderAction: compact.commanderAction } : {}),
|
||
...(asRecord(compact.githubTransient) === null ? {} : { githubTransient: compact.githubTransient }),
|
||
authScopeSummary: compact.authScopeSummary,
|
||
scopeBoundary: compact.scopeBoundary,
|
||
activeRunnerDevContainer: compact.activeRunnerDevContainer,
|
||
recommendedActions: compact.recommendedActions,
|
||
upstream: { ok: response.ok, status: response.status },
|
||
controlPlane: {
|
||
mode: "local-backend-core",
|
||
localBackendCoreMissing: false,
|
||
remoteFallbackUsed: false,
|
||
},
|
||
preflight: compact,
|
||
}, options);
|
||
}
|
||
|
||
export function codexPrPreflightQueryForTest(optionArgs: string[], transport: CodeQueuePrPreflightTransport = {}): unknown {
|
||
return codeQueuePrPreflight(optionArgs, transport);
|
||
}
|
||
|
||
export async function codexPrPreflightQueryAsync(optionArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||
const options = parsePrPreflightOptions(optionArgs);
|
||
const path = codeQueueProxyPath(`/api/runtime-preflight${queryString({
|
||
remote: options.remote ? 1 : undefined,
|
||
pushDryRun: options.pushDryRun ? 1 : undefined,
|
||
pushDryRunRef: options.pushDryRunRef,
|
||
prCreateDryRun: options.prCreateDryRun ? 1 : undefined,
|
||
prCreateDryRunHead: options.prCreateDryRunHead,
|
||
issue: options.issueNumber,
|
||
})}`);
|
||
const response = asRecord(await fetcher(path));
|
||
if (response?.ok !== true) throw new Error(upstreamError(response));
|
||
const body = asRecord(response.body);
|
||
const preflight = asRecord(body?.runtimePreflight);
|
||
if (preflight === null) {
|
||
return {
|
||
ok: false,
|
||
runnerDisposition: "infra-blocked",
|
||
failureKind: "proxy-gap",
|
||
degradedReason: "runtime-preflight-missing",
|
||
message: "Code Queue runtime-preflight response did not include runtimePreflight",
|
||
controlPlane: {
|
||
mode: "remote-frontend",
|
||
localBackendCoreMissing: false,
|
||
remoteFallbackUsed: false,
|
||
},
|
||
upstream: { ok: response.ok, status: response.status },
|
||
commands: {
|
||
retry: "bun scripts/cli.ts codex pr-preflight --remote",
|
||
},
|
||
};
|
||
}
|
||
const compact = compactPrRuntimePreflight(preflight, options);
|
||
return compactPrPreflightCommanderView({
|
||
ok: compact.ok,
|
||
runnerDisposition: compact.runnerDisposition,
|
||
failureKind: compact.failureKind ?? null,
|
||
degradedReason: compact.degradedReason ?? null,
|
||
...(compact.retryable === true ? { retryable: true } : {}),
|
||
...(typeof compact.commanderAction === "string" ? { commanderAction: compact.commanderAction } : {}),
|
||
...(asRecord(compact.githubTransient) === null ? {} : { githubTransient: compact.githubTransient }),
|
||
authScopeSummary: compact.authScopeSummary,
|
||
scopeBoundary: compact.scopeBoundary,
|
||
activeRunnerDevContainer: compact.activeRunnerDevContainer,
|
||
recommendedActions: compact.recommendedActions,
|
||
upstream: { ok: response.ok, status: response.status },
|
||
controlPlane: {
|
||
mode: "remote-frontend",
|
||
localBackendCoreMissing: false,
|
||
remoteFallbackUsed: false,
|
||
},
|
||
preflight: compact,
|
||
}, options);
|
||
}
|
||
|
||
export function codexSubmitRoutingRecommendationForTest(prompt: string, model?: string): SubmitRoutingRecommendation {
|
||
return submitRoutingRecommendation({
|
||
prompt,
|
||
model,
|
||
queueId: undefined,
|
||
providerId: undefined,
|
||
cwd: undefined,
|
||
reasoningEffort: undefined,
|
||
executionMode: undefined,
|
||
maxAttempts: undefined,
|
||
referenceTaskIds: [],
|
||
dryRun: true,
|
||
});
|
||
}
|
||
|
||
export function codexPromptLiveAuthorizationLintForTest(prompt: string): PromptLiveAuthorizationLint {
|
||
return buildPromptLiveAuthorizationLint(prompt);
|
||
}
|
||
|
||
export function codexSubmitModelRegistryForTest(models: string[] = sharedDefaultCodeModels): ReturnType<typeof submitModelRegistry> {
|
||
return submitModelRegistry(models);
|
||
}
|
||
|
||
function codexPromptLintTask(args: string[]): unknown {
|
||
const options = parsePromptLintOptions(args);
|
||
return buildPromptLiveAuthorizationLint(options.prompt);
|
||
}
|
||
|
||
function codexSubmitTask(args: string[]): unknown {
|
||
const options = parseSubmitOptions(args);
|
||
const payload = submitPayload(options);
|
||
const promptLint = buildPromptLiveAuthorizationLint(options.prompt);
|
||
if (options.dryRun) {
|
||
return {
|
||
ok: true,
|
||
dryRun: true,
|
||
promptLint,
|
||
executionMode: executionModeSummary(options.executionMode),
|
||
runnerPermissions: dryRunRunnerPermissionsSummary(),
|
||
routingRecommendation: submitRoutingRecommendation(options),
|
||
modelRegistry: submitModelRegistry(),
|
||
request: {
|
||
...payload,
|
||
prompt: textView(options.prompt, true, 3000),
|
||
},
|
||
commands: {
|
||
submitAsRequested: "remove --dry-run to submit exactly this payload",
|
||
minimaxCandidate: `bun scripts/cli.ts codex submit --prompt-file <path> --model ${minimaxSubmitModel} --dry-run`,
|
||
deepseekCandidate: `bun scripts/cli.ts codex submit --prompt-file <path> --model ${deepseekSubmitModel} --dry-run`,
|
||
gptCandidate: `bun scripts/cli.ts codex submit --prompt-file <path> --model ${gptSubmitModel} --dry-run`,
|
||
},
|
||
};
|
||
}
|
||
const locked = runWithSubmitLock(() => unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath("/api/tasks"), { method: "POST", body: payload })));
|
||
const response = locked.result;
|
||
return compactSubmitSuccessResponse(response.body, response.upstream, locked.lock);
|
||
}
|
||
|
||
function codexInterruptTask(taskId: string): unknown {
|
||
const response = unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/interrupt`), { method: "POST" }));
|
||
return {
|
||
upstream: response.upstream,
|
||
task: compactTaskMutationResponse(response.body.task),
|
||
queue: compactQueueMutationSummary(response.body.queue),
|
||
};
|
||
}
|
||
|
||
function codexSteerTask(taskId: string, args: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||
const options = parseSteerOptions(args);
|
||
const promptLint = buildPromptLiveAuthorizationLint(options.prompt);
|
||
const steerId = options.steerId ?? createSteerId(taskId, options.prompt);
|
||
const targetPath = `/api/tasks/${encodeURIComponent(taskId)}/steer`;
|
||
const stableProxyPath = codeQueueProxyPath(targetPath);
|
||
const rawProxyEquivalent = codeQueueProxyEquivalentCommand(targetPath, `{"prompt":"...","steerId":"${steerId}"}`);
|
||
const prompt = textView(options.prompt, false, steerPromptPreviewChars);
|
||
const request = {
|
||
path: targetPath,
|
||
stableProxyPath,
|
||
method: "POST",
|
||
steerId,
|
||
retryPolicy: {
|
||
defaultAttempts: defaultSteerRetryAttempts,
|
||
maxAttempts: options.retryAttempts,
|
||
delayMs: options.retryDelayMs,
|
||
retryableReasons: ["stable-proxy-failed", "backend-core-unreachable"],
|
||
deliveryConfirmation: "success confirms Code Queue accepted the steer request; timeout-like failures trigger a bounded trace confirmation lookup by steerId",
|
||
idempotency: "the same steerId is reused for every CLI retry and suppresses duplicate trace injection on a backend that supports the contract",
|
||
},
|
||
bodySummary: {
|
||
steerId,
|
||
promptChars: options.prompt.length,
|
||
promptPreviewChars: steerPromptPreviewChars,
|
||
promptTruncated: prompt.truncated,
|
||
},
|
||
body: { steerId, prompt },
|
||
};
|
||
if (options.dryRun) {
|
||
return {
|
||
ok: true,
|
||
dryRun: true,
|
||
promptLint,
|
||
request,
|
||
commands: {
|
||
run: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --steer-id ${steerId}`,
|
||
noRetry: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --steer-id ${steerId} --no-retry`,
|
||
traceConfirm: steerConfirmationCommand(taskId, steerId),
|
||
rawProxy: rawProxyEquivalent,
|
||
},
|
||
};
|
||
}
|
||
const attempts: CodexSteerAttemptSummary[] = [];
|
||
let failedResponse: { ok: false; diagnostics: ClassifiedCodexSteerError; rawResponse: unknown } | null = null;
|
||
let successfulResponse: { ok: true; upstream: { ok: unknown; status: unknown }; body: Record<string, unknown> } | null = null;
|
||
for (let attempt = 1; attempt <= options.retryAttempts; attempt += 1) {
|
||
const startedAt = Date.now();
|
||
const response = unwrapSteerResponse(fetcher(stableProxyPath, { method: "POST", body: { prompt: options.prompt, steerId } }), targetPath, stableProxyPath, rawProxyEquivalent);
|
||
const durationMs = Date.now() - startedAt;
|
||
if (response.ok) {
|
||
attempts.push(steerSuccessAttempt(attempt, durationMs, response.upstream, asString(response.body.steerId) || steerId, asString(response.body.deliveryState) as CodexSteerDeliveryState || "accepted"));
|
||
successfulResponse = response;
|
||
break;
|
||
}
|
||
attempts.push(steerFailureAttempt(attempt, durationMs, response.diagnostics));
|
||
failedResponse = response;
|
||
const terminalRejection = compactTerminalSteerRejection(taskId, steerId, response.rawResponse);
|
||
if (terminalRejection !== null) {
|
||
terminalRejection.commands = {
|
||
...(asRecord(terminalRejection.commands) ?? {}),
|
||
fullDetails: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --full`,
|
||
rawDetails: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --raw`,
|
||
};
|
||
return attachSteerDisclosure(terminalRejection, options, response.rawResponse, targetPath, stableProxyPath, rawProxyEquivalent);
|
||
}
|
||
if (!shouldRetrySteerFailure(response.diagnostics, attempt, options.retryAttempts)) break;
|
||
sleepSync(options.retryDelayMs);
|
||
}
|
||
if (successfulResponse === null) {
|
||
const diagnostics = failedResponse?.diagnostics ?? classifySteerFailure(null, targetPath, stableProxyPath, rawProxyEquivalent);
|
||
const transportDeliveryUnconfirmed = diagnostics.reason === "stable-proxy-failed" || diagnostics.reason === "backend-core-unreachable";
|
||
const traceConfirmation = transportDeliveryUnconfirmed ? safeFetchSteerTraceConfirmation(taskId, steerId, fetcher) : null;
|
||
const confirmedAccepted = asBoolean(traceConfirmation?.accepted);
|
||
const deliveryState = confirmedAccepted ? "accepted_response_timeout" : transportDeliveryUnconfirmed ? "unknown" : "not_accepted";
|
||
const acceptanceStatus: CodexSteerAcceptanceStatus = confirmedAccepted ? "accepted_response_timeout" : transportDeliveryUnconfirmed ? "unknown" : "not_accepted";
|
||
const compactDiagnostics = compactSteerFailureDiagnostics(diagnostics, options.full);
|
||
return {
|
||
ok: confirmedAccepted,
|
||
steer: {
|
||
accepted: confirmedAccepted,
|
||
status: acceptanceStatus,
|
||
deliveryState,
|
||
steerId,
|
||
promptChars: options.prompt.length,
|
||
promptOmitted: true,
|
||
attempts,
|
||
deliveryUnconfirmed: transportDeliveryUnconfirmed && !confirmedAccepted,
|
||
duplicateSuppressionKey: steerId,
|
||
retryPolicy: {
|
||
attempted: attempts.length,
|
||
maxAttempts: options.retryAttempts,
|
||
retryDelayMs: options.retryDelayMs,
|
||
retried: attempts.length > 1,
|
||
exhausted: transportDeliveryUnconfirmed && attempts.length >= options.retryAttempts,
|
||
note: "codex steer retried only retryable stable-proxy/backend-core failures; raw microservice proxy uses the same tunnel and is diagnostic, not a lower-noise fallback.",
|
||
},
|
||
},
|
||
...(options.full ? {
|
||
request: {
|
||
...request,
|
||
body: { prompt: { chars: options.prompt.length, textOmitted: true } },
|
||
},
|
||
} : {}),
|
||
diagnostics: {
|
||
...compactDiagnostics,
|
||
attempts,
|
||
retryPolicy: {
|
||
attempted: attempts.length,
|
||
maxAttempts: options.retryAttempts,
|
||
retryDelayMs: options.retryDelayMs,
|
||
retried: attempts.length > 1,
|
||
exhausted: transportDeliveryUnconfirmed && attempts.length >= options.retryAttempts,
|
||
},
|
||
operatorGuidance: {
|
||
rawProxyEquivalentIsFallback: false,
|
||
deliveryUnconfirmed: transportDeliveryUnconfirmed && !confirmedAccepted,
|
||
traceConfirmationChecked: traceConfirmation !== null,
|
||
nextStep: confirmedAccepted
|
||
? "The stable proxy response timed out, but trace confirmation found this steerId. Do not resend the same corrective prompt."
|
||
: transportDeliveryUnconfirmed
|
||
? "Run the bounded trace confirmation command before retrying. If it remains unknown, retry with the same steerId to avoid duplicate trace injection on an updated backend."
|
||
: "Inspect the non-retryable reason before resubmitting the correction.",
|
||
},
|
||
},
|
||
traceConfirmation,
|
||
commands: {
|
||
dryRun: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --steer-id ${steerId} --dry-run`,
|
||
retry: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --steer-id ${steerId}`,
|
||
noRetry: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --steer-id ${steerId} --no-retry`,
|
||
traceConfirm: steerConfirmationCommand(taskId, steerId),
|
||
fullDetails: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --steer-id ${steerId} --full`,
|
||
rawDetails: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --steer-id ${steerId} --raw`,
|
||
rawProxy: rawProxyEquivalent,
|
||
tasks: "bun scripts/cli.ts codex tasks --view supervisor --limit 20",
|
||
health: "bun scripts/cli.ts microservice health code-queue",
|
||
},
|
||
disclosure: {
|
||
defaultPolicy: "compact rejection; request prompt, upstream body preview, and raw response require explicit --full or --raw",
|
||
full: options.full,
|
||
raw: options.raw,
|
||
defaultOmitted: ["request.body.prompt.text", "diagnostics.upstreamBodyPreview", "rawFailure"],
|
||
},
|
||
...(options.raw ? { rawFailure: failedResponse?.rawResponse ?? null } : {}),
|
||
};
|
||
}
|
||
const responseSteerId = asString(successfulResponse.body.steerId) || steerId;
|
||
const traceConfirmation = compactSteerTraceConfirmation(successfulResponse.body.traceConfirmation, taskId, responseSteerId);
|
||
const deliveryState = asString(successfulResponse.body.deliveryState) || asString(traceConfirmation.deliveryState) || "accepted";
|
||
const duplicateSuppressed = successfulResponse.body.duplicateSuppressed === true;
|
||
return {
|
||
ok: true,
|
||
upstream: successfulResponse.upstream,
|
||
steer: {
|
||
accepted: true,
|
||
status: "accepted",
|
||
deliveryState,
|
||
steerId: responseSteerId,
|
||
taskId,
|
||
promptChars: options.prompt.length,
|
||
promptOmitted: true,
|
||
duplicateSuppressed,
|
||
duplicateSuppressionKey: responseSteerId,
|
||
attempts,
|
||
retryPolicy: {
|
||
attempted: attempts.length,
|
||
maxAttempts: options.retryAttempts,
|
||
retryDelayMs: options.retryDelayMs,
|
||
retried: attempts.length > 1,
|
||
},
|
||
outputPolicy: {
|
||
default: "write-confirmation",
|
||
promptEchoed: false,
|
||
taskDetailEchoed: false,
|
||
reason: "codex steer is a write operation; default output confirms delivery and provides drill-down commands without echoing prompt text or full task state.",
|
||
},
|
||
},
|
||
traceConfirmation,
|
||
task: compactSteerTaskConfirmation(successfulResponse.body.task, responseSteerId),
|
||
queue: compactSubmitQueueConfirmation(successfulResponse.body.queue),
|
||
commands: {
|
||
show: `bun scripts/cli.ts codex task ${taskId}`,
|
||
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
|
||
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
|
||
traceConfirm: steerConfirmationCommand(taskId, responseSteerId),
|
||
output: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`,
|
||
supervisor: `bun scripts/cli.ts codex tasks --view supervisor --limit ${defaultTasksLimit}`,
|
||
},
|
||
};
|
||
}
|
||
|
||
function codexSteerTraceConfirm(taskId: string, args: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||
const options = parseSteerConfirmOptions(args);
|
||
const path = steerConfirmationPath(taskId, options.steerId);
|
||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(path)));
|
||
const confirmation = compactSteerTraceConfirmation(response.body, taskId, options.steerId);
|
||
return {
|
||
ok: asBoolean(confirmation.accepted),
|
||
upstream: response.upstream,
|
||
traceConfirmation: confirmation,
|
||
delivery: {
|
||
steerId: options.steerId,
|
||
status: asBoolean(confirmation.accepted) ? "accepted" : "unknown",
|
||
deliveryState: confirmation.deliveryState,
|
||
promptOmitted: true,
|
||
},
|
||
commands: {
|
||
task: `bun scripts/cli.ts codex task ${taskId}`,
|
||
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
|
||
retrySameSteerId: `bun scripts/cli.ts codex steer ${taskId} --prompt-file <path> --steer-id ${options.steerId}`,
|
||
rawLookup: rawSteerConfirmationCommand(taskId, options.steerId),
|
||
},
|
||
...(options.raw ? { raw: response.body } : {}),
|
||
};
|
||
}
|
||
|
||
export async function runCodeQueueCommand(config: UniDeskConfig, args: string[]): Promise<unknown> {
|
||
const [action = "task", taskIdArg] = args;
|
||
if (action === "prompt-lint" || action === "lint-prompt") {
|
||
return codexPromptLintTask(args.slice(1));
|
||
}
|
||
if (action === "submit" || action === "enqueue") {
|
||
return codexSubmitTask(args.slice(1));
|
||
}
|
||
if (action === "task" || action === "summary" || action === "show") {
|
||
const taskId = requireTaskId(taskIdArg, `codex ${action}`);
|
||
return codexTaskQuery(taskId, args.slice(2));
|
||
}
|
||
if (action === "tasks" || action === "overview") {
|
||
return codexTasksQuery(args.slice(1));
|
||
}
|
||
if (action === "unread" || action === "terminal-unread") {
|
||
return codexUnreadTriage(args.slice(1));
|
||
}
|
||
if (action === "dev-ready" || action === "health") {
|
||
assertKnownOptions(args.slice(1), {}, `codex ${action}`);
|
||
return codeQueueDevReady();
|
||
}
|
||
if (action === "skills-sync") return codeQueueSkillsSync(args.slice(1));
|
||
if (action === "pr-preflight" || action === "runtime-preflight") return codeQueuePrPreflight(args.slice(1), { config });
|
||
if (action === "output") {
|
||
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 === "read" || action === "mark-read") {
|
||
const taskId = requireTaskId(taskIdArg, `codex ${action}`);
|
||
return codexReadTask(taskId);
|
||
}
|
||
if (action === "queues") return codeQueues(args.slice(1));
|
||
if (action === "queue") {
|
||
const sub = taskIdArg ?? "list";
|
||
if (sub === "list") return codeQueues(args.slice(2));
|
||
if (sub === "create") return codexCreateQueue(requireQueueId(args.slice(2), "queue create"));
|
||
if (sub === "merge") {
|
||
const mergeArgs = args.slice(2);
|
||
return codexMergeQueue(requireMergeSourceQueueId(mergeArgs, "queue merge"), requireMergeTargetQueueId(mergeArgs, "queue merge"));
|
||
}
|
||
}
|
||
if (action === "move") {
|
||
const taskId = requireTaskId(taskIdArg, "codex move");
|
||
return codexMoveTask(taskId, requireQueueId(args.slice(2), "codex move"));
|
||
}
|
||
if (action === "interrupt" || action === "cancel") {
|
||
const taskId = requireTaskId(taskIdArg, `codex ${action}`);
|
||
return codexInterruptTask(taskId);
|
||
}
|
||
if (action === "steer") {
|
||
const taskId = requireTaskId(taskIdArg, "codex steer");
|
||
return codexSteerTask(taskId, args.slice(2));
|
||
}
|
||
if (action === "steer-confirm" || action === "steer-confirmation") {
|
||
const taskId = requireTaskId(taskIdArg, `codex ${action}`);
|
||
return codexSteerTraceConfirm(taskId, args.slice(2));
|
||
}
|
||
throw new Error("codex command must be one of: prompt-lint, submit, enqueue, task, summary, show, tasks, overview, unread, terminal-unread, output, judge, read, mark-read, dev-ready, health, skills-sync, pr-preflight, runtime-preflight, queues, queue list, queue create, queue merge, move, steer, steer-confirm, interrupt, cancel");
|
||
}
|