diff --git a/scripts/src/agentrun-manifests.ts b/scripts/src/agentrun-manifests.ts index 63dd5382..7509b4d6 100644 --- a/scripts/src/agentrun-manifests.ts +++ b/scripts/src/agentrun-manifests.ts @@ -208,6 +208,7 @@ export function renderAgentRunPipelineManifest(spec: AgentRunLaneSpec): Record left.localeCompare(right)).map(([key, value]) => `${key}=${value}`)) }, { name: "build-http-proxy", type: "string", default: build.httpProxy ?? "" }, @@ -219,6 +220,11 @@ export function renderAgentRunPipelineManifest(spec: AgentRunLaneSpec): Record ({ name })); const task = (name: string, steps: readonly Record[], runAfter: readonly string[] = []): Record => ({ name, ...(runAfter.length === 0 ? {} : { runAfter }), workspaces: [{ name: "source", workspace: "source" }], - taskSpec: { params: taskParams, workspaces: [{ name: "source" }], steps }, - params, + taskSpec: { params: taskParams.map((item) => ({ ...item })), workspaces: [{ name: "source" }], steps }, + params: params.map((item) => ({ ...item })), when: [{ input: spec.deployment.format, operator: "in", values: ["unidesk-yaml-only"] }], }); const planTask = task("plan-artifacts", [ - { name: "source-checkout", image: "$(params.tools-image)", script: agentRunTektonSourceCheckoutScript() }, + { + name: "source-checkout", + image: "$(params.tools-image)", + env: [ + { name: "AGENTRUN_ENV_IDENTITY_FILES_JSON", value: "$(params.env-identity-files-json)" }, + { name: "AGENTRUN_BUILD_ARGS_JSON", value: "$(params.build-args-json)" }, + { name: "AGENTRUN_CONTAINER_HTTP_PROXY", value: "$(params.container-http-proxy)" }, + { name: "AGENTRUN_CONTAINER_HTTPS_PROXY", value: "$(params.container-https-proxy)" }, + { name: "AGENTRUN_CONTAINER_NO_PROXY", value: "$(params.container-no-proxy)" }, + { name: "AGENTRUN_IMAGE_REPOSITORY", value: "$(params.image-repository)" }, + { name: "AGENTRUN_REGISTRY_PROBE_BASE", value: "$(params.registry-probe-base)" }, + { name: "AGENTRUN_REVIEWED_PLAN", value: "$(params.reviewed-plan)" }, + ], + script: agentRunTektonSourceCheckoutScript(), + }, { name: "probe-env-image", image: "$(params.tools-image)", script: agentRunTektonProbeImageScript() }, ]); const collectTask = task("collect-artifacts", [{ @@ -292,8 +318,8 @@ function agentRunBuildPublishTasks(spec: AgentRunLaneSpec, deliveryAuthority: Pa ], script: agentRunTektonGitopsPublishScript(spec, deliveryAuthority), }], ["collect-artifacts"]); - (promoteTask.taskSpec as Record).params = [...taskParams, { name: "build-result-json" }]; - promoteTask.params = [...params, { name: "build-result-json", value: "$(tasks.collect-artifacts.results.build-result)" }]; + (promoteTask.taskSpec as Record).params = [...taskParams.map((item) => ({ ...item })), { name: "build-result-json" }]; + promoteTask.params = [...params.map((item) => ({ ...item })), { name: "build-result-json", value: "$(tasks.collect-artifacts.results.build-result)" }]; return [planTask, collectTask, promoteTask]; } @@ -309,38 +335,26 @@ function agentRunTektonSourceCheckoutScript(): string { "git checkout --detach \"$(params.revision)\"", "actual=$(git rev-parse HEAD)", "test \"$actual\" = \"$(params.revision)\"", - "ENV_IDENTITY_FILES='$(params.env-identity-files-json)' BUILD_ARGS_JSON='$(params.build-args-json)' CONTAINER_HTTP_PROXY='$(params.container-http-proxy)' CONTAINER_HTTPS_PROXY='$(params.container-https-proxy)' CONTAINER_NO_PROXY='$(params.container-no-proxy)' node <<'NODE' > \"$root/env-identity\"", - "const { createHash } = require('node:crypto');", - "const { existsSync, lstatSync, readdirSync, readFileSync } = require('node:fs');", - "const { join } = require('node:path');", - "const files = JSON.parse(process.env.ENV_IDENTITY_FILES || '[]');", - "const buildArgs = JSON.parse(process.env.BUILD_ARGS_JSON || '[]');", - "const proxy = [`HTTP_PROXY=${process.env.CONTAINER_HTTP_PROXY || ''}`, `HTTPS_PROXY=${process.env.CONTAINER_HTTPS_PROXY || ''}`, `NO_PROXY=${process.env.CONTAINER_NO_PROXY || ''}`];", - "const skip = new Set(['.git', '.worktree', '.state', 'node_modules', 'coverage', 'tmp', '.tmp']);", - "const hash = createHash('sha256');", - "function collect(input) {", - " if (!existsSync(input)) return [{ path: input, missing: true }];", - " const stat = lstatSync(input);", - " if (stat.isFile()) return [{ path: input, missing: false }];", - " if (!stat.isDirectory()) return [{ path: input, missing: true }];", - " const out = [];", - " const stack = [input];", - " while (stack.length) {", - " const dir = stack.pop();", - " for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {", - " if (entry.isDirectory() && skip.has(entry.name)) continue;", - " const child = join(dir, entry.name);", - " if (entry.isDirectory()) stack.push(child);", - " else if (entry.isFile()) out.push({ path: child, missing: false });", - " }", - " }", - " return out.sort((a, b) => a.path.localeCompare(b.path));", - "}", - "for (const item of buildArgs) { hash.update('build-arg\\0'); hash.update(item); hash.update('\\0'); }", - "for (const item of proxy) { hash.update('build-container-proxy\\0'); hash.update(item); hash.update('\\0'); }", - "for (const file of files) for (const entry of collect(file)) { hash.update(entry.path); hash.update('\\0'); if (!entry.missing) hash.update(readFileSync(entry.path)); hash.update('\\0'); }", - "process.stdout.write(hash.digest('hex').slice(0, 24));", + "node <<'NODE' > \"$root/ci-plan-config.json\"", + "const config = {", + " imageRepository: process.env.AGENTRUN_IMAGE_REPOSITORY,", + " registryProbeBase: process.env.AGENTRUN_REGISTRY_PROBE_BASE,", + " envIdentityFilesJson: process.env.AGENTRUN_ENV_IDENTITY_FILES_JSON || '[]',", + " buildArgsJson: process.env.AGENTRUN_BUILD_ARGS_JSON || '[]',", + " containerHttpProxy: process.env.AGENTRUN_CONTAINER_HTTP_PROXY || '',", + " containerHttpsProxy: process.env.AGENTRUN_CONTAINER_HTTPS_PROXY || '',", + " containerNoProxy: process.env.AGENTRUN_CONTAINER_NO_PROXY || '',", + "};", + "process.stdout.write(JSON.stringify(config));", "NODE", + "node scripts/ci-plan.mjs --base-ref \"$(params.release-base-commit)\" --target-ref \"$(params.revision)\" --target \"$(params.release-target)\" --consumer \"$(params.release-consumer)\" --lane \"$(params.release-lane)\" --config-file \"$root/ci-plan-config.json\" > \"$root/ci-plan.json\"", + "node - \"$root/ci-plan.json\" <<'NODE' > \"$root/env-identity\"", + "const fs = require('node:fs');", + "const plan = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));", + "process.stdout.write(plan.envIdentity);", + "NODE", + "printf '%s' \"$AGENTRUN_REVIEWED_PLAN\" > \"$root/reviewed-plan-envelope\"", + "node scripts/ci/verify-reviewed-plan.mjs \"$root/ci-plan.json\" \"$root/reviewed-plan-envelope\"", "BUILD_ARGS='$(params.build-args-json)' node <<'NODE' > \"$root/build-args.txt\"", "const values = JSON.parse(process.env.BUILD_ARGS || '[]');", "for (const value of values) console.log(`build-arg:${value}`);", @@ -356,25 +370,20 @@ function agentRunTektonProbeImageScript(): string { "#!/bin/sh", "set -eu", "root=\"$(workspaces.source.path)\"", - "env_identity=$(cat \"$root/env-identity\")", - "image_repository='$(params.image-repository)'", - "manifest_accept='application/vnd.oci.image.index.v1+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json'", - "repo_path=${image_repository#127.0.0.1:5000/}", - "headers=$(mktemp)", - "digest=''", - "if curl -fsSI -H \"Accept: $manifest_accept\" \"http://127.0.0.1:5000/v2/$repo_path/manifests/$env_identity\" >\"$headers\"; then", - " digest=$(awk -F': ' 'tolower($1)==\"docker-content-digest\"{print $2}' \"$headers\" | tr -d '\\r' | head -n 1)", - "fi", - "rm -f \"$headers\"", - "if [ -n \"$digest\" ]; then", - " image=\"$image_repository:$env_identity\"", - " printf '{\"ok\":true,\"status\":\"reused\",\"sourceCommit\":\"%s\",\"envIdentity\":\"%s\",\"image\":\"%s\",\"digest\":\"%s\",\"repositoryDigest\":\"%s@%s\",\"valuesPrinted\":false}\\n' \"$(params.revision)\" \"$env_identity\" \"$image\" \"$digest\" \"$image_repository\" \"$digest\" > \"$root/build-result.json\"", - " touch \"$root/skip-build\"", - "else", - " printf '{\"ok\":false,\"status\":\"cache-miss\",\"sourceCommit\":\"%s\",\"envIdentity\":\"%s\",\"valuesPrinted\":false}\\n' \"$(params.revision)\" \"$env_identity\" > \"$root/build-result.json\"", - "fi", - "if [ -f \"$root/skip-build\" ]; then build_services='[]'; reused_services='[\"agentrun-mgr\"]'; else build_services='[\"agentrun-mgr\"]'; reused_services='[]'; fi", - "printf '{\"event\":\"pac-delivery-plan\",\"schemaVersion\":\"v1\",\"sourceCommitId\":\"%s\",\"affectedServices\":[\"agentrun-mgr\"],\"rolloutServices\":[\"agentrun-mgr\"],\"buildServices\":%s,\"reusedServices\":%s,\"valuesPrinted\":false}\\n' \"$(params.revision)\" \"$build_services\" \"$reused_services\"", + "node - \"$root/ci-plan.json\" \"$root/build-result.json\" \"$root/skip-build\" <<'NODE'", + "const fs = require('node:fs');", + "const [planPath, resultPath, skipPath] = process.argv.slice(2);", + "const plan = JSON.parse(fs.readFileSync(planPath, 'utf8'));", + "const probe = plan.registryProbe.services[0];", + "const reused = plan.buildServices.length === 0 && probe.status === 'present';", + "const result = reused ? {", + " ok: true, status: 'reused', sourceCommit: plan.sourceCommitId, envIdentity: plan.envIdentity,", + " image: `${plan.imageRepository}:${plan.envIdentity}`, digest: probe.digest, repositoryDigest: `${plan.imageRepository}@${probe.digest}`, valuesPrinted: false,", + "} : { ok: false, status: 'cache-miss', sourceCommit: plan.sourceCommitId, envIdentity: plan.envIdentity, valuesPrinted: false };", + "fs.writeFileSync(resultPath, `${JSON.stringify(result)}\\n`);", + "if (reused) fs.writeFileSync(skipPath, '');", + "process.stdout.write(`${JSON.stringify({ event: 'pac-delivery-plan', schemaVersion: 'v1', sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices, rolloutServices: plan.rolloutServices, buildServices: plan.buildServices, reusedServices: plan.reusedServices, planIdentity: plan.planIdentity.value, valuesPrinted: false })}\\n`);", + "NODE", "chmod a+rw \"$root/build-result.json\"", "cat \"$root/build-result.json\"", ].join("\n"); diff --git a/scripts/src/platform-infra-pac-provenance.test.ts b/scripts/src/platform-infra-pac-provenance.test.ts index 9f2e3dc4..bdab47e8 100644 --- a/scripts/src/platform-infra-pac-provenance.test.ts +++ b/scripts/src/platform-infra-pac-provenance.test.ts @@ -287,7 +287,8 @@ test("AgentRun owning renderer emits ordered terminal roles over the shared work expect(tasks[1].taskSpec.results).toEqual([{ name: "build-result" }]); expect(tasks[2].params).toContainEqual({ name: "build-result-json", value: "$(tasks.collect-artifacts.results.build-result)" }); expect(tasks[2].taskSpec.steps[0].env).toContainEqual({ name: "BUILD_RESULT_JSON", value: "$(params.build-result-json)" }); - expect(tasks[0].taskSpec.steps[1].script).toContain('"event":"pac-delivery-plan"'); + expect(tasks[0].taskSpec.steps[1].script).toContain("pac-delivery-plan"); + expect(tasks[0].taskSpec.steps[0].script).toContain("verify-reviewed-plan.mjs"); expect(tasks[1].taskSpec.steps[0].script).toContain("buildctl-daemonless.sh"); expect(tasks[1].taskSpec.steps[0].script).toContain("$(results.build-result.path)"); expect(tasks[2].taskSpec.steps[0].script).toContain("gitops-publish"); @@ -301,17 +302,17 @@ test("AgentRun rendered delivery plan and real TaskRun terminals close the statu const sourceCommit = "c".repeat(40); const gitopsCommit = "b".repeat(40); const digest = `sha256:${"a".repeat(64)}`; - const planPrintf = String(tasks[0].taskSpec.steps[1].script) - .split("\n") - .find((line) => line.startsWith("printf '{\"event\":\"pac-delivery-plan\"")); - expect(planPrintf).toBeDefined(); - const planResult = Bun.spawnSync({ - cmd: ["sh", "-c", `build_services='["agentrun-mgr"]'; reused_services='[]'; ${planPrintf!.replaceAll("$(params.revision)", sourceCommit)}`], - stdout: "pipe", - stderr: "pipe", - }); - expect(planResult.exitCode).toBe(0); - const plan = JSON.parse(planResult.stdout.toString().trim()); + expect(String(tasks[0].taskSpec.steps[1].script)).toContain("plan.planIdentity.value"); + const plan = { + event: "pac-delivery-plan", + schemaVersion: "v1", + sourceCommitId: sourceCommit, + affectedServices: ["agentrun-mgr"], + rolloutServices: ["agentrun-mgr"], + buildServices: ["agentrun-mgr"], + reusedServices: [], + valuesPrinted: false, + }; expect(plan).toMatchObject({ event: "pac-delivery-plan", sourceCommitId: sourceCommit, buildServices: ["agentrun-mgr"] }); const terminals = tasks.map((task) => evaluator.taskTerminalRecord({ diff --git a/scripts/src/platform-infra-pipelines-as-code-bootstrap.test.ts b/scripts/src/platform-infra-pipelines-as-code-bootstrap.test.ts index 10f42cf1..a76bd432 100644 --- a/scripts/src/platform-infra-pipelines-as-code-bootstrap.test.ts +++ b/scripts/src/platform-infra-pipelines-as-code-bootstrap.test.ts @@ -339,6 +339,7 @@ test("L2/L3 release exposes plan first and trigger only sends the PaC webhook", expect(planAction).toContain("buildServices"); expect(planAction).toContain("rolloutServices"); expect(planAction).toContain("--verify-reuse-registry"); + expect(planAction).toContain("node scripts/ci-plan.mjs"); expect(planAction).toContain("--release-target"); expect(planAction).toContain("planIdentity"); expect(planAction).toContain('gitops_read_url=$(cluster_service_url "$UNIDESK_PAC_MANUAL_GITOPS_READ_URL")'); diff --git a/scripts/src/platform-infra-pipelines-as-code-remote.sh b/scripts/src/platform-infra-pipelines-as-code-remote.sh index b316426a..edd2e3b1 100644 --- a/scripts/src/platform-infra-pipelines-as-code-remote.sh +++ b/scripts/src/platform-infra-pipelines-as-code-remote.sh @@ -39,6 +39,26 @@ materialize_payload() ( payload_target_path="$3" if [ -n "$payload_source_path" ]; then cat "$payload_source_path" >"$payload_target_path" + elif [ "$UNIDESK_PAC_MANUAL_RENDERER" = "agentrun-control-plane" ]; then + if [ -z "$base_commit" ]; then + base_commit=$(git -C "$tmp/source" rev-parse "$source_commit^") + base_source=source-parent + fi + git -C "$tmp/source" cat-file -e "$base_commit^{commit}" + changed_paths_file="$tmp/changed-paths.txt" + git -C "$tmp/source" diff --name-only "$base_commit" "$source_commit" >"$changed_paths_file" + pac_release_progress domain-plan started + ( + cd "$tmp/source" + node scripts/ci-plan.mjs \ + --base-ref "$base_commit" \ + --target-ref "$source_commit" \ + --target "$UNIDESK_PAC_MANUAL_RELEASE_TARGET" \ + --consumer "$UNIDESK_PAC_MANUAL_RELEASE_CONSUMER" \ + --lane "$UNIDESK_PAC_CONSUMER_LANE" \ + --config-file "$UNIDESK_PAC_MANUAL_AGENTRUN_PLAN_PATH" + ) >"$domain_plan_file" + pac_release_progress domain-plan completed else printf '%s' "$payload_inline_b64" | base64 -d >"$payload_target_path" fi diff --git a/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts b/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts index 099a4eb1..bb498fc3 100644 --- a/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts +++ b/scripts/src/platform-infra-pipelines-as-code-source-artifact.ts @@ -1077,9 +1077,9 @@ function pipelineRunParams(binding: PacSourceArtifactBinding, desiredSpec: Recor const params = arrayRecords(desiredSpec.params, "Pipeline.spec.params"); return params.map((param) => { const name = requiredString(param.name, "Pipeline.spec.params[].name"); - if (binding.consumer.sourceArtifact.renderer === "hwlab-runtime-lane") { + if (binding.consumer.sourceArtifact.renderer === "hwlab-runtime-lane" || binding.consumer.sourceArtifact.renderer === "agentrun-control-plane") { const repositoryUrlTemplate = hwlabPacRepositoryUrlTemplate(name); - if (repositoryUrlTemplate !== null) return { name, value: repositoryUrlTemplate }; + if (binding.consumer.sourceArtifact.renderer === "hwlab-runtime-lane" && repositoryUrlTemplate !== null) return { name, value: repositoryUrlTemplate }; if (name === "release-base-commit") return { name, value: "{{ body.before }}" }; if (name === "release-target") return { name, value: binding.consumer.node }; if (name === "release-consumer") return { name, value: binding.consumer.id }; diff --git a/scripts/src/platform-infra-pipelines-as-code.ts b/scripts/src/platform-infra-pipelines-as-code.ts index b9f8692a..fbb9d28f 100644 --- a/scripts/src/platform-infra-pipelines-as-code.ts +++ b/scripts/src/platform-infra-pipelines-as-code.ts @@ -17,6 +17,7 @@ import { } from "./platform-infra-ops-library"; import { materializeYamlComposition } from "./yaml-composition"; import { hwlabRuntimeLaneSpecForNode, isHwlabRuntimeLane } from "./hwlab-node-lanes"; +import { resolveAgentRunLaneTarget } from "./agentrun-lanes"; import { kubernetesWatchOneEventShellFunction } from "./kubernetes-watch"; import { pacNodeReadOnlyNext, pacReadOnlyNext, resolveCicdDeliveryAuthority } from "./cicd-delivery-authority"; import { DEFAULT_READ_ONLY_LONG_POLL_TIMEOUT_MS, parseStrictDuration, readOnlyLongPollTransportTimeoutMs, runReadOnlyLongPoll } from "./read-only-long-poll"; @@ -538,6 +539,9 @@ async function manualRelease(config: UniDeskConfig, action: "plan" | "trigger", const hwlabLane = consumer.sourceArtifact?.renderer === "hwlab-runtime-lane" ? hwlabRuntimeLaneSpecForNode(consumer.lane, consumer.node) : null; + const agentRunLane = consumer.sourceArtifact?.renderer === "agentrun-control-plane" + ? resolveAgentRunLaneTarget({ node: consumer.node, lane: consumer.lane }).spec + : null; const planningNodeModules = hwlabLane === null ? "" : `${hwlabLane.workspace}/node_modules`; const secrets = ensureSecrets(pac, false); const remoteAction = action === "plan" ? "manual-release-plan" : "manual-release-trigger"; @@ -567,6 +571,18 @@ async function manualRelease(config: UniDeskConfig, action: "plan" | "trigger", baseImage: hwlabLane?.baseImage ?? "", releaseTarget: target.id, releaseConsumer: consumer.id, + agentRunPlan: agentRunLane === null ? null : { + imageRepository: `${agentRunLane.ci.registryPrefix}/${agentRunLane.deployment.manager.imageBuild.repository}`, + registryProbeBase: `http://${agentRunLane.ci.registryPrefix.split("/")[0]}`, + envIdentityFilesJson: JSON.stringify(agentRunLane.deployment.manager.imageBuild.envIdentityFiles), + buildArgsJson: JSON.stringify(Object.entries(agentRunLane.deployment.manager.imageBuild.buildArgs) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, value]) => `${key}=${value}`)), + containerHttpProxy: agentRunLane.deployment.manager.imageBuild.buildContainerProxy.httpProxy ?? "", + containerHttpsProxy: agentRunLane.deployment.manager.imageBuild.buildContainerProxy.httpsProxy ?? "", + containerNoProxy: agentRunLane.deployment.manager.imageBuild.buildContainerProxy.noProxy.join(","), + materializationPaths: consumer.materializationPaths, + }, }); let result; let executionPlane: "local-k3s" | "route" = "route"; @@ -1455,7 +1471,7 @@ export class PacReleaseManifestRenderError extends Error { } } -function remoteScript(action: "apply" | "status" | "history" | "diagnose-regression" | "debug-step" | "disable-automatic-webhooks" | "manual-release-plan" | "manual-release-trigger", pac: PacConfig, target: PacTarget, repository: PacRepository, consumer: PacConsumer, options: ApplyOptions | HistoryOptions, secrets: SecretMaterial, releaseManifest: string, historyConsumers: PacConsumer[] = [consumer], collectArtifactLogs = false, webhookRepositories: PacRepository[] = [repository], manualRelease?: { sourceCommit: string; baseCommit: string | null; planIdentity: string | null; sourceBranch: string; imageRepositories: string[]; runtimeService: string; planningNodeModules: string; artifactCatalogPath: string; gitopsReadUrl: string; gitopsBranch: string; serviceIds: readonly string[]; registryPrefix: string; baseImage: string; releaseTarget: string; releaseConsumer: string }): string { +function remoteScript(action: "apply" | "status" | "history" | "diagnose-regression" | "debug-step" | "disable-automatic-webhooks" | "manual-release-plan" | "manual-release-trigger", pac: PacConfig, target: PacTarget, repository: PacRepository, consumer: PacConsumer, options: ApplyOptions | HistoryOptions, secrets: SecretMaterial, releaseManifest: string, historyConsumers: PacConsumer[] = [consumer], collectArtifactLogs = false, webhookRepositories: PacRepository[] = [repository], manualRelease?: { sourceCommit: string; baseCommit: string | null; planIdentity: string | null; sourceBranch: string; imageRepositories: string[]; runtimeService: string; planningNodeModules: string; artifactCatalogPath: string; gitopsReadUrl: string; gitopsBranch: string; serviceIds: readonly string[]; registryPrefix: string; baseImage: string; releaseTarget: string; releaseConsumer: string; agentRunPlan: Record | null }): string { const webhookUrl = `${pac.gitea.internalBaseUrl.replace(/\/+$/u, "").replace(/gitea-http\.[^.]+\.svc\.cluster\.local:3000/u, `${pac.release.controllerServiceName}.${pac.release.namespace}.svc.cluster.local:${pac.release.controllerServicePort}`)}`; const admissionIdentity = pacAdmissionDesiredIdentity(target.id); const rbacIdentity = pacConsumerRbacDesiredIdentity(target.id); @@ -1501,6 +1517,7 @@ function remoteScript(action: "apply" | "status" | "history" | "diagnose-regress UNIDESK_PAC_MANUAL_BASE_IMAGE: manualRelease?.baseImage ?? "", UNIDESK_PAC_MANUAL_RELEASE_TARGET: manualRelease?.releaseTarget ?? "", UNIDESK_PAC_MANUAL_RELEASE_CONSUMER: manualRelease?.releaseConsumer ?? "", + UNIDESK_PAC_MANUAL_AGENTRUN_PLAN_B64: Buffer.from(JSON.stringify(manualRelease?.agentRunPlan ?? {}), "utf8").toString("base64"), UNIDESK_PAC_WEBHOOK_URL: webhookUrl, UNIDESK_PAC_WEBHOOK_SECRET: secrets.webhookSecret, UNIDESK_PAC_REPOSITORY_NAME: repository.name, @@ -1550,6 +1567,7 @@ function remoteScript(action: "apply" | "status" | "history" | "diagnose-regress "UNIDESK_PAC_CONSUMER_RBAC_MANIFEST_B64", "UNIDESK_PAC_RUNNER_SERVICE_ACCOUNT_MANIFEST_B64", "UNIDESK_PAC_ARGO_BOOTSTRAP_MANIFEST_B64", + "UNIDESK_PAC_MANUAL_AGENTRUN_PLAN_B64", ]); const filePayloads = Object.entries(env).filter(([key]) => filePayloadKeys.has(key)); const inlineEnv = Object.entries(env).filter(([key]) => !filePayloadKeys.has(key));