Merge pull request #346 from pikasTech/fix/344-live-status-attribution
fix: attribute read-only workbench status to code agent
This commit is contained in:
@@ -158,7 +158,6 @@ function classifyApiProbe(probe, context) {
|
||||
if (codeAgentProbe?.kind === "readonly") {
|
||||
return {
|
||||
...codeAgentProbe,
|
||||
apiPath: context.apiPath,
|
||||
rawStatus: rawStatusFrom(payload)
|
||||
};
|
||||
}
|
||||
@@ -302,6 +301,17 @@ function classifyCodeAgentProbe(live = {}) {
|
||||
meta: { provider, backend, capability }
|
||||
});
|
||||
}
|
||||
if (capability === "read-only-session-tools") {
|
||||
return readOnlyProbe({
|
||||
serviceId,
|
||||
apiPath,
|
||||
reasonCode: "code_agent_read_only_session_tools",
|
||||
reason: codeAgentReadOnlySessionToolsReason(availability),
|
||||
traceId: traceIdFrom(availability),
|
||||
evidenceSummary: evidenceSummaryFrom(availability),
|
||||
meta: { provider, backend, capability }
|
||||
});
|
||||
}
|
||||
if (availability.status === "blocked" || availability.ready === false) {
|
||||
return errorProbe({
|
||||
serviceId,
|
||||
@@ -472,20 +482,22 @@ function classifyM3StatusProbe(live = {}) {
|
||||
function durableRuntimeReason(payload) {
|
||||
const runtime = payload?.runtime;
|
||||
const durability = payload?.readiness?.durability;
|
||||
const reasonCode = firstNonEmpty(
|
||||
runtime?.blocker,
|
||||
durability?.blocker,
|
||||
runtime?.connection?.queryResult,
|
||||
durability?.queryResult,
|
||||
runtime?.durabilityContract?.blockedLayer,
|
||||
durability?.blockedLayer,
|
||||
blockerCodeStartingWith(payload, "runtime_durable_adapter_")
|
||||
);
|
||||
const blockedLayer = firstNonEmpty(runtime?.durabilityContract?.blockedLayer, durability?.blockedLayer);
|
||||
const runtimeDurable = runtime?.durable === true || durability?.durable === true;
|
||||
const runtimeExplicitlyNonDurable = runtime?.durable === false || durability?.durable === false;
|
||||
const runtimeReady = runtime?.ready === true || durability?.ready === true;
|
||||
const durabilityReady = durability?.ready === true || normalizedStatus(durability?.status) === "ready";
|
||||
const liveRuntimeEvidence = runtime?.liveRuntimeEvidence === true || durability?.liveRuntimeEvidence === true;
|
||||
if (runtimeDurable && runtimeReady && durabilityReady && !hasDurableRuntimeBlockedSignal(payload)) return null;
|
||||
const reasonCode = firstNonEmpty(
|
||||
runtime?.blocker,
|
||||
durability?.blocker,
|
||||
runtime?.durabilityContract?.blockedLayer,
|
||||
durability?.blockedLayer,
|
||||
blockedDurabilityQueryResult(runtime?.connection?.queryResult),
|
||||
blockedDurabilityQueryResult(durability?.queryResult),
|
||||
blockerCodeStartingWith(payload, "runtime_durable_adapter_")
|
||||
);
|
||||
const blockedLayer = firstNonEmpty(runtime?.durabilityContract?.blockedLayer, durability?.blockedLayer);
|
||||
if (!reasonCode && runtimeDurable && runtimeReady && liveRuntimeEvidence) return null;
|
||||
if (runtimeExplicitlyNonDurable) {
|
||||
return {
|
||||
@@ -511,6 +523,27 @@ function durableRuntimeReason(payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasDurableRuntimeBlockedSignal(payload) {
|
||||
const runtime = payload?.runtime;
|
||||
const durability = payload?.readiness?.durability;
|
||||
return [
|
||||
runtime?.blocker,
|
||||
durability?.blocker,
|
||||
runtime?.durabilityContract?.blockedLayer,
|
||||
durability?.blockedLayer,
|
||||
blockedDurabilityQueryResult(runtime?.connection?.queryResult),
|
||||
blockedDurabilityQueryResult(durability?.queryResult),
|
||||
blockerCodeStartingWith(payload, "runtime_durable_adapter_")
|
||||
].some(Boolean);
|
||||
}
|
||||
|
||||
function blockedDurabilityQueryResult(value) {
|
||||
const text = normalizedStatus(value);
|
||||
if (!text || text === "durable_readiness_ready" || text === "ready") return "";
|
||||
if (text.endsWith("_blocked") || text.includes("blocked")) return String(value).trim();
|
||||
return "";
|
||||
}
|
||||
|
||||
function firstBlocker(payload) {
|
||||
const blockers = [
|
||||
...(Array.isArray(payload?.blockers) ? payload.blockers : []),
|
||||
@@ -609,6 +642,15 @@ function isReadOnlyReason(value) {
|
||||
return READ_ONLY_REASON_CODES.some((code) => text.includes(code));
|
||||
}
|
||||
|
||||
function codeAgentReadOnlySessionToolsReason(availability) {
|
||||
const flags = [
|
||||
...(Array.isArray(availability?.runnerLimitations) ? availability.runnerLimitations : []),
|
||||
...(Array.isArray(availability?.runner?.limitations) ? availability.runner.limitations : [])
|
||||
].filter(Boolean);
|
||||
const issue = firstNonEmpty(availability?.issue, availability?.trackingIssue, "#317");
|
||||
return `capability=read-only-session-tools; ${issue} full pass not complete${flags.length > 0 ? `; ${flags.join(",")}` : ""}`;
|
||||
}
|
||||
|
||||
function traceIdFrom(value) {
|
||||
return firstNonEmpty(
|
||||
value?.traceId,
|
||||
|
||||
@@ -365,6 +365,88 @@ test("classifies Code Agent provider timeout or fallback with concrete attributi
|
||||
assert.match(fallback.detail, /capability=text-chat-only/u);
|
||||
});
|
||||
|
||||
test("keeps ready durable runtime green while attributing read-only mode to Code Agent session-tools", () => {
|
||||
const status = classifyWorkbenchLiveStatus({
|
||||
healthLive: {
|
||||
ok: true,
|
||||
status: 200,
|
||||
data: {
|
||||
serviceId: "hwlab-cloud-api",
|
||||
status: "ok",
|
||||
ready: true,
|
||||
runtime: {
|
||||
status: "ready",
|
||||
ready: true,
|
||||
durable: true,
|
||||
liveRuntimeEvidence: true,
|
||||
blocker: null,
|
||||
connection: {
|
||||
queryResult: "durable_readiness_ready"
|
||||
}
|
||||
},
|
||||
readiness: {
|
||||
status: "ready",
|
||||
ready: true,
|
||||
durability: {
|
||||
status: "ready",
|
||||
ready: true,
|
||||
runtimeStatus: "ready",
|
||||
blockedLayer: null,
|
||||
blocker: null,
|
||||
queryResult: "durable_readiness_ready"
|
||||
}
|
||||
},
|
||||
codeAgent: {
|
||||
endpoint: "POST /v1/agent/chat",
|
||||
status: "available",
|
||||
ready: true,
|
||||
provider: "codex-readonly-runner",
|
||||
backend: "hwlab-cloud-api/controlled-readonly-session-registry",
|
||||
capabilityLevel: "read-only-session-tools",
|
||||
trackingIssue: "#317",
|
||||
runnerLimitations: ["not-codex-stdio", "not-write-capable", "not-durable-session"]
|
||||
}
|
||||
}
|
||||
},
|
||||
restIndex: {
|
||||
ok: true,
|
||||
status: 200,
|
||||
data: {
|
||||
serviceId: "hwlab-cloud-api",
|
||||
status: "ok",
|
||||
ready: true,
|
||||
codeAgent: {
|
||||
endpoint: "POST /v1/agent/chat",
|
||||
status: "available",
|
||||
ready: true,
|
||||
provider: "codex-readonly-runner",
|
||||
backend: "hwlab-cloud-api/controlled-readonly-session-registry",
|
||||
capabilityLevel: "read-only-session-tools",
|
||||
trackingIssue: "#317",
|
||||
runnerLimitations: ["not-codex-stdio", "not-write-capable", "not-durable-session"]
|
||||
}
|
||||
}
|
||||
},
|
||||
health: okApi,
|
||||
adapter: okApi,
|
||||
m3Control: okM3Control,
|
||||
m3Status: okM3Status
|
||||
});
|
||||
|
||||
assert.equal(status.kind, "readonly");
|
||||
assert.equal(status.label, "只读模式");
|
||||
assert.equal(status.serviceId, "hwlab-cloud-api");
|
||||
assert.equal(status.apiPath, "POST /v1/agent/chat");
|
||||
assert.equal(status.reasonCode, "code_agent_read_only_session_tools");
|
||||
assert.match(status.detail, /capability=read-only-session-tools/u);
|
||||
assert.match(status.detail, /#317 full pass not complete/u);
|
||||
assert.doesNotMatch(status.detail, /runtime durable blocked/u);
|
||||
assert.doesNotMatch(status.detail, /blocked at durability/u);
|
||||
const healthLiveProbe = status.probes.find((probe) => probe.apiPath === "/health/live");
|
||||
assert.equal(healthLiveProbe.kind, "pass");
|
||||
assert.doesNotMatch(healthLiveProbe.reason, /blocked/u);
|
||||
});
|
||||
|
||||
test("classifies M3 control blockers by cloud-api/gateway/box/patch-panel layer", () => {
|
||||
const status = classifyWorkbenchLiveStatus({
|
||||
healthLive: okApi,
|
||||
|
||||
Reference in New Issue
Block a user