Files
pikasTech-HWLAB/web/hwlab-cloud-web/code-agent-facts.mjs
T
2026-05-25 20:55:33 +08:00

569 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_APP_SERVER_RUNNER = "codex-app-server-stdio-runner";
const CODEX_APP_SERVER_SESSION_MODE = "codex-app-server-stdio-long-lived";
const CODEX_APP_SERVER_IMPLEMENTATION = "repo-owned-codex-app-server-stdio-session";
const CODEX_APP_SERVER_PROTOCOL = "codex-app-server-jsonrpc-stdio";
const CODEX_MCP_RUNNER = "codex-mcp-stdio-runner";
const CODEX_MCP_SESSION_MODE = "codex-mcp-stdio-long-lived";
const CODEX_MCP_IMPLEMENTATION = "repo-owned-codex-mcp-stdio-session";
const MISSING_FIELD_VALUE = "字段缺失:证据不足";
const REQUIRED_ATTRIBUTION_KEYS = Object.freeze([
"provider",
"backend",
"runnerKind",
"protocol",
"implementationType",
"providerTrace.command",
"providerTrace.terminalStatus",
"sessionMode",
"capabilityLevel",
"toolCalls",
"traceId",
"operationId",
"audit",
"evidence"
]);
export function codeAgentFactsFromMessage(message) {
if (!hasCodeAgentFields(message)) return null;
const classification = classifyCodeAgentFacts(message);
const rows = [
{
label: "runner/provider/backend",
value: compactFields([
requiredField("runner", message.runner?.kind ?? runnerKindFromMessage(message)),
requiredField("provider", message.provider),
requiredField("backend", message.backend)
])
},
{
label: "session",
value: compactFields([
field("id", message.session?.sessionId ?? message.sessionId ?? message.runner?.sessionId),
field("status", message.session?.status),
requiredField("sessionMode", message.sessionMode ?? message.runner?.sessionMode ?? message.runner?.session)
])
},
{
label: "workspace",
value: valueOrMissing(message.workspace ?? message.runner?.workspace)
},
{
label: "sandbox",
value: valueOrMissing(message.sandbox ?? message.runner?.sandbox)
},
{
label: "capabilityLevel",
value: valueOrMissing(message.capabilityLevel ?? message.runner?.capabilityLevel)
},
{
label: "toolCalls",
value: toolCallsSummary(message.toolCalls)
},
{
label: "skills",
value: skillsSummary(message.skills)
},
{
label: "runnerTrace/traceId",
value: compactFields([
requiredField("traceId", message.traceId),
field("runnerTrace", runnerTraceSummary(message.runnerTrace))
])
}
];
return {
...classification,
rows
};
}
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;
const runtimePath = codeAgentRuntimePathFromMessage(message);
if (
provider === "codex-stdio" &&
longLivedGate?.status === "pass" &&
longLivedGate?.pass === true &&
runtimePath?.kind === "codex-app-server-stdio" &&
runtimePath?.criticalMissing === false &&
runtimePath?.terminalStatusOk === true
) {
return {
kind: "codex-stdio-long-lived",
statusLabel: "真实 runnerCodex app-server stdio 长会话",
tone: "dev-live",
summary: "repo-owned Codex app-server stdio runner 已通过长会话 gateproviderTrace 明确显示 Responses wire API、stdio command 和 terminalStatus。",
fullCodeAgent: true,
codexSessionGatePass: true
};
}
if (isCodexMcpPath({ runnerKind, sessionMode, implementationType, protocol: runtimePath?.protocol })) {
return {
kind: "codex-mcp-or-other-runner",
statusLabel: "MCP/其他 runner:非当前完整 Code Agent",
tone: "source",
summary: "这是 Codex stdio/MCP 或其他 runner 形态;当前完整 Code Agent 口径要求 repo-owned codex app-server --listen stdio:// 和 providerTrace 终态证据。",
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: "一次性 runner:非长会话",
tone: "source",
summary: "回复来自一次性 runner;不可当作可复用或长会话 Code Agent。",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
if (runnerKind === TEXT_FALLBACK_RUNNER || provider === "openai-responses" || capabilityLevel === "text-chat-only") {
return {
kind: "text-chat-only",
statusLabel: "OpenAI fallback:只是文本回答 / text-chat-only",
tone: "source",
summary: "只是文本回答;当前不能执行 workspace、tools、skills 或 Codex session 控制。",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
if (provider === "codex-readonly-runner" || runnerKind === "hwlab-readonly-runner" || sessionMode === READONLY_SESSION_MODE) {
return {
kind: READONLY_SESSION_MODE,
statusLabel: "只读长会话:controlled-readonly-session-registry",
tone: "source",
summary: "只读 session registry 可复用 conversation/session;只能使用 read-only-session-tools,不能写工作区,也不能冒充完整 Codex stdio Code Agent。",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
if (provider === "codex-stdio" || sessionMode === CODEX_APP_SERVER_SESSION_MODE) {
return {
kind: runtimePath?.providerTraceMissing ? "codex-stdio-provider-trace-missing" : "codex-stdio-blocked",
statusLabel: runtimePath?.providerTraceMissing ? "Codex stdio 降级:providerTrace 缺失" : "Codex stdio 受阻:当前不能执行",
tone: "blocked",
summary: runtimePath?.providerTraceMissing
? "Codex stdio 字段存在,但 providerTrace 缺失;界面不会猜测它是完整 app-server runner。"
: "Codex stdio 字段存在,但长会话 gate 或 providerTrace 终态未通过;当前不能标记为完整 runner 控制。",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
return {
kind: "unknown-code-agent-path",
statusLabel: message?.status === "failed" ? "BLOCKED:当前不能执行" : "证据不足:字段缺失",
tone: message?.status === "failed" ? "blocked" : "source",
summary: "payload 缺少足够 provider/backend/runner/session 证据;只展示结构化字段缺失,不假定具备执行能力。",
fullCodeAgent: false,
codexSessionGatePass: false
};
}
export function codeAgentAttributionFromMessage(message) {
if (!message || typeof message !== "object") return null;
if (message.role && message.role !== "agent") return null;
if (!hasCodeAgentFields(message)) return null;
const classification = classifyCodeAgentFacts(message);
const ids = operationAuditEvidenceIds(message);
const runtimePath = codeAgentRuntimePathFromMessage(message);
const runnerKind = message.runner?.kind ?? runnerKindFromMessage(message);
const sessionMode = message.sessionMode ?? message.runner?.sessionMode ?? message.runner?.session;
const capabilityLevel = message.capabilityLevel ?? message.runner?.capabilityLevel;
const fields = [
attributionField("provider", message.provider, { message }),
attributionField("backend", message.backend, { message }),
attributionField("runnerKind", runnerKind, { message }),
attributionField("protocol", runtimePath?.protocol, { message }),
attributionField("implementationType", runtimePath?.implementationType, { message }),
attributionField("providerTrace.command", runtimePath?.command, { message }),
attributionField("providerTrace.terminalStatus", runtimePath?.terminalStatus, { message }),
attributionField("sessionMode", sessionMode, { message }),
attributionField("capabilityLevel", capabilityLevel, { message }),
attributionField("toolCalls", toolCallsAttributionSummary(message.toolCalls), { message, missingWhenNone: false }),
attributionField("traceId", message.traceId ?? message.runnerTrace?.traceId ?? message.providerTrace?.traceId, { message }),
attributionField("operationId", ids.operationId, { message }),
attributionField("audit", ids.auditId, { message }),
attributionField("evidence", ids.evidenceId, { message })
];
const missingFields = fields.filter((field) => field.missing).map((field) => field.key);
return {
kind: classification.kind,
tone: classification.tone,
label: messageAttributionLabel(message, classification),
summary: messageAttributionSummary(message, classification, missingFields, runtimePath),
fields,
missingFields
};
}
export function codeAgentRuntimePathFromMessage(message) {
if (!message || typeof message !== "object") return null;
if (message.role && message.role !== "agent") return null;
if (!hasCodeAgentFields(message)) return null;
const providerTrace = objectOrNull(message.providerTrace);
const runnerTrace = objectOrNull(message.runnerTrace);
const runnerKind = firstText(message.runner?.kind, runnerTrace?.runnerKind, runnerKindFromMessage(message));
const provider = nullableText(message.provider);
const protocol = firstText(providerTrace?.protocol, runnerTrace?.protocol);
const implementationType = firstText(message.implementationType, message.runner?.implementationType, runnerTrace?.implementationType);
const command = firstText(providerTrace?.command);
const terminalStatus = firstText(providerTrace?.terminalStatus);
const wireApi = firstText(providerTrace?.wireApi, isCodexAppServerPath({ provider, runnerKind, protocol, implementationType, command }) ? "responses" : null);
const providerTraceMissing = !providerTrace;
const required = {
provider,
runnerKind,
protocol,
implementationType,
"providerTrace.command": command,
"providerTrace.terminalStatus": terminalStatus
};
const missingFields = Object.entries(required)
.filter(([, value]) => !nullableText(value))
.map(([key]) => key);
const isAppServer = isCodexAppServerPath({ provider, runnerKind, protocol, implementationType, command });
const isMcp = isCodexMcpPath({ runnerKind, sessionMode: message.sessionMode, implementationType, protocol });
const isFallback = provider === "openai-responses" || runnerKind === TEXT_FALLBACK_RUNNER || message.capabilityLevel === "text-chat-only";
const isReadonly = provider === "codex-readonly-runner" || runnerKind === "hwlab-readonly-runner" || message.sessionMode === READONLY_SESSION_MODE;
const terminalStatusOk = terminalStatus === "completed" || terminalStatus === "succeeded";
const criticalMissing = missingFields.length > 0;
const blocked = ["failed", "blocked", "timeout", "error", "canceled"].includes(String(message.status ?? "").toLowerCase());
let kind = "unknown";
let label = "运行路径证据不足";
let summary = "payload 缺少 providerTrace 或 runner 关键字段;界面不会推断完整 Code Agent。";
let tone = blocked ? "blocked" : "warn";
if (isAppServer) {
kind = "codex-app-server-stdio";
if (!providerTrace) {
label = "DEGRADEDproviderTrace 缺失";
summary = "检测到 Codex app-server 形态字段,但 providerTrace 缺失;不能确认 command、protocol 或 terminalStatus。";
} else if (criticalMissing) {
label = "DEGRADED:运行路径字段不完整";
summary = `repo-owned Codex app-server stdio 已暴露部分字段,但仍缺少 ${missingFields.join(", ")}。`;
} else if (!terminalStatusOk) {
label = "BLOCKEDCodex app-server 终态未完成";
summary = `repo-owned Codex app-server stdio 返回 terminalStatus=${terminalStatus};本次不标为完整完成。`;
tone = "blocked";
} else {
label = "repo-owned Codex app-server stdio";
summary = `Codex app-server 通过 stdio JSON-RPC 运行,使用 Responses wire APIwire_api=${wireApi ?? "未观测"}),不是 MCP、fallback 或 Cloud Web 直连 OpenAI。`;
tone = "ok";
}
} else if (isFallback) {
kind = "openai-text-fallback";
label = "DEGRADEDOpenAI text fallback";
summary = "这是 OpenAI text fallback/text-chat-only;没有 workspace runner、Codex app-server session 或工具控制能力。";
tone = blocked ? "blocked" : "warn";
} else if (isMcp) {
kind = "mcp-or-other-runner";
label = "DEGRADEDMCP/其他 runner";
summary = "这是 MCP 或其他 runner 形态,不等同于 repo-owned Codex app-server stdio。";
tone = blocked ? "blocked" : "warn";
} else if (isReadonly) {
kind = "readonly-runner";
label = "DEGRADEDread-only/source runner";
summary = "这是只读或 source runner;不能写 workspace,也不能冒充完整 Code Agent 完成。";
tone = blocked ? "blocked" : "warn";
} else if (providerTraceMissing) {
kind = "provider-trace-missing";
label = blocked ? "BLOCKEDproviderTrace 缺失" : "DEGRADEDproviderTrace 缺失";
summary = "providerTrace 缺失;不会把 fallback/read-only/source 或未知路径显示成完整 Code Agent。";
}
return {
kind,
label,
summary,
tone,
provider,
runnerKind,
protocol,
implementationType,
command,
terminalStatus,
wireApi,
providerTraceMissing,
criticalMissing,
missingFields,
terminalStatusOk,
rows: [
runtimePathRow("provider", provider),
runtimePathRow("runnerKind", runnerKind),
runtimePathRow("protocol", protocol),
runtimePathRow("implementationType", implementationType),
runtimePathRow("providerTrace.command", command),
runtimePathRow("providerTrace.terminalStatus", terminalStatus),
runtimePathRow("wireApi", wireApi)
]
};
}
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),
field("implementationType", runnerTrace.implementationType),
field("protocol", runnerTrace.protocol),
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 ||
message.providerTrace ||
(message.role === "agent" && (message.traceId || message.error || message.status === "failed" || message.status === "running"))
)
);
}
function messageAttributionLabel(message, classification) {
if (message?.status === "failed" || message?.sourceKind === "BLOCKED") return "BLOCKED:当前不能执行";
if (classification.kind === "text-chat-only") return "OpenAI fallback:只是文本回答";
if (classification.kind === READONLY_SESSION_MODE) return "read-only-session-tools:只读长会话";
if (classification.kind === "codex-stdio-long-lived") return "真实 runnerCodex app-server stdio 长会话";
if (classification.kind === "codex-mcp-or-other-runner") return "MCP/其他 runner:非当前完整 Code Agent";
if (classification.kind === "stateless-one-shot") return "一次性 runner:非长会话";
if (classification.kind === "codex-stdio-provider-trace-missing") return "DEGRADEDproviderTrace 缺失";
if (classification.kind === "codex-stdio-blocked") return "Codex stdio 受阻:当前不能执行";
return classification.statusLabel;
}
function messageAttributionSummary(message, classification, missingFields, runtimePath = null) {
const prefix = messageAttributionLabel(message, classification);
const missing = missingFields.length > 0
? `证据不足:${missingFields.join(",")} 字段缺失。`
: "关键归因字段已观测。";
const runtime = runtimePath?.summary ? `运行路径:${runtimePath.summary}` : "";
if (classification.kind === "text-chat-only") {
return `${prefix};只是文本回答,当前不能执行工具或 runner 控制。${missing}${runtime}`;
}
if (message?.status === "failed" || message?.sourceKind === "BLOCKED") {
return `${prefix};本次回复没有执行能力。${missing}${runtime}`;
}
return `${prefix}${missing}${runtime}`;
}
function attributionField(key, rawValue, { message, missingWhenNone = true } = {}) {
const text = nullableText(rawValue);
const noneLike = !text || ["none", "unknown", "null", "undefined"].includes(text.toLowerCase());
const missing = missingWhenNone && noneLike;
const value = missing ? MISSING_FIELD_VALUE : localizedAttributionValue(key, text ?? "none", message);
return {
key,
label: key,
value,
missing,
chip: `${key}=${value}`
};
}
function localizedAttributionValue(key, value, message) {
const normalized = String(value ?? "").trim();
if (key === "provider" && normalized === "openai-responses") return `${normalized} / OpenAI fallback:只是文本回答`;
if (key === "runnerKind" && normalized === CODEX_APP_SERVER_RUNNER) return `${normalized} / repo-owned Codex app-server stdio`;
if (key === "runnerKind" && normalized === CODEX_MCP_RUNNER) return `${normalized} / MCP/其他 runner`;
if (key === "runnerKind" && normalized === TEXT_FALLBACK_RUNNER) return `${normalized} / 不是 runner 控制`;
if (key === "runnerKind" && normalized === "hwlab-readonly-runner") return `${normalized} / 只读 runner`;
if (key === "protocol" && normalized === CODEX_APP_SERVER_PROTOCOL) return `${normalized} / Responses wire API`;
if (key === "implementationType" && normalized === CODEX_APP_SERVER_IMPLEMENTATION) return `${normalized} / repo-owned app-server stdio session`;
if (key === "implementationType" && normalized === CODEX_MCP_IMPLEMENTATION) return `${normalized} / MCP stdio session`;
if (key === "sessionMode" && normalized === READONLY_SESSION_MODE) return `${normalized} / 只读 session registry`;
if (key === "sessionMode" && normalized === CODEX_APP_SERVER_SESSION_MODE) return `${normalized} / app-server stdio 长会话`;
if (key === "sessionMode" && normalized === CODEX_MCP_SESSION_MODE) return `${normalized} / MCP stdio 长会话`;
if (key === "capabilityLevel" && normalized === "text-chat-only") return "text-chat-only / 只是文本回答";
if (key === "capabilityLevel" && normalized === "read-only-session-tools") return `${normalized} / 只读工具`;
if (key === "toolCalls" && normalized === "none") {
return message?.capabilityLevel === "text-chat-only" ? "0/0 / 无工具调用:只是文本回答" : "0/0 / 无工具调用";
}
return normalized;
}
function operationAuditEvidenceIds(message) {
const providerTrace = objectOrNull(message?.providerTrace);
const runnerTrace = objectOrNull(message?.runnerTrace);
const toolCalls = Array.isArray(message?.toolCalls) ? message.toolCalls : [];
const parsedToolPayloads = toolCalls.map((toolCall) => parseToolStdout(toolCall?.stdout)).filter(Boolean);
return {
operationId: firstText(
message?.operationId,
message?.operation?.operationId,
providerTrace?.operationId,
runnerTrace?.operationId,
...toolCalls.map((toolCall) => toolCall?.operationId ?? toolCall?.hwlabApi?.operationId),
...parsedToolPayloads.map((payload) => payload?.operationId ?? payload?.operation?.operationId)
),
auditId: firstText(
message?.auditId,
message?.audit?.auditId,
providerTrace?.auditId,
runnerTrace?.auditId,
...toolCalls.map((toolCall) => toolCall?.auditId ?? toolCall?.audit?.auditId ?? toolCall?.hwlabApi?.auditId),
...parsedToolPayloads.map((payload) => payload?.auditId ?? payload?.audit?.auditId)
),
evidenceId: firstText(
message?.evidenceId,
message?.evidence?.evidenceId,
providerTrace?.evidenceId,
runnerTrace?.evidenceId,
...toolCalls.map((toolCall) => toolCall?.evidenceId ?? toolCall?.evidence?.evidenceId ?? toolCall?.hwlabApi?.evidenceId),
...parsedToolPayloads.map((payload) => payload?.evidenceId ?? payload?.evidence?.evidenceId)
)
};
}
function toolCallsAttributionSummary(toolCalls) {
const summary = toolCallsSummary(toolCalls);
return summary === "none" ? "none" : summary;
}
function objectOrNull(value) {
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
}
function runnerKindFromMessage(message) {
return firstText(
message?.runner?.kind,
message?.runnerTrace?.runnerKind,
message?.provider === "openai-responses" ? TEXT_FALLBACK_RUNNER : null
);
}
function runtimePathRow(label, value) {
const text = nullableText(value);
return {
label,
value: text ?? MISSING_FIELD_VALUE,
missing: !text
};
}
function isCodexAppServerPath({ provider, runnerKind, protocol, implementationType, command }) {
return provider === "codex-stdio" && (
runnerKind === CODEX_APP_SERVER_RUNNER ||
protocol === CODEX_APP_SERVER_PROTOCOL ||
implementationType === CODEX_APP_SERVER_IMPLEMENTATION ||
/\bcodex\s+app-server\s+--listen\s+stdio:\/\//u.test(String(command ?? ""))
);
}
function isCodexMcpPath({ runnerKind, sessionMode, implementationType, protocol }) {
return (
runnerKind === CODEX_MCP_RUNNER ||
sessionMode === CODEX_MCP_SESSION_MODE ||
implementationType === CODEX_MCP_IMPLEMENTATION ||
/mcp/iu.test(String(protocol ?? ""))
);
}
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 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 requiredField(label, value) {
const normalized = nullableText(value);
return `${label}=${normalized ?? MISSING_FIELD_VALUE}`;
}
function valueOrMissing(value) {
return nullableText(value) ?? MISSING_FIELD_VALUE;
}
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();
}