diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index 82bee8a9..e3a77e73 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -19,6 +19,7 @@ import { safeOpaqueId, safeSessionId, safeTraceId, + text, truthyFlag } from "./server-http-utils.ts"; @@ -94,6 +95,7 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI const fetchImpl = options.fetchImpl ?? globalThis.fetch; const timeoutMs = parsePositiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, 20_000); const startedAt = nowIso(options.now); + const ownerApiKey = await resolveOwnerApiKey({ params, options, now: () => nowIso(options.now) }); traceStore.ensure(traceId, agentRunTraceMeta(env, params)); traceStore.append(traceId, { @@ -130,7 +132,7 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI }); let runnerJob = null; try { - const runnerJobInput = buildAgentRunRunnerJobInput({ env, traceId, commandId }); + const runnerJobInput = buildAgentRunRunnerJobInput({ env, traceId, commandId, ownerApiKey }); runnerJob = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(reusable.mapping.runId)}/runner-jobs`, { method: "POST", body: runnerJobInput, @@ -228,7 +230,7 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI message: "AgentRun command " + commandId + " created; hwlab-cloud-api will start a runner Job explicitly without relying on scheduler automation.", runId, commandId, backendProfile, waitingFor: "agentrun-runner-job-create", valuesPrinted: false, }); - const runnerJobInput = buildAgentRunRunnerJobInput({ env, traceId, commandId }); + const runnerJobInput = buildAgentRunRunnerJobInput({ env, traceId, commandId, ownerApiKey }); try { runnerJob = await agentRunJson(fetchImpl, managerUrl, "/api/v1/runs/" + encodeURIComponent(runId) + "/runner-jobs", { method: "POST", body: runnerJobInput, timeoutMs }); } catch (error) { @@ -665,8 +667,22 @@ function buildAgentRunSteerCommandInput({ params, traceId, steerTraceId, mapped }; } -function buildAgentRunRunnerJobInput({ env, traceId, commandId }) { - const transientEnv = buildAgentRunTransientEnv(env); +async function resolveOwnerApiKey({ params, options, now }) { + const ownerUserId = text(params.ownerUserId); + if (!ownerUserId) return ""; + const accessController = options.accessController; + if (!accessController?.store?.findActiveDefaultApiKeyForUser) return ""; + const key = await accessController.store.findActiveDefaultApiKeyForUser(ownerUserId) ?? null; + if (!key) return ""; + return text(key.displaySecret ?? ""); +} +function buildAgentRunRunnerJobInput({ env, traceId, commandId, ownerApiKey }) { + const baseTransient = buildAgentRunTransientEnv(env); + const transientEnv = ownerApiKey + ? baseTransient + .filter((entry) => entry.name !== "HWLAB_DEVICE_POD_API_KEY") + .concat([{ name: "HWLAB_API_KEY", value: ownerApiKey, sensitive: true }]) + : baseTransient; return { commandId, idempotencyKey: traceId, @@ -690,11 +706,10 @@ function buildAgentRunTransientEnv(env = process.env) { "HWLAB_RUNTIME_LANE", "HWLAB_RUNTIME_ENDPOINT_LOCKED", "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME", - "HWLAB_DEVICE_POD_API_KEY", "UNIDESK_MAIN_SERVER_IP" ]) { const value = firstNonEmpty(env[name]); - if (value) entries.push({ name, value, sensitive: name === "HWLAB_DEVICE_POD_API_KEY" }); + if (value) entries.push({ name, value, sensitive: false }); } return entries; } diff --git a/internal/cloud/code-agent-session-registry.test.ts b/internal/cloud/code-agent-session-registry.test.ts index c00a29cb..de1c4cef 100644 --- a/internal/cloud/code-agent-session-registry.test.ts +++ b/internal/cloud/code-agent-session-registry.test.ts @@ -220,7 +220,7 @@ test("Codex child env carries only device-pod API key needed by Code Agent tools OPENAI_API_KEY: "test-openai-key-material", HWLAB_DEVICE_POD_API_URL: "http://127.0.0.1:6667", HWLAB_CLOUD_API_URL: "http://127.0.0.1:6667", - HWLAB_DEVICE_POD_API_KEY: "device-pod-api-key", + HWLAB_API_KEY: "hwl_live_user-default-key", HWLAB_SESSION_COOKIE: "hwlab_session=browser-cookie", HWLAB_SESSION_TOKEN: "browser-session-token", HWLAB_BEARER_TOKEN: "browser-bearer-token" @@ -228,7 +228,7 @@ test("Codex child env carries only device-pod API key needed by Code Agent tools assert.equal(child.HWLAB_DEVICE_POD_API_URL, "http://127.0.0.1:6667"); assert.equal(child.HWLAB_CLOUD_API_URL, "http://127.0.0.1:6667"); - assert.equal(child.HWLAB_DEVICE_POD_API_KEY, "device-pod-api-key"); + assert.equal(child.HWLAB_API_KEY, "hwl_live_user-default-key"); assert.equal(child.OPENAI_API_KEY, undefined); assert.equal(child.HWLAB_SESSION_COOKIE, undefined); assert.equal(child.HWLAB_SESSION_TOKEN, undefined); diff --git a/internal/cloud/codex-stdio-session-helpers.ts b/internal/cloud/codex-stdio-session-helpers.ts index 84d22119..c9eabe4e 100644 --- a/internal/cloud/codex-stdio-session-helpers.ts +++ b/internal/cloud/codex-stdio-session-helpers.ts @@ -1664,7 +1664,7 @@ export function childProcessEnv(env = process.env) { ...(env.HWLAB_RUNTIME_ENDPOINT_SOURCE ? { HWLAB_RUNTIME_ENDPOINT_SOURCE: env.HWLAB_RUNTIME_ENDPOINT_SOURCE } : {}), ...(env.HWLAB_RUNTIME_ENDPOINT_LOCKED ? { HWLAB_RUNTIME_ENDPOINT_LOCKED: env.HWLAB_RUNTIME_ENDPOINT_LOCKED } : {}), ...(env.HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME ? { HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: env.HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME } : {}), - ...(env.HWLAB_DEVICE_POD_API_KEY ? { HWLAB_DEVICE_POD_API_KEY: env.HWLAB_DEVICE_POD_API_KEY } : {}), + ...(env.HWLAB_API_KEY ? { HWLAB_API_KEY: env.HWLAB_API_KEY } : {}), NO_PROXY: noProxy, no_proxy: noProxy, ...(env.LANG ? { LANG: env.LANG } : {}), diff --git a/internal/cloud/server-agent-chat.test.ts b/internal/cloud/server-agent-chat.test.ts index c0d95682..d45d9af1 100644 --- a/internal/cloud/server-agent-chat.test.ts +++ b/internal/cloud/server-agent-chat.test.ts @@ -274,7 +274,6 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte "HWLAB_RUNTIME_LANE", "HWLAB_RUNTIME_ENDPOINT_LOCKED", "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME", - "HWLAB_DEVICE_POD_API_KEY", "UNIDESK_MAIN_SERVER_IP" ]); const transientEnv = Object.fromEntries(body.transientEnv.map((entry) => [entry.name, entry.value])); @@ -284,9 +283,8 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte assert.equal(transientEnv.HWLAB_RUNTIME_LANE, "v02"); assert.equal(transientEnv.HWLAB_RUNTIME_ENDPOINT_LOCKED, "1"); assert.equal(transientEnv.HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME, "1"); - assert.equal(transientEnv.HWLAB_DEVICE_POD_API_KEY, "test-device-pod-api-key"); assert.equal(transientEnv.UNIDESK_MAIN_SERVER_IP, "http://74.48.78.17:18081"); - assert.ok(body.transientEnv.length >= 8); + assert.ok(body.transientEnv.length >= 7); assert.equal(Object.hasOwn(transientEnv, "UNIDESK_SSH_CLIENT_TOKEN"), false); assert.equal(Object.hasOwn(transientEnv, "GH_TOKEN"), false); assert.equal(Object.hasOwn(transientEnv, "HWLAB_DEVICE_POD_API_URL"), false); diff --git a/internal/cloud/server-code-agent-http.ts b/internal/cloud/server-code-agent-http.ts index 97b135d1..4c6b3e91 100644 --- a/internal/cloud/server-code-agent-http.ts +++ b/internal/cloud/server-code-agent-http.ts @@ -489,7 +489,7 @@ function stripSyntheticConversationContext(params = {}, traceId, options = {}) { async function codeAgentChatExecutionOptions(options = {}, params = {}) { const env = codeAgentProviderProfileEnv(options.env ?? process.env, params); - Object.assign(env, await codeAgentDevicePodAuthEnv(options, env)); + Object.assign(env, await codeAgentAuthEnv(options, env, params)); return { callProvider: options.callCodeAgentProvider, timeoutMs: options.codeAgentTimeoutMs, @@ -507,10 +507,12 @@ async function codeAgentChatExecutionOptions(options = {}, params = {}) { }; } -async function codeAgentDevicePodAuthEnv(options = {}, env = process.env) { - if (typeof options.accessController?.createCodeAgentDevicePodApiKey !== "function") return {}; - const auth = await options.accessController.createCodeAgentDevicePodApiKey(); - if (!auth?.apiKey) return {}; +async function codeAgentAuthEnv(options = {}, env = process.env, params = {}) { + const ownerUserId = textValue(params.ownerUserId ?? options.actor?.id ?? ""); + const store = options.accessController?.store; + const key = ownerUserId && store?.findActiveDefaultApiKeyForUser + ? await store.findActiveDefaultApiKeyForUser(ownerUserId) + : null; const runtimeNamespace = runtimeNamespaceForEnv(env); const runtimeLane = runtimeLaneForNamespace(runtimeNamespace) ?? runtimeLaneForEnv(env); const inClusterApiUrl = internalCodeAgentApiUrl(env, runtimeNamespace); @@ -534,7 +536,7 @@ async function codeAgentDevicePodAuthEnv(options = {}, env = process.env) { HWLAB_RUNTIME_ENDPOINT_SOURCE: codeAgentAgentRunAdapterEnabled(env) ? "runtime-namespace" : "runtime-env", HWLAB_RUNTIME_ENDPOINT_LOCKED: "1", HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1", - HWLAB_DEVICE_POD_API_KEY: auth.apiKey + ...(key?.displaySecret ? { HWLAB_API_KEY: key.displaySecret } : {}) }; } diff --git a/internal/cloud/server-http-utils.ts b/internal/cloud/server-http-utils.ts index 15bec4b1..fba9fcf2 100644 --- a/internal/cloud/server-http-utils.ts +++ b/internal/cloud/server-http-utils.ts @@ -71,3 +71,8 @@ export function positiveInteger(value, fallback) { const parsed = Number.parseInt(value ?? "", 10); return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; } + +export function text(value) { + if (value === null || value === undefined) return ""; + return String(value); +}