// 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> { 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> { 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 & { ok: boolean; sourceCommit?: string | null; sourcePayload?: Record }> { 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, configPath: string, waited: boolean): Promise & { ok: boolean; buildStatus?: Record & { ok: boolean; payload: Record }; buildSubmitPayload?: Record }> { 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 }; }