339 lines
12 KiB
JavaScript
339 lines
12 KiB
JavaScript
import assert from "node:assert/strict";
|
||
import test from "node:test";
|
||
|
||
import {
|
||
codeAgentAttributionFromMessage,
|
||
codeAgentFactsFromMessage,
|
||
compactHwlabApiFact
|
||
} from "./code-agent-facts.mjs";
|
||
|
||
test("codex-readonly-runner is shown as partial read-only session registry", () => {
|
||
const facts = codeAgentFactsFromMessage({
|
||
status: "source",
|
||
provider: "codex-readonly-runner",
|
||
model: "read-only-tools",
|
||
backend: "hwlab-cloud-api/codex-readonly-runner",
|
||
workspace: "/workspace/hwlab",
|
||
sandbox: "read-only",
|
||
capabilityLevel: "read-only-session-tools",
|
||
sessionMode: "controlled-readonly-session-registry",
|
||
implementationType: "controlled-readonly-session-registry",
|
||
session: {
|
||
sessionId: "ses_readonly",
|
||
status: "idle",
|
||
turn: 1,
|
||
lastTraceId: "trc_readonly"
|
||
},
|
||
sessionReuse: {
|
||
reused: false,
|
||
turn: 1,
|
||
status: "idle"
|
||
},
|
||
runner: {
|
||
kind: "hwlab-readonly-runner",
|
||
codexStdio: false,
|
||
writeCapable: false,
|
||
durableSession: false,
|
||
longLivedSession: true
|
||
},
|
||
longLivedSessionGate: {
|
||
status: "blocked",
|
||
pass: false,
|
||
blockers: [{ code: "codex_stdio_blocked_readonly_session_available" }]
|
||
},
|
||
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
|
||
},
|
||
traceId: "trc_readonly"
|
||
});
|
||
|
||
assert.equal(facts.kind, "controlled-readonly-session-registry");
|
||
assert.equal(facts.fullCodeAgent, false);
|
||
assert.equal(facts.codexSessionGatePass, false);
|
||
assert.match(facts.statusLabel, /只读长会话/u);
|
||
assert.match(facts.summary, /可复用/u);
|
||
assert.match(facts.summary, /Codex stdio/u);
|
||
assert.match(facts.rows.find((row) => row.label === "runner/provider/backend").value, /runner=hwlab-readonly-runner/u);
|
||
assert.match(facts.rows.find((row) => row.label === "toolCalls").value, /1\/1 pwd\(completed\)/u);
|
||
});
|
||
|
||
test("long-lived Codex stdio session fields are shown as full Code Agent session", () => {
|
||
const facts = codeAgentFactsFromMessage({
|
||
status: "completed",
|
||
provider: "codex-stdio",
|
||
model: "gpt-5.5",
|
||
backend: "hwlab-cloud-api/codex-mcp-stdio",
|
||
workspace: "/workspace/hwlab",
|
||
sandbox: "workspace-write",
|
||
capabilityLevel: "long-lived-codex-stdio-session",
|
||
sessionMode: "codex-mcp-stdio-long-lived",
|
||
implementationType: "repo-owned-codex-mcp-stdio-session",
|
||
session: {
|
||
sessionId: "ses_stdio",
|
||
status: "idle",
|
||
turn: 3,
|
||
idleTimeoutMs: 1800000,
|
||
lastTraceId: "trc_stdio",
|
||
longLivedSession: true,
|
||
codexStdio: true
|
||
},
|
||
runner: {
|
||
kind: "codex-mcp-stdio-runner",
|
||
codexStdio: true,
|
||
writeCapable: true,
|
||
durableSession: true
|
||
},
|
||
longLivedSessionGate: {
|
||
status: "pass",
|
||
pass: true
|
||
},
|
||
toolCalls: [{ name: "codex-reply", status: "completed" }],
|
||
skills: { status: "not_requested", count: 0 },
|
||
runnerTrace: {
|
||
traceId: "trc_stdio",
|
||
runnerKind: "codex-mcp-stdio-runner",
|
||
sessionMode: "codex-mcp-stdio-long-lived",
|
||
turn: 3
|
||
},
|
||
traceId: "trc_stdio"
|
||
});
|
||
|
||
assert.equal(facts.kind, "codex-stdio-long-lived");
|
||
assert.equal(facts.statusLabel, "真实 runner:Codex stdio 长会话");
|
||
assert.equal(facts.fullCodeAgent, true);
|
||
assert.equal(facts.codexSessionGatePass, true);
|
||
assert.match(facts.rows.find((row) => row.label === "sandbox").value, /workspace-write/u);
|
||
});
|
||
|
||
test("openai-responses fallback is explicitly text-chat-only", () => {
|
||
const message = {
|
||
status: "source",
|
||
provider: "openai-responses",
|
||
model: "gpt-5.5",
|
||
backend: "hwlab-cloud-api/openai-responses",
|
||
capabilityLevel: "text-chat-only",
|
||
sessionMode: "provider-text-request",
|
||
runner: {
|
||
kind: "openai-responses-fallback",
|
||
codexStdio: false,
|
||
writeCapable: false,
|
||
durableSession: false
|
||
},
|
||
longLivedSessionGate: {
|
||
status: "blocked",
|
||
pass: false,
|
||
blockers: [{ code: "openai_responses_fallback_not_session" }]
|
||
},
|
||
toolCalls: [],
|
||
skills: { status: "not_requested", count: 0 },
|
||
runnerTrace: {
|
||
traceId: "trc_fallback",
|
||
runnerKind: "openai-responses-fallback",
|
||
events: ["fallback:text-chat-only"]
|
||
},
|
||
traceId: "trc_fallback"
|
||
};
|
||
const facts = codeAgentFactsFromMessage(message);
|
||
const attribution = codeAgentAttributionFromMessage(message);
|
||
|
||
assert.equal(facts.kind, "text-chat-only");
|
||
assert.equal(facts.statusLabel, "OpenAI fallback:只是文本回答 / text-chat-only");
|
||
assert.equal(facts.fullCodeAgent, false);
|
||
assert.equal(facts.codexSessionGatePass, false);
|
||
assert.match(facts.summary, /只是文本回答/u);
|
||
assert.match(attribution.label, /只是文本回答/u);
|
||
assert.match(attribution.summary, /当前不能执行工具/u);
|
||
assert.equal(attribution.fields.find((field) => field.key === "runner").value, "openai-responses-fallback / 不是 runner 控制");
|
||
assert.equal(attribution.fields.find((field) => field.key === "capabilityLevel").value, "text-chat-only / 只是文本回答");
|
||
assert.equal(attribution.fields.find((field) => field.key === "toolCalls").value, "0/0 / 无工具调用:只是文本回答");
|
||
});
|
||
|
||
test("stateless one-shot is not wrapped as a long-lived session", () => {
|
||
const facts = codeAgentFactsFromMessage({
|
||
status: "source",
|
||
provider: "codex-cli",
|
||
model: "gpt-5.5",
|
||
backend: "hwlab-cloud-api/codex-cli",
|
||
workspace: "/workspace/hwlab",
|
||
sandbox: "read-only",
|
||
capabilityLevel: "text-chat-only",
|
||
sessionMode: "ephemeral-one-shot",
|
||
implementationType: "codex-cli-one-shot-ephemeral",
|
||
session: {
|
||
sessionId: "cnv_one_shot",
|
||
status: "expired",
|
||
idleTimeoutMs: 0,
|
||
lastTraceId: "trc_one_shot"
|
||
},
|
||
runner: {
|
||
kind: "codex-cli-one-shot-ephemeral",
|
||
codexStdio: false,
|
||
writeCapable: false,
|
||
durableSession: false
|
||
},
|
||
longLivedSessionGate: {
|
||
status: "blocked",
|
||
pass: false,
|
||
blockers: [{ code: "one_shot_runner_not_long_lived" }]
|
||
},
|
||
traceId: "trc_one_shot"
|
||
});
|
||
|
||
assert.equal(facts.kind, "stateless-one-shot");
|
||
assert.equal(facts.fullCodeAgent, false);
|
||
assert.equal(facts.codexSessionGatePass, false);
|
||
assert.match(facts.summary, /一次性 runner/u);
|
||
});
|
||
|
||
test("HWLAB Skill CLI toolCall shows compact copyable /v1/m3/io facts", () => {
|
||
const facts = codeAgentFactsFromMessage({
|
||
status: "source",
|
||
provider: "hwlab-skill-cli",
|
||
model: "controlled-m3-io",
|
||
backend: "hwlab-cloud-api/hwlab-agent-runtime-skill-cli",
|
||
workspace: "/workspace/hwlab",
|
||
sandbox: "hwlab-api-route-only",
|
||
capabilityLevel: "hwlab-api-control-ready",
|
||
sessionMode: "controlled-m3-io-skill-cli",
|
||
runner: {
|
||
kind: "hwlab-m3-io-skill-cli",
|
||
codexStdio: false,
|
||
writeCapable: true,
|
||
durableSession: false
|
||
},
|
||
longLivedSessionGate: {
|
||
status: "blocked",
|
||
pass: false
|
||
},
|
||
toolCalls: [
|
||
{
|
||
name: "hwlab-agent-runtime.m3-io",
|
||
type: "skill-cli",
|
||
status: "completed",
|
||
route: "/v1/m3/io",
|
||
method: "POST",
|
||
operationId: "op_m3_do_write_cli",
|
||
traceId: "trc_m3_skill_cli",
|
||
auditId: "aud_m3_do_write_cli_succeeded",
|
||
evidenceId: "evd_m3_do_write_cli_succeeded",
|
||
audit: { auditId: "aud_m3_do_write_cli_succeeded" },
|
||
evidence: { evidenceId: "evd_m3_do_write_cli_succeeded" },
|
||
accepted: true,
|
||
readback: {
|
||
status: "succeeded",
|
||
value: false,
|
||
resourceId: "res_boxsimu_2",
|
||
port: "DI1"
|
||
},
|
||
blocker: { code: "runtime_durable_not_green" },
|
||
stdout: JSON.stringify({
|
||
route: "/v1/m3/io",
|
||
method: "POST",
|
||
status: "succeeded",
|
||
accepted: true,
|
||
traceId: "trc_m3_skill_cli",
|
||
operationId: "op_m3_do_write_cli",
|
||
auditId: "aud_m3_do_write_cli_succeeded",
|
||
evidenceId: "evd_m3_do_write_cli_succeeded",
|
||
audit: { auditId: "aud_m3_do_write_cli_succeeded" },
|
||
evidence: { evidenceId: "evd_m3_do_write_cli_succeeded" },
|
||
readback: {
|
||
status: "succeeded",
|
||
value: false,
|
||
resourceId: "res_boxsimu_2",
|
||
port: "DI1"
|
||
},
|
||
durable: { blocker: "runtime_durable_not_green" }
|
||
})
|
||
}
|
||
],
|
||
skills: {
|
||
status: "used",
|
||
count: 1,
|
||
items: [{ name: "hwlab-agent-runtime.m3-io" }]
|
||
},
|
||
runnerTrace: {
|
||
traceId: "trc_m3_skill_cli",
|
||
runnerKind: "hwlab-m3-io-skill-cli",
|
||
sessionMode: "controlled-m3-io-skill-cli",
|
||
route: "/v1/m3/io",
|
||
status: "succeeded"
|
||
},
|
||
traceId: "trc_m3_skill_cli"
|
||
});
|
||
|
||
assert.equal(facts.kind, "hwlab-skill-cli");
|
||
assert.equal(facts.fullCodeAgent, false);
|
||
assert.equal(facts.hwlabApiFacts.length, 1);
|
||
assert.deepEqual(facts.hwlabApiFacts[0], {
|
||
tool: "hwlab-agent-runtime.m3-io",
|
||
route: "/v1/m3/io",
|
||
method: "POST",
|
||
operationId: "op_m3_do_write_cli",
|
||
traceId: "trc_m3_skill_cli",
|
||
auditId: "aud_m3_do_write_cli_succeeded",
|
||
evidenceId: "evd_m3_do_write_cli_succeeded",
|
||
accepted: true,
|
||
readback: {
|
||
status: "succeeded",
|
||
value: false,
|
||
resourceId: "res_boxsimu_2",
|
||
port: "DI1"
|
||
},
|
||
blocker: "runtime_durable_not_green",
|
||
status: "completed"
|
||
});
|
||
assert.equal(
|
||
compactHwlabApiFact(facts.hwlabApiFacts[0]),
|
||
"route=/v1/m3/io / method=POST / operationId=op_m3_do_write_cli / traceId=trc_m3_skill_cli / auditId=aud_m3_do_write_cli_succeeded / evidenceId=evd_m3_do_write_cli_succeeded / blocker=runtime_durable_not_green"
|
||
);
|
||
const attribution = codeAgentAttributionFromMessage({
|
||
role: "agent",
|
||
status: "source",
|
||
provider: "hwlab-skill-cli",
|
||
backend: "hwlab-cloud-api/hwlab-agent-runtime-skill-cli",
|
||
capabilityLevel: "hwlab-api-control-ready",
|
||
sessionMode: "controlled-m3-io-skill-cli",
|
||
runner: { kind: "hwlab-m3-io-skill-cli" },
|
||
toolCalls: facts.hwlabApiFacts.map((fact) => ({
|
||
name: fact.tool,
|
||
status: fact.status,
|
||
route: fact.route,
|
||
operationId: fact.operationId,
|
||
traceId: fact.traceId,
|
||
auditId: fact.auditId,
|
||
evidenceId: fact.evidenceId
|
||
})),
|
||
traceId: "trc_m3_skill_cli"
|
||
});
|
||
assert.equal(attribution.kind, "hwlab-skill-cli");
|
||
assert.equal(attribution.fields.find((field) => field.key === "operationId").value, "op_m3_do_write_cli");
|
||
assert.equal(attribution.fields.find((field) => field.key === "audit").value, "aud_m3_do_write_cli_succeeded");
|
||
assert.equal(attribution.fields.find((field) => field.key === "evidence").value, "evd_m3_do_write_cli_succeeded");
|
||
assert.match(attribution.fields.find((field) => field.key === "toolCalls").value, /hwlab-agent-runtime\.m3-io\(completed\)@\/v1\/m3\/io/u);
|
||
});
|
||
|
||
test("missing attribution fields render structured evidence insufficiency", () => {
|
||
const attribution = codeAgentAttributionFromMessage({
|
||
role: "agent",
|
||
status: "failed",
|
||
traceId: "trc_missing_fields",
|
||
error: {
|
||
code: "provider_unavailable",
|
||
message: "blocked"
|
||
}
|
||
});
|
||
|
||
assert.equal(attribution.label, "BLOCKED:当前不能执行");
|
||
assert.equal(attribution.fields.find((field) => field.key === "traceId").value, "trc_missing_fields");
|
||
assert.equal(attribution.fields.find((field) => field.key === "provider").missing, true);
|
||
assert.equal(attribution.fields.find((field) => field.key === "backend").value, "字段缺失:证据不足");
|
||
assert.equal(attribution.fields.find((field) => field.key === "runner").chip, "runner=字段缺失:证据不足");
|
||
assert.match(attribution.summary, /字段缺失/u);
|
||
});
|