Merge pull request #355 from pikasTech/feat/code-agent-facts-78

feat: expose Code Agent capability facts
This commit is contained in:
Lyon
2026-05-23 22:46:00 +08:00
committed by GitHub
8 changed files with 742 additions and 17 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+57 -15
View File
@@ -1,4 +1,11 @@
import { gateSummary } from "./gate-summary.mjs";
import {
codeAgentFactsFromMessage,
compactHwlabApiFact,
runnerTraceSummary as compactRunnerTraceSummary,
skillsSummary as compactSkillsSummary,
toolCallsSummary as compactToolCallsSummary
} from "./code-agent-facts.mjs";
import { classifyWorkbenchLiveStatus } from "./live-status.mjs";
import { marked } from "./third_party/marked/marked.esm.js";
import { wiringPresentationFromSummary } from "./wiring-status.mjs";
@@ -2397,11 +2404,55 @@ function messageCard(message) {
article.append(textSpan(roleLabel(message.role), "message-role"));
article.append(textSpan(message.title, "message-title"));
article.append(textSpan(message.text, "message-copy"));
const capabilityFacts = codeAgentCapabilityFactsPanel(message);
if (capabilityFacts) article.append(capabilityFacts);
const messageEvidence = messageEvidencePanel(message);
if (messageEvidence) article.append(messageEvidence);
return article;
}
function codeAgentCapabilityFactsPanel(message) {
const facts = codeAgentFactsFromMessage(message);
if (!facts) return null;
const section = document.createElement("section");
section.className = `code-agent-facts tone-border-${toneClass(facts.tone)}`;
section.dataset.codeAgentFactKind = facts.kind;
section.append(codeAgentFactsHeader(facts));
const grid = document.createElement("dl");
grid.className = "code-agent-fact-grid";
for (const row of facts.rows) {
const item = document.createElement("div");
item.className = "code-agent-fact-row";
item.append(textSpan(row.label, "code-agent-fact-key"), textSpan(row.value ?? "none", "code-agent-fact-value mono"));
grid.append(item);
}
section.append(grid);
if (facts.hwlabApiFacts.length > 0) {
const apiList = document.createElement("div");
apiList.className = "hwlab-api-facts";
apiList.append(textSpan("HWLAB API", "hwlab-api-facts-title"));
for (const fact of facts.hwlabApiFacts) {
const code = document.createElement("code");
code.className = "hwlab-api-fact-code";
code.textContent = compactHwlabApiFact(fact);
apiList.append(code);
}
section.append(apiList);
}
return section;
}
function codeAgentFactsHeader(facts) {
const header = document.createElement("div");
header.className = "code-agent-facts-head";
header.append(badge(facts.statusLabel, facts.tone));
header.append(textSpan(facts.summary, "code-agent-facts-summary"));
return header;
}
function messageEvidencePanel(message) {
const fields = messageEvidenceFields(message).map(boundedEvidenceField);
if (fields.length === 0) return null;
@@ -2464,27 +2515,18 @@ function providerTraceSummary(providerTrace) {
}
function toolCallsSummary(toolCalls) {
if (!Array.isArray(toolCalls) || toolCalls.length === 0) return null;
const completed = toolCalls.filter((tool) => tool?.status === "completed").length;
const names = toolCalls.map((tool) => tool?.name).filter(Boolean).slice(0, 3).join(",");
return `${completed}/${toolCalls.length}${names ? `:${names}` : ""}`;
const summary = compactToolCallsSummary(toolCalls);
return summary === "none" ? null : summary;
}
function skillsSummary(skills) {
if (!skills || typeof skills !== "object") return null;
if (skills.status === "not_requested") return "not_requested";
const count = skills.totalCount ?? skills.count ?? (Array.isArray(skills.items) ? skills.items.length : 0);
return `${skills.status ?? "unknown"}:${count}`;
const summary = compactSkillsSummary(skills);
return summary === "none" ? null : summary;
}
function runnerTraceSummary(runnerTrace) {
if (!runnerTrace || typeof runnerTrace !== "object") return null;
const parts = [
runnerTrace.runnerKind ?? "present",
runnerTrace.sessionMode,
typeof runnerTrace.turn === "number" ? `turn${runnerTrace.turn}` : null
].filter(Boolean);
return parts.join(":");
const summary = compactRunnerTraceSummary(runnerTrace);
return summary === "none" ? null : summary;
}
function sessionReuseSummary(sessionReuse) {
+307
View File
@@ -0,0 +1,307 @@
const M3_IO_ROUTE = "/v1/m3/io";
const TEXT_FALLBACK_RUNNER = "openai-responses-fallback";
const CODEX_ONE_SHOT_RUNNER = "codex-cli-one-shot-ephemeral";
const READONLY_SESSION_MODE = "controlled-readonly-session-registry";
const CODEX_STDIO_SESSION_MODE = "codex-mcp-stdio-long-lived";
const HWLAB_SKILL_RUNNER = "hwlab-m3-io-skill-cli";
export function codeAgentFactsFromMessage(message) {
if (!hasCodeAgentFields(message)) return null;
const classification = classifyCodeAgentFacts(message);
const rows = [
{
label: "runner/provider/backend",
value: compactFields([
field("runner", message.runner?.kind ?? runnerKindFromMessage(message)),
field("provider", message.provider),
field("backend", message.backend)
])
},
{
label: "session",
value: compactFields([
field("id", message.session?.sessionId ?? message.sessionId ?? message.runner?.sessionId),
field("status", message.session?.status),
field("sessionMode", message.sessionMode ?? message.runner?.sessionMode ?? message.runner?.session)
])
},
{
label: "workspace",
value: message.workspace ?? message.runner?.workspace ?? "none"
},
{
label: "sandbox",
value: message.sandbox ?? message.runner?.sandbox ?? "none"
},
{
label: "capabilityLevel",
value: message.capabilityLevel ?? message.runner?.capabilityLevel ?? "unknown"
},
{
label: "toolCalls",
value: toolCallsSummary(message.toolCalls)
},
{
label: "skills",
value: skillsSummary(message.skills)
},
{
label: "runnerTrace/traceId",
value: compactFields([
field("traceId", message.traceId),
field("runnerTrace", runnerTraceSummary(message.runnerTrace))
])
}
];
return {
...classification,
rows,
hwlabApiFacts: hwlabApiFactsFromMessage(message)
};
}
export function classifyCodeAgentFacts(message) {
const runnerKind = runnerKindFromMessage(message);
const provider = String(message?.provider ?? "").trim();
const sessionMode = String(message?.sessionMode ?? message?.runner?.sessionMode ?? message?.runner?.session ?? "").trim();
const implementationType = String(message?.implementationType ?? message?.runner?.implementationType ?? "").trim();
const capabilityLevel = String(message?.capabilityLevel ?? message?.runner?.capabilityLevel ?? "").trim();
const longLivedGate = message?.longLivedSessionGate;
if (provider === "codex-stdio" && longLivedGate?.status === "pass" && longLivedGate?.pass === true) {
return {
kind: "codex-stdio-long-lived",
statusLabel: "完整 Code Agent session",
tone: "dev-live",
summary: "Codex stdio long-lived session gate passed; session lifecycle and trace capture are available.",
fullCodeAgent: true,
codexSessionGatePass: true
};
}
if (provider === "hwlab-skill-cli" || runnerKind === HWLAB_SKILL_RUNNER || hasM3IoToolCall(message?.toolCalls)) {
return {
kind: "hwlab-skill-cli",
statusLabel: "HWLAB Skill CLI",
tone: "source",
summary: "Controlled Skill CLI -> HWLAB API path only; this is not OpenAI fallback and not a generic Codex session.",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
if (runnerKind === CODEX_ONE_SHOT_RUNNER || sessionMode === "ephemeral-one-shot" || implementationType === "codex-cli-one-shot-ephemeral") {
return {
kind: "stateless-one-shot",
statusLabel: "stateless-one-shot",
tone: "source",
summary: "Ephemeral one-shot runner result; do not treat it as a reusable or long-lived Code Agent session.",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
if (runnerKind === TEXT_FALLBACK_RUNNER || provider === "openai-responses" || capabilityLevel === "text-chat-only") {
return {
kind: "text-chat-only",
statusLabel: "openai-responses-fallback / text-chat-only",
tone: "source",
summary: "Text chat fallback can answer ordinary messages, but it has no workspace, tools, skills, or Codex session gate.",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
if (provider === "codex-readonly-runner" || runnerKind === "hwlab-readonly-runner" || sessionMode === READONLY_SESSION_MODE) {
return {
kind: READONLY_SESSION_MODE,
statusLabel: "只读 session registry",
tone: "source",
summary: "Bounded read-only tools and session mapping are available, but Codex stdio, write capability, and durable session remain blocked.",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
if (provider === "codex-stdio" || sessionMode === CODEX_STDIO_SESSION_MODE) {
return {
kind: "codex-stdio-blocked",
statusLabel: "Codex stdio blocked",
tone: "blocked",
summary: "Codex stdio fields are present, but the long-lived session gate has not passed.",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
return {
kind: "unknown-code-agent-path",
statusLabel: "Code Agent facts",
tone: message?.status === "failed" ? "blocked" : "source",
summary: "Provider response is shown with its raw runner/session facts; completion is not promoted without a passing gate.",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
export function hwlabApiFactsFromMessage(message) {
const facts = [];
for (const toolCall of Array.isArray(message?.toolCalls) ? message.toolCalls : []) {
const parsed = parseToolStdout(toolCall.stdout);
const route = firstText(toolCall.route, toolCall.hwlabApi?.route, parsed?.route);
if (route !== M3_IO_ROUTE) continue;
const blocker = firstText(
toolCall.blocker?.code,
toolCall.capabilityBlocker?.code,
toolCall.trustBlocker?.code,
firstBlockerCode(toolCall.blockers),
parsed?.blocker?.code,
parsed?.blocker,
parsed?.durable?.blocker
);
facts.push({
tool: firstText(toolCall.name, "skill-cli"),
route,
operationId: nullableText(toolCall.operationId ?? parsed?.operationId),
traceId: nullableText(toolCall.traceId ?? parsed?.traceId ?? message?.traceId),
auditId: nullableText(toolCall.audit?.auditId ?? parsed?.audit?.auditId ?? parsed?.auditId),
evidenceId: nullableText(toolCall.evidence?.evidenceId ?? parsed?.evidence?.evidenceId ?? parsed?.evidenceId),
blocker: nullableText(blocker),
status: firstText(toolCall.status, parsed?.status, "unknown")
});
}
return facts;
}
export function compactHwlabApiFact(fact) {
if (!fact) return "";
return compactFields([
field("route", fact.route),
field("operationId", fact.operationId),
field("traceId", fact.traceId),
field("auditId", fact.auditId),
field("evidenceId", fact.evidenceId),
field("blocker", fact.blocker)
]);
}
export function toolCallsSummary(toolCalls) {
if (!Array.isArray(toolCalls) || toolCalls.length === 0) return "none";
const completed = toolCalls.filter((tool) => tool?.status === "completed").length;
const names = toolCalls
.map((tool) => compactFields([
firstText(tool?.name, "tool"),
tool?.status ? `(${tool.status})` : null,
tool?.route ? `@${tool.route}` : null
], ""))
.filter(Boolean)
.slice(0, 4)
.join(", ");
return `${completed}/${toolCalls.length}${names ? ` ${names}` : ""}`;
}
export function skillsSummary(skills) {
if (!skills || typeof skills !== "object") return "none";
const count = skills.totalCount ?? skills.count ?? (Array.isArray(skills.items) ? skills.items.length : 0);
const names = Array.isArray(skills.items)
? skills.items.map((item) => item?.name).filter(Boolean).slice(0, 3).join(",")
: "";
const blockers = Array.isArray(skills.blockers)
? skills.blockers.map((blocker) => blocker?.code).filter(Boolean).slice(0, 2).join(",")
: "";
return compactFields([
skills.status ?? "unknown",
`count=${count}`,
names ? `items=${names}` : null,
blockers ? `blockers=${blockers}` : null
], ":");
}
export function runnerTraceSummary(runnerTrace) {
if (!runnerTrace || typeof runnerTrace !== "object") return "none";
return compactFields([
field("traceId", runnerTrace.traceId),
field("runnerKind", runnerTrace.runnerKind),
field("sessionMode", runnerTrace.sessionMode),
typeof runnerTrace.turn === "number" ? field("turn", runnerTrace.turn) : null,
field("route", runnerTrace.route),
field("status", runnerTrace.status)
]);
}
function hasCodeAgentFields(message) {
return Boolean(
message &&
typeof message === "object" &&
(
message.provider ||
message.backend ||
message.runner ||
message.session ||
message.sessionMode ||
message.capabilityLevel ||
Array.isArray(message.toolCalls) ||
message.runnerTrace
)
);
}
function hasM3IoToolCall(toolCalls) {
return Array.isArray(toolCalls) && toolCalls.some((toolCall) =>
toolCall?.route === M3_IO_ROUTE ||
toolCall?.hwlabApi?.route === M3_IO_ROUTE ||
parseToolStdout(toolCall?.stdout)?.route === M3_IO_ROUTE
);
}
function runnerKindFromMessage(message) {
return firstText(
message?.runner?.kind,
message?.runnerTrace?.runnerKind,
message?.provider === "openai-responses" ? TEXT_FALLBACK_RUNNER : null
);
}
function parseToolStdout(stdout) {
if (typeof stdout !== "string" || !stdout.trim()) return null;
const text = stdout.trim();
const direct = parseJson(text);
if (direct) return direct;
const start = text.indexOf("{");
const end = text.lastIndexOf("}");
if (start === -1 || end <= start) return null;
return parseJson(text.slice(start, end + 1));
}
function parseJson(text) {
try {
const parsed = JSON.parse(text);
return parsed && typeof parsed === "object" ? parsed : null;
} catch {
return null;
}
}
function firstBlockerCode(blockers) {
if (!Array.isArray(blockers)) return null;
return blockers.map((blocker) => blocker?.code).find(Boolean) ?? null;
}
function compactFields(fields, separator = " / ") {
return fields.filter((item) => item !== null && item !== undefined && String(item).trim()).map((item) => String(item).trim()).join(separator);
}
function field(label, value) {
const normalized = nullableText(value);
return normalized ? `${label}=${normalized}` : null;
}
function firstText(...values) {
return values.map(nullableText).find(Boolean) ?? null;
}
function nullableText(value) {
if (value === undefined || value === null || value === "") return null;
return String(value).replace(/\s+/gu, " ").trim();
}
@@ -0,0 +1,256 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
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
},
longLivedSessionGate: {
status: "blocked",
pass: false,
blockers: [{ code: "controlled_readonly_not_long_lived_stdio" }]
},
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.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, "完整 Code Agent session");
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 facts = codeAgentFactsFromMessage({
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"
});
assert.equal(facts.kind, "text-chat-only");
assert.equal(facts.statusLabel, "openai-responses-fallback / text-chat-only");
assert.equal(facts.fullCodeAgent, false);
assert.equal(facts.codexSessionGatePass, false);
assert.match(facts.summary, /no workspace, tools, skills, or Codex session gate/u);
});
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, /Ephemeral one-shot 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",
operationId: "op_m3_do_write_cli",
traceId: "trc_m3_skill_cli",
audit: { auditId: "aud_m3_do_write_cli_succeeded" },
evidence: { evidenceId: "evd_m3_do_write_cli_succeeded" },
blocker: { code: "runtime_durable_not_green" },
stdout: JSON.stringify({
route: "/v1/m3/io",
status: "succeeded",
traceId: "trc_m3_skill_cli",
operationId: "op_m3_do_write_cli",
audit: { auditId: "aud_m3_do_write_cli_succeeded" },
evidence: { evidenceId: "evd_m3_do_write_cli_succeeded" },
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",
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",
status: "completed"
});
assert.equal(
compactHwlabApiFact(facts.hwlabApiFacts[0]),
"route=/v1/m3/io / 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"
);
});
+1 -1
View File
@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"m3-readonly": "node scripts/m3-readonly-contract.mjs",
"check": "node --test wiring-status.test.mjs && node scripts/check.mjs",
"check": "node --test code-agent-facts.test.mjs wiring-status.test.mjs && node scripts/check.mjs",
"build": "node scripts/build.mjs",
"layout": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --static --report ../../reports/dev-gate/dev-cloud-workbench-layout.json",
"layout:build": "node ../../scripts/dev-cloud-workbench-layout-smoke.mjs --build --report ../../reports/dev-gate/dev-cloud-workbench-layout-build.json",
+20
View File
@@ -22,6 +22,7 @@ const requiredFiles = [
"index.html",
"styles.css",
"app.mjs",
"code-agent-facts.mjs",
"live-status.mjs",
"gate-summary.mjs",
"runtime.mjs",
@@ -42,6 +43,7 @@ for (const file of requiredFiles) {
const html = fs.readFileSync(path.resolve(rootDir, "index.html"), "utf8");
const styles = fs.readFileSync(path.resolve(rootDir, "styles.css"), "utf8");
const app = fs.readFileSync(path.resolve(rootDir, "app.mjs"), "utf8");
const codeAgentFacts = fs.readFileSync(path.resolve(rootDir, "code-agent-facts.mjs"), "utf8");
const liveStatus = fs.readFileSync(path.resolve(rootDir, "live-status.mjs"), "utf8");
const wiringStatus = fs.readFileSync(path.resolve(rootDir, "wiring-status.mjs"), "utf8");
const helpMarkdown = fs.readFileSync(path.resolve(rootDir, "help.md"), "utf8");
@@ -640,6 +642,19 @@ assert.match(app, /SOURCE fixture 只可显示为 SOURCE 回复,不能冒充 D
assert.match(app, /function messageEvidencePanel/);
assert.match(app, /function messageEvidenceSummary/);
assert.match(app, /function boundedEvidenceField/);
assert.match(app, /codeAgentFactsFromMessage/);
assert.match(app, /function codeAgentCapabilityFactsPanel/);
assert.match(app, /compactHwlabApiFact/);
assert.match(codeAgentFacts, /openai-responses-fallback/);
assert.match(codeAgentFacts, /text-chat-only/);
assert.match(codeAgentFacts, /stateless-one-shot/);
assert.match(codeAgentFacts, /controlled-readonly-session-registry/);
assert.match(codeAgentFacts, /hwlab-m3-io-skill-cli/);
assert.match(codeAgentFacts, /route", fact\.route/);
assert.match(codeAgentFacts, /operationId", fact\.operationId/);
assert.match(codeAgentFacts, /auditId", fact\.auditId/);
assert.match(codeAgentFacts, /evidenceId", fact\.evidenceId/);
assert.match(codeAgentFacts, /blocker", fact\.blocker/);
assert.match(app, /sourceKind:\s*completion\.sourceKind/);
assert.match(app, /status === "completed" \? "dev-live"/);
assert.doesNotMatch(app, /status === "failed" \? "dev-live"/);
@@ -652,12 +667,17 @@ assert.match(styles, /\.message-user\s*{/);
assert.match(styles, /\.message-agent,\s*\n\.message-system\s*{/);
assert.match(styles, /\.message-evidence\s*{/);
assert.match(styles, /\.message-evidence-chip\s*{/);
assert.match(styles, /\.code-agent-facts\s*{/);
assert.match(styles, /\.code-agent-fact-grid\s*{/);
assert.match(styles, /\.hwlab-api-fact-code\s*{/);
assert.match(styles, /\.hwlab-api-fact-code\s*{[^}]*user-select:\s*all;/s);
assert.match(styles, /\.rail-button\s*{[^}]*min-width:\s*0;[^}]*min-height:\s*36px;[^}]*text-align:\s*center;[^}]*word-break:\s*keep-all;/s);
assert.match(styles, /\.side-tab\s*{[^}]*min-width:\s*0;[^}]*min-height:\s*34px;[^}]*overflow-wrap:\s*anywhere;[^}]*word-break:\s*keep-all;/s);
assert.match(styles, /\.(?:status-dot|state-tag|badge)[^{]*{[^}]*max-width:\s*100%;[^}]*line-height:\s*1\.25;[^}]*white-space:\s*normal;[^}]*overflow-wrap:\s*anywhere;/s);
assert.match(styles, /\.probe-card\s*{[^}]*min-width:\s*0;/s);
assert.match(styles, /\.probe-card strong\s*{[^}]*line-height:\s*1\.2;[^}]*overflow-wrap:\s*anywhere;/s);
assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.message-evidence\s*{[\s\S]*?grid-column:\s*1;/);
assert.match(styles, /@media \(max-width: 860px\)[\s\S]*?\.code-agent-fact-grid\s*{[\s\S]*?grid-template-columns:\s*1fr;/);
for (const userFacingFunctionName of [
"codeAgentStatusMessage",
"codeAgentPromptText",
@@ -9,6 +9,7 @@ export const cloudWebDistRuntimeFiles = Object.freeze([
"index.html",
"styles.css",
"app.mjs",
"code-agent-facts.mjs",
"live-status.mjs",
"gate-summary.mjs",
"runtime.mjs",
+99
View File
@@ -663,6 +663,99 @@ h3 {
min-width: 0;
}
.code-agent-facts {
grid-column: 2;
min-width: 0;
display: grid;
gap: 7px;
padding: 8px;
background: rgba(15, 17, 16, 0.72);
border: 1px solid var(--line);
border-left-width: 3px;
}
.code-agent-facts-head {
min-width: 0;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: 7px;
align-items: start;
}
.code-agent-facts-summary {
min-width: 0;
color: var(--muted);
font-size: 11px;
overflow-wrap: anywhere;
}
.code-agent-fact-grid {
min-width: 0;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 5px;
margin: 0;
}
.code-agent-fact-row {
min-width: 0;
display: grid;
gap: 2px;
padding: 6px;
background: var(--surface);
border: 1px solid var(--line);
}
.code-agent-fact-key,
.code-agent-fact-value {
min-width: 0;
overflow-wrap: anywhere;
word-break: break-word;
}
.code-agent-fact-key {
color: var(--dim);
font-family: var(--mono);
font-size: 9px;
font-weight: 760;
text-transform: uppercase;
}
.code-agent-fact-value {
color: var(--text);
font-size: 10px;
}
.hwlab-api-facts {
min-width: 0;
display: grid;
gap: 5px;
}
.hwlab-api-facts-title {
color: var(--accent-2);
font-family: var(--mono);
font-size: 9px;
font-weight: 760;
text-transform: uppercase;
}
.hwlab-api-fact-code {
min-width: 0;
display: block;
padding: 6px;
background: var(--surface);
border: 1px solid var(--line);
color: var(--text);
font-family: var(--mono);
font-size: 10px;
line-height: 1.35;
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
user-select: all;
}
.message-evidence summary {
cursor: pointer;
list-style-position: inside;
@@ -1628,9 +1721,15 @@ tbody tr:last-child td {
grid-template-columns: 1fr;
}
.code-agent-facts,
.message-evidence {
grid-column: 1;
}
.code-agent-facts-head,
.code-agent-fact-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 520px) {