feat: add artifact publish preflight
This commit is contained in:
+122
-1
@@ -6,6 +6,12 @@ import { parseNetworkPerfOptions, runNetworkPerf } from "./network-perf";
|
||||
import { isSshSkillDiscoveryArgs, parseSshArgs } from "./ssh";
|
||||
import { codexJudgeQueryAsync, codexOutputQueryAsync, codexTaskQueryAsync, codexTasksQueryAsync } from "./code-queue";
|
||||
import { runDecisionCenterCommandAsync } from "./decision-center";
|
||||
import {
|
||||
artifactRegistryReadonlyResultFromCommand,
|
||||
buildArtifactRegistryReadonlyProbe,
|
||||
parseArtifactRegistryOptions,
|
||||
} from "./artifact-registry";
|
||||
import { runCiPublishUserServiceDryRunPreflight } from "./ci";
|
||||
|
||||
export interface RemoteCliOptions {
|
||||
host: string | null;
|
||||
@@ -517,6 +523,113 @@ async function remoteMicroservice(session: FrontendSession, args: string[]): Pro
|
||||
throw new Error("remote microservice command must be: microservice list | status <id> | health <id> | diagnostics <id> | tunnel-self-test <id> | proxy <id> <path>");
|
||||
}
|
||||
|
||||
function commandResultFromFrontendTask(command: string[], task: { status?: string; result?: Record<string, unknown> } | undefined) {
|
||||
const result = task?.result ?? {};
|
||||
const stdout = typeof result.stdout === "string" ? result.stdout : "";
|
||||
const stderr = typeof result.stderr === "string" ? result.stderr : "";
|
||||
return {
|
||||
command,
|
||||
cwd: ".",
|
||||
exitCode: typeof result.exitCode === "number" ? result.exitCode : null,
|
||||
stdout,
|
||||
stderr,
|
||||
signal: null,
|
||||
timedOut: task?.status !== "succeeded" && stderr.includes("timeout"),
|
||||
};
|
||||
}
|
||||
|
||||
async function dispatchHostSshJson(
|
||||
session: FrontendSession,
|
||||
providerId: string,
|
||||
command: string,
|
||||
timeoutMs: number,
|
||||
waitMs = Math.max(timeoutMs + 5000, 20_000),
|
||||
): Promise<{ dispatch: FetchJsonResult; wait: unknown; task?: { status?: string; result?: Record<string, unknown> }; taskId: string | null }> {
|
||||
const dispatch = await frontendJson(session, "/api/dispatch", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
providerId,
|
||||
command: "host.ssh",
|
||||
payload: { source: "cli-remote-artifact-registry", mode: "exec", command, timeoutMs },
|
||||
}),
|
||||
});
|
||||
const taskId = (dispatch as { body?: { taskId?: string } }).body?.taskId ?? "";
|
||||
const wait = taskId.length > 0 ? await waitForFrontendTask(session, taskId, waitMs) : null;
|
||||
const task = (wait as { task?: { status?: string; result?: Record<string, unknown> } } | null)?.task;
|
||||
return { dispatch, wait, task, taskId: taskId.length > 0 ? taskId : null };
|
||||
}
|
||||
|
||||
async function remoteArtifactRegistry(session: FrontendSession, args: string[]): Promise<unknown> {
|
||||
const action = args[1] ?? "status";
|
||||
if (action !== "status" && action !== "health") {
|
||||
throw new Error("remote frontend transport supports artifact-registry status|health only; use main-server SSH for mutating install/deploy commands");
|
||||
}
|
||||
const options = parseArtifactRegistryOptions(args.slice(2));
|
||||
const probe = buildArtifactRegistryReadonlyProbe(action, options);
|
||||
const dispatched = await dispatchHostSshJson(session, probe.providerId, probe.script, probe.timeoutMs);
|
||||
const command = ["frontend", "/api/dispatch", probe.providerId, "host.ssh", action];
|
||||
const result = commandResultFromFrontendTask(command, dispatched.task);
|
||||
const registryResult = artifactRegistryReadonlyResultFromCommand(probe, result);
|
||||
return {
|
||||
transport: "frontend",
|
||||
readonly: true,
|
||||
dispatch: dispatched.dispatch,
|
||||
wait: dispatched.wait,
|
||||
result: dispatched.taskId === null
|
||||
? {
|
||||
ok: false,
|
||||
readonly: true,
|
||||
installed: false,
|
||||
healthy: false,
|
||||
decision: "infra-blocked",
|
||||
retryable: true,
|
||||
runnerDisposition: "infra-blocked",
|
||||
healthyScopes: [],
|
||||
failedScopes: ["backend-core-api"],
|
||||
runtimeApiHealthy: false,
|
||||
channels: [
|
||||
{ channel: "backend-core-api", ok: false, requiredFor: "frontend /api/dispatch backend-core session creation", detail: dispatched.dispatch },
|
||||
{ channel: "provider-dispatch", ok: false, requiredFor: "host.ssh task creation", detail: dispatched.dispatch },
|
||||
],
|
||||
registry: registryResult,
|
||||
}
|
||||
: registryResult,
|
||||
};
|
||||
}
|
||||
|
||||
async function remoteCi(session: FrontendSession, config: UniDeskConfig, args: string[]): Promise<unknown> {
|
||||
const action = args[1] ?? "status";
|
||||
if (action !== "publish-user-service" || !args.includes("--dry-run")) {
|
||||
throw new Error("remote frontend transport supports only ci publish-user-service --dry-run preflight; real CI publication must run from the controlled main-server CLI after preflight is ready");
|
||||
}
|
||||
return {
|
||||
transport: "frontend",
|
||||
readonly: true,
|
||||
result: await runCiPublishUserServiceDryRunPreflight(config, args.slice(1), {
|
||||
coreFetch: (path, init) => frontendJson(session, path, init === undefined ? undefined : {
|
||||
method: init.method,
|
||||
body: init.body === undefined ? undefined : JSON.stringify(init.body),
|
||||
}, 12_000, init?.maxResponseBytes ?? 500_000),
|
||||
dispatchHostSsh: async (command, waitMs, remoteTimeoutMs) => {
|
||||
const dispatched = await dispatchHostSshJson(session, "D601", command, remoteTimeoutMs, waitMs);
|
||||
const task = dispatched.task;
|
||||
const result = task?.result ?? {};
|
||||
return {
|
||||
ok: task?.status === "succeeded" && (typeof result.exitCode !== "number" || result.exitCode === 0) && dispatched.taskId !== null,
|
||||
taskId: dispatched.taskId,
|
||||
status: task?.status ?? null,
|
||||
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
||||
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
||||
exitCode: typeof result.exitCode === "number" ? result.exitCode : null,
|
||||
raw: task ?? dispatched.wait ?? dispatched.dispatch,
|
||||
};
|
||||
},
|
||||
commandCwd: ".",
|
||||
artifactRegistryCommand: (probe) => ["frontend", "/api/dispatch", probe.providerId, "host.ssh", probe.action],
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
async function remoteCodeQueue(session: FrontendSession, args: string[]): Promise<unknown> {
|
||||
const action = args[1] ?? "task";
|
||||
if (action !== "task" && action !== "summary" && action !== "show" && action !== "tasks" && action !== "overview" && action !== "output" && action !== "judge") {
|
||||
@@ -610,7 +723,7 @@ async function runRemoteCliOverFrontend(options: RemoteCliOptions, config: UniDe
|
||||
emitRemoteJson(name, {
|
||||
transport: "frontend",
|
||||
baseUrl: session.baseUrl,
|
||||
commands: ["debug health", "debug dispatch", "debug task", "ssh <providerId> <command>", "ssh <providerId> skills", "microservice list", "microservice status <id>", "microservice health <id>", "microservice diagnostics <id>", "microservice tunnel-self-test <id>", "microservice proxy <id> <path>", "decision upload <markdown-file>", "decision list", "decision show <id>", "codex task <taskId>", "codex tasks", "codex judge <taskId> --attempt N", "network perf"],
|
||||
commands: ["debug health", "debug dispatch", "debug task", "ssh <providerId> <command>", "ssh <providerId> skills", "artifact-registry status|health", "ci publish-user-service --dry-run", "microservice list", "microservice status <id>", "microservice health <id>", "microservice diagnostics <id>", "microservice tunnel-self-test <id>", "microservice proxy <id> <path>", "decision upload <markdown-file>", "decision list", "decision show <id>", "codex task <taskId>", "codex tasks", "codex judge <taskId> --attempt N", "network perf"],
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
@@ -630,6 +743,14 @@ async function runRemoteCliOverFrontend(options: RemoteCliOptions, config: UniDe
|
||||
emitRemoteJson(name, await remoteMicroservice(session, args));
|
||||
return 0;
|
||||
}
|
||||
if (top === "artifact-registry") {
|
||||
emitRemoteJson(name, await remoteArtifactRegistry(session, args));
|
||||
return 0;
|
||||
}
|
||||
if (top === "ci") {
|
||||
emitRemoteJson(name, await remoteCi(session, config, args));
|
||||
return 0;
|
||||
}
|
||||
if (top === "decision" || top === "decision-center") {
|
||||
const fetcher = (path: string, init?: { method?: string; body?: unknown }): Promise<FetchJsonResult> => {
|
||||
const requestInit = init === undefined
|
||||
|
||||
Reference in New Issue
Block a user