// SPEC: issue-1616 YAML-first AgentRun provider profile online configuration. // Provides a thin CLI over YAML-declared provider credential Secrets, runtime GitOps env and bounded validation. import type { UniDeskConfig } from "../config"; import { startJob } from "../jobs"; import { sha256Fingerprint } from "../platform-infra-ops-library"; import { agentRunImageArtifact, renderedFilesDigest, renderAgentRunGitopsFiles, } from "../agentrun-manifests"; import { agentRunLaneSummary, agentRunProviderCredentialRefs, resolveAgentRunLaneTarget, type AgentRunLaneSecretSpec, type AgentRunLaneSpec, } from "../agentrun-lanes"; import { refreshYamlLaneScript } from "./git-mirror"; import type { ConfirmOptions, DisclosureOptions } from "./options"; import { AGENTRUN_PROVIDER_ACTIVATION_FENCE_ISSUE, providerSecretActivationPreflight, providerSecretDesiredEncodedFingerprint, providerSecretStagedActivation, type ProviderSecretActivationTarget, } from "./provider-activation"; import { compactAgentRunLaneStatusTarget } from "./trigger"; import { collectLaneSecretSources, createYamlLaneJobScript, inspectSecretSourceValue, restartYamlLaneScript, type LaneSecretSource, type LaneSecretSourceInspection, yamlLaneGitopsPublishJobManifest, yamlLaneGitopsPublishPayloadFromProbe, yamlLaneJobProbeScript, } from "./secrets"; import { capture, captureJsonPayload, compactCapture, progressEvent, record, shQuote, sleep, stringOrNull } from "./utils"; export interface ProviderProfileOptions extends ConfirmOptions, DisclosureOptions { readonly node: string | null; readonly lane: string | null; readonly profile: string | null; readonly wait: boolean; readonly prompt: string | null; } export function parseProviderProfileOptions(args: string[]): ProviderProfileOptions { let node: string | null = null; let lane: string | null = null; let profile: string | null = null; let full = false; let raw = false; let wait = false; let prompt: string | null = null; let confirm = false; let dryRun = false; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (arg === "--confirm") { confirm = true; continue; } if (arg === "--dry-run") { dryRun = true; continue; } if (arg === "--full") { full = true; continue; } if (arg === "--raw") { raw = true; continue; } if (arg === "--wait") { wait = true; continue; } if (arg === "--node" || arg === "--lane" || arg === "--profile" || arg === "--prompt") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`); if (arg === "--node") node = value; if (arg === "--lane") lane = value; if (arg === "--profile") profile = value; if (arg === "--prompt") prompt = value; index += 1; continue; } throw new Error(`unsupported provider-profile option: ${arg}`); } if (confirm && dryRun) throw new Error("accepts only one of --confirm or --dry-run"); return { node, lane, profile, full, raw, wait, prompt, confirm, dryRun: dryRun || !confirm }; } export function providerProfileHelp(): string { return [ "Usage: bun scripts/cli.ts agentrun provider-profile --node --lane --profile [options]", "", "Examples:", " bun scripts/cli.ts agentrun provider-profile plan --node NC01 --lane nc01-v02 --profile gpt.pika", " bun scripts/cli.ts agentrun provider-profile status --node NC01 --lane nc01-v02 --profile gpt.pika", " bun scripts/cli.ts agentrun provider-profile runtime-status --node NC01 --lane nc01-v02 --profile gpt.pika", " bun scripts/cli.ts agentrun provider-profile apply --node NC01 --lane nc01-v02 --profile gpt.pika --confirm", " bun scripts/cli.ts job status --tail-bytes 12000", " bun scripts/cli.ts agentrun provider-profile validate --node NC01 --lane nc01-v02 --profile gpt.pika --wait", "", "Boundary:", " confirmed apply returns an async job only after the provider Secret gate reports an exact data no-op; the worker skips the provider Secret write, publishes runtime GitOps using the current manager image, refreshes Argo and restarts the manager without rebuilding images or creating a PipelineRun.", ].join("\n"); } export async function runProviderProfileCommand(config: UniDeskConfig, action: string | undefined, args: string[]): Promise> { const options = parseProviderProfileOptions(args); if (action === "plan") return providerProfilePlan(options); if (action === "status") return providerProfileStatus(config, options); if (action === "runtime-status") return providerProfileRuntimeStatus(config, options); if (action === "apply") { if (options.confirm && !options.wait) return providerProfileApplyAsync(config, options); return providerProfileApply(config, options); } if (action === "validate") return providerProfileValidate(config, options); throw new Error(`unsupported provider-profile action: ${action ?? "(empty)"}`); } async function providerProfileApplyAsync(config: UniDeskConfig, options: ProviderProfileOptions): Promise> { const plan = providerProfilePlan({ ...options, confirm: false, dryRun: true }); if (plan.ok !== true) return plan; const { spec, profile } = resolveProviderProfile(options); const secrets = spec.secrets.filter((secret) => secret.providerCredentialProfile === profile); const activationTargets = providerProfileActivationTargets(inspectProviderProfileSources(spec, secrets)); const activationPreflight = await providerSecretActivationPreflight(config, spec, activationTargets, { full: options.full || options.raw }); if (activationPreflight.ok !== true) { return providerProfileActivationBlockedResult(spec, profile, secrets, activationPreflight, "async-submit"); } const args = [ "bun", "scripts/cli.ts", "agentrun", "provider-profile", "apply", "--node", spec.nodeId, "--lane", spec.lane, "--profile", profile, "--confirm", "--wait", ]; const jobName = `agentrun_${spec.lane}_provider_profile_${profile.replace(/[^A-Za-z0-9_-]/gu, "-")}_apply`; const job = startJob(jobName, args, `Apply YAML-declared AgentRun provider profile ${profile} on ${spec.nodeId}/${spec.lane}`); const statusCommand = `bun scripts/cli.ts job status ${job.id} --tail-bytes 12000`; return { ok: true, command: "agentrun provider-profile apply", mode: "async-job", mutation: true, node: spec.nodeId, lane: spec.lane, profile, job, jobCreated: true, activationPreflight, statusCommand, next: { status: statusCommand, full: `bun scripts/cli.ts job status ${job.id} --tail-bytes 12000 --full`, }, valuesPrinted: false, }; } async function providerProfileRuntimeStatus(config: UniDeskConfig, options: ProviderProfileOptions): Promise> { const { configPath, spec, profile, secrets } = resolveProviderProfile(options); const live = await capture(config, spec.nodeKubeRoute, ["sh", "--", providerProfileRuntimeStatusScript(spec, secrets)]); const payload = captureJsonPayload(live); const ready = live.exitCode === 0 && payload.ok === true && record(payload.secrets).ready === true && record(payload.providerServices).ready === true; return { ok: ready, command: "agentrun provider-profile runtime-status", mode: "runtime-only-profile-preflight", status: ready ? "ready" : "blocked", degradedReason: ready ? null : "runtime-provider-profile-unavailable", mutation: false, ownerSourceInspected: false, runtimeSecretValuesDecoded: false, configPath, target: options.full ? agentRunLaneSummary(spec) : compactAgentRunLaneStatusTarget(spec), profile: providerProfileSummary(spec, profile, secrets), runtime: payload, capture: compactCapture(live, { full: options.full || options.raw || live.exitCode !== 0, stdoutTailChars: 2000, stderrTailChars: 2000 }), valuesPrinted: false, }; } function resolveProviderProfile(options: ProviderProfileOptions): { configPath: string; spec: AgentRunLaneSpec; profile: string; secrets: AgentRunLaneSecretSpec[] } { if (options.profile === null) throw new Error("provider-profile requires --profile"); const target = resolveAgentRunLaneTarget(options); const secrets = target.spec.secrets.filter((secret) => secret.providerCredentialProfile === options.profile); if (secrets.length === 0) { const declared = agentRunProviderCredentialRefs(target.spec).map((item) => item.profile); throw new Error(`provider profile ${options.profile} is not declared in ${target.configPath}; declared profiles: ${declared.join(", ") || "(none)"}`); } return { ...target, profile: options.profile, secrets }; } function providerProfilePlan(options: ProviderProfileOptions): Record { const { configPath, spec, profile, secrets } = resolveProviderProfile(options); const inspected = inspectProviderProfileSources(spec, secrets); const source = providerProfileSourceSummary(inspected); const discloseFull = options.full || options.raw; const values = inspected.map(({ secret, inspection }) => { return { id: secret.id, sourceRef: secret.sourceRef, sourceMode: secret.sourceMode, transform: secret.transform, targetRef: secret.targetRef, key: secret.targetRef.key, codexConfig: secret.codexConfig === null ? null : codexConfigSummary(secret.codexConfig), ...inspection.summary, valuesPrinted: false, }; }); return { ok: source.ready, command: "agentrun provider-profile plan", mode: "yaml-first-online-profile", mutation: false, status: source.ready ? "ready" : "blocked", degradedReason: source.ready ? null : "lane-secret-source-unavailable", blockedBeforeMutation: !source.ready, configPath, target: discloseFull ? agentRunLaneSummary(spec) : compactAgentRunLaneStatusTarget(spec), profile: providerProfileSummary(spec, profile, secrets), source: discloseFull ? source : compactProviderProfileSource(source), plan: { secretCount: values.length, secrets: discloseFull ? values : compactProviderProfilePlanSecrets(values), runtime: { managerDeployment: spec.runtime.managerDeployment, runnerEgressProxyUrl: spec.deployment.runner.egressProxyUrl, runnerNoProxyExtra: spec.deployment.runner.noProxyExtra, providerServices: providerRuntimeServiceTargets(secrets), valuesPrinted: false, }, applySteps: ["activation-preflight", "provider-secret-no-op-skip", "gitops-publish-current-image", "argocd-refresh-sync", "manager-rollout-restart"], activationPreflightRequired: true, providerSecretMutationPolicy: { mode: "fail-closed-until-manager-activation-fence", exactNoOpWriteSkipped: true, trackingIssue: AGENTRUN_PROVIDER_ACTIVATION_FENCE_ISSUE, valuesPrinted: false, }, rebuildImages: false, pipelineRun: false, valuesPrinted: false, }, next: { apply: `bun scripts/cli.ts agentrun provider-profile apply --node ${spec.nodeId} --lane ${spec.lane} --profile ${profile} --confirm`, status: `bun scripts/cli.ts agentrun provider-profile status --node ${spec.nodeId} --lane ${spec.lane} --profile ${profile}`, }, valuesPrinted: false, }; } function compactProviderProfilePlanSecrets(values: readonly Record[]): Record[] { return values.map((item) => ({ id: item.id ?? null, targetRef: item.targetRef ?? null, key: item.key ?? null, present: item.present === true, fingerprint: item.fingerprint ?? null, valueBytes: item.valueBytes ?? null, degradedReason: item.degradedReason ?? null, valuesPrinted: false, })); } async function providerProfileStatus(config: UniDeskConfig, options: ProviderProfileOptions): Promise> { const { configPath, spec, profile, secrets } = resolveProviderProfile(options); const inspected = inspectProviderProfileSources(spec, secrets); const source = providerProfileSourceSummary(inspected); if (!source.ready) return providerProfileSourceUnavailable("status", configPath, spec, profile, secrets, source); const live = await capture(config, spec.nodeKubeRoute, ["sh", "--", providerProfileLiveStatusScript(spec, secrets)]); const payload = captureJsonPayload(live); const comparison = providerProfileLiveComparison(inspected, payload); const discloseFull = options.full || options.raw; const yamlDesired = { secrets: inspected.map(({ secret, inspection }) => ({ id: secret.id, targetRef: secret.targetRef, key: secret.targetRef.key, fingerprint: inspection.summary.fingerprint, valueBytes: inspection.summary.valueBytes, codexConfig: secret.codexConfig === null ? null : codexConfigSummary(secret.codexConfig), valuesPrinted: false, })), runnerNoProxyExtra: spec.deployment.runner.noProxyExtra, runnerEgressProxyUrl: spec.deployment.runner.egressProxyUrl, valuesPrinted: false, }; return { ok: live.exitCode === 0 && payload.ok !== false && payload.runtimeSecretValuesDecoded === false && comparison.ready, command: "agentrun provider-profile status", mode: "yaml-first-online-profile", mutation: false, configPath, target: options.full ? agentRunLaneSummary(spec) : compactAgentRunLaneStatusTarget(spec), profile: providerProfileSummary(spec, profile, secrets), source: discloseFull ? source : compactProviderProfileSource(source), yamlDesired: discloseFull ? yamlDesired : compactProviderProfileDesired(yamlDesired), live: discloseFull ? { ...payload, desiredComparison: comparison, valuesPrinted: false } : compactProviderProfileLive(payload, comparison), capture: compactCapture(live, { full: options.full || options.raw || live.exitCode !== 0, stdoutTailChars: 3000, stderrTailChars: 3000 }), next: { apply: `bun scripts/cli.ts agentrun provider-profile apply --node ${spec.nodeId} --lane ${spec.lane} --profile ${profile} --confirm`, validate: `bun scripts/cli.ts agentrun provider-profile validate --node ${spec.nodeId} --lane ${spec.lane} --profile ${profile} --wait`, }, valuesPrinted: false, }; } function compactProviderProfileSource(source: ReturnType): Record { return { ready: source.ready, count: source.count, presentCount: source.presentCount, missingCount: source.missingCount, missing: source.items .filter((item) => item.present !== true) .map((item) => ({ id: item.id ?? null, targetRef: item.targetRef ?? null, degradedReason: item.degradedReason ?? null, valuesPrinted: false })), valuesPrinted: false, }; } function compactProviderProfileDesired(desired: Record): Record { const secrets = Array.isArray(desired.secrets) ? desired.secrets.map(record) : []; return { secretCount: secrets.length, secrets: secrets.map((item) => ({ id: item.id ?? null, targetRef: item.targetRef ?? null, fingerprint: item.fingerprint ?? null, valueBytes: item.valueBytes ?? null, valuesPrinted: false, })), runnerEgressProxyUrl: desired.runnerEgressProxyUrl ?? null, runnerNoProxyExtraCount: Array.isArray(desired.runnerNoProxyExtra) ? desired.runnerNoProxyExtra.length : 0, valuesPrinted: false, }; } function compactProviderProfileLive(payload: Record, comparison: ReturnType): Record { const manager = record(payload.manager); const secrets = record(payload.secrets); return { ok: payload.ok === true, manager: { deploymentExists: manager.deploymentExists === true, readyReplicas: manager.readyReplicas ?? null, replicas: manager.replicas ?? null, sourceCommit: manager.sourceCommit ?? null, envIdentity: manager.envIdentity ?? null, runnerEgressProxyUrl: manager.runnerEgressProxyUrl ?? null, noProxyContainsHyue: manager.noProxyContainsHyue === true, noProxyContainsPikapython: manager.noProxyContainsPikapython === true, valuesPrinted: false, }, secrets: { count: secrets.count ?? null, ready: secrets.ready === true, items: Array.isArray(secrets.items) ? secrets.items.map(record).map((item) => ({ namespace: item.namespace ?? null, name: item.name ?? null, key: item.key ?? null, present: item.present === true, keyPresent: item.keyPresent === true, encodedBytes: item.encodedBytes ?? null, encodedFingerprint: item.encodedFingerprint ?? null, valuesPrinted: false, })) : [], valuesPrinted: false, }, providerServices: { count: record(payload.providerServices).count ?? null, ready: record(payload.providerServices).ready === true, items: Array.isArray(record(payload.providerServices).items) ? (record(payload.providerServices).items as unknown[]).map(record).map((item) => ({ providerId: item.providerId ?? null, baseUrl: item.baseUrl ?? null, namespace: item.namespace ?? null, name: item.name ?? null, port: item.port ?? null, present: item.present === true, portPresent: item.portPresent === true, valuesPrinted: false, })) : [], valuesPrinted: false, }, desiredComparison: comparison, runtimeSecretValuesDecoded: payload.runtimeSecretValuesDecoded === true, valuesPrinted: false, }; } async function providerProfileApply(config: UniDeskConfig, options: ProviderProfileOptions): Promise> { const { configPath, spec, profile, secrets } = resolveProviderProfile(options); if (options.dryRun || !options.confirm) return providerProfilePlan({ ...options, dryRun: true, confirm: false }); const inspected = inspectProviderProfileSources(spec, secrets); const source = providerProfileSourceSummary(inspected); if (!source.ready) return providerProfileSourceUnavailable("apply", configPath, spec, profile, secrets, source); providerProfileApplyProgress(spec, profile, "activation-preflight", "running"); const activationPreflight = await providerSecretActivationPreflight(config, spec, providerProfileActivationTargets(inspected), { full: options.full || options.raw }); providerProfileApplyProgress(spec, profile, "activation-preflight", activationPreflight.ok ? "succeeded" : "failed"); if (activationPreflight.ok !== true) { return providerProfileActivationBlockedResult(spec, profile, secrets, activationPreflight, "worker-before-secret-sync"); } const secretPayload = { ok: true, status: "skipped-exact-provider-secret-no-op", secretSyncStarted: false, providerSecretWriteSkipped: true, skippedProviderSecretCount: inspected.length, reason: "provider-secret-writes-require-agentrun-manager-activation-fence", activationFenceIssue: AGENTRUN_PROVIDER_ACTIVATION_FENCE_ISSUE, runtimeSecretValuesDecoded: false, valuesPrinted: false, }; providerProfileApplyProgress(spec, profile, "secret-sync", "succeeded", { status: secretPayload.status }); providerProfileApplyProgress(spec, profile, "current-image-resolve", "running"); const liveBefore = await capture(config, spec.nodeKubeRoute, ["sh", "--", currentManagerImageScript(spec)]); const liveBeforePayload = captureJsonPayload(liveBefore); const sourceCommit = stringOrNull(liveBeforePayload.sourceCommit); const envIdentity = stringOrNull(liveBeforePayload.envIdentity); const digest = stringOrNull(liveBeforePayload.digest); if (liveBefore.exitCode !== 0 || sourceCommit === null || envIdentity === null || digest === null) { providerProfileApplyProgress(spec, profile, "current-image-resolve", "failed"); return applyFailure(configPath, spec, profile, "current-image-resolve", liveBeforePayload, liveBefore); } providerProfileApplyProgress(spec, profile, "current-image-resolve", "succeeded", { sourceCommit }); const image = agentRunImageArtifact(spec, { sourceCommit, envIdentity, digest, status: "reused" }); const renderedFiles = renderAgentRunGitopsFiles(spec, { sourceCommit, image }); providerProfileApplyProgress(spec, profile, "gitops-publish", "running", { sourceCommit }); const publish = await publishProfileGitops(config, spec, sourceCommit, renderedFiles); providerProfileApplyProgress(spec, profile, "gitops-publish", publish.ok ? "succeeded" : "failed", { sourceCommit }); if (publish.ok !== true) { return { ok: false, command: "agentrun provider-profile apply", mode: "confirmed-online-profile", mutation: true, configPath, target: compactAgentRunLaneStatusTarget(spec), profile, phase: "gitops-publish", result: publish, valuesPrinted: false }; } providerProfileApplyProgress(spec, profile, "argocd-refresh-sync", "running", { sourceCommit }); const argo = await capture(config, spec.nodeKubeRoute, ["sh", "--", providerProfileArgoRefreshSyncScript(spec)]); const argoPayload = captureJsonPayload(argo); providerProfileApplyProgress(spec, profile, "argocd-refresh-sync", argo.exitCode === 0 && argoPayload.ok !== false ? "succeeded" : "failed", { sourceCommit }); if (argo.exitCode !== 0 || argoPayload.ok === false) { return applyFailure(configPath, spec, profile, "argocd-refresh-sync", argoPayload, argo); } providerProfileApplyProgress(spec, profile, "manager-restart", "running", { sourceCommit }); const restart = await capture(config, spec.nodeKubeRoute, ["sh", "--", restartYamlLaneScript(spec)]); const restartPayload = captureJsonPayload(restart); providerProfileApplyProgress(spec, profile, "manager-restart", restart.exitCode === 0 && restartPayload.ok !== false ? "succeeded" : "failed", { sourceCommit }); if (restart.exitCode !== 0 || restartPayload.ok === false) { return applyFailure(configPath, spec, profile, "manager-restart", restartPayload, restart); } providerProfileApplyProgress(spec, profile, "runtime-status", "running", { sourceCommit }); const liveAfter = await capture(config, spec.nodeKubeRoute, ["sh", "--", providerProfileLiveStatusScript(spec, secrets)]); const liveAfterPayload = captureJsonPayload(liveAfter); providerProfileApplyProgress(spec, profile, "runtime-status", liveAfter.exitCode === 0 && liveAfterPayload.ok !== false ? "succeeded" : "failed", { sourceCommit }); return { ok: liveAfter.exitCode === 0 && liveAfterPayload.ok !== false, command: "agentrun provider-profile apply", mode: "confirmed-online-profile", mutation: true, configPath, target: compactAgentRunLaneStatusTarget(spec), profile: providerProfileSummary(spec, profile, secrets), steps: { activationPreflight, secretSync: secretPayload, gitops: publish.payload, argo: argoPayload, restart: restartPayload, live: liveAfterPayload, valuesPrinted: false, }, renderedFiles: { count: renderedFiles.length, digest: renderedFilesDigest(renderedFiles), valuesPrinted: false, }, image: { sourceCommit, envIdentity, digest, status: "reused", valuesPrinted: false }, rebuildImages: false, pipelineRun: false, captures: options.full || options.raw || liveAfter.exitCode !== 0 ? { argo: compactCapture(argo, { full: options.full || options.raw || argo.exitCode !== 0 }), restart: compactCapture(restart, { full: options.full || options.raw || restart.exitCode !== 0 }), live: compactCapture(liveAfter, { full: options.full || options.raw || liveAfter.exitCode !== 0 }), } : undefined, next: { status: `bun scripts/cli.ts agentrun provider-profile status --node ${spec.nodeId} --lane ${spec.lane} --profile ${profile}`, validate: `bun scripts/cli.ts agentrun provider-profile validate --node ${spec.nodeId} --lane ${spec.lane} --profile ${profile} --wait`, }, valuesPrinted: false, }; } function providerProfileActivationBlockedResult( spec: AgentRunLaneSpec, profile: string, secrets: readonly AgentRunLaneSecretSpec[], activationPreflight: Record, stage: "async-submit" | "worker-before-secret-sync", ): Record { return { ok: false, command: "agentrun provider-profile apply", mode: stage === "async-submit" ? "activation-preflight-blocked-before-job" : "activation-preflight-blocked-before-secret-sync", status: "blocked", degradedReason: activationPreflight.reason ?? "existing-runner-provider-secret-activation-risk", phase: "activation-preflight", stage, mutation: false, blockedBeforeMutation: true, jobCreated: stage === "worker-before-secret-sync", jobAlreadyRunning: stage === "worker-before-secret-sync", secretSyncStarted: false, target: { node: spec.nodeId, lane: spec.lane, namespace: spec.runtime.namespace, valuesPrinted: false }, profile: providerProfileSummary(spec, profile, secrets), activationPreflight, stagedActivation: providerSecretStagedActivation(spec, [profile]), next: { observeRunners: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane} --dry-run`, retryAfterManagerFence: `bun scripts/cli.ts agentrun provider-profile apply --node ${spec.nodeId} --lane ${spec.lane} --profile ${profile} --confirm`, }, valuesPrinted: false, }; } function providerProfileApplyProgress( spec: AgentRunLaneSpec, profile: string, phase: string, status: "running" | "succeeded" | "failed", detail: Record = {}, ): void { progressEvent("agentrun.provider-profile.apply.progress", { node: spec.nodeId, lane: spec.lane, profile, phase, status, ...detail, valuesPrinted: false, }); } async function providerProfileValidate(config: UniDeskConfig, options: ProviderProfileOptions): Promise> { const { configPath, spec, profile, secrets } = resolveProviderProfile(options); const inspected = inspectProviderProfileSources(spec, secrets); const source = providerProfileSourceSummary(inspected); if (!source.ready) return providerProfileSourceUnavailable("validate", configPath, spec, profile, secrets, source); const prompt = options.prompt ?? `请只回复:${profile} upstream OK`; const live = await capture(config, spec.nodeKubeRoute, ["sh", "--", providerProfileLiveStatusScript(spec, secrets)]); const livePayload = captureJsonPayload(live); const canRun = live.exitCode === 0 && livePayload.ok !== false; return { ok: canRun, command: "agentrun provider-profile validate", mode: "profile-runtime-preflight", mutation: false, configPath, target: compactAgentRunLaneStatusTarget(spec), profile: providerProfileSummary(spec, profile, secrets), source, promptBytes: Buffer.byteLength(prompt, "utf8"), live: livePayload, note: "This command validates YAML/live profile wiring. User-entry Web validation still runs through HWLAB web-probe so it uses the same Workbench path.", webProbeNext: `bun scripts/cli.ts web-probe observe command --type sendPrompt --provider ${profile} --text '${prompt.replaceAll("'", "'\\''")}'`, valuesPrinted: false, }; } interface ProviderProfileInspectedSecret { readonly secret: AgentRunLaneSecretSpec; readonly source: LaneSecretSource | null; readonly inspection: LaneSecretSourceInspection; } function inspectProviderProfileSources(spec: AgentRunLaneSpec, secrets: readonly AgentRunLaneSecretSpec[]): ProviderProfileInspectedSecret[] { const sources = new Map(collectLaneSecretSources(spec).map((source) => [source.id, source])); return secrets.map((secret) => { const source = sources.get(secret.id) ?? null; if (source !== null) return { secret, source, inspection: inspectSecretSourceValue(spec, source) }; return { secret, source, inspection: { ok: false, sourceValue: null, summary: { present: false, sourceFilePresent: false, sourcePath: secret.sourceRef, fingerprint: null, valueBytes: null, degradedReason: "lane-secret-source-declaration-missing", message: `secret ${secret.id} has no resolved lane source`, valuesPrinted: false, }, }, }; }); } function providerProfileActivationTargets(items: readonly ProviderProfileInspectedSecret[]): ProviderSecretActivationTarget[] { return items.map(({ secret, inspection }) => { if (inspection.sourceValue === null) throw new Error(`provider profile source ${secret.id} is unavailable before activation preflight`); return { id: secret.id, targetRef: secret.targetRef, desiredEncodedFingerprint: providerSecretDesiredEncodedFingerprint(inspection.sourceValue.value), }; }); } function providerProfileSourceSummary(items: readonly ProviderProfileInspectedSecret[]): { ready: boolean; count: number; presentCount: number; missingCount: number; items: Record[]; valuesPrinted: false; } { const summaries = items.map(({ secret, inspection }) => ({ id: secret.id, sourceRef: secret.sourceRef, sourceMode: secret.sourceMode, transform: secret.transform, targetRef: secret.targetRef, ...inspection.summary, valuesPrinted: false, })); const presentCount = summaries.filter((item) => item.present === true).length; return { ready: items.length > 0 && items.every((item) => item.inspection.ok), count: items.length, presentCount, missingCount: items.length - presentCount, items: summaries, valuesPrinted: false, }; } function providerProfileSourceUnavailable( action: "status" | "apply" | "validate", configPath: string, spec: AgentRunLaneSpec, profile: string, secrets: readonly AgentRunLaneSecretSpec[], source: ReturnType, ): Record { return { ok: false, command: `agentrun provider-profile ${action}`, mode: "profile-source-preflight", status: "blocked", degradedReason: "lane-secret-source-unavailable", mutation: false, blockedBeforeMutation: true, configPath, target: compactAgentRunLaneStatusTarget(spec), profile: providerProfileSummary(spec, profile, secrets), source, next: { recheck: `bun scripts/cli.ts agentrun provider-profile status --node ${spec.nodeId} --lane ${spec.lane} --profile ${profile} --raw`, applyAfterOwnerProvision: `bun scripts/cli.ts agentrun provider-profile apply --node ${spec.nodeId} --lane ${spec.lane} --profile ${profile} --confirm`, }, valuesPrinted: false, }; } function providerProfileLiveComparison(items: readonly ProviderProfileInspectedSecret[], payload: Record): Record & { ready: boolean } { const liveItems = Array.isArray(record(payload.secrets).items) ? (record(payload.secrets).items as unknown[]).map(record) : []; const byTarget = new Map(liveItems.map((item) => [ `${stringOrNull(item.namespace) ?? ""}/${stringOrNull(item.name) ?? ""}/${stringOrNull(item.key) ?? ""}`, item, ])); const comparisons = items.map(({ secret, inspection }) => { const target = secret.targetRef; const live = byTarget.get(`${target.namespace}/${target.name}/${target.key}`) ?? {}; const desiredEncodedFingerprint = inspection.sourceValue === null ? null : sha256Fingerprint(Buffer.from(inspection.sourceValue.value, "utf8").toString("base64")); const liveEncodedFingerprint = stringOrNull(live.encodedFingerprint); return { id: secret.id, targetRef: target, desiredEncodedFingerprint, liveEncodedFingerprint, present: live.present === true, keyPresent: live.keyPresent === true, encodedFingerprintMatches: desiredEncodedFingerprint !== null && liveEncodedFingerprint === desiredEncodedFingerprint, valuesPrinted: false, }; }); return { ready: comparisons.length > 0 && comparisons.every((item) => item.keyPresent && item.encodedFingerprintMatches), count: comparisons.length, matchingCount: comparisons.filter((item) => item.encodedFingerprintMatches).length, items: comparisons, valuesPrinted: false, }; } function applyFailure(configPath: string, spec: AgentRunLaneSpec, profile: string, phase: string, payload: Record, captureResult: Awaited>): Record { return { ok: false, command: "agentrun provider-profile apply", mode: "confirmed-online-profile", mutation: true, configPath, target: compactAgentRunLaneStatusTarget(spec), profile, phase, result: payload, capture: compactCapture(captureResult, { full: true, stdoutTailChars: 4000, stderrTailChars: 4000 }), valuesPrinted: false, }; } async function publishProfileGitops(config: UniDeskConfig, spec: AgentRunLaneSpec, sourceCommit: string, files: readonly { path: string; content: string }[]): Promise & { ok: boolean; payload: Record }> { const jobName = `profile-gitops-${spec.nodeId.toLowerCase()}-${spec.lane}-${Date.now().toString(36)}`.slice(0, 63); const manifest = yamlLaneGitopsPublishJobManifest(spec, files, jobName); const created = await capture(config, spec.nodeKubeRoute, ["sh", "--", createYamlLaneJobScript(spec.gitMirror.namespace, jobName, manifest)]); if (created.exitCode !== 0) return { ok: false, payload: { ok: false, status: "create-failed", jobName, valuesPrinted: false }, capture: compactCapture(created, { full: true }), valuesPrinted: false }; const startedAt = Date.now(); let polls = 0; let lastPayload: Record = {}; while (Date.now() - startedAt < 300_000) { polls += 1; const probe = await capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneJobProbeScript(spec.gitMirror.namespace, jobName)]); const probePayload = captureJsonPayload(probe); const publishPayload = yamlLaneGitopsPublishPayloadFromProbe(probePayload); if (Object.keys(publishPayload).length > 0) lastPayload = publishPayload; progressEvent("agentrun.provider-profile.gitops-publish.progress", { node: spec.nodeId, lane: spec.lane, sourceCommit, jobName, polls, status: stringOrNull(publishPayload.status) ?? "running", valuesPrinted: false }); if (probePayload.succeeded === true) return { ok: publishPayload.ok === true, payload: publishPayload, jobName, polls, elapsedMs: Date.now() - startedAt, probe: compactCapture(probe), valuesPrinted: false }; if (probePayload.failed === true) return { ok: false, payload: { ...lastPayload, ok: false, status: "failed", jobName, valuesPrinted: false }, jobName, polls, elapsedMs: Date.now() - startedAt, probe: compactCapture(probe, { full: true }), valuesPrinted: false }; await sleep(3_000); } return { ok: false, payload: { ...lastPayload, ok: false, status: "timeout", jobName, valuesPrinted: false }, jobName, polls, elapsedMs: Date.now() - startedAt, valuesPrinted: false }; } function providerProfileSummary(spec: AgentRunLaneSpec, profile: string, secrets: readonly AgentRunLaneSecretSpec[]): Record { const credential = agentRunProviderCredentialRefs(spec, profile)[0] ?? null; return { profile, declared: true, backendProfile: profile === "gpt.pika" ? "gpt-pika" : profile, secretIds: secrets.map((secret) => secret.id), secretRef: credential?.secretRef ?? null, configuredKeys: credential?.secretRef.keys ?? secrets.map((secret) => secret.targetRef.key), valuesPrinted: false, }; } function codexConfigSummary(config: NonNullable): Record { return { modelProvider: config.modelProvider, model: config.model, reviewModel: config.reviewModel, modelContextWindow: config.modelContextWindow, modelAutoCompactTokenLimit: config.modelAutoCompactTokenLimit, modelCatalogJson: config.modelCatalogJson, approvalsReviewer: config.approvalsReviewer, modelProviderCount: config.modelProviders.length, providers: config.modelProviders.map((provider) => ({ id: provider.id, baseUrl: provider.baseUrl, wireApi: provider.wireApi, requiresOpenaiAuth: provider.requiresOpenaiAuth, })), valuesPrinted: false, }; } function providerRuntimeServiceTargets(secrets: readonly AgentRunLaneSecretSpec[]): Record[] { const targets: Record[] = []; const seen = new Set(); for (const secret of secrets) { for (const provider of secret.codexConfig?.modelProviders ?? []) { const parsed = new URL(provider.baseUrl); const hostParts = parsed.hostname.split("."); if (hostParts.length < 4 || hostParts[2] !== "svc" || hostParts[3] !== "cluster") continue; const port = parsed.port.length > 0 ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80; const key = `${provider.id}/${hostParts[1]}/${hostParts[0]}/${port}`; if (seen.has(key)) continue; seen.add(key); targets.push({ providerId: provider.id, baseUrl: provider.baseUrl, host: parsed.hostname, namespace: hostParts[1], name: hostParts[0], port, valuesPrinted: false, }); } } return targets; } function currentManagerImageScript(spec: AgentRunLaneSpec): string { return [ "set -eu", `namespace=${shQuote(spec.runtime.namespace)}`, `deployment=${shQuote(spec.runtime.managerDeployment)}`, "kubectl -n \"$namespace\" get deployment \"$deployment\" -o json >/tmp/agentrun-provider-profile-current-manager.json", "node <<'NODE'", "const fs = require('node:fs');", " const j = JSON.parse(fs.readFileSync('/tmp/agentrun-provider-profile-current-manager.json', 'utf8'));", " const c = (j.spec?.template?.spec?.containers || [])[0] || {};", " const env = new Map((c.env || []).map((e) => [e.name, e.value || null]));", " const image = c.image || '';", " const digest = image.includes('@') ? image.split('@').pop() : null;", " console.log(JSON.stringify({ ok: Boolean(env.get('AGENTRUN_SOURCE_COMMIT') && env.get('AGENTRUN_ENV_IDENTITY') && digest), sourceCommit: env.get('AGENTRUN_SOURCE_COMMIT'), envIdentity: env.get('AGENTRUN_ENV_IDENTITY'), digest, imageStatus: 'reused', valuesPrinted: false }));", "NODE", ].join("\n"); } function providerProfileLiveStatusScript(spec: AgentRunLaneSpec, secrets: readonly AgentRunLaneSecretSpec[]): string { const refs = Buffer.from(JSON.stringify({ secrets: secrets.map((secret) => secret.targetRef), providerServices: providerRuntimeServiceTargets(secrets), }), "utf8").toString("base64"); return [ "set -eu", `namespace=${shQuote(spec.runtime.namespace)}`, `deployment=${shQuote(spec.runtime.managerDeployment)}`, `refs_b64=${shQuote(refs)}`, "kubectl -n \"$namespace\" get deployment \"$deployment\" -o json >/tmp/agentrun-provider-profile-deploy.json", "REFS_B64=\"$refs_b64\" node <<'NODE'", "const fs = require('node:fs');", "const cp = require('node:child_process');", "const refs = JSON.parse(Buffer.from(process.env.REFS_B64 || '', 'base64').toString('utf8'));", "const deployment = JSON.parse(fs.readFileSync('/tmp/agentrun-provider-profile-deploy.json', 'utf8'));", "const container = deployment.spec?.template?.spec?.containers?.[0] || {};", "const env = new Map((container.env || []).map((e) => [e.name, e.value || null]));", "const noProxy = env.get('AGENTRUN_RUNNER_NO_PROXY_EXTRA') || '';", "const secretItems = (refs.secrets || []).map((ref) => {", " const out = cp.spawnSync('kubectl', ['-n', ref.namespace, 'get', 'secret', ref.name, '-o', 'json'], { encoding: 'utf8' });", " if (out.status !== 0) return { namespace: ref.namespace, name: ref.name, key: ref.key, present: false, keyPresent: false, valuesPrinted: false };", " const secret = JSON.parse(out.stdout);", " const raw = secret.data?.[ref.key] || '';", " const encodedBytes = raw ? Buffer.byteLength(raw, 'utf8') : 0;", " const encodedFingerprint = raw ? 'sha256:' + require('node:crypto').createHash('sha256').update(raw).digest('hex') : null;", " return { namespace: ref.namespace, name: ref.name, key: ref.key, present: true, keyPresent: Boolean(raw), encodedBytes, encodedFingerprint, valuesPrinted: false };", "});", "const serviceItems = (refs.providerServices || []).map((target) => {", " const out = cp.spawnSync('kubectl', ['-n', target.namespace, 'get', 'service', target.name, '-o', 'json'], { encoding: 'utf8' });", " if (out.status !== 0) return { ...target, present: false, portPresent: false, valuesPrinted: false };", " const service = JSON.parse(out.stdout);", " const ports = Array.isArray(service.spec?.ports) ? service.spec.ports : [];", " return { ...target, present: true, portPresent: ports.some((item) => Number(item.port) === Number(target.port)), valuesPrinted: false };", "});", "const secretsReady = secretItems.every((item) => item.keyPresent);", "const servicesReady = serviceItems.every((item) => item.present && item.portPresent);", "console.log(JSON.stringify({ ok: secretsReady && servicesReady, manager: { deploymentExists: Boolean(deployment.metadata?.name), readyReplicas: deployment.status?.readyReplicas ?? null, replicas: deployment.status?.replicas ?? null, sourceCommit: env.get('AGENTRUN_SOURCE_COMMIT'), envIdentity: env.get('AGENTRUN_ENV_IDENTITY'), runnerEgressProxyUrl: env.get('AGENTRUN_RUNNER_EGRESS_PROXY_URL'), runnerNoProxyExtra: noProxy, noProxyContainsHyue: noProxy.includes('hyueapi.com'), noProxyContainsPikapython: noProxy.includes('api.pikapython.com') || noProxy.includes('.pikapython.com'), valuesPrinted: false }, secrets: { count: secretItems.length, ready: secretsReady, items: secretItems, valuesPrinted: false }, providerServices: { count: serviceItems.length, ready: servicesReady, items: serviceItems, valuesPrinted: false }, runtimeSecretValuesDecoded: false, valuesPrinted: false }));", "NODE", ].join("\n"); } function providerProfileRuntimeStatusScript(spec: AgentRunLaneSpec, secrets: readonly AgentRunLaneSecretSpec[]): string { const refs = Buffer.from(JSON.stringify({ secrets: secrets.map((secret) => ({ id: secret.id, targetRef: secret.targetRef })), providerServices: providerRuntimeServiceTargets(secrets), }), "utf8").toString("base64"); return [ "set -eu", `namespace=${shQuote(spec.runtime.namespace)}`, `deployment=${shQuote(spec.runtime.managerDeployment)}`, `refs_b64=${shQuote(refs)}`, "kubectl -n \"$namespace\" get deployment \"$deployment\" -o json >/tmp/agentrun-provider-profile-runtime-deploy.json", "REFS_B64=\"$refs_b64\" node <<'NODE'", "const fs = require('node:fs');", "const cp = require('node:child_process');", "const refs = JSON.parse(Buffer.from(process.env.REFS_B64 || '', 'base64').toString('utf8'));", "const deployment = JSON.parse(fs.readFileSync('/tmp/agentrun-provider-profile-runtime-deploy.json', 'utf8'));", "const secretItems = (refs.secrets || []).map((entry) => {", " const ref = entry.targetRef || {};", " const out = cp.spawnSync('kubectl', ['-n', ref.namespace, 'get', 'secret', ref.name, '-o', 'json'], { encoding: 'utf8' });", " if (out.status !== 0) return { id: entry.id || null, namespace: ref.namespace || null, name: ref.name || null, key: ref.key || null, present: false, keyPresent: false, valuesPrinted: false };", " const secret = JSON.parse(out.stdout);", " const data = secret.data && typeof secret.data === 'object' ? secret.data : {};", " const keyPresent = Object.prototype.hasOwnProperty.call(data, ref.key) && typeof data[ref.key] === 'string' && data[ref.key].length > 0;", " return { id: entry.id || null, namespace: ref.namespace || null, name: ref.name || null, key: ref.key || null, present: true, keyPresent, valuesPrinted: false };", "});", "const serviceItems = (refs.providerServices || []).map((target) => {", " const out = cp.spawnSync('kubectl', ['-n', target.namespace, 'get', 'service', target.name, '-o', 'json'], { encoding: 'utf8' });", " if (out.status !== 0) return { ...target, present: false, portPresent: false, valuesPrinted: false };", " const service = JSON.parse(out.stdout);", " const ports = Array.isArray(service.spec?.ports) ? service.spec.ports : [];", " return { ...target, present: true, portPresent: ports.some((item) => Number(item.port) === Number(target.port)), valuesPrinted: false };", "});", "const secretsReady = secretItems.length > 0 && secretItems.every((item) => item.keyPresent);", "const servicesReady = serviceItems.every((item) => item.present && item.portPresent);", "console.log(JSON.stringify({ ok: secretsReady && servicesReady, manager: { deploymentExists: Boolean(deployment.metadata?.name), readyReplicas: deployment.status?.readyReplicas ?? null, replicas: deployment.status?.replicas ?? null, valuesPrinted: false }, secrets: { count: secretItems.length, ready: secretsReady, presentCount: secretItems.filter((item) => item.present).length, keyPresentCount: secretItems.filter((item) => item.keyPresent).length, items: secretItems, valuesPrinted: false }, providerServices: { count: serviceItems.length, ready: servicesReady, items: serviceItems, valuesPrinted: false }, ownerSourceInspected: false, runtimeSecretValuesDecoded: false, valuesPrinted: false }));", "NODE", ].join("\n"); } function providerProfileArgoRefreshSyncScript(spec: AgentRunLaneSpec): string { return [ refreshYamlLaneScript(spec), `namespace=${shQuote(spec.gitops.argoNamespace)}`, `application=${shQuote(spec.gitops.argoApplication)}`, "kubectl -n \"$namespace\" patch application \"$application\" --type=merge -p '{\"operation\":{\"sync\":{\"syncStrategy\":{\"hook\":{}}}}}' >/tmp/agentrun-provider-profile-argo-sync.out 2>/tmp/agentrun-provider-profile-argo-sync.err || true", "for i in $(seq 1 60); do", " app_json=$(kubectl -n \"$namespace\" get application \"$application\" -o json 2>/dev/null || printf '{}')", " APP_JSON=\"$app_json\" node <<'NODE' >/tmp/agentrun-provider-profile-argo-state.json", "const app = JSON.parse(process.env.APP_JSON || '{}');", "const sync = app.status?.sync || {};", "const health = app.status?.health || {};", "const op = app.status?.operationState || null;", "console.log(JSON.stringify({sync: sync.status || null, health: health.status || null, revision: sync.revision || null, operationPhase: op?.phase || null, message: op?.message || null}));", "NODE", " if node -e \"const s=require('fs').readFileSync('/tmp/agentrun-provider-profile-argo-state.json','utf8'); const j=JSON.parse(s); process.exit(j.sync==='Synced' && j.health==='Healthy' && (!j.operationPhase || j.operationPhase==='Succeeded') ? 0 : 1)\"; then break; fi", " sleep 3", "done", "cat /tmp/agentrun-provider-profile-argo-state.json | node -e \"let s='';process.stdin.on('data',d=>s+=d);process.stdin.on('end',()=>{const j=JSON.parse(s||'{}'); console.log(JSON.stringify({ok:j.sync==='Synced' && j.health==='Healthy', ...j, valuesPrinted:false}));})\"", ].join("\n"); }