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:
@@ -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 } : {}),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 } : {})
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user