1393 lines
60 KiB
TypeScript
1393 lines
60 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
|
|
import { defaultCodeAgentTraceStore } from "./code-agent-trace-store.ts";
|
|
import {
|
|
CODEX_APP_SERVER_PROTOCOL,
|
|
CODEX_APP_SERVER_WIRE_API,
|
|
CODEX_STDIO_BACKEND,
|
|
CODEX_STDIO_CAPABILITY_LEVEL,
|
|
CODEX_STDIO_IMPLEMENTATION_TYPE,
|
|
CODEX_STDIO_PROVIDER,
|
|
CODEX_STDIO_RUNNER_KIND,
|
|
CODEX_STDIO_SESSION_MODE,
|
|
longLivedSessionGate
|
|
} from "./codex-stdio-session.ts";
|
|
import {
|
|
parsePositiveInteger,
|
|
safeConversationId,
|
|
safeOpaqueId,
|
|
safeSessionId,
|
|
safeTraceId,
|
|
truthyFlag
|
|
} from "./server-http-utils.ts";
|
|
|
|
const ADAPTER_ID = "agentrun-v01";
|
|
const DEFAULT_AGENTRUN_MGR_URL = "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080";
|
|
const DEFAULT_TENANT_ID = "hwlab";
|
|
const DEFAULT_PROJECT_ID = "pikasTech/HWLAB";
|
|
const DEFAULT_PROVIDER_ID = "G14";
|
|
const DEFAULT_REPO_URL = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
|
|
const DEFAULT_RUNNER_NAMESPACE = "agentrun-v01";
|
|
const DEFAULT_TIMEOUT_MS = 1_200_000;
|
|
const AGENTRUN_BACKENDS = Object.freeze(["codex", "deepseek", "minimax-m3"]);
|
|
const THREAD_CONTINUITY_POLICY = "hwlab-agentrun-v01-reuse-runner-thread";
|
|
const SESSION_POLICY_RUN_LOCAL = "hwlab-agentrun-v01-session-runner-reuse";
|
|
const TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "blocked", "cancelled", "canceled"]);
|
|
const HWLAB_RESOURCE_TOOL_ALIASES = Object.freeze([
|
|
Object.freeze({ name: "hwpod", path: "tools/device-pod-cli.mjs", kind: "node-script" }),
|
|
Object.freeze({ name: "unidesk-ssh", path: "tools/unidesk-ssh.mjs", kind: "bun-script" })
|
|
]);
|
|
const HWLAB_RESOURCE_PROMPT_REFS = Object.freeze([
|
|
Object.freeze({ name: "hwlab-v02-runtime", path: "internal/agent/prompts/hwlab-v02-runtime.md", inject: "thread-start", required: true })
|
|
]);
|
|
const HWLAB_RESOURCE_SKILL_REFS = Object.freeze([
|
|
Object.freeze({ name: "device-pod-cli", path: "skills/device-pod-cli/SKILL.md", required: true, aggregateAs: "device-pod-cli" }),
|
|
Object.freeze({ name: "hwlab-agent-runtime", path: "skills/hwlab-agent-runtime/SKILL.md", required: true, aggregateAs: "hwlab-agent-runtime" })
|
|
]);
|
|
|
|
export function codeAgentAgentRunAdapterEnabled(env = process.env) {
|
|
const value = String(env.HWLAB_CODE_AGENT_ADAPTER ?? env.HWLAB_CODE_AGENT_PROVIDER ?? "").trim().toLowerCase();
|
|
return value === ADAPTER_ID || value === "agentrun" || value === "agentrun-v0.1";
|
|
}
|
|
|
|
export function initialAgentRunChatResult({ params = {}, options = {}, traceId }) {
|
|
const env = options.env ?? process.env;
|
|
const timestamp = nowIso(options.now);
|
|
const backendProfile = resolveAgentRunBackendProfile(env, params);
|
|
return {
|
|
accepted: true,
|
|
status: "running",
|
|
shortConnection: true,
|
|
traceId,
|
|
conversationId: safeConversationId(params.conversationId) || null,
|
|
sessionId: safeSessionId(params.sessionId) || agentRunSessionId(traceId),
|
|
threadId: safeOpaqueId(params.threadId) || null,
|
|
messageId: `msg_${safeTraceId(traceId)?.slice(4) || randomUUID()}`,
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
provider: CODEX_STDIO_PROVIDER,
|
|
model: modelForBackendProfile(backendProfile, env),
|
|
backend: CODEX_STDIO_BACKEND,
|
|
infrastructureBackend: `agentrun-v01/${backendProfile}`,
|
|
capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
|
|
sessionMode: CODEX_STDIO_SESSION_MODE,
|
|
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
|
|
threadContinuityPolicy: THREAD_CONTINUITY_POLICY,
|
|
agentRun: {
|
|
adapter: ADAPTER_ID,
|
|
status: "pending-dispatch",
|
|
backendProfile,
|
|
providerId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID),
|
|
managerUrl: resolveAgentRunManagerUrl(env),
|
|
repoUrl: resolveAgentRunRepoUrl(env),
|
|
valuesPrinted: false
|
|
},
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
export async function submitAgentRunChatTurn({ params = {}, options = {}, traceId, traceStore = defaultCodeAgentTraceStore, results }) {
|
|
const env = options.env ?? process.env;
|
|
const managerUrl = resolveAgentRunManagerUrl(env);
|
|
const backendProfile = resolveAgentRunBackendProfile(env, params);
|
|
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
const timeoutMs = parsePositiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, 20_000);
|
|
const startedAt = nowIso(options.now);
|
|
|
|
traceStore.ensure(traceId, agentRunTraceMeta(env, params));
|
|
traceStore.append(traceId, {
|
|
type: "request",
|
|
status: "accepted",
|
|
label: "agentrun:request:accepted",
|
|
message: "HWLAB Code Agent request accepted by the AgentRun v0.1 adapter; hwlab-cloud-api will reuse an active runner when the HWLAB session has an active AgentRun reuse window, otherwise it will create run/command/runner-job over the k3s Service DNS.",
|
|
waitingFor: "agentrun-run-reuse-or-create",
|
|
adapter: ADAPTER_ID,
|
|
managerHost: new URL(managerUrl).hostname,
|
|
valuesPrinted: false
|
|
}, agentRunTraceMeta(env, params));
|
|
|
|
const reusable = await resolveReusableAgentRun({ params, options, env, managerUrl, fetchImpl, timeoutMs, backendProfile, traceId, traceStore });
|
|
if (reusable?.mapping) {
|
|
try {
|
|
const commandInput = buildAgentRunCommandInput({ params, traceId, backendProfile, sessionId: reusable.sessionId });
|
|
const command = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(reusable.mapping.runId)}/commands`, {
|
|
method: "POST",
|
|
body: commandInput,
|
|
timeoutMs
|
|
});
|
|
const commandId = requiredString(command?.id, "command.id");
|
|
const mapping = agentRunReusedMapping({ previous: reusable.mapping, run: reusable.run, command, traceId, startedAt, backendProfile, managerUrl, env });
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:command:created",
|
|
message: `AgentRun command ${commandId} created on reused run ${mapping.runId}; hwlab-cloud-api will not start a new runner Job for this turn.`,
|
|
runId: mapping.runId,
|
|
commandId,
|
|
backendProfile,
|
|
waitingFor: "agentrun-result",
|
|
valuesPrinted: false
|
|
});
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:runner-job:reused",
|
|
message: `AgentRun runner Job ${mapping.jobName ?? "unknown"} is reused for this HWLAB session turn; no new bundle materialization is requested.`,
|
|
runId: mapping.runId,
|
|
commandId: mapping.commandId,
|
|
attemptId: mapping.attemptId,
|
|
runnerId: mapping.runnerId,
|
|
jobName: mapping.jobName,
|
|
namespace: mapping.namespace,
|
|
waitingFor: "agentrun-result",
|
|
valuesPrinted: false
|
|
});
|
|
return decorateAgentRunRunningResult({ base: initialAgentRunChatResult({ params, options, traceId }), mapping, traceStore, traceId });
|
|
} catch (error) {
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:run:reuse-command-failed",
|
|
message: `AgentRun reused run ${reusable.mapping.runId} rejected the new command; hwlab-cloud-api will create a fresh run/runner Job for this turn.`,
|
|
errorCode: error?.code ?? "agentrun_reuse_command_failed",
|
|
runId: reusable.mapping.runId,
|
|
commandId: reusable.mapping.commandId ?? null,
|
|
waitingFor: "agentrun-run-create",
|
|
valuesPrinted: false
|
|
});
|
|
}
|
|
}
|
|
|
|
const sessionId = scopedAgentRunSessionIdForParams(params, traceId, backendProfile);
|
|
const runInput = buildAgentRunCreateRunInput({ params, env, traceId, backendProfile, sessionId });
|
|
const run = await agentRunJson(fetchImpl, managerUrl, "/api/v1/runs", {
|
|
method: "POST",
|
|
body: runInput,
|
|
timeoutMs
|
|
});
|
|
const runId = requiredString(run?.id, "run.id");
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:run:created",
|
|
message: `AgentRun run ${runId} created through internal k3s Service DNS.`,
|
|
runId,
|
|
backendProfile,
|
|
waitingFor: "agentrun-command-create",
|
|
valuesPrinted: false
|
|
});
|
|
|
|
const commandInput = buildAgentRunCommandInput({ params, traceId, backendProfile, sessionId });
|
|
const command = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(runId)}/commands`, {
|
|
method: "POST",
|
|
body: commandInput,
|
|
timeoutMs
|
|
});
|
|
const commandId = requiredString(command?.id, "command.id");
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:command:created",
|
|
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 runnerJob = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(runId)}/runner-jobs`, {
|
|
method: "POST",
|
|
body: runnerJobInput,
|
|
timeoutMs
|
|
});
|
|
const mapping = agentRunMapping({ env, managerUrl, backendProfile, run, command, runnerJob, traceId, startedAt });
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:runner-job:created",
|
|
message: `AgentRun runner Job ${mapping.jobName ?? "unknown"} created in namespace ${mapping.namespace ?? DEFAULT_RUNNER_NAMESPACE}.`,
|
|
runId: mapping.runId,
|
|
commandId: mapping.commandId,
|
|
attemptId: mapping.attemptId,
|
|
runnerId: mapping.runnerId,
|
|
jobName: mapping.jobName,
|
|
namespace: mapping.namespace,
|
|
waitingFor: "agentrun-result",
|
|
valuesPrinted: false
|
|
});
|
|
return decorateAgentRunRunningResult({ base: initialAgentRunChatResult({ params, options, traceId }), mapping, traceStore, traceId });
|
|
}
|
|
|
|
export async function syncAgentRunChatResult({ traceId, currentResult = null, options = {}, traceStore = defaultCodeAgentTraceStore }) {
|
|
const mapped = currentResult ?? await loadPersistedAgentRunResult(traceId, options);
|
|
if (!mapped?.agentRun?.runId || !mapped?.agentRun?.commandId) return { result: currentResult, runnerTrace: traceStore.snapshot(traceId), found: Boolean(currentResult) };
|
|
const env = options.env ?? process.env;
|
|
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
const managerUrl = resolveAgentRunManagerUrl(env, mapped.agentRun.managerUrl);
|
|
const timeoutMs = parsePositiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, 20_000);
|
|
const eventsResponse = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(mapped.agentRun.runId)}/events?afterSeq=${encodeURIComponent(String(mapped.agentRun.lastSeq ?? 0))}&limit=500`, {
|
|
method: "GET",
|
|
timeoutMs
|
|
});
|
|
const events = Array.isArray(eventsResponse?.items) ? eventsResponse.items : [];
|
|
appendAgentRunEventsToTrace(traceStore, traceId, events, mapped.agentRun);
|
|
const result = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(mapped.agentRun.runId)}/commands/${encodeURIComponent(mapped.agentRun.commandId)}/result`, {
|
|
method: "GET",
|
|
timeoutMs
|
|
});
|
|
const nextMapping = {
|
|
...mapped.agentRun,
|
|
...agentRunResultRefs(result),
|
|
lastSeq: Math.max(Number(mapped.agentRun.lastSeq ?? 0), Number(result?.lastSeq ?? 0), ...events.map((event) => Number(event?.seq ?? 0))),
|
|
status: result?.status ?? mapped.agentRun.status ?? "running",
|
|
runStatus: result?.runStatus ?? mapped.agentRun.runStatus ?? null,
|
|
commandState: result?.commandState ?? mapped.agentRun.commandState ?? null,
|
|
terminalStatus: result?.terminalStatus ?? mapped.agentRun.terminalStatus ?? null,
|
|
updatedAt: nowIso(options.now),
|
|
valuesPrinted: false
|
|
};
|
|
const base = { ...mapped, agentRun: nextMapping, updatedAt: nowIso(options.now) };
|
|
const payload = agentRunResultToCodeAgentPayload({ base, result, traceStore, traceId });
|
|
options.codeAgentChatResults?.set?.(traceId, payload);
|
|
return { result: payload, runnerTrace: payload.runnerTrace ?? traceStore.snapshot(traceId), found: true };
|
|
}
|
|
|
|
export async function refreshAgentRunTrace({ traceId, result = null, options = {}, traceStore = defaultCodeAgentTraceStore }) {
|
|
const mapped = result ?? await loadPersistedAgentRunResult(traceId, options);
|
|
if (!mapped?.agentRun?.runId) return traceStore.snapshot(traceId);
|
|
const env = options.env ?? process.env;
|
|
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
const managerUrl = resolveAgentRunManagerUrl(env, mapped.agentRun.managerUrl);
|
|
const timeoutMs = parsePositiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, 20_000);
|
|
const eventsResponse = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(mapped.agentRun.runId)}/events?afterSeq=${encodeURIComponent(String(mapped.agentRun.lastSeq ?? 0))}&limit=500`, {
|
|
method: "GET",
|
|
timeoutMs
|
|
});
|
|
const events = Array.isArray(eventsResponse?.items) ? eventsResponse.items : [];
|
|
appendAgentRunEventsToTrace(traceStore, traceId, events, mapped.agentRun);
|
|
const lastSeq = Math.max(Number(mapped.agentRun.lastSeq ?? 0), ...events.map((event) => Number(event?.seq ?? 0)));
|
|
if (lastSeq !== Number(mapped.agentRun.lastSeq ?? 0)) {
|
|
options.codeAgentChatResults?.set?.(traceId, { ...mapped, agentRun: { ...mapped.agentRun, lastSeq, updatedAt: nowIso(options.now) } });
|
|
}
|
|
return traceStore.snapshot(traceId);
|
|
}
|
|
|
|
export async function cancelAgentRunChatTurn({ traceId, currentResult = null, options = {}, traceStore = defaultCodeAgentTraceStore }) {
|
|
const mapped = currentResult ?? await loadPersistedAgentRunResult(traceId, options);
|
|
if (!mapped?.agentRun?.commandId) return null;
|
|
const env = options.env ?? process.env;
|
|
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
const managerUrl = resolveAgentRunManagerUrl(env, mapped.agentRun.managerUrl);
|
|
const timeoutMs = parsePositiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, 20_000);
|
|
await agentRunJson(fetchImpl, managerUrl, `/api/v1/commands/${encodeURIComponent(mapped.agentRun.commandId)}/cancel`, {
|
|
method: "POST",
|
|
body: { reason: "hwlab-user-cancel", traceId },
|
|
timeoutMs
|
|
});
|
|
traceStore.append(traceId, {
|
|
type: "cancel",
|
|
status: "canceled",
|
|
label: "agentrun:cancel:canceled",
|
|
message: "HWLAB forwarded cancel to AgentRun command cancel API.",
|
|
runId: mapped.agentRun.runId,
|
|
commandId: mapped.agentRun.commandId,
|
|
terminal: true,
|
|
valuesPrinted: false
|
|
});
|
|
const now = nowIso(options.now);
|
|
const agentRun = { ...mapped.agentRun, status: "cancelled", commandState: "cancelled", terminalStatus: "cancelled", updatedAt: now };
|
|
const payload = {
|
|
...mapped,
|
|
status: "canceled",
|
|
canceled: true,
|
|
updatedAt: now,
|
|
agentRun,
|
|
runnerTrace: traceStore.snapshot(traceId),
|
|
error: {
|
|
code: "agentrun_canceled",
|
|
layer: "agentrun",
|
|
category: "canceled",
|
|
retryable: true,
|
|
message: "AgentRun command was canceled by user request",
|
|
userMessage: "当前 AgentRun 请求已取消;traceId/runId/commandId 已保留,可重试上一条消息。",
|
|
traceId,
|
|
route: "/v1/agent/chat/cancel",
|
|
toolName: "agentrun.command.cancel"
|
|
},
|
|
userMessage: "当前 AgentRun 请求已取消;traceId/runId/commandId 已保留,可重试上一条消息。"
|
|
};
|
|
options.codeAgentChatResults?.set?.(traceId, payload);
|
|
return payload;
|
|
}
|
|
|
|
export async function steerAgentRunChatTurn({ traceId, currentResult = null, params = {}, options = {}, traceStore = defaultCodeAgentTraceStore }) {
|
|
const mapped = currentResult ?? await loadPersistedAgentRunResult(traceId, options);
|
|
if (!safeTraceId(traceId) || !mapped?.agentRun?.runId || !mapped?.agentRun?.commandId) return null;
|
|
const message = firstNonEmpty(params.message, params.prompt, params.text);
|
|
if (!message) {
|
|
throw Object.assign(new Error("steer command requires non-empty message text"), { code: "steer_message_missing", statusCode: 400 });
|
|
}
|
|
const env = options.env ?? process.env;
|
|
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
const managerUrl = resolveAgentRunManagerUrl(env, mapped.agentRun.managerUrl);
|
|
const timeoutMs = parsePositiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS, 20_000);
|
|
const steerTraceId = safeTraceId(params.steerTraceId ?? params.steerId) || `trc_steer_${randomUUID().replace(/-/gu, "")}`;
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:steer:accepted",
|
|
message: "HWLAB accepted an in-flight steer request and will create an AgentRun type=steer command on the existing run.",
|
|
runId: mapped.agentRun.runId,
|
|
commandId: mapped.agentRun.commandId,
|
|
steerTraceId,
|
|
waitingFor: "agentrun-steer-command-create",
|
|
valuesPrinted: false
|
|
});
|
|
const commandInput = buildAgentRunSteerCommandInput({ params: { ...params, message }, traceId, steerTraceId, mapped });
|
|
const command = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(mapped.agentRun.runId)}/commands`, {
|
|
method: "POST",
|
|
body: commandInput,
|
|
timeoutMs
|
|
});
|
|
const steerCommandId = requiredString(command?.id, "command.id");
|
|
const now = nowIso(options.now);
|
|
const agentRun = {
|
|
...mapped.agentRun,
|
|
lastSteerCommandId: steerCommandId,
|
|
lastSteerTraceId: steerTraceId,
|
|
updatedAt: now,
|
|
valuesPrinted: false
|
|
};
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:steer:command-created",
|
|
message: `AgentRun steer command ${steerCommandId} created on run ${mapped.agentRun.runId}; runner will apply it if the target Codex turn is still active.`,
|
|
runId: mapped.agentRun.runId,
|
|
commandId: mapped.agentRun.commandId,
|
|
steerCommandId,
|
|
targetCommandId: mapped.agentRun.commandId,
|
|
steerTraceId,
|
|
waitingFor: "agentrun-steer-apply",
|
|
valuesPrinted: false
|
|
});
|
|
const payload = {
|
|
ok: true,
|
|
accepted: true,
|
|
status: "running",
|
|
shortConnection: true,
|
|
controlSemantics: "steer-active-turn-and-poll-target-trace",
|
|
route: "/v1/agent/chat/steer",
|
|
traceId,
|
|
targetTraceId: traceId,
|
|
steerTraceId,
|
|
conversationId: safeConversationId(mapped.conversationId ?? params.conversationId) || null,
|
|
sessionId: safeSessionId(mapped.sessionId ?? params.sessionId) || null,
|
|
threadId: safeOpaqueId(mapped.threadId ?? params.threadId) || null,
|
|
resultUrl: `/v1/agent/chat/result/${encodeURIComponent(traceId)}`,
|
|
traceUrl: `/v1/agent/chat/trace/${encodeURIComponent(traceId)}`,
|
|
agentRun: {
|
|
...agentRun,
|
|
steerCommandId,
|
|
targetCommandId: mapped.agentRun.commandId,
|
|
steerCommandState: command.state ?? null,
|
|
valuesPrinted: false
|
|
},
|
|
runnerTrace: traceStore.snapshot(traceId, agentRunTraceMeta({}, {})),
|
|
updatedAt: now,
|
|
valuesPrinted: false
|
|
};
|
|
options.codeAgentChatResults?.set?.(traceId, { ...mapped, agentRun, runnerTrace: payload.runnerTrace, updatedAt: now });
|
|
return payload;
|
|
}
|
|
|
|
export async function loadPersistedAgentRunResult(traceId, options = {}) {
|
|
if (!safeTraceId(traceId) || typeof options.accessController?.getAgentSessionByTraceId !== "function") return null;
|
|
const session = await options.accessController.getAgentSessionByTraceId(traceId);
|
|
const agentRun = session?.session?.agentRun;
|
|
if (!agentRun || typeof agentRun !== "object" || !agentRun.runId || !agentRun.commandId) return null;
|
|
return {
|
|
accepted: true,
|
|
status: session.status === "canceled" ? "canceled" : "running",
|
|
shortConnection: true,
|
|
traceId,
|
|
conversationId: safeConversationId(session.conversationId) || agentRun.conversationId || null,
|
|
sessionId: safeSessionId(session.id) || agentRun.sessionId || null,
|
|
threadId: safeOpaqueId(session.threadId) || agentRun.threadId || null,
|
|
messageId: session.session?.messageId ?? `msg_${traceId.slice(4)}`,
|
|
createdAt: session.startedAt ?? session.updatedAt ?? nowIso(options.now),
|
|
updatedAt: session.updatedAt ?? nowIso(options.now),
|
|
ownerUserId: session.ownerUserId,
|
|
provider: CODEX_STDIO_PROVIDER,
|
|
model: modelForBackendProfile(agentRun.backendProfile, options.env ?? process.env),
|
|
backend: CODEX_STDIO_BACKEND,
|
|
infrastructureBackend: `agentrun-v01/${agentRun.backendProfile ?? "deepseek"}`,
|
|
capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
|
|
sessionMode: CODEX_STDIO_SESSION_MODE,
|
|
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
|
|
agentRun: { ...agentRun, adapter: ADAPTER_ID, valuesPrinted: false },
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
export function agentRunSessionEvidence(payload = {}) {
|
|
if (!payload?.agentRun) return {};
|
|
return {
|
|
agentRun: {
|
|
adapter: ADAPTER_ID,
|
|
runId: payload.agentRun.runId ?? null,
|
|
commandId: payload.agentRun.commandId ?? null,
|
|
attemptId: payload.agentRun.attemptId ?? null,
|
|
runnerId: payload.agentRun.runnerId ?? null,
|
|
jobName: payload.agentRun.jobName ?? null,
|
|
namespace: payload.agentRun.namespace ?? null,
|
|
backendProfile: payload.agentRun.backendProfile ?? null,
|
|
managerUrl: payload.agentRun.managerUrl ?? DEFAULT_AGENTRUN_MGR_URL,
|
|
repoUrl: payload.agentRun.repoUrl ?? DEFAULT_REPO_URL,
|
|
status: payload.agentRun.status ?? null,
|
|
runStatus: payload.agentRun.runStatus ?? null,
|
|
commandState: payload.agentRun.commandState ?? null,
|
|
terminalStatus: payload.agentRun.terminalStatus ?? null,
|
|
lastSeq: payload.agentRun.lastSeq ?? 0,
|
|
providerId: payload.agentRun.providerId ?? DEFAULT_PROVIDER_ID,
|
|
reuseEligible: payload.agentRun.reuseEligible ?? false,
|
|
sessionId: payload.agentRun.sessionId ?? payload.sessionId ?? null,
|
|
conversationId: payload.conversationId ?? payload.agentRun.conversationId ?? null,
|
|
threadId: payload.threadId ?? payload.agentRun.threadId ?? null,
|
|
providerTrace: payload.providerTrace ?? payload.agentRun.providerTrace ?? null,
|
|
valuesPrinted: false
|
|
}
|
|
};
|
|
}
|
|
|
|
function buildAgentRunCreateRunInput({ params, env, traceId, backendProfile, sessionId }) {
|
|
const commitId = fullSourceCommit(env);
|
|
const threadId = safeOpaqueId(params.threadId);
|
|
const resourceBundleRef = commitId
|
|
? {
|
|
kind: "git",
|
|
repoUrl: resolveAgentRunRepoUrl(env),
|
|
commitId,
|
|
submodules: false,
|
|
lfs: false,
|
|
toolAliases: HWLAB_RESOURCE_TOOL_ALIASES,
|
|
promptRefs: HWLAB_RESOURCE_PROMPT_REFS,
|
|
skillRefs: HWLAB_RESOURCE_SKILL_REFS
|
|
}
|
|
: null;
|
|
return {
|
|
tenantId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_TENANT_ID, DEFAULT_TENANT_ID),
|
|
projectId: firstNonEmpty(params.projectId, env.HWLAB_CODE_AGENT_AGENTRUN_PROJECT_ID, DEFAULT_PROJECT_ID),
|
|
workspaceRef: {
|
|
kind: "opaque",
|
|
repo: DEFAULT_PROJECT_ID,
|
|
branch: firstNonEmpty(env.HWLAB_BOOT_REF, env.HWLAB_CODE_AGENT_AGENTRUN_BRANCH, "v0.2"),
|
|
runtimeNamespace: firstNonEmpty(env.HWLAB_RUNTIME_NAMESPACE, env.POD_NAMESPACE, env.HWLAB_NAMESPACE, "hwlab-v02"),
|
|
hwlabTraceId: traceId,
|
|
valuesPrinted: false
|
|
},
|
|
sessionRef: {
|
|
sessionId: sessionId ?? scopedAgentRunSessionIdForParams(params, traceId, backendProfile),
|
|
...(safeConversationId(params.conversationId) ? { conversationId: safeConversationId(params.conversationId) } : {}),
|
|
...(threadId ? { threadId } : {}),
|
|
metadata: {
|
|
adapter: ADAPTER_ID,
|
|
hwlabTraceId: traceId,
|
|
hwlabApi: "/v1/agent/chat",
|
|
hwlabSessionId: safeSessionId(params.sessionId) || null,
|
|
threadContinuityPolicy: THREAD_CONTINUITY_POLICY,
|
|
sessionPolicy: SESSION_POLICY_RUN_LOCAL,
|
|
agentRunSessionProfile: backendProfile,
|
|
agentRunSessionPolicy: "backend-profile-scoped",
|
|
valuesPrinted: false
|
|
}
|
|
},
|
|
resourceBundleRef,
|
|
providerId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID),
|
|
backendProfile,
|
|
executionPolicy: {
|
|
sandbox: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_SANDBOX, env.HWLAB_CODE_AGENT_CODEX_SANDBOX, "danger-full-access"),
|
|
approval: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_APPROVAL, "never"),
|
|
timeoutMs: parsePositiveInteger(env.HWLAB_CODE_AGENT_AGENTRUN_TIMEOUT_MS, parsePositiveInteger(env.HWLAB_CODE_AGENT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS)),
|
|
network: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_NETWORK, "enabled"),
|
|
secretScope: {
|
|
allowCredentialEcho: false,
|
|
providerCredentials: [providerCredentialSecretRef(backendProfile, env)],
|
|
toolCredentials: agentRunToolCredentials(env)
|
|
}
|
|
},
|
|
traceSink: {
|
|
kind: "hwlab-cloud-api",
|
|
traceId,
|
|
resultUrl: `/v1/agent/chat/result/${encodeURIComponent(traceId)}`,
|
|
traceUrl: `/v1/agent/chat/trace/${encodeURIComponent(traceId)}`,
|
|
valuesPrinted: false
|
|
}
|
|
};
|
|
}
|
|
|
|
function buildAgentRunCommandInput({ params, traceId, backendProfile, sessionId }) {
|
|
const prompt = String(params.message ?? params.prompt ?? "").trim();
|
|
const threadId = safeOpaqueId(params.threadId);
|
|
return {
|
|
type: "turn",
|
|
payload: {
|
|
prompt,
|
|
message: prompt,
|
|
traceId,
|
|
conversationId: safeConversationId(params.conversationId) || null,
|
|
sessionId: sessionId ?? scopedAgentRunSessionIdForParams(params, traceId, backendProfile),
|
|
hwlabSessionId: safeSessionId(params.sessionId) || null,
|
|
threadId: threadId || null,
|
|
threadContinuityPolicy: THREAD_CONTINUITY_POLICY,
|
|
sessionPolicy: SESSION_POLICY_RUN_LOCAL,
|
|
providerProfile: backendProfile,
|
|
source: "hwlab-cloud-api",
|
|
valuesPrinted: false
|
|
},
|
|
idempotencyKey: traceId
|
|
};
|
|
}
|
|
|
|
function buildAgentRunSteerCommandInput({ params, traceId, steerTraceId, mapped }) {
|
|
const prompt = String(params.message ?? params.prompt ?? params.text ?? "").trim();
|
|
return {
|
|
type: "steer",
|
|
payload: {
|
|
prompt,
|
|
message: prompt,
|
|
text: prompt,
|
|
traceId: steerTraceId,
|
|
targetTraceId: traceId,
|
|
targetCommandId: mapped.agentRun.commandId,
|
|
conversationId: safeConversationId(mapped.conversationId ?? params.conversationId) || null,
|
|
sessionId: mapped.agentRun.sessionId ?? safeSessionId(mapped.sessionId ?? params.sessionId) ?? null,
|
|
hwlabSessionId: safeSessionId(mapped.sessionId ?? params.sessionId) || null,
|
|
threadId: safeOpaqueId(mapped.threadId ?? params.threadId) || null,
|
|
source: "hwlab-cloud-api",
|
|
valuesPrinted: false
|
|
},
|
|
idempotencyKey: steerTraceId
|
|
};
|
|
}
|
|
|
|
function buildAgentRunRunnerJobInput({ env, traceId, commandId }) {
|
|
const transientEnv = buildAgentRunTransientEnv(env);
|
|
return {
|
|
commandId,
|
|
idempotencyKey: traceId,
|
|
namespace: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE, env.AGENTRUN_RUNTIME_NAMESPACE, DEFAULT_RUNNER_NAMESPACE),
|
|
...(firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_IMAGE, env.AGENTRUN_RUNNER_IMAGE)
|
|
? { image: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_IMAGE, env.AGENTRUN_RUNNER_IMAGE) }
|
|
: {}),
|
|
...(firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_SERVICE_ACCOUNT, env.AGENTRUN_RUNNER_SERVICE_ACCOUNT)
|
|
? { serviceAccountName: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_SERVICE_ACCOUNT, env.AGENTRUN_RUNNER_SERVICE_ACCOUNT) }
|
|
: {}),
|
|
...(transientEnv.length > 0 ? { transientEnv } : {})
|
|
};
|
|
}
|
|
|
|
function buildAgentRunTransientEnv(env = process.env) {
|
|
const entries = [];
|
|
for (const name of [
|
|
"HWLAB_RUNTIME_API_URL",
|
|
"HWLAB_RUNTIME_WEB_URL",
|
|
"HWLAB_RUNTIME_NAMESPACE",
|
|
"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" });
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
function agentRunToolCredentials(env = process.env) {
|
|
const namespace = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_TOOL_SECRET_NAMESPACE, env.HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE, DEFAULT_RUNNER_NAMESPACE);
|
|
const githubSecretName = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_GITHUB_TOOL_SECRET_NAME, "agentrun-v01-tool-github-pr");
|
|
const githubSecretKey = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_GITHUB_TOOL_SECRET_KEY, "GH_TOKEN");
|
|
const unideskSecretName = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_UNIDESK_SSH_TOOL_SECRET_NAME, "agentrun-v01-tool-unidesk-ssh");
|
|
return [
|
|
{
|
|
tool: "github",
|
|
purpose: "pull-request",
|
|
secretRef: { namespace, name: githubSecretName, keys: [githubSecretKey] },
|
|
projection: { kind: "env", envName: githubSecretKey, secretKey: githubSecretKey }
|
|
},
|
|
{
|
|
tool: "unidesk-ssh",
|
|
purpose: "ssh-passthrough",
|
|
secretRef: { namespace, name: unideskSecretName, keys: ["UNIDESK_SSH_CLIENT_TOKEN"] },
|
|
projection: { kind: "env", envName: "UNIDESK_SSH_CLIENT_TOKEN", secretKey: "UNIDESK_SSH_CLIENT_TOKEN" }
|
|
}
|
|
];
|
|
}
|
|
|
|
async function resolveReusableAgentRun({ params = {}, options = {}, env = process.env, managerUrl, fetchImpl, timeoutMs, backendProfile, traceId, traceStore }) {
|
|
const hwlabSessionId = hwlabSessionIdForParams(params, traceId);
|
|
if (!safeSessionId(hwlabSessionId) || hwlabSessionId === agentRunSessionId(traceId) || typeof options.accessController?.getAgentSession !== "function") {
|
|
return null;
|
|
}
|
|
const session = await options.accessController.getAgentSession(hwlabSessionId);
|
|
if (!canReuseAgentRunSessionForOwner(session, params, options)) {
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:run:reuse-owner-skipped",
|
|
message: "Stored AgentRun session is not visible to the current actor; a new runner Job will be created for this request.",
|
|
sessionId: hwlabSessionId,
|
|
waitingFor: "agentrun-run-create",
|
|
valuesPrinted: false
|
|
});
|
|
return null;
|
|
}
|
|
const mapping = session?.session?.agentRun;
|
|
if (!isReusableAgentRunMapping(mapping, backendProfile)) {
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:run:reuse-skipped",
|
|
message: "No reusable AgentRun run was found for this HWLAB session; a new runner Job will be created.",
|
|
sessionId: hwlabSessionId,
|
|
waitingFor: "agentrun-run-create",
|
|
valuesPrinted: false
|
|
});
|
|
return null;
|
|
}
|
|
let run = null;
|
|
try {
|
|
run = await agentRunJson(fetchImpl, managerUrl, `/api/v1/runs/${encodeURIComponent(mapping.runId)}`, {
|
|
method: "GET",
|
|
timeoutMs
|
|
});
|
|
} catch (error) {
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:run:reuse-unavailable",
|
|
message: `Stored AgentRun run ${mapping.runId} could not be read; a new runner Job will be created.`,
|
|
errorCode: error?.code ?? "agentrun_run_lookup_failed",
|
|
runId: mapping.runId,
|
|
sessionId: hwlabSessionId,
|
|
waitingFor: "agentrun-run-create",
|
|
valuesPrinted: false
|
|
});
|
|
return null;
|
|
}
|
|
const reason = agentRunReuseBlocker(run, mapping);
|
|
if (reason) {
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:run:reuse-blocked",
|
|
message: `Stored AgentRun run ${mapping.runId} cannot be reused: ${reason}; a new runner Job will be created.`,
|
|
runId: mapping.runId,
|
|
sessionId: hwlabSessionId,
|
|
waitingFor: "agentrun-run-create",
|
|
valuesPrinted: false
|
|
});
|
|
return null;
|
|
}
|
|
traceStore.append(traceId, {
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:run:reused",
|
|
message: `AgentRun run ${mapping.runId} is reused for HWLAB session ${hwlabSessionId}; runner reuse window is active and no new bundle will be requested for this turn.`,
|
|
runId: mapping.runId,
|
|
commandId: mapping.commandId ?? null,
|
|
runnerId: mapping.runnerId ?? null,
|
|
jobName: mapping.jobName ?? null,
|
|
namespace: mapping.namespace ?? null,
|
|
sessionId: hwlabSessionId,
|
|
waitingFor: "agentrun-command-create",
|
|
valuesPrinted: false
|
|
});
|
|
return { sessionId: mapping.sessionId ?? scopedAgentRunSessionIdForParams(params, traceId, backendProfile), hwlabSessionId, session, mapping, run };
|
|
}
|
|
|
|
function isReusableAgentRunMapping(mapping, backendProfile) {
|
|
return Boolean(
|
|
mapping &&
|
|
typeof mapping === "object" &&
|
|
mapping.adapter === ADAPTER_ID &&
|
|
mapping.reuseEligible !== false &&
|
|
mapping.runId &&
|
|
mapping.jobName &&
|
|
(!mapping.backendProfile || mapping.backendProfile === backendProfile)
|
|
);
|
|
}
|
|
|
|
function canReuseAgentRunSessionForOwner(session, params = {}, options = {}) {
|
|
if (!session) return false;
|
|
const ownerUserId = options.actor?.id ?? params.ownerUserId ?? null;
|
|
const ownerRole = options.actor?.role ?? params.ownerRole ?? null;
|
|
if (ownerRole === "admin") return true;
|
|
if (!session.ownerUserId) return true;
|
|
return Boolean(ownerUserId && session.ownerUserId === ownerUserId);
|
|
}
|
|
|
|
function agentRunReuseBlocker(run = {}, mapping = {}) {
|
|
const status = String(run?.terminalStatus ?? run?.status ?? mapping.runStatus ?? "").toLowerCase();
|
|
if (TERMINAL_RUN_STATUSES.has(status)) return `run_status_${status}`;
|
|
if (!run?.claimedBy) return "runner_not_claimed";
|
|
if (runLeaseExpired(run.leaseExpiresAt)) return "runner_reuse_window_expired";
|
|
return null;
|
|
}
|
|
|
|
function runLeaseExpired(leaseExpiresAt) {
|
|
const expiresMs = Date.parse(String(leaseExpiresAt ?? ""));
|
|
return !Number.isFinite(expiresMs) || expiresMs <= Date.now();
|
|
}
|
|
|
|
function providerCredentialSecretRef(profile, env) {
|
|
const profileEnvKey = String(profile ?? "").toUpperCase().replace(/[^A-Z0-9]+/gu, "_");
|
|
return {
|
|
profile,
|
|
secretRef: {
|
|
namespace: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE, DEFAULT_RUNNER_NAMESPACE),
|
|
name: firstNonEmpty(env[`HWLAB_CODE_AGENT_AGENTRUN_${profileEnvKey}_SECRET_NAME`], env[`HWLAB_CODE_AGENT_AGENTRUN_${String(profile ?? "").toUpperCase()}_SECRET_NAME`], env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_SECRET_NAME, `agentrun-v01-provider-${profile}`),
|
|
keys: ["auth.json", "config.toml"],
|
|
mountPath: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_CODEX_HOME_MOUNT, "~/.codex")
|
|
}
|
|
};
|
|
}
|
|
|
|
function agentRunProviderTrace({ base = {}, result = {}, terminalStatus = null } = {}) {
|
|
return {
|
|
transport: "stdio",
|
|
protocol: CODEX_APP_SERVER_PROTOCOL,
|
|
wireApi: CODEX_APP_SERVER_WIRE_API,
|
|
runnerKind: CODEX_STDIO_RUNNER_KIND,
|
|
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
|
|
command: "codex app-server --listen stdio://",
|
|
toolName: "agentrun.v01.command.turn",
|
|
source: ADAPTER_ID,
|
|
backendProfile: base.agentRun?.backendProfile ?? null,
|
|
runId: base.agentRun?.runId ?? result?.runId ?? null,
|
|
commandId: base.agentRun?.commandId ?? result?.commandId ?? null,
|
|
runnerId: base.agentRun?.runnerId ?? result?.runnerId ?? null,
|
|
jobName: base.agentRun?.jobName ?? result?.jobName ?? null,
|
|
namespace: base.agentRun?.namespace ?? result?.namespace ?? null,
|
|
traceId: base.traceId ?? base.agentRun?.traceId ?? null,
|
|
threadId: result?.sessionRef?.threadId ?? base.agentRun?.threadId ?? base.threadId ?? null,
|
|
turnId: result?.turnId ?? null,
|
|
terminalStatus: terminalStatus ?? result?.terminalStatus ?? null,
|
|
failureKind: result?.failureKind ?? null,
|
|
failureMessage: result?.failureMessage ?? result?.blocker?.message ?? null,
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function agentRunFailureAttribution({ code, message, canceled = false } = {}) {
|
|
if (canceled) {
|
|
return {
|
|
category: "canceled",
|
|
retryable: true,
|
|
userMessage: "AgentRun 请求已取消。",
|
|
summary: message || "AgentRun command was canceled."
|
|
};
|
|
}
|
|
if (code === "provider-invalid-tool-call" || /invalid function arguments json string|invalid_prompt|tool_call_id/iu.test(String(message ?? ""))) {
|
|
return {
|
|
category: "provider_invalid_tool_call",
|
|
retryable: true,
|
|
userMessage: "Codex app-server/provider 返回了无效 tool-call arguments JSON;这不是 HWLAB device-pod 或 Cloud API 端点失败,请查看 providerTrace.failureKind 和 runnerTrace 定位上游 tool_call_id。",
|
|
summary: message || "Codex app-server/provider returned invalid tool-call arguments JSON."
|
|
};
|
|
}
|
|
if (code === "thread-resume-failed") {
|
|
return {
|
|
category: "thread_resume_failed",
|
|
retryable: true,
|
|
userMessage: "AgentRun 复用的 Codex thread 已失效;当前 turn 会按失败终止,Workbench 会清理 stale continuation 指针,下一轮自动从新 session/thread 启动。",
|
|
summary: message || "AgentRun thread/resume failed for an existing thread."
|
|
};
|
|
}
|
|
return {
|
|
category: "agentrun_failed",
|
|
retryable: true,
|
|
userMessage: "AgentRun 请求失败;请查看 runnerTrace、providerTrace 和 agentRun 字段定位 runId/commandId/jobName。",
|
|
summary: message || "AgentRun command failed."
|
|
};
|
|
}
|
|
|
|
function agentRunLongLivedSessionGate(base = {}) {
|
|
return longLivedSessionGate({
|
|
provider: CODEX_STDIO_PROVIDER,
|
|
runnerKind: CODEX_STDIO_RUNNER_KIND,
|
|
session: agentRunSessionSummary(base, "idle"),
|
|
sessionMode: CODEX_STDIO_SESSION_MODE,
|
|
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
|
|
codexStdioFeasibility: {
|
|
ready: true,
|
|
canStartLongLivedCodexStdio: true,
|
|
source: ADAPTER_ID,
|
|
valuesPrinted: false
|
|
}
|
|
});
|
|
}
|
|
|
|
function agentRunToolCalls(result = {}, status = "completed") {
|
|
return [{
|
|
name: "codex-app-server.turn",
|
|
status,
|
|
runId: result?.runId ?? null,
|
|
commandId: result?.commandId ?? null,
|
|
outputSummary: result?.reply ? `assistantChars=${String(result.reply).length}` : null,
|
|
valuesPrinted: false
|
|
}];
|
|
}
|
|
|
|
function agentRunResultToCodeAgentPayload({ base, result, traceStore, traceId }) {
|
|
const terminalStatus = String(result?.terminalStatus ?? "");
|
|
const terminal = terminalStatus === "completed" || terminalStatus === "failed" || terminalStatus === "blocked" || terminalStatus === "cancelled";
|
|
if (!terminal) {
|
|
return decorateAgentRunRunningResult({ base, mapping: base.agentRun, traceStore, traceId });
|
|
}
|
|
const now = nowIso();
|
|
const runnerTrace = traceStore.snapshot(traceId, agentRunTraceMeta({}, {}));
|
|
const terminalEventCreatedAt = agentRunResultTraceCreatedAt(runnerTrace, now);
|
|
const providerTrace = agentRunProviderTrace({ base, result, terminalStatus });
|
|
if (terminalStatus === "completed" && String(result?.reply ?? "").trim()) {
|
|
traceStore.append(traceId, {
|
|
type: "result",
|
|
status: "completed",
|
|
label: "agentrun:result:completed",
|
|
createdAt: terminalEventCreatedAt,
|
|
message: "AgentRun result is ready for HWLAB short-connection polling.",
|
|
runId: base.agentRun.runId,
|
|
commandId: base.agentRun.commandId,
|
|
terminal: true,
|
|
valuesPrinted: false
|
|
});
|
|
return {
|
|
...base,
|
|
status: "completed",
|
|
updatedAt: now,
|
|
workspace: base.workspace ?? "/home/agentrun/workspace",
|
|
sandbox: base.sandbox ?? "danger-full-access",
|
|
session: agentRunSessionSummary(base, "idle"),
|
|
sessionReuse: agentRunSessionReuseSummary(base, base.agentRun.reused === true),
|
|
runner: agentRunRunnerSummary(base.agentRun),
|
|
runnerTrace: traceStore.snapshot(traceId, agentRunTraceMeta({}, {})),
|
|
toolCalls: agentRunToolCalls(result, "completed"),
|
|
skills: { status: "delegated", provider: ADAPTER_ID, count: 0, items: [], valuesPrinted: false },
|
|
longLivedSessionGate: agentRunLongLivedSessionGate(base),
|
|
providerTrace,
|
|
reply: {
|
|
messageId: base.messageId ?? `msg_${traceId.slice(4)}`,
|
|
role: "assistant",
|
|
content: String(result.reply),
|
|
createdAt: now
|
|
},
|
|
usage: null,
|
|
agentRun: { ...base.agentRun, terminalStatus, completed: true, reuseEligible: true, providerTrace, valuesPrinted: false },
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
const canceled = terminalStatus === "cancelled";
|
|
const code = canceled ? "agentrun_canceled" : result?.failureKind ?? (terminalStatus === "blocked" ? "agentrun_blocked" : "agentrun_failed");
|
|
const message = result?.failureMessage ?? result?.blocker?.message ?? (canceled ? "AgentRun command was canceled" : "AgentRun command failed");
|
|
const attribution = agentRunFailureAttribution({ code, message, canceled });
|
|
traceStore.append(traceId, {
|
|
type: "result",
|
|
status: canceled ? "canceled" : "failed",
|
|
label: `agentrun:result:${canceled ? "canceled" : terminalStatus || "failed"}`,
|
|
createdAt: terminalEventCreatedAt,
|
|
errorCode: code,
|
|
message: attribution.summary,
|
|
runId: base.agentRun.runId,
|
|
commandId: base.agentRun.commandId,
|
|
terminal: true,
|
|
valuesPrinted: false
|
|
});
|
|
const partialContext = partialAgentRunContext(runnerTrace);
|
|
return {
|
|
...base,
|
|
status: canceled ? "canceled" : "failed",
|
|
canceled,
|
|
updatedAt: now,
|
|
session: agentRunSessionSummary(base, canceled ? "canceled" : "failed"),
|
|
sessionReuse: agentRunSessionReuseSummary(base, base.agentRun.reused === true),
|
|
runner: agentRunRunnerSummary(base.agentRun),
|
|
runnerTrace: partialContext ? { ...runnerTrace, partialContext } : runnerTrace,
|
|
toolCalls: agentRunToolCalls(result, canceled ? "canceled" : "failed"),
|
|
skills: { status: "delegated", provider: ADAPTER_ID, count: 0, items: [], valuesPrinted: false },
|
|
providerTrace,
|
|
error: {
|
|
code,
|
|
layer: "agentrun",
|
|
category: attribution.category,
|
|
retryable: attribution.retryable,
|
|
message,
|
|
userMessage: attribution.userMessage,
|
|
traceId,
|
|
route: "/v1/agent/chat",
|
|
toolName: "agentrun.manual-dispatch"
|
|
},
|
|
blocker: canceled ? null : {
|
|
code,
|
|
layer: "agentrun",
|
|
category: attribution.category,
|
|
retryable: attribution.retryable,
|
|
summary: attribution.summary,
|
|
traceId,
|
|
route: "/v1/agent/chat",
|
|
toolName: "agentrun.manual-dispatch"
|
|
},
|
|
agentRun: { ...base.agentRun, terminalStatus, completed: false, providerTrace, valuesPrinted: false },
|
|
...(partialContext ? { partialContext } : {}),
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function partialAgentRunContext(runnerTrace = {}) {
|
|
const events = Array.isArray(runnerTrace.events) ? runnerTrace.events : [];
|
|
const assistantMessages = events
|
|
.filter((event) => event?.type === "assistant" && String(event.text ?? event.message ?? "").trim())
|
|
.map((event) => String(event.text ?? event.message).trim())
|
|
.slice(-4);
|
|
const toolEvidence = events
|
|
.filter((event) => event?.type === "tool_call" && String(event.status ?? "") === "completed")
|
|
.map((event) => [event.toolName ?? event.label ?? "tool", event.command ?? event.outputSummary ?? event.stdoutSummary ?? event.message ?? ""].filter(Boolean).join(": "))
|
|
.filter(Boolean)
|
|
.slice(-6);
|
|
if (assistantMessages.length === 0 && toolEvidence.length === 0) return null;
|
|
return {
|
|
status: "partial-before-terminal",
|
|
summary: "AgentRun terminal 前已有可延续的 assistant/tool 上下文;后续同 conversation/session/thread 轮次必须能继续使用。",
|
|
assistantMessages,
|
|
toolEvidence,
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function agentRunResultTraceCreatedAt(runnerTrace = {}, fallback) {
|
|
const last = String(runnerTrace?.lastEvent?.createdAt ?? runnerTrace?.updatedAt ?? "");
|
|
return Number.isFinite(Date.parse(last)) ? last : fallback;
|
|
}
|
|
|
|
function decorateAgentRunRunningResult({ base, mapping, traceStore, traceId }) {
|
|
return {
|
|
...base,
|
|
status: "running",
|
|
accepted: true,
|
|
shortConnection: true,
|
|
updatedAt: nowIso(),
|
|
session: agentRunSessionSummary({ ...base, agentRun: mapping }, "running"),
|
|
sessionReuse: agentRunSessionReuseSummary({ ...base, agentRun: mapping }, mapping.reused === true),
|
|
runner: agentRunRunnerSummary(mapping),
|
|
runnerTrace: traceStore.snapshot(traceId, agentRunTraceMeta({}, {})),
|
|
agentRun: { ...mapping, adapter: ADAPTER_ID, valuesPrinted: false },
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function appendAgentRunEventsToTrace(traceStore, traceId, events, mapping = {}) {
|
|
for (const event of events) {
|
|
if (isForeignAgentRunCommandEvent(event, mapping)) continue;
|
|
const normalized = mapAgentRunEvent(event, mapping);
|
|
if (normalized) traceStore.append(traceId, normalized, agentRunTraceMeta({}, {}));
|
|
}
|
|
}
|
|
|
|
function isForeignAgentRunCommandEvent(event, mapping = {}) {
|
|
const currentCommandId = typeof mapping.commandId === "string" ? mapping.commandId : "";
|
|
if (!currentCommandId) return false;
|
|
const payload = event?.payload && typeof event.payload === "object" ? event.payload : {};
|
|
const eventCommandId = typeof payload.commandId === "string" ? payload.commandId : "";
|
|
return Boolean(eventCommandId && eventCommandId !== currentCommandId);
|
|
}
|
|
|
|
function mapAgentRunEvent(event, mapping = {}) {
|
|
if (!event || typeof event !== "object") return null;
|
|
const payload = event.payload && typeof event.payload === "object" ? event.payload : {};
|
|
const base = {
|
|
createdAt: event.createdAt ?? null,
|
|
source: "agentrun",
|
|
sourceSeq: event.seq ?? null,
|
|
runId: event.runId ?? mapping.runId ?? null,
|
|
commandId: payload.commandId ?? mapping.commandId ?? null,
|
|
attemptId: payload.attemptId ?? mapping.attemptId ?? null,
|
|
runnerId: payload.runnerId ?? mapping.runnerId ?? null,
|
|
jobName: payload.jobName ?? mapping.jobName ?? null,
|
|
namespace: payload.namespace ?? mapping.namespace ?? null,
|
|
valuesPrinted: false
|
|
};
|
|
if (event.type === "backend_status") {
|
|
const phase = String(payload.phase ?? "status");
|
|
return { ...base, type: "backend", status: "running", label: `agentrun:backend:${phase}`, message: textPayload(payload, phase), waitingFor: waitingForForPhase(phase) };
|
|
}
|
|
if (event.type === "assistant_message") {
|
|
const terminal = payload.replyAuthority === true || payload.final === true;
|
|
return {
|
|
...base,
|
|
type: "assistant",
|
|
status: terminal ? "completed" : "running",
|
|
label: "agentrun:assistant:message",
|
|
message: textPayload(payload, "assistant message"),
|
|
text: textPayload(payload, ""),
|
|
itemId: payload.itemId ?? null,
|
|
messageIndex: payload.messageIndex ?? null,
|
|
messageCount: payload.messageCount ?? null,
|
|
replyAuthority: payload.replyAuthority === true,
|
|
final: payload.final === true,
|
|
terminal
|
|
};
|
|
}
|
|
if (event.type === "tool_call") {
|
|
const commandExecution = agentRunCommandExecutionEvent(base, payload);
|
|
if (commandExecution) return commandExecution;
|
|
return {
|
|
...base,
|
|
type: "tool_call",
|
|
status: String(payload.status ?? (payload.method === "item/completed" ? "completed" : "running")),
|
|
label: `agentrun:tool:${String(payload.name ?? payload.method ?? "call")}`,
|
|
itemId: payload.item?.id ?? payload.itemId ?? null,
|
|
toolName: payload.item?.type ?? payload.name ?? payload.method ?? "call",
|
|
message: textPayload(payload, "tool call"),
|
|
outputBytes: typeof payload.outputBytes === "number" ? payload.outputBytes : payload.summary?.outputBytes,
|
|
outputTruncated: payload.outputTruncated === true || payload.summary?.outputTruncated === true
|
|
};
|
|
}
|
|
if (event.type === "command_output") {
|
|
const stream = payload.stream === "stderr" ? "stderr" : "stdout";
|
|
return { ...base, type: "output", status: "running", label: `agentrun:output:${stream}`, stream, message: textPayload(payload, ""), outputTruncated: payload.outputTruncated === true };
|
|
}
|
|
if (event.type === "diff") {
|
|
return { ...base, type: "diff", status: "running", label: "agentrun:diff", message: textPayload(payload, "diff") };
|
|
}
|
|
if (event.type === "error") {
|
|
return { ...base, type: "error", status: "failed", label: `agentrun:error:${String(payload.failureKind ?? "backend")}`, errorCode: payload.failureKind ?? "agentrun_error", message: textPayload(payload, "AgentRun error") };
|
|
}
|
|
if (event.type === "terminal_status") {
|
|
const terminalStatus = String(payload.terminalStatus ?? "failed");
|
|
return { ...base, type: "result", status: terminalStatus === "cancelled" ? "canceled" : terminalStatus, label: `agentrun:terminal:${terminalStatus}`, errorCode: payload.failureKind ?? null, message: textPayload(payload, `AgentRun terminal status ${terminalStatus}`), terminal: true };
|
|
}
|
|
return { ...base, type: "backend", status: "running", label: `agentrun:event:${String(event.type ?? "unknown")}`, message: textPayload(payload, "AgentRun event") };
|
|
}
|
|
|
|
function agentRunCommandExecutionEvent(base, payload = {}) {
|
|
const item = payload.item && typeof payload.item === "object" ? payload.item : null;
|
|
const payloadToolType = firstNonEmpty(payload.toolName, payload.type, payload.name);
|
|
if (item?.type !== "commandExecution" && payloadToolType !== "commandExecution") return null;
|
|
const method = String(payload.method ?? "");
|
|
const completed = method === "item/completed" || payload.status === "completed" || item?.status === "completed";
|
|
const output = firstNonEmpty(item?.aggregatedOutput, item?.stdout, item?.output, payload.outputSummary, payload.stdoutSummary, payload.summary?.text, payload.itemPreview);
|
|
return {
|
|
...base,
|
|
type: "tool_call",
|
|
status: completed ? "completed" : "started",
|
|
label: completed ? "item/commandExecution:completed" : "item/commandExecution:started",
|
|
toolName: "commandExecution",
|
|
itemId: item?.id ?? payload.itemId ?? null,
|
|
command: firstNonEmpty(item?.command, item?.commandLine, payload.command, payload.commandLine),
|
|
exitCode: Number.isInteger(item?.exitCode) ? item.exitCode : Number.isInteger(payload.exitCode) ? payload.exitCode : undefined,
|
|
durationMs: typeof item?.durationMs === "number" ? item.durationMs : typeof payload.durationMs === "number" ? payload.durationMs : undefined,
|
|
outputBytes: typeof payload.outputBytes === "number" ? payload.outputBytes : payload.summary?.outputBytes,
|
|
stdoutSummary: output,
|
|
outputSummary: output,
|
|
outputTruncated: payload.outputTruncated === true || payload.summary?.outputTruncated === true,
|
|
message: output || textPayload(payload, "commandExecution")
|
|
};
|
|
}
|
|
|
|
async function agentRunJson(fetchImpl, managerUrl, path, { method = "GET", body, timeoutMs = 20_000 } = {}) {
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
try {
|
|
const response = await fetchImpl(`${managerUrl}${path}`, {
|
|
method,
|
|
headers: body === undefined ? undefined : { "content-type": "application/json" },
|
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
signal: controller.signal
|
|
});
|
|
const text = await response.text();
|
|
const parsed = text ? JSON.parse(text) : {};
|
|
if (!response.ok || parsed?.ok === false) {
|
|
throw Object.assign(new Error(parsed?.message ?? `AgentRun ${method} ${path} failed with HTTP ${response.status}`), {
|
|
code: parsed?.failureKind ?? "agentrun_request_failed",
|
|
statusCode: response.status,
|
|
agentRunError: parsed
|
|
});
|
|
}
|
|
return parsed?.ok === true && Object.hasOwn(parsed, "data") ? parsed.data : parsed;
|
|
} catch (error) {
|
|
if (error?.name === "AbortError") {
|
|
throw Object.assign(new Error(`AgentRun ${method} ${path} timed out after ${timeoutMs}ms`), { code: "agentrun_timeout", statusCode: 504 });
|
|
}
|
|
throw error;
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|
|
|
|
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)}`), {
|
|
code: "agentrun_internal_url_required",
|
|
statusCode: 500
|
|
});
|
|
}
|
|
url.pathname = url.pathname.replace(/\/+$/u, "");
|
|
url.search = "";
|
|
url.hash = "";
|
|
return url.toString().replace(/\/+$/u, "");
|
|
}
|
|
|
|
function resolveAgentRunBackendProfile(env = process.env, params = {}) {
|
|
const value = String(params.providerProfile ?? params.codeAgentProviderProfile ?? env.HWLAB_CODE_AGENT_AGENTRUN_BACKEND_PROFILE ?? env.HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE ?? "deepseek").trim().toLowerCase();
|
|
const mapped = value === "codex-api" || value === "codex" ? "codex" : value === "runtime-default" ? String(env.HWLAB_CODE_AGENT_AGENTRUN_DEFAULT_BACKEND_PROFILE ?? "deepseek").trim().toLowerCase() : value;
|
|
return AGENTRUN_BACKENDS.includes(mapped) ? mapped : "deepseek";
|
|
}
|
|
|
|
function agentRunMapping({ env, managerUrl, backendProfile, run, command, runnerJob, traceId, startedAt }) {
|
|
return {
|
|
adapter: ADAPTER_ID,
|
|
managerUrl,
|
|
backendProfile,
|
|
providerId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID),
|
|
runId: run.id,
|
|
commandId: command.id,
|
|
attemptId: runnerJob?.attemptId ?? runnerJob?.runner?.attemptId ?? null,
|
|
runnerId: runnerJob?.runnerId ?? runnerJob?.runner?.runnerId ?? null,
|
|
runnerJobId: runnerJob?.id ?? null,
|
|
jobName: runnerJob?.jobName ?? runnerJob?.jobIdentity?.name ?? null,
|
|
namespace: runnerJob?.namespace ?? runnerJob?.jobIdentity?.namespace ?? DEFAULT_RUNNER_NAMESPACE,
|
|
status: "runner-job-created",
|
|
runStatus: run.status ?? null,
|
|
commandState: command.state ?? null,
|
|
terminalStatus: null,
|
|
lastSeq: 0,
|
|
traceId,
|
|
sessionId: run?.sessionRef?.sessionId ?? null,
|
|
conversationId: run?.sessionRef?.conversationId ?? null,
|
|
threadId: run?.sessionRef?.threadId ?? null,
|
|
reused: false,
|
|
reuseEligible: true,
|
|
createdAt: startedAt,
|
|
updatedAt: nowIso(),
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function agentRunReusedMapping({ previous = {}, run = {}, command = {}, traceId, startedAt, backendProfile, managerUrl, env }) {
|
|
return {
|
|
...previous,
|
|
adapter: ADAPTER_ID,
|
|
managerUrl,
|
|
backendProfile,
|
|
providerId: previous.providerId ?? firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID),
|
|
runId: previous.runId ?? run?.id,
|
|
commandId: command.id,
|
|
status: "runner-job-reused",
|
|
runStatus: run?.status ?? previous.runStatus ?? null,
|
|
commandState: command.state ?? null,
|
|
terminalStatus: null,
|
|
lastSeq: previous.lastSeq ?? 0,
|
|
runnerJobCount: 0,
|
|
traceId,
|
|
sessionId: run?.sessionRef?.sessionId ?? previous.sessionId ?? null,
|
|
conversationId: run?.sessionRef?.conversationId ?? previous.conversationId ?? null,
|
|
threadId: run?.sessionRef?.threadId ?? previous.threadId ?? null,
|
|
reused: true,
|
|
reuseEligible: true,
|
|
createdAt: previous.createdAt ?? startedAt,
|
|
updatedAt: nowIso(),
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function agentRunResultRefs(result = {}) {
|
|
const refs = {};
|
|
for (const key of ["attemptId", "runnerId", "jobName", "namespace", "runnerJobCount"]) {
|
|
if (result?.[key] !== undefined && result?.[key] !== null) refs[key] = result[key];
|
|
}
|
|
if (result?.sessionRef && typeof result.sessionRef === "object") {
|
|
if (result.sessionRef.sessionId) refs.sessionId = result.sessionRef.sessionId;
|
|
if (result.sessionRef.conversationId) refs.conversationId = result.sessionRef.conversationId;
|
|
if (result.sessionRef.threadId) refs.threadId = result.sessionRef.threadId;
|
|
}
|
|
return refs;
|
|
}
|
|
|
|
function hwlabSessionIdForParams(params = {}, traceId) {
|
|
return safeSessionId(params.sessionId) || agentRunSessionId(traceId);
|
|
}
|
|
|
|
function scopedAgentRunSessionIdForParams(params = {}, traceId, backendProfile) {
|
|
const baseSessionId = hwlabSessionIdForParams(params, traceId);
|
|
const profile = agentRunSessionProfileToken(backendProfile);
|
|
const base = String(baseSessionId).replace(/^ses_/u, "").replace(/[^A-Za-z0-9_]+/gu, "_").replace(/^_+|_+$/gu, "") || "session";
|
|
return `ses_agentrun_${profile}_${base}`;
|
|
}
|
|
|
|
function agentRunSessionProfileToken(backendProfile) {
|
|
return String(backendProfile ?? "deepseek").trim().toLowerCase().replace(/[^a-z0-9]+/gu, "_").replace(/^_+|_+$/gu, "") || "default";
|
|
}
|
|
|
|
function agentRunSessionId(traceId) {
|
|
return `ses_agentrun_${String(safeTraceId(traceId) || `trc_${randomUUID()}`).slice(4)}`;
|
|
}
|
|
|
|
function agentRunSessionSummary(base, status) {
|
|
return {
|
|
sessionId: base.sessionId ?? base.agentRun?.sessionId ?? null,
|
|
conversationId: base.conversationId ?? base.agentRun?.conversationId ?? null,
|
|
threadId: base.threadId ?? base.agentRun?.threadId ?? null,
|
|
status,
|
|
sessionMode: CODEX_STDIO_SESSION_MODE,
|
|
runnerKind: CODEX_STDIO_RUNNER_KIND,
|
|
lastTraceId: base.traceId ?? base.agentRun?.traceId ?? null,
|
|
longLivedSession: true,
|
|
codexStdio: true,
|
|
writeCapable: true,
|
|
durable: true,
|
|
durableSession: true,
|
|
idleTimeoutMs: parsePositiveInteger(base.agentRun?.idleTimeoutMs, 600000),
|
|
secretMaterialStored: false,
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
|
|
function agentRunSessionReuseSummary(base, reused) {
|
|
return {
|
|
sessionId: base.sessionId ?? base.agentRun?.sessionId ?? null,
|
|
conversationId: base.conversationId ?? base.agentRun?.conversationId ?? null,
|
|
threadId: base.threadId ?? base.agentRun?.threadId ?? null,
|
|
reused: Boolean(reused),
|
|
status: reused ? "reused" : "new",
|
|
runnerReused: Boolean(reused),
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
|
|
function agentRunRunnerSummary(mapping = {}) {
|
|
return {
|
|
kind: CODEX_STDIO_RUNNER_KIND,
|
|
adapter: ADAPTER_ID,
|
|
runId: mapping.runId ?? null,
|
|
commandId: mapping.commandId ?? null,
|
|
attemptId: mapping.attemptId ?? null,
|
|
runnerId: mapping.runnerId ?? null,
|
|
jobName: mapping.jobName ?? null,
|
|
namespace: mapping.namespace ?? null,
|
|
backendProfile: mapping.backendProfile ?? null,
|
|
provider: CODEX_STDIO_PROVIDER,
|
|
sessionMode: CODEX_STDIO_SESSION_MODE,
|
|
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
|
|
capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
|
|
codexStdio: true,
|
|
writeCapable: true,
|
|
durableSession: true,
|
|
longLivedSession: true,
|
|
sandbox: "danger-full-access",
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function agentRunTraceMeta(env = process.env) {
|
|
return {
|
|
runnerKind: CODEX_STDIO_RUNNER_KIND,
|
|
workspace: resolveAgentRunRepoUrl(env),
|
|
sandbox: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_SANDBOX, env.HWLAB_CODE_AGENT_CODEX_SANDBOX, "danger-full-access"),
|
|
protocol: CODEX_APP_SERVER_PROTOCOL,
|
|
sessionMode: CODEX_STDIO_SESSION_MODE,
|
|
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE
|
|
};
|
|
}
|
|
|
|
function resolveAgentRunRepoUrl(env = process.env) {
|
|
const raw = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_REPO_URL, env.HWLAB_BOOT_READ_URL, DEFAULT_REPO_URL);
|
|
const url = new URL(raw);
|
|
const host = url.hostname.toLowerCase();
|
|
const allowedHost = "git-mirror-http.devops-infra.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(`HWLAB_CODE_AGENT_AGENTRUN_REPO_URL must use internal k3s git mirror ${DEFAULT_REPO_URL}; got ${redactUrl(raw)}`), {
|
|
code: "agentrun_internal_repo_url_required",
|
|
statusCode: 500
|
|
});
|
|
}
|
|
url.pathname = url.pathname.replace(/\/+$/u, "");
|
|
url.search = "";
|
|
url.hash = "";
|
|
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 modelForBackendProfile(profile, env = process.env) {
|
|
if (profile === "codex") return firstNonEmpty(env.HWLAB_CODE_AGENT_CODEX_API_MODEL, env.HWLAB_CODE_AGENT_MODEL, "gpt-5.5");
|
|
if (profile === "minimax-m3") return firstNonEmpty(env.HWLAB_CODE_AGENT_MINIMAX_M3_MODEL, "MiniMax-M3");
|
|
return firstNonEmpty(env.HWLAB_CODE_AGENT_DEEPSEEK_MODEL, "deepseek-chat");
|
|
}
|
|
|
|
function waitingForForPhase(phase) {
|
|
if (phase.includes("runner-job")) return "agentrun-runner";
|
|
if (phase.includes("resource-bundle")) return "agentrun-resource-bundle";
|
|
if (phase.includes("backend")) return "agentrun-backend";
|
|
return "agentrun-result";
|
|
}
|
|
|
|
function textPayload(payload, fallback) {
|
|
for (const key of ["message", "text", "content", "delta", "summary", "phase"]) {
|
|
if (typeof payload?.[key] === "string" && payload[key].length > 0) return payload[key];
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
function requiredString(value, fieldName) {
|
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
throw Object.assign(new Error(`AgentRun response missing ${fieldName}`), { code: "agentrun_response_invalid", statusCode: 502 });
|
|
}
|
|
|
|
function firstNonEmpty(...values) {
|
|
for (const value of values) {
|
|
const text = String(value ?? "").trim();
|
|
if (text) return text;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function nowIso(now) {
|
|
return typeof now === "function" ? now() : new Date().toISOString();
|
|
}
|
|
|
|
function redactUrl(value) {
|
|
try {
|
|
const url = new URL(value);
|
|
if (url.username || url.password) {
|
|
url.username = "***";
|
|
url.password = "";
|
|
}
|
|
return url.toString();
|
|
} catch {
|
|
return "<invalid-url>";
|
|
}
|
|
}
|