feat(agentrun): inject owner HWLAB_API_KEY into runner transient env

- submitAgentRunChatTurn resolves owner user API key via accessController.store.findActiveDefaultApiKeyForUser
- buildAgentRunRunnerJobInput accepts ownerApiKey; when present, removes HWLAB_DEVICE_POD_API_KEY and adds HWLAB_API_KEY as sensitive
- buildAgentRunTransientEnv no longer emits HWLAB_DEVICE_POD_API_KEY
- codex-stdio child env now reads HWLAB_API_KEY (not HWLAB_DEVICE_POD_API_KEY)
- server-code-agent-http codeAgentAuthEnv looks up user default API key; URL env vars still set unconditionally
- tests: code-agent-session-registry Codex child env carries only device-pod API key now checks HWLAB_API_KEY
- tests: server-agent-chat delegates v0.2 turns removes HWLAB_DEVICE_POD_API_KEY assertion
- 58 pass / 4 pre-existing fail
This commit is contained in:
Codex
2026-06-04 02:56:08 +08:00
committed by Codex Agent
parent bcec7e6934
commit 6a8e9b3d79
6 changed files with 38 additions and 18 deletions
+21 -6
View File
@@ -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;
}
@@ -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);
@@ -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 } : {}),
+1 -3
View File
@@ -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);
+8 -6
View File
@@ -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 } : {})
};
}
+5
View File
@@ -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);
}