diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index b16520a3..8209db41 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -14,6 +14,7 @@ import { const ADAPTER_ID = "agentrun-v01"; const DEFAULT_AGENTRUN_MGR_URL = "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080"; +const AGENTRUN_MANAGER_HOST_PATTERN = /^agentrun-mgr\.[a-z0-9]([-a-z0-9]*[a-z0-9])?\.svc\.cluster\.local$/u; const DEFAULT_TENANT_ID = "hwlab"; const DEFAULT_PROJECT_ID = "pikasTech/HWLAB"; const DEFAULT_REPO_URL = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git"; @@ -152,7 +153,7 @@ export function describeAgentRunAdapterAvailability(env = process.env, options = resourceBundleSourceCommit, managerConfigured: Boolean(managerUrl), repoConfigured: Boolean(repoUrl), - internalServiceDns: managerHost === "agentrun-mgr.agentrun-v01.svc.cluster.local", + internalServiceDns: isAgentRunManagerInternalServiceHost(managerHost), ready, valuesPrinted: false }, @@ -1670,10 +1671,9 @@ function resolveAgentRunManagerUrl(env = process.env, override = null) { const raw = firstNonEmpty(override, env.AGENTRUN_MGR_URL, env.HWLAB_CODE_AGENT_AGENTRUN_MGR_URL, DEFAULT_AGENTRUN_MGR_URL); const url = new URL(raw); const host = url.hostname.toLowerCase(); - const allowedHost = "agentrun-mgr.agentrun-v01.svc.cluster.local"; const allowedByTest = truthyFlag(env.HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL) && ["127.0.0.1", "localhost"].includes(host); - if (url.protocol !== "http:" || (host !== allowedHost && !allowedByTest)) { - throw Object.assign(new Error(`AGENTRUN_MGR_URL must use internal k3s Service DNS ${DEFAULT_AGENTRUN_MGR_URL}; got ${redactUrl(raw)}`), { + if (url.protocol !== "http:" || (!isAgentRunManagerInternalServiceHost(host) && !allowedByTest)) { + throw Object.assign(new Error(`AGENTRUN_MGR_URL must use internal k3s Service DNS agentrun-mgr..svc.cluster.local; got ${redactUrl(raw)}`), { code: "agentrun_internal_url_required", statusCode: 500 }); @@ -1684,6 +1684,10 @@ function resolveAgentRunManagerUrl(env = process.env, override = null) { return url.toString().replace(/\/+$/u, ""); } +function isAgentRunManagerInternalServiceHost(host) { + return AGENTRUN_MANAGER_HOST_PATTERN.test(String(host ?? "").trim().toLowerCase()); +} + function resolveAgentRunBackendProfile(env = process.env, params = {}) { const requested = String(params.providerProfile ?? params.codeAgentProviderProfile ?? env.HWLAB_CODE_AGENT_AGENTRUN_BACKEND_PROFILE ?? env.HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE ?? "deepseek").trim().toLowerCase(); const fallback = String(env.HWLAB_CODE_AGENT_AGENTRUN_DEFAULT_BACKEND_PROFILE ?? "deepseek").trim().toLowerCase() || "deepseek"; diff --git a/internal/cloud/provider-profile-management.test.ts b/internal/cloud/provider-profile-management.test.ts index 6b5546bb..7cc41094 100644 --- a/internal/cloud/provider-profile-management.test.ts +++ b/internal/cloud/provider-profile-management.test.ts @@ -139,6 +139,36 @@ test("provider profile set-key delegates through HWLAB without echoing API key", } }); +test("provider profile management accepts lane-specific AgentRun manager namespace", async () => { + const calls: Array<{ method?: string; path?: string; host?: string }> = []; + const server = createCloudApiServer({ + env: { + AGENTRUN_MGR_URL: "http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080", + HWLAB_ACCESS_CONTROL_REQUIRED: "1" + }, + fetchImpl: async (url: string, init: any = {}) => { + const parsed = new URL(url); + calls.push({ method: init.method, path: parsed.pathname, host: parsed.hostname }); + return new Response(JSON.stringify({ ok: true, data: { items: [], count: 0, valuesPrinted: false }, valuesPrinted: false }), { status: 200 }); + }, + accessController: fakeAccessController({ actor: adminActor }) + }); + await listen(server); + const { port } = server.address() as { port: number }; + + try { + const response = await fetch(`http://127.0.0.1:${port}/v1/admin/provider-profiles`, { headers: { cookie: "hwlab_session=admin" } }); + const body = await response.json(); + assert.equal(response.status, 200); + assert.equal(body.ok, true); + assert.equal(calls[0].method, "GET"); + assert.equal(calls[0].host, "agentrun-mgr.agentrun-v02.svc.cluster.local"); + assert.equal(calls[0].path, "/api/v1/provider-profiles"); + } finally { + await close(server); + } +}); + test("provider profile auth-json delegates through HWLAB without echoing auth json", async () => { const calls: Array<{ method?: string; path?: string; body?: any }> = []; const agentRunServer = await startAgentRunServer(calls); diff --git a/internal/cloud/provider-profile-management.ts b/internal/cloud/provider-profile-management.ts index e376106c..51338461 100644 --- a/internal/cloud/provider-profile-management.ts +++ b/internal/cloud/provider-profile-management.ts @@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto"; const CONTRACT_VERSION = "provider-profile-management-v1"; const DEFAULT_AGENTRUN_MGR_URL = "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080"; -const DEFAULT_AGENTRUN_HOST = "agentrun-mgr.agentrun-v01.svc.cluster.local"; +const AGENTRUN_MANAGER_HOST_PATTERN = /^agentrun-mgr\.[a-z0-9]([-a-z0-9]*[a-z0-9])?\.svc\.cluster\.local$/u; const DEFAULT_TIMEOUT_MS = 30000; const PROVIDER_PROFILE_ALIASES = Object.freeze({ codex: "codex-api" }); @@ -400,12 +400,16 @@ function resolveAgentRunManagerUrl(env) { } const allowLocal = truthy(env.HWLAB_PROVIDER_PROFILE_AGENTRUN_ALLOW_NON_K3S_URL) || truthy(env.HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL); const localAllowed = allowLocal && ["127.0.0.1", "localhost"].includes(parsed.hostname); - if (parsed.hostname !== DEFAULT_AGENTRUN_HOST && !localAllowed) { - throw routeError(503, "agentrun_manager_url_invalid", `AgentRun manager URL must use internal k3s Service DNS ${DEFAULT_AGENTRUN_MGR_URL}`, { managerUrl: redactUrl(raw) }); + if (!isAgentRunManagerInternalServiceHost(parsed.hostname) && !localAllowed) { + throw routeError(503, "agentrun_manager_url_invalid", "AgentRun manager URL must use internal k3s Service DNS agentrun-mgr..svc.cluster.local", { managerUrl: redactUrl(raw) }); } return raw.replace(/\/+$/u, ""); } +function isAgentRunManagerInternalServiceHost(host) { + return AGENTRUN_MANAGER_HOST_PATTERN.test(String(host ?? "").trim().toLowerCase()); +} + async function readOptionalJsonObject(request, limitBytes) { const body = await readBody(request, limitBytes); if (!body.trim()) return {}; diff --git a/internal/cloud/server-health.test.ts b/internal/cloud/server-health.test.ts index 28d4ffca..241ba9ba 100644 --- a/internal/cloud/server-health.test.ts +++ b/internal/cloud/server-health.test.ts @@ -788,8 +788,8 @@ test("cloud api health reports AgentRun adapter readiness without repo-owned cod 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", + AGENTRUN_MGR_URL: "http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080", + HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: "agentrun-v02", HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "G14", HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: "0123456789abcdef0123456789abcdef01234567", HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "deepseek", @@ -833,8 +833,9 @@ test("cloud api health reports AgentRun adapter readiness without repo-owned cod assert.equal(payload.codeAgent.status, "agentrun-ready"); assert.equal(payload.codeAgent.provider, "deepseek"); assert.equal(payload.codeAgent.backend, "agentrun-v01/deepseek"); - assert.equal(payload.codeAgent.agentRun.managerHost, "agentrun-mgr.agentrun-v01.svc.cluster.local"); - assert.equal(payload.codeAgent.agentRun.runnerNamespace, "agentrun-v01"); + assert.equal(payload.codeAgent.agentRun.managerHost, "agentrun-mgr.agentrun-v02.svc.cluster.local"); + assert.equal(payload.codeAgent.agentRun.runnerNamespace, "agentrun-v02"); + assert.equal(payload.codeAgent.agentRun.internalServiceDns, true); assert.equal(payload.codeAgent.runner.kind, "agentrun-v01-shared-runner"); assert.equal(payload.codeAgent.runner.delegatedToAgentRun, true); assert.equal(payload.codeAgent.runner.codexStdio, false);