Merge pull request #884 from pikasTech/fix/v02-agentrun-health-cleanup
fix(v02): 收敛 AgentRun Code Agent 健康合同
This commit is contained in:
@@ -31,6 +31,9 @@ const DEFAULT_PROVIDER_ID = "G14";
|
|||||||
const DEFAULT_REPO_URL = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
|
const DEFAULT_REPO_URL = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
|
||||||
const DEFAULT_RUNNER_NAMESPACE = "agentrun-v01";
|
const DEFAULT_RUNNER_NAMESPACE = "agentrun-v01";
|
||||||
const DEFAULT_TIMEOUT_MS = 1_200_000;
|
const DEFAULT_TIMEOUT_MS = 1_200_000;
|
||||||
|
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_BACKENDS = Object.freeze(["codex", "deepseek", "minimax-m3"]);
|
const AGENTRUN_BACKENDS = Object.freeze(["codex", "deepseek", "minimax-m3"]);
|
||||||
const THREAD_CONTINUITY_POLICY = "hwlab-agentrun-v01-reuse-runner-thread";
|
const THREAD_CONTINUITY_POLICY = "hwlab-agentrun-v01-reuse-runner-thread";
|
||||||
const SESSION_POLICY_RUN_LOCAL = "hwlab-agentrun-v01-session-runner-reuse";
|
const SESSION_POLICY_RUN_LOCAL = "hwlab-agentrun-v01-session-runner-reuse";
|
||||||
@@ -52,6 +55,160 @@ export function codeAgentAgentRunAdapterEnabled(env = process.env) {
|
|||||||
return value === ADAPTER_ID || value === "agentrun" || value === "agentrun-v0.1";
|
return value === ADAPTER_ID || value === "agentrun" || value === "agentrun-v0.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function describeAgentRunAdapterAvailability(env = process.env, options = {}) {
|
||||||
|
const params = options.params ?? {};
|
||||||
|
const backendProfile = resolveAgentRunBackendProfile(env, params);
|
||||||
|
const provider = providerForBackendProfile(backendProfile);
|
||||||
|
const backend = `${ADAPTER_ID}/${backendProfile}`;
|
||||||
|
const model = modelForBackendProfile(backendProfile, env);
|
||||||
|
const blockers = [];
|
||||||
|
let managerUrl = null;
|
||||||
|
let repoUrl = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
managerUrl = resolveAgentRunManagerUrl(env);
|
||||||
|
} catch (error) {
|
||||||
|
blockers.push(agentRunAvailabilityBlocker(error, "manager-url"));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
repoUrl = resolveAgentRunRepoUrl(env);
|
||||||
|
} catch (error) {
|
||||||
|
blockers.push(agentRunAvailabilityBlocker(error, "repo-url"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerId = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID);
|
||||||
|
const runnerNamespace = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE, env.AGENTRUN_RUNTIME_NAMESPACE, DEFAULT_RUNNER_NAMESPACE);
|
||||||
|
const ready = blockers.length === 0 && Boolean(providerId && runnerNamespace && backendProfile && managerUrl && repoUrl);
|
||||||
|
const managerHost = hostForUrl(managerUrl);
|
||||||
|
const repoHost = hostForUrl(repoUrl);
|
||||||
|
const status = ready ? "agentrun-ready" : "blocked";
|
||||||
|
const capabilityLevel = ready ? CODEX_STDIO_CAPABILITY_LEVEL : "blocked";
|
||||||
|
|
||||||
|
return {
|
||||||
|
endpoint: "POST /v1/agent/chat",
|
||||||
|
adapter: ADAPTER_ID,
|
||||||
|
provider,
|
||||||
|
model,
|
||||||
|
backend,
|
||||||
|
infrastructureBackend: backend,
|
||||||
|
mode: ADAPTER_ID,
|
||||||
|
schema: [
|
||||||
|
"conversationId",
|
||||||
|
"sessionId",
|
||||||
|
"messageId",
|
||||||
|
"status",
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt",
|
||||||
|
"traceId",
|
||||||
|
"provider",
|
||||||
|
"model",
|
||||||
|
"backend",
|
||||||
|
"infrastructureBackend",
|
||||||
|
"agentRun",
|
||||||
|
"runner",
|
||||||
|
"runnerTrace",
|
||||||
|
"session",
|
||||||
|
"sessionMode",
|
||||||
|
"sessionReuse",
|
||||||
|
"threadContinuityPolicy",
|
||||||
|
"error.message",
|
||||||
|
"error.code"
|
||||||
|
],
|
||||||
|
runner: {
|
||||||
|
kind: AGENTRUN_RUNNER_KIND,
|
||||||
|
adapter: ADAPTER_ID,
|
||||||
|
backend,
|
||||||
|
provider,
|
||||||
|
model,
|
||||||
|
namespace: runnerNamespace,
|
||||||
|
managerHost,
|
||||||
|
repoHost,
|
||||||
|
mode: ADAPTER_ID,
|
||||||
|
sessionMode: AGENTRUN_SESSION_MODE,
|
||||||
|
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
|
||||||
|
status: ready ? "available" : "blocked",
|
||||||
|
ready,
|
||||||
|
capabilityLevel,
|
||||||
|
longLivedSession: ready,
|
||||||
|
durableSession: ready,
|
||||||
|
durable: ready,
|
||||||
|
codexStdio: false,
|
||||||
|
delegatedToAgentRun: true,
|
||||||
|
writeCapable: ready,
|
||||||
|
readOnly: false,
|
||||||
|
runnerLimitations: ready ? [] : ["agentrun-adapter-config-blocked"],
|
||||||
|
secretMaterialRead: false,
|
||||||
|
valuesRedacted: true
|
||||||
|
},
|
||||||
|
agentRun: {
|
||||||
|
adapter: ADAPTER_ID,
|
||||||
|
backendProfile,
|
||||||
|
providerId,
|
||||||
|
runnerNamespace,
|
||||||
|
managerUrl,
|
||||||
|
managerHost,
|
||||||
|
repoUrl,
|
||||||
|
repoHost,
|
||||||
|
managerConfigured: Boolean(managerUrl),
|
||||||
|
repoConfigured: Boolean(repoUrl),
|
||||||
|
internalServiceDns: managerHost === "agentrun-mgr.agentrun-v01.svc.cluster.local",
|
||||||
|
ready,
|
||||||
|
valuesPrinted: false
|
||||||
|
},
|
||||||
|
status,
|
||||||
|
readinessStatus: status,
|
||||||
|
agentKind: "agentrun-v01-adapter",
|
||||||
|
capabilityStatus: status,
|
||||||
|
blocker: ready ? null : blockers[0]?.summary ?? "AgentRun adapter runtime configuration is blocked.",
|
||||||
|
reason: ready ? null : blockers[0]?.code ?? "agentrun_adapter_blocked",
|
||||||
|
summary: ready
|
||||||
|
? "HWLAB Code Agent delegates execution to AgentRun v0.1 shared infrastructure; hwlab-cloud-api keeps session/trace/API ownership and no longer manages a repo-owned runner control plane."
|
||||||
|
: "HWLAB Code Agent is configured for AgentRun v0.1, but the adapter runtime configuration is incomplete or invalid.",
|
||||||
|
missingEnv: [],
|
||||||
|
secretRefs: [],
|
||||||
|
egress: {
|
||||||
|
ready,
|
||||||
|
mode: "agentrun-managed-provider-egress",
|
||||||
|
directPublicOpenAi: false,
|
||||||
|
secretMaterialRead: false,
|
||||||
|
valuesRedacted: true
|
||||||
|
},
|
||||||
|
safety: {
|
||||||
|
secretsRead: false,
|
||||||
|
secretValuesPrinted: false,
|
||||||
|
kubeconfigRead: false,
|
||||||
|
providerCredentialsDelegatedToAgentRun: true,
|
||||||
|
repoOwnedRunnerControlPlane: false,
|
||||||
|
valuesRedacted: true
|
||||||
|
},
|
||||||
|
capabilityLevel,
|
||||||
|
workspace: repoUrl,
|
||||||
|
sandbox: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_SANDBOX, env.HWLAB_CODE_AGENT_CODEX_SANDBOX, "danger-full-access"),
|
||||||
|
session: null,
|
||||||
|
sessionStatus: null,
|
||||||
|
sessionMode: AGENTRUN_SESSION_MODE,
|
||||||
|
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
|
||||||
|
runnerLimitations: ready ? [] : ["agentrun-adapter-config-blocked"],
|
||||||
|
longLivedSessionGate: {
|
||||||
|
status: ready ? "pass" : "blocked",
|
||||||
|
provider,
|
||||||
|
runnerKind: AGENTRUN_RUNNER_KIND,
|
||||||
|
sessionMode: AGENTRUN_SESSION_MODE,
|
||||||
|
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
|
||||||
|
adapter: ADAPTER_ID,
|
||||||
|
codexStdio: false,
|
||||||
|
delegatedToAgentRun: true,
|
||||||
|
blockers,
|
||||||
|
secretMaterialRead: false,
|
||||||
|
valuesRedacted: true
|
||||||
|
},
|
||||||
|
blockers,
|
||||||
|
blockerCodes: blockers.map((blocker) => blocker.code).filter(Boolean),
|
||||||
|
ready,
|
||||||
|
partialReady: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function initialAgentRunChatResult({ params = {}, options = {}, traceId }) {
|
export function initialAgentRunChatResult({ params = {}, options = {}, traceId }) {
|
||||||
const env = options.env ?? process.env;
|
const env = options.env ?? process.env;
|
||||||
const timestamp = nowIso(options.now);
|
const timestamp = nowIso(options.now);
|
||||||
@@ -1540,6 +1697,34 @@ function modelForBackendProfile(profile, env = process.env) {
|
|||||||
return firstNonEmpty(env.HWLAB_CODE_AGENT_DEEPSEEK_MODEL, "deepseek-chat");
|
return firstNonEmpty(env.HWLAB_CODE_AGENT_DEEPSEEK_MODEL, "deepseek-chat");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function providerForBackendProfile(profile) {
|
||||||
|
if (profile === "codex") return "codex-api";
|
||||||
|
if (profile === "minimax-m3") return "minimax-m3";
|
||||||
|
return "deepseek";
|
||||||
|
}
|
||||||
|
|
||||||
|
function agentRunAvailabilityBlocker(error, scope) {
|
||||||
|
return {
|
||||||
|
code: error?.code ?? `agentrun_${String(scope).replace(/[^a-z0-9]+/giu, "_")}_blocked`,
|
||||||
|
type: "agent_blocker",
|
||||||
|
scope: `agentrun-${scope}`,
|
||||||
|
status: "open",
|
||||||
|
sourceIssue: "pikasTech/HWLAB#879",
|
||||||
|
summary: error?.message ?? `AgentRun ${scope} is not configured correctly.` ,
|
||||||
|
secretMaterialRead: false,
|
||||||
|
valuesRedacted: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function hostForUrl(value) {
|
||||||
|
if (!value) return null;
|
||||||
|
try {
|
||||||
|
return new URL(value).hostname.toLowerCase();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function waitingForForPhase(phase) {
|
function waitingForForPhase(phase) {
|
||||||
if (phase.includes("runner-job")) return "agentrun-runner";
|
if (phase.includes("runner-job")) return "agentrun-runner";
|
||||||
if (phase.includes("resource-bundle")) return "agentrun-resource-bundle";
|
if (phase.includes("resource-bundle")) return "agentrun-resource-bundle";
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ import {
|
|||||||
codeAgentSessionLifecycleSummary,
|
codeAgentSessionLifecycleSummary,
|
||||||
decorateCodeAgentSession
|
decorateCodeAgentSession
|
||||||
} from "./code-agent-session-lifecycle.ts";
|
} from "./code-agent-session-lifecycle.ts";
|
||||||
|
import {
|
||||||
|
codeAgentAgentRunAdapterEnabled,
|
||||||
|
describeAgentRunAdapterAvailability
|
||||||
|
} from "./code-agent-agentrun-adapter.ts";
|
||||||
const DEFAULT_MODEL = DEV_CODE_AGENT_PROVIDER_CONTRACT.model;
|
const DEFAULT_MODEL = DEV_CODE_AGENT_PROVIDER_CONTRACT.model;
|
||||||
const DEFAULT_PROJECT_ID = "prj_hwlab-cloud-workbench";
|
const DEFAULT_PROJECT_ID = "prj_hwlab-cloud-workbench";
|
||||||
const READONLY_RUNNER_PROVIDER = "codex-readonly-runner";
|
const READONLY_RUNNER_PROVIDER = "codex-readonly-runner";
|
||||||
@@ -459,6 +463,10 @@ function decorateChatSessionLifecycle(payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function describeCodeAgentAvailability(env = process.env, options = {}) {
|
export async function describeCodeAgentAvailability(env = process.env, options = {}) {
|
||||||
|
if (codeAgentAgentRunAdapterEnabled(env)) {
|
||||||
|
return describeAgentRunAdapterAvailability(env, options);
|
||||||
|
}
|
||||||
|
|
||||||
const providerPlan = resolveProviderPlan(env, options);
|
const providerPlan = resolveProviderPlan(env, options);
|
||||||
const providerContract = inspectCodeAgentProviderEnv(env);
|
const providerContract = inspectCodeAgentProviderEnv(env);
|
||||||
const missingEnv = providerPlan.mode === "openai"
|
const missingEnv = providerPlan.mode === "openai"
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ function isDbReady(db) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isCodeAgentReady(codeAgent) {
|
function isCodeAgentReady(codeAgent) {
|
||||||
|
if (isAgentRunCodeAgent(codeAgent)) {
|
||||||
|
return Boolean(codeAgent?.ready === true && codeAgent?.agentRun?.managerConfigured !== false && codeAgent?.agentRun?.ready !== false);
|
||||||
|
}
|
||||||
return Boolean(
|
return Boolean(
|
||||||
codeAgent?.ready === true &&
|
codeAgent?.ready === true &&
|
||||||
codeAgent?.longLivedSessionGate?.status === "pass" &&
|
codeAgent?.longLivedSessionGate?.status === "pass" &&
|
||||||
@@ -116,18 +119,21 @@ function dbBlocker(db) {
|
|||||||
|
|
||||||
function codeAgentBlocker(codeAgent) {
|
function codeAgentBlocker(codeAgent) {
|
||||||
const primaryBlocker = Array.isArray(codeAgent?.blockers) ? codeAgent.blockers[0] : null;
|
const primaryBlocker = Array.isArray(codeAgent?.blockers) ? codeAgent.blockers[0] : null;
|
||||||
|
const agentRun = isAgentRunCodeAgent(codeAgent);
|
||||||
return {
|
return {
|
||||||
code: codeAgent?.reason ?? primaryBlocker?.code ?? "code_agent_long_lived_session_blocked",
|
code: codeAgent?.reason ?? primaryBlocker?.code ?? "code_agent_long_lived_session_blocked",
|
||||||
type: "agent_blocker",
|
type: "agent_blocker",
|
||||||
scope: "code-agent-long-lived-session",
|
scope: agentRun ? "code-agent-agentrun-adapter" : "code-agent-long-lived-session",
|
||||||
status: "open",
|
status: "open",
|
||||||
sourceIssue: primaryBlocker?.sourceIssue ?? "pikasTech/HWLAB#275",
|
sourceIssue: primaryBlocker?.sourceIssue ?? (agentRun ? "pikasTech/HWLAB#879" : "pikasTech/HWLAB#275"),
|
||||||
summary: codeAgent?.summary ?? "Code Agent provider readiness is not established",
|
summary: codeAgent?.summary ?? "Code Agent provider readiness is not established",
|
||||||
evidence: {
|
evidence: {
|
||||||
|
adapter: codeAgent?.adapter ?? codeAgent?.agentRun?.adapter ?? null,
|
||||||
provider: codeAgent?.provider ?? "unknown",
|
provider: codeAgent?.provider ?? "unknown",
|
||||||
backend: codeAgent?.backend ?? "unknown",
|
backend: codeAgent?.backend ?? "unknown",
|
||||||
agentKind: codeAgent?.agentKind ?? "unknown",
|
agentKind: codeAgent?.agentKind ?? "unknown",
|
||||||
capabilityStatus: codeAgent?.capabilityStatus ?? "unknown",
|
capabilityStatus: codeAgent?.capabilityStatus ?? "unknown",
|
||||||
|
agentRun: codeAgent?.agentRun ?? null,
|
||||||
longLivedSessionGate: codeAgent?.longLivedSessionGate ?? null,
|
longLivedSessionGate: codeAgent?.longLivedSessionGate ?? null,
|
||||||
codexStdioFeasibility: codeAgent?.codexStdioFeasibility ?? null,
|
codexStdioFeasibility: codeAgent?.codexStdioFeasibility ?? null,
|
||||||
partialReady: codeAgent?.partialReady === true,
|
partialReady: codeAgent?.partialReady === true,
|
||||||
@@ -205,6 +211,27 @@ function buildRuntimeDurabilityReadiness({ db, runtime, runtimeReady, runtimeBlo
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildProviderReadiness(codeAgent = {}) {
|
function buildProviderReadiness(codeAgent = {}) {
|
||||||
|
if (isAgentRunCodeAgent(codeAgent)) {
|
||||||
|
const ready = codeAgent?.ready === true && codeAgent?.agentRun?.ready !== false;
|
||||||
|
return {
|
||||||
|
status: ready ? "ready" : "blocked",
|
||||||
|
ready,
|
||||||
|
configured: ready,
|
||||||
|
adapter: codeAgent.adapter ?? codeAgent.agentRun?.adapter ?? "agentrun-v01",
|
||||||
|
provider: codeAgent?.provider ?? "unknown",
|
||||||
|
model: codeAgent?.model ?? "unknown",
|
||||||
|
backend: codeAgent?.backend ?? "unknown",
|
||||||
|
mode: codeAgent?.mode ?? "agentrun-v01",
|
||||||
|
backendProfile: codeAgent?.agentRun?.backendProfile ?? null,
|
||||||
|
evidence: "agentrun_runtime_adapter_config",
|
||||||
|
egressReady: codeAgent?.egress?.ready !== false,
|
||||||
|
missingEnv: [],
|
||||||
|
blocker: ready ? null : firstCode(codeAgent?.reason, codeAgent?.blockers?.[0]?.code, "agentrun_adapter_blocked"),
|
||||||
|
secretMaterialRead: false,
|
||||||
|
valuesRedacted: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const fallback = codeAgent?.fallback ?? {};
|
const fallback = codeAgent?.fallback ?? {};
|
||||||
const fallbackReady = fallback.ready === true || fallback.status === "available";
|
const fallbackReady = fallback.ready === true || fallback.status === "available";
|
||||||
const tokenBoundary = codeAgent?.codexStdioFeasibility?.tokenBoundary ?? codeAgent?.codexStdio?.tokenBoundary ?? null;
|
const tokenBoundary = codeAgent?.codexStdioFeasibility?.tokenBoundary ?? codeAgent?.codexStdio?.tokenBoundary ?? null;
|
||||||
@@ -230,6 +257,32 @@ function buildProviderReadiness(codeAgent = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildSessionRunnerReadiness(codeAgent = {}) {
|
function buildSessionRunnerReadiness(codeAgent = {}) {
|
||||||
|
if (isAgentRunCodeAgent(codeAgent)) {
|
||||||
|
const runner = codeAgent?.runner ?? {};
|
||||||
|
const ready = codeAgent?.ready === true && runner.ready !== false;
|
||||||
|
return {
|
||||||
|
status: ready ? "agentrun_ready" : "blocked",
|
||||||
|
ready,
|
||||||
|
kind: runner.kind ?? "agentrun-v01-shared-runner",
|
||||||
|
adapter: codeAgent.adapter ?? codeAgent.agentRun?.adapter ?? "agentrun-v01",
|
||||||
|
provider: codeAgent?.provider ?? runner.provider ?? "unknown",
|
||||||
|
backend: codeAgent?.backend ?? runner.backend ?? "unknown",
|
||||||
|
mode: runner.sessionMode ?? runner.mode ?? codeAgent?.sessionMode ?? "agentrun-v01-durable-session",
|
||||||
|
capabilityLevel: runner.capabilityLevel ?? codeAgent?.capabilityLevel ?? "unknown",
|
||||||
|
longLivedSession: runner.longLivedSession !== false,
|
||||||
|
durableSession: runner.durableSession !== false,
|
||||||
|
codexStdio: false,
|
||||||
|
delegatedToAgentRun: true,
|
||||||
|
writeCapable: runner.writeCapable !== false,
|
||||||
|
readOnly: false,
|
||||||
|
namespace: runner.namespace ?? codeAgent?.agentRun?.runnerNamespace ?? null,
|
||||||
|
managerHost: runner.managerHost ?? codeAgent?.agentRun?.managerHost ?? null,
|
||||||
|
blocker: ready ? null : firstCode(runner.blocker, codeAgent?.reason, codeAgent?.blockers?.[0]?.code, "agentrun_session_runner_blocked"),
|
||||||
|
runnerLimitations: Array.isArray(codeAgent?.runnerLimitations) ? [...codeAgent.runnerLimitations] : [],
|
||||||
|
secretMaterialRead: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const stdio = codeAgent?.codexStdioFeasibility ?? codeAgent?.codexStdio ?? {};
|
const stdio = codeAgent?.codexStdioFeasibility ?? codeAgent?.codexStdio ?? {};
|
||||||
const stdioCommandReady = stdio.commandProbe?.ready === true;
|
const stdioCommandReady = stdio.commandProbe?.ready === true;
|
||||||
const stdioReady = (stdio.ready === true || stdio.canStartLongLivedCodexStdio === true) && stdioCommandReady;
|
const stdioReady = (stdio.ready === true || stdio.canStartLongLivedCodexStdio === true) && stdioCommandReady;
|
||||||
@@ -263,6 +316,29 @@ function buildSessionRunnerReadiness(codeAgent = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildCodexStdioReadiness(codeAgent = {}) {
|
function buildCodexStdioReadiness(codeAgent = {}) {
|
||||||
|
if (isAgentRunCodeAgent(codeAgent)) {
|
||||||
|
return {
|
||||||
|
status: "not_applicable",
|
||||||
|
ready: false,
|
||||||
|
feasible: false,
|
||||||
|
adapter: codeAgent.adapter ?? codeAgent.agentRun?.adapter ?? "agentrun-v01",
|
||||||
|
delegatedToAgentRun: true,
|
||||||
|
blocker: null,
|
||||||
|
blockerCodes: [],
|
||||||
|
sourceIssue: "pikasTech/HWLAB#879",
|
||||||
|
commandPresent: false,
|
||||||
|
commandExecutable: false,
|
||||||
|
nativeDependencyPresent: false,
|
||||||
|
commandProbeReady: false,
|
||||||
|
commandProbe: null,
|
||||||
|
supervisorConfigured: false,
|
||||||
|
workspaceReady: false,
|
||||||
|
tokenBoundaryPresent: false,
|
||||||
|
egressReady: true,
|
||||||
|
secretMaterialRead: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const stdio = codeAgent?.codexStdioFeasibility ?? codeAgent?.codexStdio ?? {};
|
const stdio = codeAgent?.codexStdioFeasibility ?? codeAgent?.codexStdio ?? {};
|
||||||
const commandReady = stdio.commandProbe?.ready === true;
|
const commandReady = stdio.commandProbe?.ready === true;
|
||||||
const ready = (stdio.ready === true || stdio.canStartLongLivedCodexStdio === true) && commandReady;
|
const ready = (stdio.ready === true || stdio.canStartLongLivedCodexStdio === true) && commandReady;
|
||||||
@@ -297,24 +373,27 @@ function buildCodexStdioReadiness(codeAgent = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildCodeAgentStructuredReadiness({ provider, dbDurable, sessionRunner, codexStdio, codeAgent } = {}) {
|
function buildCodeAgentStructuredReadiness({ provider, dbDurable, sessionRunner, codexStdio, codeAgent } = {}) {
|
||||||
|
const agentRun = isAgentRunCodeAgent(codeAgent);
|
||||||
const currentBlockers = [
|
const currentBlockers = [
|
||||||
provider?.ready === false ? provider.blocker ?? "provider_config_blocked" : null,
|
provider?.ready === false ? provider.blocker ?? "provider_config_blocked" : null,
|
||||||
dbDurable?.ready === false ? dbDurable.blocker ?? "runtime_durable_adapter_not_ready" : null,
|
dbDurable?.ready === false ? dbDurable.blocker ?? "runtime_durable_adapter_not_ready" : null,
|
||||||
sessionRunner?.ready === false ? sessionRunner.blocker ?? "session_runner_blocked" : null,
|
sessionRunner?.ready === false ? sessionRunner.blocker ?? "session_runner_blocked" : null,
|
||||||
codexStdio?.ready === false ? codexStdio.blocker ?? "codex_stdio_feasibility_blocked" : null
|
!agentRun && codexStdio?.ready === false ? codexStdio.blocker ?? "codex_stdio_feasibility_blocked" : null
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
const ready = provider?.ready === true &&
|
const ready = provider?.ready === true &&
|
||||||
dbDurable?.ready === true &&
|
dbDurable?.ready === true &&
|
||||||
sessionRunner?.ready === true &&
|
sessionRunner?.ready === true &&
|
||||||
codexStdio?.ready === true &&
|
(agentRun || codexStdio?.ready === true) &&
|
||||||
codeAgent?.ready === true;
|
codeAgent?.ready === true;
|
||||||
return {
|
return {
|
||||||
status: ready ? "ready" : currentBlockers.length > 0 ? "blocked" : "degraded",
|
status: ready ? "ready" : currentBlockers.length > 0 ? "blocked" : "degraded",
|
||||||
ready,
|
ready,
|
||||||
|
adapter: codeAgent?.adapter ?? codeAgent?.agentRun?.adapter ?? null,
|
||||||
|
agentRunReady: agentRun ? codeAgent?.agentRun?.ready === true : null,
|
||||||
providerReady: provider?.ready === true,
|
providerReady: provider?.ready === true,
|
||||||
durableDbReady: dbDurable?.ready === true,
|
durableDbReady: dbDurable?.ready === true,
|
||||||
sessionRunnerReady: sessionRunner?.ready === true,
|
sessionRunnerReady: sessionRunner?.ready === true,
|
||||||
codexStdioFeasible: codexStdio?.ready === true,
|
codexStdioFeasible: agentRun ? false : codexStdio?.ready === true,
|
||||||
currentBlocker: currentBlockers[0] ?? null,
|
currentBlocker: currentBlockers[0] ?? null,
|
||||||
currentBlockers,
|
currentBlockers,
|
||||||
provider: {
|
provider: {
|
||||||
@@ -349,6 +428,7 @@ function buildCodeAgentStructuredReadiness({ provider, dbDurable, sessionRunner,
|
|||||||
status: codexStdio?.status ?? "unknown",
|
status: codexStdio?.status ?? "unknown",
|
||||||
ready: codexStdio?.ready === true,
|
ready: codexStdio?.ready === true,
|
||||||
feasible: codexStdio?.feasible === true,
|
feasible: codexStdio?.feasible === true,
|
||||||
|
delegatedToAgentRun: codexStdio?.delegatedToAgentRun === true,
|
||||||
blocker: codexStdio?.blocker ?? null,
|
blocker: codexStdio?.blocker ?? null,
|
||||||
blockerCodes: Array.isArray(codexStdio?.blockerCodes) ? [...codexStdio.blockerCodes] : []
|
blockerCodes: Array.isArray(codexStdio?.blockerCodes) ? [...codexStdio.blockerCodes] : []
|
||||||
},
|
},
|
||||||
@@ -357,6 +437,10 @@ function buildCodeAgentStructuredReadiness({ provider, dbDurable, sessionRunner,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAgentRunCodeAgent(codeAgent = {}) {
|
||||||
|
return codeAgent?.adapter === "agentrun-v01" || codeAgent?.agentRun?.adapter === "agentrun-v01" || codeAgent?.mode === "agentrun-v01";
|
||||||
|
}
|
||||||
|
|
||||||
function providerBlockerCode(codeAgent = {}, fallback = {}) {
|
function providerBlockerCode(codeAgent = {}, fallback = {}) {
|
||||||
if (Array.isArray(codeAgent?.missingEnv) && codeAgent.missingEnv.length > 0) return "provider_config_blocked";
|
if (Array.isArray(codeAgent?.missingEnv) && codeAgent.missingEnv.length > 0) return "provider_config_blocked";
|
||||||
if (fallback?.egress?.directPublicOpenAi === true || codeAgent?.egress?.directPublicOpenAi === true) return "provider_egress_blocked";
|
if (fallback?.egress?.directPublicOpenAi === true || codeAgent?.egress?.directPublicOpenAi === true) return "provider_egress_blocked";
|
||||||
|
|||||||
@@ -718,6 +718,83 @@ test("cloud api health reports provider, durable DB, and codex stdio ready when
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("cloud api health reports AgentRun adapter readiness without repo-owned codex stdio control plane", async () => {
|
||||||
|
const server = createCloudApiServer({
|
||||||
|
env: {
|
||||||
|
HWLAB_CODE_AGENT_ADAPTER: "agentrun-v01",
|
||||||
|
AGENTRUN_MGR_URL: "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080",
|
||||||
|
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: "agentrun-v01",
|
||||||
|
HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "G14",
|
||||||
|
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "deepseek",
|
||||||
|
HWLAB_CODE_AGENT_DEEPSEEK_MODEL: "deepseek-chat",
|
||||||
|
HWLAB_BOOT_READ_URL: "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git",
|
||||||
|
HWLAB_CLOUD_DB_URL: "postgres://hwlab_test:password@db.internal.local:5432/hwlab",
|
||||||
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||||
|
},
|
||||||
|
dbProbe: {
|
||||||
|
probe: async ({ endpointSource, timeoutMs }) => ({
|
||||||
|
attempted: true,
|
||||||
|
networkAttempted: true,
|
||||||
|
endpointSource,
|
||||||
|
probeType: "tcp-connect",
|
||||||
|
endpointRedacted: true,
|
||||||
|
valueRedacted: true,
|
||||||
|
timeoutMs,
|
||||||
|
durationMs: 1,
|
||||||
|
result: "connected",
|
||||||
|
classification: "tcp_connected",
|
||||||
|
errorCode: null,
|
||||||
|
missingEnv: []
|
||||||
|
})
|
||||||
|
},
|
||||||
|
runtimeStore: {
|
||||||
|
async readiness() {
|
||||||
|
return durableReadyRuntime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { port } = server.address();
|
||||||
|
const response = await fetch(`http://127.0.0.1:${port}/health/live`);
|
||||||
|
assert.equal(response.status, 200);
|
||||||
|
const payload = await response.json();
|
||||||
|
assert.equal(payload.status, "ok");
|
||||||
|
assert.equal(payload.ready, true);
|
||||||
|
assert.equal(payload.codeAgent.adapter, "agentrun-v01");
|
||||||
|
assert.equal(payload.codeAgent.status, "agentrun-ready");
|
||||||
|
assert.equal(payload.codeAgent.provider, "deepseek");
|
||||||
|
assert.equal(payload.codeAgent.backend, "agentrun-v01/deepseek");
|
||||||
|
assert.equal(payload.codeAgent.agentRun.managerHost, "agentrun-mgr.agentrun-v01.svc.cluster.local");
|
||||||
|
assert.equal(payload.codeAgent.agentRun.runnerNamespace, "agentrun-v01");
|
||||||
|
assert.equal(payload.codeAgent.runner.kind, "agentrun-v01-shared-runner");
|
||||||
|
assert.equal(payload.codeAgent.runner.delegatedToAgentRun, true);
|
||||||
|
assert.equal(payload.codeAgent.runner.codexStdio, false);
|
||||||
|
assert.equal(payload.readiness.components.codeAgent, "ready");
|
||||||
|
assert.equal(payload.readiness.provider.status, "ready");
|
||||||
|
assert.equal(payload.readiness.sessionRunner.status, "agentrun_ready");
|
||||||
|
assert.equal(payload.readiness.sessionRunner.delegatedToAgentRun, true);
|
||||||
|
assert.equal(payload.readiness.codexStdio.status, "not_applicable");
|
||||||
|
assert.equal(payload.readiness.codeAgent.ready, true);
|
||||||
|
assert.equal(payload.readiness.codeAgent.adapter, "agentrun-v01");
|
||||||
|
assert.equal(payload.readiness.codeAgent.agentRunReady, true);
|
||||||
|
assert.equal(payload.readiness.codeAgent.codexStdioFeasible, false);
|
||||||
|
assert.deepEqual(payload.readiness.codeAgent.currentBlockers, []);
|
||||||
|
const serialized = JSON.stringify(payload);
|
||||||
|
assert.equal(serialized.includes("repo-owned-codex"), false);
|
||||||
|
assert.equal(serialized.includes("codex-app-server-stdio"), false);
|
||||||
|
assert.equal(serialized.includes("CODEX_HOME"), false);
|
||||||
|
assert.equal(serialized.includes("/codex-home"), false);
|
||||||
|
assert.equal(serialized.includes("test-openai-key-material"), false);
|
||||||
|
assert.equal(serialized.includes("password"), false);
|
||||||
|
} finally {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
server.close((error) => (error ? reject(error) : resolve()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("cloud api health blocks full Code Agent readiness when Codex stdio command probe fails", async () => {
|
test("cloud api health blocks full Code Agent readiness when Codex stdio command probe fails", async () => {
|
||||||
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-health-codex-stdio-probe-fail-"));
|
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-health-codex-stdio-probe-fail-"));
|
||||||
const codexHome = path.join(workspace, "codex-home");
|
const codexHome = path.join(workspace, "codex-home");
|
||||||
|
|||||||
Reference in New Issue
Block a user