604 lines
21 KiB
TypeScript
604 lines
21 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import test from "node:test";
|
||
|
||
import { classifyCodeAgentStatusSummary } from "./code-agent-status.ts";
|
||
|
||
const liveRevision = Object.freeze({
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
serviceId: "hwlab-cloud-api",
|
||
status: "ok",
|
||
commitId: "7de6edd2c41f54e4265e822d2593fa67057fa41a"
|
||
}
|
||
}
|
||
});
|
||
|
||
test("summarizes long-lived Code Agent as green with current deployment revision only", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
live: liveRevision,
|
||
availability: {
|
||
status: "codex-stdio-feasible",
|
||
ready: true,
|
||
provider: "codex-stdio",
|
||
mode: "codex-stdio",
|
||
backend: "hwlab-cloud-api/codex-stdio",
|
||
capabilityLevel: "long-lived-codex-stdio-session",
|
||
sessionMode: "codex-app-server-stdio-long-lived",
|
||
implementationType: "repo-owned-codex-app-server-stdio-session",
|
||
session: {
|
||
status: "idle",
|
||
lastTraceId: "trc_ready"
|
||
},
|
||
runner: {
|
||
kind: "codex-app-server-stdio-runner",
|
||
codexStdio: true
|
||
},
|
||
longLivedSessionGate: {
|
||
status: "pass"
|
||
},
|
||
codexStdioFeasibility: {
|
||
canStartLongLivedCodexStdio: true
|
||
},
|
||
providerTrace: {
|
||
protocol: "codex-app-server-jsonrpc-stdio",
|
||
command: "codex app-server --listen stdio://",
|
||
terminalStatus: "completed",
|
||
wireApi: "responses"
|
||
}
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "long-lived-session");
|
||
assert.equal(summary.label, "长会话可用");
|
||
assert.equal(summary.tone, "ok");
|
||
assert.equal(summary.codeAgentStatus, "codex-stdio-feasible");
|
||
assert.equal(summary.providerModeBackend, "codex-stdio / codex-stdio / hwlab-cloud-api/codex-stdio");
|
||
assert.equal(summary.capabilityLevel, "long-lived-codex-stdio-session");
|
||
assert.equal(summary.sessionSummary, "codex-app-server-stdio-long-lived / idle / idle");
|
||
assert.equal(summary.sessionLifecycleStatus, "idle");
|
||
assert.match(summary.sessionLifecycleHint, /新会话已建立/u);
|
||
assert.equal(summary.runnerKind, "codex-app-server-stdio-runner");
|
||
assert.equal(summary.protocol, "codex-app-server-jsonrpc-stdio");
|
||
assert.equal(summary.implementationType, "repo-owned-codex-app-server-stdio-session");
|
||
assert.equal(summary.providerTraceCommand, "codex app-server --listen stdio://");
|
||
assert.equal(summary.providerTraceTerminalStatus, "completed");
|
||
assert.match(summary.runtimePathLabel, /repo-owned Codex app-server stdio/u);
|
||
assert.equal(summary.readinessBlockers.length, 0);
|
||
assert.equal(summary.lastTraceId, "trc_ready");
|
||
assert.equal(summary.deploymentRevision, "7de6edd2c41f");
|
||
assert.equal(Object.keys(summary.fields).includes("sourceMain"), false);
|
||
});
|
||
|
||
test("summarizes Codex app-server stdio thread as reusable long-lived session", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
latestMessage: {
|
||
status: "completed",
|
||
provider: "codex-stdio",
|
||
mode: "codex-stdio",
|
||
backend: "hwlab-cloud-api/codex-app-server-stdio",
|
||
capabilityLevel: "long-lived-codex-stdio-session",
|
||
sessionMode: "codex-app-server-stdio-long-lived",
|
||
implementationType: "repo-owned-codex-app-server-stdio-session",
|
||
session: {
|
||
sessionId: "ses_app_server",
|
||
threadId: "thread_app_server",
|
||
status: "idle",
|
||
lastTraceId: "trc_app_server",
|
||
codexStdio: true
|
||
},
|
||
sessionReuse: {
|
||
sessionId: "ses_app_server",
|
||
threadId: "thread_app_server",
|
||
reused: true,
|
||
turn: 2
|
||
},
|
||
runner: {
|
||
kind: "codex-app-server-stdio-runner",
|
||
codexStdio: true,
|
||
writeCapable: true,
|
||
durableSession: true
|
||
},
|
||
longLivedSessionGate: {
|
||
status: "pass"
|
||
},
|
||
providerTrace: {
|
||
protocol: "codex-app-server-jsonrpc-stdio",
|
||
threadId: "thread_app_server",
|
||
terminalStatus: "completed"
|
||
}
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "long-lived-session");
|
||
assert.equal(summary.tone, "ok");
|
||
assert.equal(summary.sessionIdStatus, "ses_app_server / thread_app_server / idle / idle");
|
||
assert.equal(summary.sessionLifecycleLabel, "继续当前会话");
|
||
assert.match(summary.sessionLifecycleHint, /可继续当前会话/u);
|
||
assert.equal(summary.fields.threadId, "thread_app_server");
|
||
});
|
||
|
||
test("summarizes AgentRun invalid tool-call JSON as provider-side blocked status", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
latestMessage: {
|
||
status: "failed",
|
||
provider: "codex-stdio",
|
||
backend: "hwlab-cloud-api/codex-app-server-stdio",
|
||
capabilityLevel: "long-lived-codex-stdio-session",
|
||
sessionMode: "codex-app-server-stdio-long-lived",
|
||
implementationType: "repo-owned-codex-app-server-stdio-session",
|
||
runner: { kind: "codex-app-server-stdio-runner" },
|
||
providerTrace: {
|
||
protocol: "codex-app-server-jsonrpc-stdio",
|
||
command: "codex app-server --listen stdio://",
|
||
terminalStatus: "failed",
|
||
failureKind: "provider-invalid-tool-call",
|
||
failureMessage: "invalid params, invalid function arguments json string, tool_call_id: call_function_selftest_2 (2013)"
|
||
},
|
||
error: {
|
||
code: "provider-invalid-tool-call",
|
||
category: "provider_invalid_tool_call"
|
||
},
|
||
traceId: "trc_status_invalid_tool_call"
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "blocked");
|
||
assert.equal(summary.tone, "blocked");
|
||
assert.equal(summary.providerTraceFailureKind, "provider-invalid-tool-call");
|
||
assert.equal(summary.runtimePathLabel, "BLOCKED:Codex tool-call JSON 无效");
|
||
assert.match(summary.runtimePathSummary, /不是 HWLAB device-pod/u);
|
||
});
|
||
|
||
test("maps degraded readonly session tools to warning, not green", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
availability: {
|
||
status: "partial",
|
||
ready: false,
|
||
provider: "codex-readonly-runner",
|
||
backend: "hwlab-cloud-api/controlled-readonly-session-registry",
|
||
capabilityLevel: "read-only-session-tools",
|
||
sessionMode: "controlled-readonly-session-registry",
|
||
workspace: "/workspace/hwlab",
|
||
sandbox: "read-only",
|
||
session: {
|
||
sessionId: "ses_readonly",
|
||
status: "idle",
|
||
lastTraceId: "trc_readonly",
|
||
workspace: "/workspace/hwlab",
|
||
sandbox: "read-only",
|
||
runnerKind: "hwlab-readonly-runner",
|
||
sessionMode: "controlled-readonly-session-registry"
|
||
},
|
||
runner: {
|
||
kind: "hwlab-readonly-runner",
|
||
writeCapable: false
|
||
},
|
||
toolCalls: [{ name: "pwd", status: "completed" }],
|
||
skills: { status: "not_requested", count: 0 },
|
||
runnerTrace: {
|
||
traceId: "trc_readonly",
|
||
runnerKind: "hwlab-readonly-runner",
|
||
sessionMode: "controlled-readonly-session-registry",
|
||
turn: 1
|
||
},
|
||
blockers: [
|
||
{ code: "stdio_protocol_not_wired" }
|
||
]
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "read-only-session-tools");
|
||
assert.equal(summary.label, "只读长会话工具");
|
||
assert.equal(summary.tone, "warn");
|
||
assert.equal(summary.sessionId, "ses_readonly");
|
||
assert.equal(summary.sessionIdStatus, "ses_readonly / idle / idle");
|
||
assert.equal(summary.workspace, "/workspace/hwlab");
|
||
assert.equal(summary.sandbox, "read-only");
|
||
assert.equal(summary.runnerKind, "hwlab-readonly-runner");
|
||
assert.equal(summary.toolCalls, "1/1 pwd(completed)");
|
||
assert.equal(summary.skills, "not_requested:count=0");
|
||
assert.match(summary.runnerTrace, /runnerKind=hwlab-readonly-runner/u);
|
||
assert.equal(summary.readinessBlockers.includes("stdio_protocol_not_wired"), true);
|
||
assert.equal(summary.lastTraceId, "trc_readonly");
|
||
});
|
||
|
||
test("summarizes Codex runtimeContract blockers from health availability", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
availability: {
|
||
status: "partial",
|
||
ready: false,
|
||
provider: "codex-readonly-runner",
|
||
backend: "hwlab-cloud-api/codex-readonly-runner",
|
||
capabilityLevel: "read-only-session-tools",
|
||
sessionMode: "controlled-readonly-session-registry",
|
||
runner: {
|
||
kind: "hwlab-readonly-runner",
|
||
writeCapable: false
|
||
},
|
||
runtimeContract: {
|
||
binary: { status: "missing" },
|
||
lifecycleSupervisor: { status: "blocked" },
|
||
stdioProtocol: { status: "blocked" }
|
||
}
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "read-only-session-tools");
|
||
assert.equal(summary.tone, "warn");
|
||
assert.equal(summary.readinessBlockers.includes("codex_cli_binary_missing"), true);
|
||
assert.equal(summary.readinessBlockers.includes("runner_lifecycle_missing"), true);
|
||
assert.equal(summary.readinessBlockers.includes("stdio_protocol_not_wired"), true);
|
||
});
|
||
|
||
test("maps text fallback to warning with safe labels", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
availability: {
|
||
status: "available",
|
||
ready: true,
|
||
provider: "openai-responses",
|
||
mode: "openai",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
capabilityLevel: "text-chat-only",
|
||
runner: {
|
||
kind: "openai-responses-fallback"
|
||
}
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "fallback-text-chat-only");
|
||
assert.equal(summary.label, "会话降级");
|
||
assert.equal(summary.tone, "warn");
|
||
assert.equal(summary.provider, "openai-responses");
|
||
assert.equal(summary.capabilityLevel, "text-chat-only");
|
||
assert.equal(summary.sessionLifecycleStatus, "failed");
|
||
assert.match(summary.sessionLifecycleHint, /fallback\/text-only\/read-only/u);
|
||
});
|
||
|
||
test("maps stateless one-shot runner separately from long-lived sessions", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
latestMessage: {
|
||
status: "completed",
|
||
provider: "codex-cli",
|
||
backend: "hwlab-cloud-api/codex-cli",
|
||
capabilityLevel: "text-chat-only",
|
||
sessionMode: "ephemeral-one-shot",
|
||
runner: {
|
||
kind: "codex-cli-one-shot-ephemeral"
|
||
},
|
||
runnerLimitations: ["one-shot", "not-durable-session"],
|
||
traceId: "trc_one_shot"
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "stateless-one-shot");
|
||
assert.equal(summary.label, "一次性执行");
|
||
assert.equal(summary.tone, "warn");
|
||
assert.equal(summary.sessionMode, "ephemeral-one-shot");
|
||
assert.equal(summary.lastTraceId, "trc_one_shot");
|
||
});
|
||
|
||
test("summarizes active Code Agent running request as pending with trace and session context", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
latestMessage: {
|
||
role: "agent",
|
||
status: "running",
|
||
sourceKind: "PENDING",
|
||
traceId: "trc_pending_heavy_skills",
|
||
conversationId: "cnv_pending_heavy_skills",
|
||
sessionId: "cnv_pending_heavy_skills"
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "busy");
|
||
assert.equal(summary.label, "会话忙/请等待");
|
||
assert.equal(summary.tone, "pending");
|
||
assert.equal(summary.codeAgentStatus, "running");
|
||
assert.equal(summary.sessionId, "cnv_pending_heavy_skills");
|
||
assert.equal(summary.sessionLifecycleStatus, "busy");
|
||
assert.match(summary.sessionLifecycleHint, /会话忙\/请等待/u);
|
||
assert.equal(summary.lastTraceId, "trc_pending_heavy_skills");
|
||
assert.equal(summary.readinessBlockers.length, 0);
|
||
});
|
||
|
||
test("summarizes canceled Code Agent request with preserved retry context", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
latestMessage: {
|
||
role: "agent",
|
||
status: "canceled",
|
||
traceId: "trc_cancel_keep_context",
|
||
conversationId: "cnv_cancel_keep_context",
|
||
sessionId: "ses_cancel_keep_context",
|
||
session: {
|
||
sessionId: "ses_cancel_keep_context",
|
||
status: "canceled"
|
||
},
|
||
runnerTrace: {
|
||
traceId: "trc_cancel_keep_context",
|
||
sessionStatus: "canceled",
|
||
lastEvent: {
|
||
type: "cancel",
|
||
label: "cancel:canceled",
|
||
status: "canceled"
|
||
}
|
||
},
|
||
error: {
|
||
code: "codex_stdio_canceled",
|
||
message: "请求已取消"
|
||
}
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "canceled");
|
||
assert.equal(summary.label, "请求已取消");
|
||
assert.equal(summary.tone, "blocked");
|
||
assert.equal(summary.codeAgentStatus, "canceled");
|
||
assert.equal(summary.sessionId, "ses_cancel_keep_context");
|
||
assert.equal(summary.sessionStatus, "canceled");
|
||
assert.equal(summary.sessionLifecycleStatus, "interrupted");
|
||
assert.match(summary.sessionLifecycleHint, /已中断/u);
|
||
assert.equal(summary.lastTraceId, "trc_cancel_keep_context");
|
||
});
|
||
|
||
test("summarizes timed out Code Agent request with preserved trace context", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
latestMessage: {
|
||
role: "agent",
|
||
status: "timeout",
|
||
traceId: "trc_timeout_keep_context",
|
||
conversationId: "cnv_timeout_keep_context",
|
||
sessionId: "ses_timeout_keep_context",
|
||
session: {
|
||
sessionId: "ses_timeout_keep_context",
|
||
status: "timeout"
|
||
},
|
||
runnerTrace: {
|
||
traceId: "trc_timeout_keep_context",
|
||
sessionStatus: "timeout",
|
||
lastEvent: {
|
||
type: "session",
|
||
label: "session:timeout",
|
||
status: "timeout"
|
||
}
|
||
},
|
||
error: {
|
||
code: "codex_stdio_timeout",
|
||
message: "请求超时"
|
||
}
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "timeout");
|
||
assert.equal(summary.label, "等待超时");
|
||
assert.equal(summary.tone, "blocked");
|
||
assert.equal(summary.codeAgentStatus, "timeout");
|
||
assert.equal(summary.sessionId, "ses_timeout_keep_context");
|
||
assert.equal(summary.sessionStatus, "timeout");
|
||
assert.equal(summary.sessionLifecycleStatus, "failed");
|
||
assert.equal(summary.lastTraceId, "trc_timeout_keep_context");
|
||
});
|
||
|
||
test("surfaces expired and failed Code Agent session lifecycle hints", () => {
|
||
const expired = classifyCodeAgentStatusSummary({
|
||
latestMessage: {
|
||
status: "failed",
|
||
provider: "codex-stdio",
|
||
session: {
|
||
sessionId: "ses_expired_ui",
|
||
status: "expired",
|
||
codexStdio: true
|
||
},
|
||
error: {
|
||
code: "session_expired"
|
||
}
|
||
}
|
||
});
|
||
assert.equal(expired.kind, "session-expired");
|
||
assert.equal(expired.label, "会话已过期");
|
||
assert.equal(expired.sessionLifecycleStatus, "expired");
|
||
assert.match(expired.sessionLifecycleHint, /重新发送建立新的 Code Agent session/u);
|
||
|
||
const failed = classifyCodeAgentStatusSummary({
|
||
latestMessage: {
|
||
status: "failed",
|
||
provider: "codex-stdio",
|
||
session: {
|
||
sessionId: "ses_failed_ui",
|
||
status: "failed",
|
||
codexStdio: true
|
||
},
|
||
error: {
|
||
code: "session_failed"
|
||
}
|
||
}
|
||
});
|
||
assert.equal(failed.kind, "turn-failed-session-continuable");
|
||
assert.equal(failed.label, "当前轮次失败");
|
||
assert.equal(failed.sessionLifecycleStatus, "failed");
|
||
assert.match(failed.sessionLifecycleHint, /session\/thread 已保留/u);
|
||
});
|
||
|
||
test("missing Code Agent session evidence is shown as lifecycle degradation", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
latestMessage: {
|
||
status: "completed",
|
||
provider: "codex-stdio",
|
||
capabilityLevel: "long-lived-codex-stdio-session",
|
||
sessionMode: "codex-app-server-stdio-long-lived",
|
||
implementationType: "repo-owned-codex-app-server-stdio-session",
|
||
runner: {
|
||
kind: "codex-app-server-stdio-runner",
|
||
codexStdio: true,
|
||
writeCapable: true
|
||
},
|
||
blocker: {
|
||
code: "code_agent_session_evidence_missing"
|
||
}
|
||
}
|
||
});
|
||
|
||
assert.notEqual(summary.tone, "ok");
|
||
assert.equal(summary.sessionLifecycleLabel, "会话降级");
|
||
assert.match(summary.sessionLifecycleHint, /会话证据不完整/u);
|
||
});
|
||
|
||
test("maps blockers to blocked tone and redacts secret-like fields", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
availability: {
|
||
status: "blocked",
|
||
ready: false,
|
||
provider: "openai-responses",
|
||
backend: "Secret hwlab-code-agent-provider should not show",
|
||
capabilityLevel: "blocked",
|
||
reason: "OPENAI_API_KEY_missing",
|
||
error: {
|
||
code: "provider_unavailable",
|
||
providerStatus: 503
|
||
},
|
||
missingEnv: ["OPENAI_API_KEY"],
|
||
traceId: "trc_blocked"
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "blocked");
|
||
assert.equal(summary.label, "服务受阻");
|
||
assert.equal(summary.tone, "blocked");
|
||
assert.equal(summary.backend, "已隐藏");
|
||
assert.equal(summary.readinessBlockers.includes("provider_config_blocked"), true);
|
||
assert.equal(summary.readinessBlockers.includes("provider_unavailable"), true);
|
||
assert.equal(summary.readinessBlockers.includes("provider_http_503"), true);
|
||
assert.equal(summary.lastTraceId, "trc_blocked");
|
||
});
|
||
|
||
test("uses structured readiness current blockers and ignores stale provider blockers", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
live: {
|
||
restIndex: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
readiness: {
|
||
codeAgent: {
|
||
status: "blocked",
|
||
ready: false,
|
||
providerReady: true,
|
||
durableDbReady: true,
|
||
sessionRunnerReady: true,
|
||
codexStdioFeasible: false,
|
||
currentBlocker: "codex_stdio_supervisor_disabled",
|
||
currentBlockers: ["codex_stdio_supervisor_disabled"],
|
||
provider: {
|
||
status: "ready",
|
||
ready: true,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
mode: "openai",
|
||
blocker: null
|
||
},
|
||
dbDurable: {
|
||
status: "ready",
|
||
ready: true,
|
||
adapter: "postgres",
|
||
blocker: null,
|
||
queryResult: "durable_readiness_ready"
|
||
},
|
||
sessionRunner: {
|
||
status: "read_only_long_lived_ready",
|
||
ready: true,
|
||
kind: "hwlab-readonly-runner",
|
||
mode: "controlled-readonly-session-registry",
|
||
capabilityLevel: "read-only-session-tools",
|
||
longLivedSession: true,
|
||
durableSession: false,
|
||
codexStdio: false,
|
||
writeCapable: false,
|
||
blocker: null
|
||
},
|
||
codexStdio: {
|
||
status: "blocked",
|
||
ready: false,
|
||
feasible: false,
|
||
blocker: "codex_stdio_supervisor_disabled",
|
||
blockerCodes: ["codex_stdio_supervisor_disabled"]
|
||
}
|
||
}
|
||
},
|
||
codeAgent: {
|
||
status: "partial",
|
||
ready: false,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
capabilityLevel: "read-only-session-tools",
|
||
blockerCodes: [
|
||
"provider_unavailable",
|
||
"runtime_durable_adapter_auth_blocked"
|
||
]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "read-only-session-tools");
|
||
assert.equal(summary.tone, "warn");
|
||
assert.equal(summary.provider, "openai-responses");
|
||
assert.equal(summary.capabilityLevel, "read-only-session-tools");
|
||
assert.deepEqual(summary.readinessBlockers, ["codex_stdio_supervisor_disabled"]);
|
||
assert.equal(summary.readinessBlockers.includes("provider_unavailable"), false);
|
||
assert.equal(summary.readinessBlockers.includes("runtime_durable_adapter_auth_blocked"), false);
|
||
});
|
||
|
||
test("structured readiness without codeAgent payload degrades into blocked status", () => {
|
||
const summary = classifyCodeAgentStatusSummary({
|
||
live: {
|
||
healthLive: {
|
||
ok: true,
|
||
status: 200,
|
||
data: {
|
||
readiness: {
|
||
codeAgent: {
|
||
status: "blocked",
|
||
ready: false,
|
||
providerReady: false,
|
||
durableDbReady: true,
|
||
sessionRunnerReady: false,
|
||
codexStdioFeasible: false,
|
||
currentBlocker: "provider_config_blocked",
|
||
currentBlockers: ["provider_config_blocked"],
|
||
provider: {
|
||
status: "blocked",
|
||
ready: false,
|
||
provider: "openai-responses",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
mode: "openai",
|
||
blocker: "provider_config_blocked"
|
||
},
|
||
sessionRunner: {
|
||
status: "blocked",
|
||
ready: false,
|
||
kind: "unknown",
|
||
mode: "unknown",
|
||
capabilityLevel: "blocked",
|
||
blocker: "session_runner_blocked"
|
||
},
|
||
codexStdio: {
|
||
status: "blocked",
|
||
ready: false,
|
||
blocker: "codex_stdio_supervisor_disabled",
|
||
blockerCodes: ["codex_stdio_supervisor_disabled"]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
assert.equal(summary.kind, "blocked");
|
||
assert.equal(summary.label, "服务受阻");
|
||
assert.equal(summary.tone, "blocked");
|
||
assert.equal(summary.readinessBlockers.includes("provider_config_blocked"), true);
|
||
assert.equal(summary.provider, "openai-responses");
|
||
});
|