Merge pull request #885 from pikasTech/fix/v02-agentrun-result-contract-cleanup

fix(v02): report AgentRun result contract
This commit is contained in:
Lyon
2026-06-05 02:27:53 +08:00
committed by GitHub
2 changed files with 96 additions and 61 deletions
+62 -58
View File
@@ -2,17 +2,6 @@ import { randomUUID } from "node:crypto";
import { defaultCodeAgentTraceStore } from "./code-agent-trace-store.ts";
import { decorateCodeAgentSession } from "./code-agent-session-lifecycle.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,
@@ -31,9 +20,13 @@ 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_BACKEND_PREFIX = ADAPTER_ID;
const AGENTRUN_RUNNER_KIND = "agentrun-v01-shared-runner";
const AGENTRUN_SESSION_MODE = "agentrun-v01-durable-session";
const AGENTRUN_IMPLEMENTATION_TYPE = "agentrun-v01-shared-execution-infra";
const AGENTRUN_CAPABILITY_LEVEL = "agentrun-v01-shared-code-agent-session";
const AGENTRUN_PROVIDER_TRACE_PROTOCOL = "agentrun-v01-jsonrpc";
const AGENTRUN_PROVIDER_TRACE_WIRE_API = "agentrun-v01-command-result";
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";
@@ -82,7 +75,7 @@ export function describeAgentRunAdapterAvailability(env = process.env, options =
const managerHost = hostForUrl(managerUrl);
const repoHost = hostForUrl(repoUrl);
const status = ready ? "agentrun-ready" : "blocked";
const capabilityLevel = ready ? CODEX_STDIO_CAPABILITY_LEVEL : "blocked";
const capabilityLevel = ready ? AGENTRUN_CAPABILITY_LEVEL : "blocked";
return {
endpoint: "POST /v1/agent/chat",
@@ -224,13 +217,13 @@ export function initialAgentRunChatResult({ params = {}, options = {}, traceId }
messageId: `msg_${safeTraceId(traceId)?.slice(4) || randomUUID()}`,
createdAt: timestamp,
updatedAt: timestamp,
provider: CODEX_STDIO_PROVIDER,
provider: providerForBackendProfile(backendProfile),
model: modelForBackendProfile(backendProfile, env),
backend: CODEX_STDIO_BACKEND,
backend: backendForBackendProfile(backendProfile),
infrastructureBackend: `agentrun-v01/${backendProfile}`,
capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
sessionMode: CODEX_STDIO_SESSION_MODE,
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
capabilityLevel: AGENTRUN_CAPABILITY_LEVEL,
sessionMode: AGENTRUN_SESSION_MODE,
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
threadContinuityPolicy: THREAD_CONTINUITY_POLICY,
agentRun: {
adapter: ADAPTER_ID,
@@ -623,13 +616,13 @@ export async function loadPersistedAgentRunResult(traceId, options = {}) {
createdAt: session.startedAt ?? session.updatedAt ?? nowIso(options.now),
updatedAt: session.updatedAt ?? nowIso(options.now),
ownerUserId: session.ownerUserId,
provider: CODEX_STDIO_PROVIDER,
provider: providerForBackendProfile(agentRun.backendProfile ?? "deepseek"),
model: modelForBackendProfile(agentRun.backendProfile, options.env ?? process.env),
backend: CODEX_STDIO_BACKEND,
backend: backendForBackendProfile(agentRun.backendProfile ?? "deepseek"),
infrastructureBackend: `agentrun-v01/${agentRun.backendProfile ?? "deepseek"}`,
capabilityLevel: CODEX_STDIO_CAPABILITY_LEVEL,
sessionMode: CODEX_STDIO_SESSION_MODE,
implementationType: CODEX_STDIO_IMPLEMENTATION_TYPE,
capabilityLevel: AGENTRUN_CAPABILITY_LEVEL,
sessionMode: AGENTRUN_SESSION_MODE,
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
finalResponse: session.session?.finalResponse ?? null,
traceSummary: session.session?.traceSummary ?? null,
agentRun: { ...agentRun, adapter: ADAPTER_ID, valuesPrinted: false },
@@ -1025,12 +1018,12 @@ function providerCredentialSecretRef(profile, env) {
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://",
transport: "agentrun-v01",
protocol: AGENTRUN_PROVIDER_TRACE_PROTOCOL,
wireApi: AGENTRUN_PROVIDER_TRACE_WIRE_API,
runnerKind: AGENTRUN_RUNNER_KIND,
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
command: "agentrun.v01.command.turn",
toolName: "agentrun.v01.command.turn",
source: ADAPTER_ID,
backendProfile: base.agentRun?.backendProfile ?? null,
@@ -1062,15 +1055,15 @@ function agentRunFailureAttribution({ code, message, canceled = false } = {}) {
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."
userMessage: "AgentRun/provider 返回了无效 tool-call arguments JSON;这不是 HWLAB device-pod 或 Cloud API 端点失败,请查看 providerTrace.failureKind 和 runnerTrace 定位上游 tool_call_id。",
summary: message || "AgentRun/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 启动。",
userMessage: "AgentRun 复用的 thread 已失效;当前 turn 会按失败终止,Workbench 会清理 stale continuation 指针,下一轮自动从新 session/thread 启动。",
summary: message || "AgentRun thread/resume failed for an existing thread."
};
}
@@ -1083,24 +1076,29 @@ function agentRunFailureAttribution({ code, message, canceled = false } = {}) {
}
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
}
});
const session = agentRunSessionSummary(base, "idle");
return {
status: "pass",
pass: true,
requiredCapability: AGENTRUN_CAPABILITY_LEVEL,
currentCapability: AGENTRUN_IMPLEMENTATION_TYPE,
sessionId: session.sessionId ?? null,
sessionStatus: session.status ?? null,
runnerKind: AGENTRUN_RUNNER_KIND,
provider: providerForBackendProfile(base.agentRun?.backendProfile ?? "deepseek"),
sessionMode: AGENTRUN_SESSION_MODE,
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
adapter: ADAPTER_ID,
delegatedToAgentRun: true,
codexStdio: false,
blockers: [],
valuesPrinted: false
};
}
function agentRunToolCalls(result = {}, status = "completed") {
return [{
name: "codex-app-server.turn",
name: "agentrun.v01.command.turn",
status,
runId: result?.runId ?? null,
commandId: result?.commandId ?? null,
@@ -1584,11 +1582,12 @@ function agentRunSessionSummary(base, status) {
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,
sessionMode: AGENTRUN_SESSION_MODE,
runnerKind: AGENTRUN_RUNNER_KIND,
lastTraceId: base.traceId ?? base.agentRun?.traceId ?? null,
longLivedSession: true,
codexStdio: true,
codexStdio: false,
delegatedToAgentRun: true,
writeCapable: true,
durable: true,
durableSession: true,
@@ -1624,7 +1623,7 @@ function agentRunSessionReuseSummary(base, reused, options = {}) {
function agentRunRunnerSummary(mapping = {}) {
return {
kind: CODEX_STDIO_RUNNER_KIND,
kind: AGENTRUN_RUNNER_KIND,
adapter: ADAPTER_ID,
runId: mapping.runId ?? null,
commandId: mapping.commandId ?? null,
@@ -1633,11 +1632,12 @@ function agentRunRunnerSummary(mapping = {}) {
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,
provider: providerForBackendProfile(mapping.backendProfile ?? "deepseek"),
sessionMode: AGENTRUN_SESSION_MODE,
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
capabilityLevel: AGENTRUN_CAPABILITY_LEVEL,
codexStdio: false,
delegatedToAgentRun: true,
writeCapable: true,
durableSession: true,
longLivedSession: true,
@@ -1648,15 +1648,19 @@ function agentRunRunnerSummary(mapping = {}) {
function agentRunTraceMeta(env = process.env) {
return {
runnerKind: CODEX_STDIO_RUNNER_KIND,
runnerKind: AGENTRUN_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
protocol: AGENTRUN_PROVIDER_TRACE_PROTOCOL,
sessionMode: AGENTRUN_SESSION_MODE,
implementationType: AGENTRUN_IMPLEMENTATION_TYPE
};
}
function backendForBackendProfile(profile) {
return `${AGENTRUN_BACKEND_PREFIX}/${resolveAgentRunBackendProfile({}, { providerProfile: profile })}`;
}
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);
+34 -3
View File
@@ -437,15 +437,32 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte
const payload = await pollAgentResult(port, traceId);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.provider, "deepseek");
assert.equal(payload.backend, "agentrun-v01/deepseek");
assert.equal(payload.infrastructureBackend, "agentrun-v01/deepseek");
assert.equal(payload.providerTrace.protocol, "codex-app-server-jsonrpc-stdio");
assert.equal(payload.providerTrace.command, "codex app-server --listen stdio://");
assert.equal(payload.capabilityLevel, "agentrun-v01-shared-code-agent-session");
assert.equal(payload.sessionMode, "agentrun-v01-durable-session");
assert.equal(payload.implementationType, "agentrun-v01-shared-execution-infra");
assert.equal(payload.runner.kind, "agentrun-v01-shared-runner");
assert.equal(payload.runner.provider, "deepseek");
assert.equal(payload.runner.codexStdio, false);
assert.equal(payload.runner.delegatedToAgentRun, true);
assert.equal(payload.providerTrace.protocol, "agentrun-v01-jsonrpc");
assert.equal(payload.providerTrace.command, "agentrun.v01.command.turn");
assert.equal(payload.providerTrace.runnerKind, "agentrun-v01-shared-runner");
assert.equal(payload.providerTrace.terminalStatus, "completed");
assert.equal(payload.longLivedSessionGate.status, "pass");
assert.equal(payload.longLivedSessionGate.provider, "deepseek");
assert.equal(payload.longLivedSessionGate.codexStdio, false);
assert.equal(payload.longLivedSessionGate.delegatedToAgentRun, true);
assert.equal(payload.agentRun.runId, "run_hwlab_adapter");
assert.equal(payload.agentRun.commandId, "cmd_hwlab_adapter");
assert.equal(payload.agentRun.jobName, "agentrun-v01-runner-hwlab-adapter");
const serializedPayload = JSON.stringify(payload);
assert.equal(serializedPayload.includes("repo-owned-codex"), false);
assert.equal(serializedPayload.includes("codex-app-server-stdio"), false);
assert.equal(serializedPayload.includes("\"provider\":\"codex-stdio\""), false);
assert.equal(serializedPayload.includes("\"codexStdio\":true"), false);
assert.match(payload.reply.content, /AgentRun adapter/u);
assert.ok(payload.runnerTrace.events.some((event) => event.label === "agentrun:backend:runner-job-created"));
const bundleEvent = payload.runnerTrace.events.find((event) => event.label === "agentrun:backend:resource-bundle-materialized");
@@ -560,6 +577,10 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte
const secondPayload = await pollAgentResult(port, secondTraceId);
validateCodeAgentChatSchema(secondPayload);
assert.equal(secondPayload.status, "completed");
assert.equal(secondPayload.provider, "deepseek");
assert.equal(secondPayload.backend, "agentrun-v01/deepseek");
assert.equal(secondPayload.runner.kind, "agentrun-v01-shared-runner");
assert.equal(secondPayload.runner.codexStdio, false);
assert.equal(secondPayload.agentRun.runId, "run_hwlab_adapter");
assert.equal(secondPayload.agentRun.commandId, "cmd_hwlab_adapter_second");
assert.equal(secondPayload.providerTrace.commandId, "cmd_hwlab_adapter_second");
@@ -889,6 +910,7 @@ test("cloud api AgentRun adapter exposes invalid tool-call attribution in result
assert.equal(payload.error.code, "provider-invalid-tool-call");
assert.equal(payload.error.category, "provider_invalid_tool_call");
assert.match(payload.error.userMessage, / tool-call arguments JSON/u);
assert.match(payload.error.userMessage, /AgentRun\/provider/u);
assert.match(payload.error.userMessage, / HWLAB device-pod/u);
assert.equal(payload.blocker.category, "provider_invalid_tool_call");
assert.match(payload.blocker.summary, /invalid function arguments json string/u);
@@ -897,6 +919,10 @@ test("cloud api AgentRun adapter exposes invalid tool-call attribution in result
assert.equal(payload.sessionReuse.threadId, "thread_invalid_tool");
assert.equal(payload.agentRun.reuseEligible, false);
assert.equal(payload.reuseEligible, false);
const serializedPayload = JSON.stringify(payload);
assert.equal(serializedPayload.includes("repo-owned-codex"), false);
assert.equal(serializedPayload.includes("codex-app-server-stdio"), false);
assert.equal(serializedPayload.includes("\"provider\":\"codex-stdio\""), false);
} finally {
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
await new Promise((resolve, reject) => agentRunServer.close((error) => (error ? reject(error) : resolve())));
@@ -1031,7 +1057,12 @@ test("cloud api AgentRun adapter maps minimax-m3 provider profile to AgentRun ba
const payload = await pollAgentResult(port, traceId);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.provider, "minimax-m3");
assert.equal(payload.backend, "agentrun-v01/minimax-m3");
assert.equal(payload.infrastructureBackend, "agentrun-v01/minimax-m3");
assert.equal(payload.runner.kind, "agentrun-v01-shared-runner");
assert.equal(payload.runner.provider, "minimax-m3");
assert.equal(payload.runner.codexStdio, false);
assert.equal(payload.agentRun.backendProfile, "minimax-m3");
assert.equal(payload.sessionId, "ses_server-test-minimax-m3");
assert.equal(payload.agentRun.sessionId, "ses_agentrun_minimax_m3_server_test_minimax_m3");