diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index f828ee4d..dc63aef0 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -60,6 +60,7 @@ export function describeAgentRunAdapterAvailability(env = process.env, options = const blockers = []; let managerUrl = null; let repoUrl = null; + let resourceBundleSourceCommit = null; try { managerUrl = resolveAgentRunManagerUrl(env); @@ -71,6 +72,11 @@ export function describeAgentRunAdapterAvailability(env = process.env, options = } catch (error) { blockers.push(agentRunAvailabilityBlocker(error, "repo-url")); } + try { + resourceBundleSourceCommit = requireAgentRunSourceCommit(env); + } catch (error) { + blockers.push(agentRunAvailabilityBlocker(error, "resource-bundle-source-commit")); + } const providerId = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID); const runnerNamespace = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE, env.AGENTRUN_RUNTIME_NAMESPACE, DEFAULT_RUNNER_NAMESPACE); @@ -145,6 +151,7 @@ export function describeAgentRunAdapterAvailability(env = process.env, options = managerHost, repoUrl, repoHost, + resourceBundleSourceCommit, managerConfigured: Boolean(managerUrl), repoConfigured: Boolean(repoUrl), internalServiceDns: managerHost === "agentrun-mgr.agentrun-v01.svc.cluster.local", @@ -235,6 +242,7 @@ export function initialAgentRunChatResult({ params = {}, options = {}, traceId } providerId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID), managerUrl: resolveAgentRunManagerUrl(env), repoUrl: resolveAgentRunRepoUrl(env), + resourceBundleSourceCommit: requireAgentRunSourceCommit(env), valuesPrinted: false }, valuesPrinted: false @@ -710,22 +718,20 @@ function newSessionIdAfterEviction(baseSessionId, traceId) { return baseSessionId + "-reset-" + profile; } function buildAgentRunCreateRunInput({ params, env, traceId, backendProfile, sessionId, toolCapabilities = null }) { - const commitId = fullSourceCommit(env); + const commitId = requireAgentRunSourceCommit(env); const threadId = safeOpaqueId(params.threadId); const hwlabProjectId = firstNonEmpty(params.projectId); const toolAliases = filteredResourceToolAliases(toolCapabilities); - const resourceBundleRef = commitId - ? { - kind: "git", - repoUrl: resolveAgentRunRepoUrl(env), - commitId, - submodules: false, - lfs: false, - toolAliases, - promptRefs: HWLAB_RESOURCE_PROMPT_REFS, - skillRefs: HWLAB_RESOURCE_SKILL_REFS - } - : null; + const resourceBundleRef = { + kind: "git", + repoUrl: resolveAgentRunRepoUrl(env), + commitId, + submodules: false, + lfs: false, + toolAliases, + promptRefs: HWLAB_RESOURCE_PROMPT_REFS, + skillRefs: HWLAB_RESOURCE_SKILL_REFS + }; return { tenantId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_TENANT_ID, DEFAULT_TENANT_ID), projectId: agentRunProjectIdForEnv(env), @@ -1705,20 +1711,13 @@ function resolveAgentRunRepoUrl(env = process.env) { return url.toString().replace(/\/+$/u, ""); } -function fullSourceCommit(env) { - for (const value of [ - env.HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT, - env.HWLAB_BOOT_COMMIT, - env.HWLAB_GITOPS_SOURCE_COMMIT, - env.HWLAB_COMMIT_ID, - env.HWLAB_GIT_SHA, - env.GIT_COMMIT, - env.HWLAB_REVISION - ]) { - const text = String(value ?? "").trim().toLowerCase(); - if (/^[0-9a-f]{40}$/u.test(text)) return text; - } - return null; +function requireAgentRunSourceCommit(env) { + const text = String(env.HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT ?? "").trim().toLowerCase(); + if (/^[0-9a-f]{40}$/u.test(text)) return text; + throw Object.assign(new Error("HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT must be a full 40-character source commit before AgentRun dispatch"), { + code: "agentrun_bundle_source_commit_invalid", + statusCode: 500 + }); } function modelForBackendProfile(profile, env = process.env) { diff --git a/internal/cloud/health-contract.ts b/internal/cloud/health-contract.ts index 89215beb..d3a0e7b3 100644 --- a/internal/cloud/health-contract.ts +++ b/internal/cloud/health-contract.ts @@ -390,6 +390,7 @@ function buildCodeAgentStructuredReadiness({ provider, dbDurable, sessionRunner, ready, adapter: codeAgent?.adapter ?? codeAgent?.agentRun?.adapter ?? null, agentRunReady: agentRun ? codeAgent?.agentRun?.ready === true : null, + resourceBundleSourceCommit: agentRun ? codeAgent?.agentRun?.resourceBundleSourceCommit ?? null : null, providerReady: provider?.ready === true, durableDbReady: dbDurable?.ready === true, sessionRunnerReady: sessionRunner?.ready === true, diff --git a/internal/cloud/server-health.test.ts b/internal/cloud/server-health.test.ts index 7c24fb99..8823a616 100644 --- a/internal/cloud/server-health.test.ts +++ b/internal/cloud/server-health.test.ts @@ -725,6 +725,7 @@ test("cloud api health reports AgentRun adapter readiness without repo-owned cod AGENTRUN_MGR_URL: "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080", HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: "agentrun-v01", HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "G14", + HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: "0123456789abcdef0123456789abcdef01234567", HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "deepseek", HWLAB_CODE_AGENT_DEEPSEEK_MODEL: "deepseek-chat", HWLAB_BOOT_READ_URL: "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git", @@ -779,6 +780,7 @@ test("cloud api health reports AgentRun adapter readiness without repo-owned cod assert.equal(payload.readiness.codeAgent.ready, true); assert.equal(payload.readiness.codeAgent.adapter, "agentrun-v01"); assert.equal(payload.readiness.codeAgent.agentRunReady, true); + assert.equal(payload.readiness.codeAgent.resourceBundleSourceCommit, "0123456789abcdef0123456789abcdef01234567"); assert.equal(payload.readiness.codeAgent.codexStdioFeasible, false); assert.deepEqual(payload.readiness.codeAgent.currentBlockers, []); const serialized = JSON.stringify(payload); @@ -795,6 +797,60 @@ test("cloud api health reports AgentRun adapter readiness without repo-owned cod } }); +test("cloud api health blocks AgentRun adapter when explicit bundle source commit is missing", async () => { + const server = createCloudApiServer({ + env: { + HWLAB_CODE_AGENT_ADAPTER: "agentrun-v01", + AGENTRUN_MGR_URL: "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080", + HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: "agentrun-v01", + HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "G14", + HWLAB_COMMIT_ID: "0123456789abcdef0123456789abcdef01234567", + HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "deepseek", + HWLAB_BOOT_READ_URL: "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git", + HWLAB_CLOUD_DB_URL: "postgres://hwlab_test:password@db.internal.local:5432/hwlab", + HWLAB_CLOUD_DB_SSL_MODE: "disable" + }, + dbProbe: { + probe: async ({ endpointSource, timeoutMs }) => ({ + attempted: true, + networkAttempted: true, + endpointSource, + probeType: "tcp-connect", + endpointRedacted: true, + valueRedacted: true, + timeoutMs, + durationMs: 1, + result: "connected", + classification: "tcp_connected", + errorCode: null, + missingEnv: [] + }) + }, + runtimeStore: { + async readiness() { + return durableReadyRuntime(); + } + } + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + + try { + const { port } = server.address(); + const response = await fetch(`http://127.0.0.1:${port}/health/live`); + assert.equal(response.status, 200); + const payload = await response.json(); + assert.equal(payload.codeAgent.status, "blocked"); + assert.equal(payload.codeAgent.ready, false); + assert.equal(payload.codeAgent.reason, "agentrun_bundle_source_commit_invalid"); + assert.equal(payload.codeAgent.agentRun.ready, false); + assert.deepEqual(payload.codeAgent.blockerCodes, ["agentrun_bundle_source_commit_invalid"]); + } finally { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); + } +}); + test("cloud api health blocks full Code Agent readiness when Codex stdio command probe fails", async () => { const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-health-codex-stdio-probe-fail-")); const codexHome = path.join(workspace, "codex-home");