Files
pikasTech-unidesk/scripts/src/agentrun/cleanup-scripts.ts
T
2026-07-01 11:03:18 +00:00

343 lines
15 KiB
TypeScript

// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. cleanup-scripts module for scripts/src/agentrun.ts.
// Moved mechanically from scripts/src/agentrun.ts:3187-3310 for #903.
// SPEC: PJ2026-01020108 cancel lifecycle + PJ2026-01020205 AipodSpec binding + PJ2026-01020302 session policy + PJ2026-01020305 cancel control + PJ2026-01060305/06 YAML execution policy and bounded output draft-2026-06-25-p0.
// Exposes AgentRun lane-scoped policy, AipodSpec SecretRef binding, cancel lifecycle, and bounded default output in the UniDesk CLI.
import { chmodSync, copyFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
import { rootPath, type UniDeskConfig } from "../config";
import type { RenderedCliResult } from "../output";
import { applyLocalCaddyManagedSite } from "../pk01-caddy";
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";
import { runRemoteSshCommandCapture } from "../remote";
import { startJob } from "../jobs";
import {
AGENTRUN_CONFIG_PATH,
agentRunLaneSummary,
agentRunPipelineRunName,
agentRunProviderCredentialRefs,
resolveAgentRunLaneTarget,
type AgentRunCancelLifecycleSpec,
type AgentRunLaneSpec,
} from "../agentrun-lanes";
import {
agentRunImageArtifact,
placeholderAgentRunImage,
renderedFilesDigest,
renderedObjectsDigest,
renderAgentRunControlPlaneManifests,
renderAgentRunGitopsFiles,
type AgentRunArtifactService,
} from "../agentrun-manifests";
import { sha256Fingerprint } from "../platform-infra-ops-library";
import { runYamlLaneGitMirrorFlushJob, runYamlLaneGitMirrorSyncJob, yamlLanePipelineRunCreateScript } from "./secrets";
import { capture, captureJsonPayload, compactCapture, isGitSha, progressEvent, stringOrNull } from "./utils";
import { runYamlLaneGitopsPublishJob, runYamlLaneK3sBuildImageJob, waitForYamlLaneBuildImage, waitForYamlLaneSourceBootstrap, yamlLaneBuildImageSubmitScript, yamlLaneK3sSourceStatusScript, yamlLaneSourceBootstrapSubmitScript, yamlLaneSourceRestoreScript } from "./yaml-lane";
export async function triggerCurrentYamlLaneConfirmed(config: UniDeskConfig, spec: AgentRunLaneSpec, configPath: string, waited: boolean): Promise<Record<string, unknown>> {
const result = await triggerCurrentYamlLaneConfirmedSteps(config, spec, configPath, waited);
if (spec.source.statusMode === "k3s-git-mirror") {
return {
...result,
sourceWorkspaceRestore: { ok: true, status: "skipped", statusMode: spec.source.statusMode, reason: "k3s-git-mirror-does-not-use-host-worktree", valuesPrinted: false },
valuesPrinted: false,
};
}
const restore = await capture(config, spec.nodeRoute, ["sh", "--", yamlLaneSourceRestoreScript(spec)]);
const restorePayload = captureJsonPayload(restore);
const restoreOk = restore.exitCode === 0 && restorePayload.ok === true;
const existingWarnings = Array.isArray(result.warnings) ? result.warnings.filter((item): item is string => typeof item === "string") : [];
return {
...result,
warnings: restoreOk ? existingWarnings : Array.from(new Set([...existingWarnings, "source-worktree-restore-failed"])),
sourceWorkspaceRestore: restorePayload,
sourceWorkspaceRestoreCapture: compactCapture(restore, { full: !restoreOk, stdoutTailChars: 3000, stderrTailChars: 3000 }),
valuesPrinted: false,
};
}
export async function triggerCurrentYamlLaneConfirmedSteps(config: UniDeskConfig, spec: AgentRunLaneSpec, configPath: string, waited: boolean): Promise<Record<string, unknown>> {
const source = await resolveTriggerCurrentSource(config, spec, configPath, waited);
if (source.ok !== true) return source;
const sourceCommit = stringOrNull(source.sourceCommit);
const bootstrapPayload = source.sourcePayload;
if (sourceCommit === null || !isGitSha(sourceCommit)) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "source-resolve",
degradedReason: "yaml-lane-source-commit-invalid",
result: bootstrapPayload,
valuesPrinted: false,
};
}
const buildResult = await buildTriggerCurrentImage(config, spec, sourceCommit, bootstrapPayload, configPath, waited);
if (buildResult.ok !== true) return buildResult;
const build = buildResult.buildStatus;
const buildSubmitPayload = buildResult.buildSubmitPayload;
const buildPayload = build.payload;
const digest = stringOrNull(buildPayload.digest);
const envIdentity = stringOrNull(buildPayload.envIdentity);
if (build.ok !== true || digest === null || envIdentity === null) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "image-build",
sourceCommit,
degradedReason: "yaml-lane-image-build-failed",
sourceBootstrap: bootstrapPayload,
buildSubmit: buildSubmitPayload,
result: buildPayload,
buildStatus: build,
valuesPrinted: false,
};
}
const image = agentRunImageArtifact(spec, { sourceCommit, envIdentity, digest, status: stringOrNull(buildPayload.status) ?? "built" });
const renderedFiles = renderAgentRunGitopsFiles(spec, { sourceCommit, image });
const mirrorPrePublish = await runYamlLaneGitMirrorSyncJob(config, spec);
if (mirrorPrePublish.ok !== true) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "git-mirror-prepublish-sync",
sourceCommit,
image,
degradedReason: "yaml-lane-git-mirror-prepublish-sync-failed",
sourceBootstrap: bootstrapPayload,
imageBuild: buildPayload,
result: mirrorPrePublish,
valuesPrinted: false,
};
}
progressEvent("agentrun.yaml-lane.gitops-publish.progress", {
node: spec.nodeId,
lane: spec.lane,
sourceCommit,
status: "submitting",
});
const gitops = await runYamlLaneGitopsPublishJob(config, spec, sourceCommit, renderedFiles);
const gitopsPayload = gitops.payload;
if (gitops.ok !== true || gitopsPayload.ok === false) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "gitops-publish",
sourceCommit,
image,
degradedReason: "yaml-lane-gitops-publish-failed",
sourceBootstrap: bootstrapPayload,
imageBuild: buildPayload,
gitMirrorPrePublish: mirrorPrePublish,
result: gitopsPayload,
gitopsPublishStatus: gitops,
valuesPrinted: false,
};
}
const mirrorFlush = await runYamlLaneGitMirrorFlushJob(config, spec);
if (mirrorFlush.ok !== true) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "git-mirror-flush",
sourceCommit,
image,
gitops: gitopsPayload,
gitMirrorPrePublish: mirrorPrePublish,
degradedReason: "yaml-lane-git-mirror-flush-failed",
result: mirrorFlush,
valuesPrinted: false,
};
}
const pipelineRun = agentRunPipelineRunName(spec, sourceCommit);
const created = await capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLanePipelineRunCreateScript(spec, sourceCommit, pipelineRun)]);
const createPayload = captureJsonPayload(created);
return {
ok: created.exitCode === 0 && createPayload.ok !== false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
mutation: true,
configPath,
target: agentRunLaneSummary(spec),
sourceCommit,
pipelineRun,
image,
sourceBootstrap: bootstrapPayload,
imageBuildSubmit: buildSubmitPayload,
imageBuild: buildPayload,
renderedFiles: {
count: renderedFiles.length,
digest: renderedFilesDigest(renderedFiles),
},
gitops: gitopsPayload,
gitMirrorPrePublish: mirrorPrePublish,
gitMirror: mirrorFlush,
created: createPayload,
capture: compactCapture(created, { full: created.exitCode !== 0, stdoutTailChars: 4000, stderrTailChars: 4000 }),
next: {
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
statusByPipelineRun: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane} --pipeline-run ${pipelineRun} --full`,
},
valuesPrinted: false,
};
}
async function resolveTriggerCurrentSource(config: UniDeskConfig, spec: AgentRunLaneSpec, configPath: string, waited: boolean): Promise<Record<string, unknown> & { ok: boolean; sourceCommit?: string | null; sourcePayload?: Record<string, unknown> }> {
if (spec.source.statusMode === "k3s-git-mirror") {
progressEvent("agentrun.yaml-lane.source-snapshot.progress", {
node: spec.nodeId,
lane: spec.lane,
statusMode: spec.source.statusMode,
status: "syncing",
});
const sourceSync = await runYamlLaneGitMirrorSyncJob(config, spec);
if (sourceSync.ok !== true) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "source-snapshot-sync",
degradedReason: "yaml-lane-k3s-source-snapshot-sync-failed",
result: sourceSync,
valuesPrinted: false,
};
}
progressEvent("agentrun.yaml-lane.source-status.progress", {
node: spec.nodeId,
lane: spec.lane,
statusMode: spec.source.statusMode,
status: "probing-snapshot",
});
const sourceStatus = await capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneK3sSourceStatusScript(spec)]);
const sourcePayload = captureJsonPayload(sourceStatus);
const sourceCommit = stringOrNull(sourcePayload.sourceCommit) ?? stringOrNull(sourcePayload.remoteBranchCommit);
if (sourceStatus.exitCode !== 0 || sourcePayload.ok !== true || sourceCommit === null || !isGitSha(sourceCommit)) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "source-status",
degradedReason: stringOrNull(sourcePayload.degradedReason) ?? "yaml-lane-k3s-source-status-failed",
gitMirrorSync: sourceSync,
result: sourcePayload,
capture: compactCapture(sourceStatus, { full: true, stdoutTailChars: 5000, stderrTailChars: 5000 }),
valuesPrinted: false,
};
}
return { ok: true, sourceCommit, sourcePayload: { ...sourcePayload, gitMirrorSync: sourceSync, valuesPrinted: false }, valuesPrinted: false };
}
progressEvent("agentrun.yaml-lane.source-bootstrap.progress", {
node: spec.nodeId,
lane: spec.lane,
status: "submitting",
});
const bootstrapSubmit = await capture(config, spec.nodeRoute, ["sh", "--", yamlLaneSourceBootstrapSubmitScript(spec)]);
const bootstrapSubmitPayload = captureJsonPayload(bootstrapSubmit);
if (bootstrapSubmit.exitCode !== 0 || bootstrapSubmitPayload.ok === false) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "source-bootstrap-submit",
degradedReason: "yaml-lane-source-bootstrap-submit-failed",
result: bootstrapSubmitPayload,
capture: compactCapture(bootstrapSubmit, { full: true, stdoutTailChars: 5000, stderrTailChars: 5000 }),
valuesPrinted: false,
};
}
const bootstrap = await waitForYamlLaneSourceBootstrap(config, spec, stringOrNull(bootstrapSubmitPayload.jobId));
const sourcePayload = bootstrap.payload;
const sourceCommit = stringOrNull(sourcePayload.sourceCommit);
if (bootstrap.ok !== true || sourceCommit === null || !isGitSha(sourceCommit)) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "source-bootstrap",
degradedReason: "yaml-lane-source-bootstrap-failed",
result: sourcePayload,
bootstrapStatus: bootstrap,
valuesPrinted: false,
};
}
return { ok: true, sourceCommit, sourcePayload, sourceBootstrapSubmit: bootstrapSubmitPayload, valuesPrinted: false };
}
async function buildTriggerCurrentImage(config: UniDeskConfig, spec: AgentRunLaneSpec, sourceCommit: string, sourcePayload: Record<string, unknown>, configPath: string, waited: boolean): Promise<Record<string, unknown> & { ok: boolean; buildStatus?: Record<string, unknown> & { ok: boolean; payload: Record<string, unknown> }; buildSubmitPayload?: Record<string, unknown> }> {
if (spec.source.statusMode === "k3s-git-mirror") {
const build = await runYamlLaneK3sBuildImageJob(config, spec, sourceCommit);
const buildSubmitPayload = {
ok: build.ok !== false,
status: "submitted",
mode: "k3s-buildkit-job",
jobName: stringOrNull(build.jobName) ?? null,
valuesPrinted: false,
};
if (build.ok !== true) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "image-build",
sourceCommit,
degradedReason: "yaml-lane-k3s-image-build-failed",
sourceBootstrap: sourcePayload,
buildSubmit: buildSubmitPayload,
result: build.payload,
buildStatus: build,
valuesPrinted: false,
};
}
return { ok: true, buildStatus: build, buildSubmitPayload, valuesPrinted: false };
}
const buildSubmit = await capture(config, spec.nodeRoute, ["sh", "--", yamlLaneBuildImageSubmitScript(spec, sourceCommit)]);
const buildSubmitPayload = captureJsonPayload(buildSubmit);
if (buildSubmit.exitCode !== 0 || buildSubmitPayload.ok === false) {
return {
ok: false,
command: "agentrun control-plane trigger-current",
mode: waited ? "confirmed-waited" : "confirmed-trigger",
configPath,
target: agentRunLaneSummary(spec),
phase: "image-build-submit",
sourceCommit,
degradedReason: "yaml-lane-image-build-submit-failed",
sourceBootstrap: sourcePayload,
result: buildSubmitPayload,
capture: compactCapture(buildSubmit, { full: true, stdoutTailChars: 5000, stderrTailChars: 5000 }),
valuesPrinted: false,
};
}
const build = await waitForYamlLaneBuildImage(config, spec, sourceCommit, stringOrNull(buildSubmitPayload.jobId));
return { ok: true, buildStatus: build, buildSubmitPayload, valuesPrinted: false };
}