From 42272115449f76116d38caeb0e83b34cc8a29518 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 27 Jun 2026 16:04:20 +0000 Subject: [PATCH] fix(hwlab): expose d518 code agent runtime config --- config/hwlab-node-lanes.yaml | 11 ++ scripts/src/hwlab-node-lanes.ts | 42 +++++++ scripts/src/hwlab-node/plan.ts | 15 +++ scripts/src/hwlab-node/render.ts | 83 +++++++++++-- scripts/src/hwlab-node/web-probe.ts | 174 ++++++++++++++++++++++++++++ 5 files changed, 316 insertions(+), 9 deletions(-) diff --git a/config/hwlab-node-lanes.yaml b/config/hwlab-node-lanes.yaml index 95e96940..fb8099fe 100644 --- a/config/hwlab-node-lanes.yaml +++ b/config/hwlab-node-lanes.yaml @@ -689,6 +689,17 @@ lanes: sourceRef: hwlab/d518-v03-code-agent-provider.env openaiSourceKey: OPENAI_API_KEY opencodeSourceKey: OPENCODE_API_KEY + codeAgentRuntime: + enabled: true + adapter: agentrun-v01 + managerUrl: http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080 + apiKeySecretName: hwlab-v03-master-server-admin-api-key + apiKeySecretKey: api-key + runnerNamespace: agentrun-v02 + secretNamespace: agentrun-v02 + repoUrlFrom: runtimeGitReadUrl + providerIdFrom: runtimeNodeId + defaultProviderProfile: deepseek publicExposure: mode: pk01-caddy-frp publicBaseUrl: https://hwlab.pikapython.com diff --git a/scripts/src/hwlab-node-lanes.ts b/scripts/src/hwlab-node-lanes.ts index ce018fd8..089f3def 100644 --- a/scripts/src/hwlab-node-lanes.ts +++ b/scripts/src/hwlab-node-lanes.ts @@ -313,6 +313,19 @@ export interface HwlabRuntimeCodeAgentProviderSpec { readonly opencodeSourceKey?: string; } +export interface HwlabRuntimeCodeAgentRuntimeSpec { + readonly enabled: boolean; + readonly adapter: string; + readonly managerUrl: string; + readonly apiKeySecretName: string; + readonly apiKeySecretKey: string; + readonly runnerNamespace: string; + readonly secretNamespace: string; + readonly repoUrlFrom: "runtimeGitReadUrl"; + readonly providerIdFrom: "runtimeNodeId"; + readonly defaultProviderProfile: string; +} + export interface HwlabRuntimeSourceWorkspaceSpec { readonly requiredCommands: readonly string[]; readonly requiredFiles: readonly string[]; @@ -361,6 +374,7 @@ export interface HwlabRuntimeLaneSpec { readonly buildkit?: HwlabRuntimeBuildkitSpec; readonly bootstrapAdmin?: HwlabRuntimeBootstrapAdminSpec; readonly codeAgentProvider?: HwlabRuntimeCodeAgentProviderSpec; + readonly codeAgentRuntime?: HwlabRuntimeCodeAgentRuntimeSpec; readonly sourceWorkspace?: HwlabRuntimeSourceWorkspaceSpec; readonly externalPostgres?: HwlabRuntimeExternalPostgresSpec; readonly runtimeStore?: HwlabRuntimeStoreSpec; @@ -412,6 +426,7 @@ interface HwlabLaneConfig { readonly buildkit?: HwlabRuntimeBuildkitSpec; readonly bootstrapAdmin?: HwlabRuntimeBootstrapAdminSpec; readonly codeAgentProvider?: HwlabRuntimeCodeAgentProviderSpec; + readonly codeAgentRuntime?: HwlabRuntimeCodeAgentRuntimeSpec; readonly sourceWorkspace?: HwlabRuntimeSourceWorkspaceSpec; readonly externalPostgres?: HwlabRuntimeExternalPostgresSpec; readonly runtimeStore?: HwlabRuntimeStoreSpec; @@ -655,6 +670,7 @@ function laneConfig(id: HwlabRuntimeLane, raw: Record): HwlabLa buildkit: buildkitConfig(raw.buildkit, `lanes.${id}.buildkit`), bootstrapAdmin: bootstrapAdminConfig(raw.bootstrapAdmin, `lanes.${id}.bootstrapAdmin`), codeAgentProvider: codeAgentProviderConfig(raw.codeAgentProvider, `lanes.${id}.codeAgentProvider`), + codeAgentRuntime: codeAgentRuntimeConfig(raw.codeAgentRuntime, `lanes.${id}.codeAgentRuntime`), sourceWorkspace: sourceWorkspaceConfig(raw.sourceWorkspace, `lanes.${id}.sourceWorkspace`), externalPostgres: externalPostgresConfig(raw.externalPostgres, `lanes.${id}.externalPostgres`), runtimeStore: runtimeStoreConfig(raw.runtimeStore, `lanes.${id}.runtimeStore`), @@ -679,6 +695,7 @@ function laneTargetConfig(id: HwlabRuntimeLane, nodeId: string, baseRaw: Record< buildkit: mergeOptionalRecord(baseRaw.buildkit, targetRaw.buildkit), bootstrapAdmin: mergeOptionalRecord(baseRaw.bootstrapAdmin, targetRaw.bootstrapAdmin), codeAgentProvider: mergeOptionalRecord(baseRaw.codeAgentProvider, targetRaw.codeAgentProvider), + codeAgentRuntime: mergeOptionalRecord(baseRaw.codeAgentRuntime, targetRaw.codeAgentRuntime), sourceWorkspace: mergeOptionalRecord(baseRaw.sourceWorkspace, targetRaw.sourceWorkspace), externalPostgres: mergeOptionalRecord(baseRaw.externalPostgres, targetRaw.externalPostgres), runtimeStore: mergeOptionalRecord(baseRaw.runtimeStore, targetRaw.runtimeStore), @@ -746,6 +763,30 @@ function codeAgentProviderConfig(value: unknown, path: string): HwlabRuntimeCode }; } +function codeAgentRuntimeConfig(value: unknown, path: string): HwlabRuntimeCodeAgentRuntimeSpec | undefined { + if (value === undefined) return undefined; + const raw = asRecord(value, path); + const managerUrl = stringField(raw, "managerUrl", path); + try { + const parsed = new URL(managerUrl); + if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("unsupported protocol"); + } catch { + throw new Error(`${path}.managerUrl must be an http(s) URL`); + } + return { + enabled: booleanField(raw, "enabled", path), + adapter: stringField(raw, "adapter", path), + managerUrl, + apiKeySecretName: stringField(raw, "apiKeySecretName", path), + apiKeySecretKey: secretKeyField(raw, "apiKeySecretKey", path), + runnerNamespace: stringField(raw, "runnerNamespace", path), + secretNamespace: stringField(raw, "secretNamespace", path), + repoUrlFrom: enumStringField(raw, "repoUrlFrom", path, ["runtimeGitReadUrl"]), + providerIdFrom: enumStringField(raw, "providerIdFrom", path, ["runtimeNodeId"]), + defaultProviderProfile: stringField(raw, "defaultProviderProfile", path), + }; +} + function commandNameField(value: string, path: string): string { if (!/^[A-Za-z0-9._+-]+$/u.test(value)) throw new Error(`${path} must be a simple command name`); return value; @@ -1351,6 +1392,7 @@ function buildRuntimeLaneSpec(config: HwlabLaneConfig): HwlabRuntimeLaneSpec { ...(config.buildkit === undefined ? {} : { buildkit: config.buildkit }), ...(config.bootstrapAdmin === undefined ? {} : { bootstrapAdmin: config.bootstrapAdmin }), ...(config.codeAgentProvider === undefined ? {} : { codeAgentProvider: config.codeAgentProvider }), + ...(config.codeAgentRuntime === undefined ? {} : { codeAgentRuntime: config.codeAgentRuntime }), ...(config.sourceWorkspace === undefined ? {} : { sourceWorkspace: config.sourceWorkspace }), ...(config.externalPostgres === undefined ? {} : { externalPostgres: config.externalPostgres }), ...(config.runtimeStore === undefined ? {} : { runtimeStore: config.runtimeStore }), diff --git a/scripts/src/hwlab-node/plan.ts b/scripts/src/hwlab-node/plan.ts index ebca62d3..44d02c47 100644 --- a/scripts/src/hwlab-node/plan.ts +++ b/scripts/src/hwlab-node/plan.ts @@ -144,6 +144,21 @@ export function nodeRuntimeExpected(spec: HwlabRuntimeLaneSpec): Record 0 && workloadReadiness.every((item) => item.ready); const localPostgresExpectedAbsent = nodeRuntimeLocalPostgresExpectedAbsent(spec); const localPostgresReady = localPostgresExpectedAbsent ? localPostgresObjects.length === 0 : localPostgresObjects.length > 0; - const runtimeReady = namespaceExists && localPostgresReady && workloadsReady && (activeExternalPostgres === undefined || (bridge.ready && secrets.ready)); + const runtimeReady = namespaceExists && localPostgresReady && workloadsReady && (activeExternalPostgres === undefined || (bridge.ready && secrets.ready)) && codeAgentRuntime.ready === true; + const codeAgentRuntimeDegradedReason = typeof codeAgentRuntime.degradedReason === "string" ? codeAgentRuntime.degradedReason : null; + const runtimeDegradedReason = !namespaceExists + ? "runtime-namespace-missing" + : !localPostgresReady + ? "runtime-local-postgres-not-ready" + : !workloadsReady + ? "runtime-workloads-not-ready" + : activeExternalPostgres !== undefined && bridge.ready !== true + ? "external-postgres-bridge-not-ready" + : activeExternalPostgres !== undefined && secrets.ready !== true + ? "external-postgres-secrets-not-ready" + : codeAgentRuntime.ready !== true + ? codeAgentRuntimeDegradedReason ?? "code-agent-runtime-not-ready" + : "runtime-not-ready"; const argoReady = argo.exitCode === 0 && repoURL === spec.argoRepoUrl && targetRevision === spec.gitopsBranch && path === spec.runtimePath && syncStatus === "Synced" && health === "Healthy"; const pipelineRunReady = pipelineRunProbe !== null && pipelineRunProbe.status === "True"; const pipelineRunDegradedReason = typeof pipelineRunDiagnostics?.degradedReason === "string" @@ -254,6 +269,7 @@ export function nodeRuntimeControlPlaneStatus(scoped: ReturnType env && env.name === name);", " return item ? item.value : undefined;", "}", + "function envSecretRef(container, name) {", + " if (!isObject(container) || !Array.isArray(container.env)) return {};", + " const item = container.env.find((env) => env && env.name === name);", + " return item && item.valueFrom && item.valueFrom.secretKeyRef ? item.valueFrom.secretKeyRef : {};", + "}", "function isEnvReuseContainer(container) { return envValue(container, 'HWLAB_RUNTIME_MODE') === 'env-reuse-git-mirror-checkout' || envValue(container, 'HWLAB_BOOT_SH') !== undefined || envValue(container, 'HWLAB_BOOT_COMMIT') !== undefined; }", "function workloadName(item) { return item && item.metadata && item.metadata.labels && item.metadata.labels['app.kubernetes.io/name'] ? String(item.metadata.labels['app.kubernetes.io/name']) : String(item && item.metadata && item.metadata.name || ''); }", "function expectedPublicEndpoint(item) { return workloadName(item) === 'hwlab-cloud-web' ? overlay.publicWebUrl : overlay.publicApiUrl; }", @@ -1814,7 +1857,17 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { " const staleGitReadUrls = [];", " const wrongPublicEndpoints = [];", " const wrongDbSslModes = [];", + " const wrongCodeAgentRuntimeEnvs = [];", " const rewriteSources = new Set((overlay.runtimeImageRewrites || []).map((item) => item && item.source).filter(Boolean));", + " const codeAgentRuntime = overlay.codeAgentRuntime;", + " function checkCodeAgentRuntimeValue(item, file, container, envName, expected) {", + " const value = envValue(container, envName);", + " if (value !== expected) wrongCodeAgentRuntimeEnvs.push({ ...workloadRef(item, file, container), envName, kind: 'value', expected, value: value ?? null });", + " }", + " function checkCodeAgentRuntimeSecret(item, file, container, envName, expectedSecret, expectedKey) {", + " const secretRef = envSecretRef(container, envName);", + " if (secretRef.name !== expectedSecret || secretRef.key !== expectedKey) wrongCodeAgentRuntimeEnvs.push({ ...workloadRef(item, file, container), envName, kind: 'secretKeyRef', expectedSecret, expectedKey, secret: secretRef.name ?? null, key: secretRef.key ?? null });", + " }", " for (const file of yamlFiles(runtimePath)) {", " if (path.basename(file) === 'kustomization.yaml') continue;", " for (const doc of readYamlDocuments(file)) {", @@ -1831,12 +1884,22 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { " if (publicEndpoint !== undefined && publicEndpoint !== expectedPublicEndpoint(item)) wrongPublicEndpoints.push({ ...workloadRef(item, file, container), value: publicEndpoint, expected: expectedPublicEndpoint(item) });", " const dbSslMode = envValue(container, 'HWLAB_CLOUD_DB_SSL_MODE');", " if (overlay.externalPostgres && overlay.externalPostgres.sslmode && dbSslMode !== undefined && dbSslMode !== overlay.externalPostgres.sslmode) wrongDbSslModes.push({ ...workloadRef(item, file, container), value: dbSslMode, expected: overlay.externalPostgres.sslmode });", + " if (codeAgentRuntime && codeAgentRuntime.enabled && workloadName(item) === 'hwlab-cloud-api' && container.name === 'hwlab-cloud-api') {", + " checkCodeAgentRuntimeValue(item, file, container, 'HWLAB_CODE_AGENT_ADAPTER', String(codeAgentRuntime.adapter));", + " checkCodeAgentRuntimeValue(item, file, container, 'AGENTRUN_MGR_URL', String(codeAgentRuntime.managerUrl));", + " checkCodeAgentRuntimeSecret(item, file, container, 'AGENTRUN_API_KEY', String(codeAgentRuntime.apiKeySecretName), String(codeAgentRuntime.apiKeySecretKey));", + " checkCodeAgentRuntimeValue(item, file, container, 'HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE', String(codeAgentRuntime.runnerNamespace));", + " checkCodeAgentRuntimeValue(item, file, container, 'HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE', String(codeAgentRuntime.secretNamespace));", + " checkCodeAgentRuntimeValue(item, file, container, 'HWLAB_CODE_AGENT_AGENTRUN_REPO_URL', String(codeAgentRuntime.repoUrl));", + " checkCodeAgentRuntimeValue(item, file, container, 'HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID', String(codeAgentRuntime.providerId));", + " checkCodeAgentRuntimeValue(item, file, container, 'HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE', String(codeAgentRuntime.defaultProviderProfile));", + " }", " }", " if (Array.isArray(podSpec.volumes) && podSpec.volumes.some((volume) => volume && volume.name === 'hwlab-metrics-sidecar')) metricsRefs.push(workloadRef(item, file, { name: 'volume/hwlab-metrics-sidecar' }));", " }", " }", " }", - " return { metricsRefs, missingStartupProbes, publicRuntimeImages, staleGitReadUrls, wrongPublicEndpoints, wrongDbSslModes };", + " return { metricsRefs, missingStartupProbes, publicRuntimeImages, staleGitReadUrls, wrongPublicEndpoints, wrongDbSslModes, wrongCodeAgentRuntimeEnvs };", "}", "const checks = [];", "const workloadCheck = workloadChecks();", @@ -1858,6 +1921,8 @@ export function nodeRuntimePipelinePostprocessScript(): string[] { "checks.push('runtime-public-endpoint');", "if (workloadCheck.wrongDbSslModes.length > 0) fail('runtime-db-ssl-mode-mismatch', { refs: workloadCheck.wrongDbSslModes.slice(0, 12), count: workloadCheck.wrongDbSslModes.length });", "if (overlay.externalPostgres && overlay.externalPostgres.sslmode) checks.push('runtime-db-ssl-mode');", + "if (workloadCheck.wrongCodeAgentRuntimeEnvs.length > 0) fail('code-agent-runtime-env-mismatch', { refs: workloadCheck.wrongCodeAgentRuntimeEnvs.slice(0, 12), count: workloadCheck.wrongCodeAgentRuntimeEnvs.length });", + "if (overlay.codeAgentRuntime && overlay.codeAgentRuntime.enabled) checks.push('code-agent-runtime-env');", "const pg = overlay.externalPostgres;", "if (pg && pg.serviceName) {", " const access = pg.runtimeAccess || { endpointAddress: pg.endpointAddress, port: pg.port };", diff --git a/scripts/src/hwlab-node/web-probe.ts b/scripts/src/hwlab-node/web-probe.ts index 9edc6cac..e18632f2 100644 --- a/scripts/src/hwlab-node/web-probe.ts +++ b/scripts/src/hwlab-node/web-probe.ts @@ -94,6 +94,21 @@ export function nodeRuntimeRenderOverlay(spec: HwlabRuntimeLaneSpec): Record { + const runtime = spec.codeAgentRuntime; + if (runtime === undefined || runtime.enabled === false) return { required: false, ready: true, enabled: runtime?.enabled ?? false }; + if (!namespaceExists) return { required: true, ready: false, enabled: true, degradedReason: "runtime-namespace-missing" }; + const managerTarget = nodeRuntimeCodeAgentManagerTarget(runtime.managerUrl, runtime.runnerNamespace); + const runnerNamespace = runNodeK3sArgs(spec, ["kubectl", "get", "ns", runtime.runnerNamespace, "-o", "name"], 60); + const secretNamespace = runNodeK3sArgs(spec, ["kubectl", "get", "ns", runtime.secretNamespace, "-o", "name"], 60); + const managerNamespace = runNodeK3sArgs(spec, ["kubectl", "get", "ns", managerTarget.namespace, "-o", "name"], 60); + const managerService = runNodeK3sArgs(spec, ["kubectl", "-n", managerTarget.namespace, "get", "service", managerTarget.serviceName, "-o", "name"], 60); + const managerDeployment = runNodeK3sArgs(spec, ["kubectl", "-n", managerTarget.namespace, "get", "deployment", managerTarget.serviceName, "-o", "jsonpath={.status.readyReplicas}{\"\\n\"}{.spec.replicas}{\"\\n\"}"], 60); + const [managerReadyReplicasRaw = "", managerDesiredReplicasRaw = ""] = managerDeployment.stdout.split(/\r?\n/u); + const managerReadyReplicas = numericField(managerReadyReplicasRaw); + const managerDesiredReplicas = numericField(managerDesiredReplicasRaw); + const managerDeploymentReady = managerDeployment.exitCode === 0 && (managerReadyReplicas ?? 0) >= Math.max(1, managerDesiredReplicas ?? 1); + const apiKey = secretKeyStatus(spec, runtime.apiKeySecretName, runtime.apiKeySecretKey); + const cloudApiEnv = nodeRuntimeCodeAgentCloudApiEnvStatus(spec, runtime); + const ready = runnerNamespace.exitCode === 0 + && secretNamespace.exitCode === 0 + && managerNamespace.exitCode === 0 + && managerService.exitCode === 0 + && managerDeploymentReady + && apiKey.present === true + && cloudApiEnv.ready === true; + const degradedReason = ready + ? undefined + : runnerNamespace.exitCode !== 0 + ? "agentrun-runner-namespace-missing" + : secretNamespace.exitCode !== 0 + ? "agentrun-secret-namespace-missing" + : managerNamespace.exitCode !== 0 + ? "agentrun-manager-namespace-missing" + : managerService.exitCode !== 0 + ? "agentrun-manager-service-missing" + : !managerDeploymentReady + ? "agentrun-manager-deployment-not-ready" + : apiKey.present !== true + ? "agentrun-api-key-secret-missing" + : "hwlab-cloud-api-code-agent-env-mismatch"; + return { + required: true, + enabled: true, + ready, + degradedReason, + adapter: runtime.adapter, + managerUrl: runtime.managerUrl, + manager: { + namespace: managerTarget.namespace, + serviceName: managerTarget.serviceName, + serviceExists: managerService.exitCode === 0, + deploymentReady: managerDeploymentReady, + readyReplicas: managerReadyReplicas, + desiredReplicas: managerDesiredReplicas, + namespaceResult: compactRuntimeCommand(managerNamespace), + serviceResult: compactRuntimeCommand(managerService), + deploymentResult: compactRuntimeCommand(managerDeployment), + }, + runnerNamespace: { + name: runtime.runnerNamespace, + exists: runnerNamespace.exitCode === 0, + result: compactRuntimeCommand(runnerNamespace), + }, + secretNamespace: { + name: runtime.secretNamespace, + exists: secretNamespace.exitCode === 0, + result: compactRuntimeCommand(secretNamespace), + }, + apiKey, + cloudApiEnv, + repoUrlFrom: runtime.repoUrlFrom, + providerIdFrom: runtime.providerIdFrom, + valuesPrinted: false, + }; +} + +function nodeRuntimeCodeAgentManagerTarget(managerUrl: string, fallbackNamespace: string): { namespace: string; serviceName: string } { + let parsed: URL | null = null; + try { + parsed = new URL(managerUrl); + } catch { + parsed = null; + } + const host = parsed?.hostname ?? ""; + const parts = host.split(".").filter(Boolean); + if (parts.length >= 3 && parts[2] === "svc") return { serviceName: parts[0], namespace: parts[1] }; + return { serviceName: parts[0] || "agentrun-mgr", namespace: parts[1] || fallbackNamespace }; +} + +function nodeRuntimeCodeAgentCloudApiEnvStatus(spec: HwlabRuntimeLaneSpec, runtime: NonNullable): Record { + const deployment = runNodeK3sArgs(spec, ["kubectl", "-n", spec.runtimeNamespace, "get", "deployment", "hwlab-cloud-api", "-o", "json"], 60); + if (deployment.exitCode !== 0) { + return { + ready: false, + deploymentExists: false, + mismatches: [{ name: "hwlab-cloud-api", reason: "deployment-missing" }], + result: compactCodeAgentDeploymentProbeResult(deployment), + valuesPrinted: false, + }; + } + let parsed: unknown; + try { + parsed = JSON.parse(deployment.stdout) as unknown; + } catch (error) { + return { + ready: false, + deploymentExists: true, + mismatches: [{ name: "hwlab-cloud-api", reason: "deployment-json-invalid", message: error instanceof Error ? error.message : String(error) }], + result: compactCodeAgentDeploymentProbeResult(deployment), + valuesPrinted: false, + }; + } + const deploy = record(parsed); + const specRecord = record(record(record(deploy.spec).template).spec); + const containers = Array.isArray(specRecord.containers) ? specRecord.containers.map(record) : []; + const container = containers.find((item) => item.name === "hwlab-cloud-api") ?? containers[0]; + const env = Array.isArray(container?.env) ? container.env.map(record) : []; + const envByName = new Map(env.map((item) => [String(item.name || ""), item])); + const mismatches: Array> = []; + const expectValue = (name: string, expected: string): void => { + const item = envByName.get(name); + const value = typeof item?.value === "string" ? item.value : null; + if (value !== expected) mismatches.push({ name, expected, value, kind: "value" }); + }; + const expectSecret = (name: string, expectedSecret: string, expectedKey: string): void => { + const item = envByName.get(name); + const secretRef = record(record(item?.valueFrom).secretKeyRef); + if (secretRef.name !== expectedSecret || secretRef.key !== expectedKey) { + mismatches.push({ name, expectedSecret, expectedKey, secret: secretRef.name ?? null, key: secretRef.key ?? null, kind: "secretKeyRef" }); + } + }; + expectValue("HWLAB_CODE_AGENT_ADAPTER", runtime.adapter); + expectValue("AGENTRUN_MGR_URL", runtime.managerUrl); + expectSecret("AGENTRUN_API_KEY", runtime.apiKeySecretName, runtime.apiKeySecretKey); + expectValue("HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE", runtime.runnerNamespace); + expectValue("HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE", runtime.secretNamespace); + expectValue("HWLAB_CODE_AGENT_AGENTRUN_REPO_URL", spec.gitReadUrl); + expectValue("HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID", spec.nodeId); + expectValue("HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE", runtime.defaultProviderProfile); + return { + ready: mismatches.length === 0, + deploymentExists: true, + deploymentName: "hwlab-cloud-api", + containerName: container?.name ?? null, + checkedEnvCount: 8, + mismatches, + result: compactCodeAgentDeploymentProbeResult(deployment), + valuesPrinted: false, + }; +} + +function compactCodeAgentDeploymentProbeResult(result: CommandResult): Record { + return { + exitCode: result.exitCode, + stdoutBytes: Buffer.byteLength(result.stdout, "utf8"), + stderrBytes: Buffer.byteLength(result.stderr, "utf8"), + stderrTail: result.stderr.slice(-600), + timedOut: result.timedOut === true, + }; +} + export function externalPostgresBridgeStatus(spec: HwlabRuntimeLaneSpec, namespaceExists: boolean): Record { const pg = hwlabRuntimeActiveExternalPostgres(spec); if (pg === undefined) return { required: false, ready: true };