fix: route code agent chat through codex

This commit is contained in:
lyon
2026-05-25 20:14:05 +08:00
parent a713b73d63
commit 303ff8fafd
9 changed files with 279 additions and 4504 deletions
+23 -173
View File
@@ -46,26 +46,26 @@ const codeAgentExternalNetworkPrompt = Object.freeze({
const codeAgentQuickPromptFixtures = Object.freeze([
{
id: "pwd",
label: "Skill CLIpwd",
prompt: "通过 Skill CLI 执行 pwd,列出当前工作目录。",
label: "Codexpwd",
prompt: "交给 Codex 执行 pwd,列出当前工作目录。",
writable: false
},
{
id: "skills",
label: "Skill CLI:列出 skill",
prompt: "通过 Skill CLI 列出你能使用的所有 skill。",
label: "Codex:列出 skill",
prompt: "交给 Codex 列出你能使用的所有 skill。",
writable: false
},
{
id: "do1-true-di1",
label: "HWLAB APIDO1=true 后读 DI1",
prompt: "通过 HWLAB API / Skill CLI 把 res_boxsimu_1 的 DO1 写成 true,然后读取 res_boxsimu_2 的 DI1;我点击发送后才确认执行这次写入请求。",
label: "CodexDO1=true 后读 DI1",
prompt: "交给 Codex 把 res_boxsimu_1 的 DO1 写成 true,然后读取 res_boxsimu_2 的 DI1;我点击发送后才确认执行这次写入请求。",
writable: true
},
{
id: "do1-false-di1",
label: "HWLAB APIDO1=false 复核 DI1",
prompt: "通过 HWLAB API / Skill CLI 把 res_boxsimu_1 的 DO1 写成 false,并复核 res_boxsimu_2 的 DI1;我点击发送后才确认执行这次写入请求。",
label: "CodexDO1=false 复核 DI1",
prompt: "交给 Codex 把 res_boxsimu_1 的 DO1 写成 false,并复核 res_boxsimu_2 的 DI1;我点击发送后才确认执行这次写入请求。",
writable: true
}
]);
@@ -326,10 +326,7 @@ const requiredCodeAgentEvidenceTerms = Object.freeze([
"echo/mock/stub",
"untrusted_completion",
"SOURCE fixture",
"Code Agent SOURCE 回复",
"hwlab-skill-cli",
"hwlab-m3-io-skill-cli",
"message-m3-evidence"
"Code Agent SOURCE 回复"
]);
const blockedCodeAgentUiLabels = Object.freeze([
@@ -616,7 +613,7 @@ function runStaticSmoke() {
evidence: ["command-send", "agent-chat-status", "/v1/agent/chat", "conversationId/messageId/status/timestamps/error.message"]
});
addCheck(checks, blockers, "code-agent-quick-prompts-contract", hasCodeAgentQuickPromptContract(files), "Code Agent quick prompts fill the input with Skill CLI / HWLAB API examples while hardware-write prompts require explicit send confirmation.", {
addCheck(checks, blockers, "code-agent-quick-prompts-contract", hasCodeAgentQuickPromptContract(files), "Code Agent quick prompts fill the input with Codex examples while hardware-write prompts require explicit send confirmation.", {
blocker: "safety_blocker",
evidence: codeAgentQuickPromptFixtures.flatMap((prompt) => [prompt.id, prompt.label, prompt.prompt])
});
@@ -2498,14 +2495,14 @@ function hasCodeAgentQuickPromptContract({ html, app, styles }) {
/我点击发送后才确认执行这次写入请求/u.test(tag)
);
});
const promptCopyUsesApiBoundary = codeAgentQuickPromptFixtures.every((prompt) =>
/通过 (?:Skill CLI|HWLAB API \/ Skill CLI)/u.test(prompt.prompt) &&
!/(?:\bgateway\b|\bbox\b|\bpatch-panel\b|PROD|真实硬件直控)/iu.test(prompt.prompt)
const promptCopyUsesCodexRoute = codeAgentQuickPromptFixtures.every((prompt) =>
/交给 Codex/u.test(prompt.prompt) &&
!/PROD/iu.test(prompt.prompt)
);
return (
labels &&
writeButtonsRequireExplicitSend &&
promptCopyUsesApiBoundary &&
promptCopyUsesCodexRoute &&
/data-agent-quick-prompt/u.test(app) &&
/function\s+fillAgentQuickPrompt\s*\(/u.test(app) &&
/el\.commandInput\.value = prompt/u.test(app) &&
@@ -4623,15 +4620,6 @@ export async function runDevCloudWorkbenchSlowBlockerFixtureSmoke(options = {})
});
}
export async function runDevCloudWorkbenchExternalNetworkFixtureSmoke(options = {}) {
return runLocalAgentFixtureSmoke({
mode: "local-agent-external-network-fixture-browser",
responseDelayMs: options.responseDelayMs ?? localAgentFixtureDelayMs,
agentFixtureMode: "external-network-blocker",
expectTimeout: false
});
}
export async function runDevCloudWorkbenchSessionContinuityFixtureSmoke(options = {}) {
let chromium;
try {
@@ -4891,7 +4879,7 @@ export async function runDevCloudWorkbenchQuickPromptsFixtureSmoke() {
{
id: "quick-prompts-copy-boundary",
status: promptResults.every((result) => result.copyBoundaryOk) ? "pass" : "blocked",
summary: "Quick prompt labels and filled text say HWLAB API / Skill CLI and avoid direct gateway/box/patch-panel or PROD claims.",
summary: "Quick prompt labels and filled text say Codex and avoid PROD claims.",
observations: promptResults.map(({ viewport, id, label, inputValue, copyBoundaryOk, forbiddenHits }) => ({
viewport,
id,
@@ -5068,8 +5056,6 @@ async function runLocalAgentFixtureSmoke({
} else {
const prompts = agentFixtureMode === "slow-blocker"
? [codeAgentE2ePrompts[1]]
: agentFixtureMode === "external-network-blocker"
? [codeAgentExternalNetworkPrompt]
: codeAgentE2ePrompts;
for (const prompt of prompts) {
promptResults.push(await runCodeAgentPromptJourney(page, prompt, { sourceFixture: true }));
@@ -5103,7 +5089,7 @@ async function runLocalAgentFixtureSmoke({
ui.timeoutActionPanelContained &&
!ui.completedMessageVisible
)
: agentFixtureMode === "slow-blocker" || agentFixtureMode === "external-network-blocker"
: agentFixtureMode === "slow-blocker"
? (
promptResults.length === 1 &&
promptResults[0]?.status === "blocked" &&
@@ -5111,9 +5097,7 @@ async function runLocalAgentFixtureSmoke({
promptResults[0]?.legacyWindow?.permanentFailureAround4500ms === false &&
promptResults[0]?.legacyWindow?.pendingChineseVisible === true &&
promptResults[0]?.legacyWindow?.pendingTraceShowsEvents === true &&
(agentFixtureMode === "external-network-blocker"
? ["能力未开放", "Runner 受阻", "服务受阻"].includes(ui.agentChatStatus)
: ui.agentChatStatus === "Runner 受阻") &&
ui.agentChatStatus === "Runner 受阻" &&
ui.failedMessageVisible &&
ui.failedMessageHasTrace &&
ui.retryInputPreserved &&
@@ -5188,7 +5172,7 @@ async function runLocalAgentFixtureSmoke({
status: pass ? "pass" : "blocked",
summary: expectTimeout
? "Local SOURCE fixture proves a bounded Code Agent timeout stays user-visible/BLOCKED in the UI, preserves trace context, and keeps the user's input for retry."
: agentFixtureMode === "slow-blocker" || agentFixtureMode === "external-network-blocker"
: agentFixtureMode === "slow-blocker"
? "Local SOURCE fixture returns a delayed structured blocker and the conversation shows the Chinese reason with trace evidence while preserving the input."
: "Local SOURCE fixture sends input and reaches a user-visible replied state without marking the fixture as DEV-LIVE.",
observations: expectTimeout ? {
@@ -5239,15 +5223,11 @@ async function runLocalAgentFixtureSmoke({
summary: "390x844 mobile view shows Chinese Code Agent pending status, trace/session context, and no pending-card text overflow before the delayed response arrives.",
observations: mobilePending
});
} else if (!expectTimeout && (agentFixtureMode === "slow-blocker" || agentFixtureMode === "external-network-blocker")) {
} else if (!expectTimeout && agentFixtureMode === "slow-blocker") {
checks.push({
id: agentFixtureMode === "external-network-blocker"
? "local-agent-fixture-external-network-blocker"
: "local-agent-fixture-slow-structured-blocker",
id: "local-agent-fixture-slow-structured-blocker",
status: pass ? "pass" : "blocked",
summary: agentFixtureMode === "external-network-blocker"
? "External network blocker for a GitHub prompt displays Runner 受阻 with network trace, keeps the user prompt, and does not restore main evidence/facts noise."
: "Delayed structured blocker arrives after the legacy 4500ms window, displays Runner 受阻 instead of a generic 后端失败 state, keeps trace visible, and leaves the user prompt for continuing the same conversation.",
summary: "Delayed structured blocker arrives after the legacy 4500ms window, displays Runner 受阻 instead of a generic 后端失败 state, keeps trace visible, and leaves the user prompt for continuing the same conversation.",
observations: {
legacyFailureWindowMs,
fixtureDelayMs: responseDelayMs,
@@ -5738,7 +5718,7 @@ async function inspectQuickPromptsViewport(browser, url, viewport) {
const button = document.querySelector(`[data-agent-quick-prompt="${id}"]`);
const input = document.querySelector("#command-input");
const text = `${button?.textContent ?? ""}\n${button?.getAttribute("title") ?? ""}\n${button?.dataset.promptText ?? ""}`;
const forbiddenPattern = /\b(?:PROD|gateway|box|patch-panel|真实硬件直控)\b/iu;
const forbiddenPattern = /\bPROD\b/iu;
const forbiddenHits = [...new Set(text.match(forbiddenPattern) ?? [])];
return {
label: button?.textContent?.replace(/\s+/gu, " ").trim() ?? "",
@@ -5748,7 +5728,7 @@ async function inspectQuickPromptsViewport(browser, url, viewport) {
focused: document.activeElement === input,
explicitSendRequired: button?.dataset.requiresExplicitSend === "true",
copyBoundaryOk:
/(?:HWLAB API|Skill CLI)/u.test(text) &&
/Codex/u.test(text) &&
!forbiddenPattern.test(text) &&
(button?.dataset.requiresExplicitSend !== "true" || /只填充输入框|点击发送后才确认/u.test(text)),
forbiddenHits
@@ -6899,136 +6879,6 @@ async function handleLocalAgentFixtureApi({ request, response, url, options = {}
}));
return true;
}
if (options.agentFixtureMode === "external-network-blocker") {
const blocker = {
code: "external_network_blocked",
category: "capability_unavailable",
layer: "network",
userMessage: "当前 Code Agent 运行策略禁止外部网络访问;未访问外网,也不会伪造成功。",
retryable: false,
traceId,
sessionId: conversationId,
conversationId,
stage: "policy",
elapsedMs: options.agentDelayMs ?? 0,
lastEvent: { seq: 4, traceId, type: "network", stage: "policy", status: "blocked", label: "network:blocked", errorCode: "external_network_blocked" }
};
jsonResponse(response, 200, {
conversationId,
sessionId: conversationId,
messageId,
status: "failed",
sourceKind: "SOURCE",
evidenceLevel: "SOURCE",
createdAt: timestamp,
updatedAt: timestamp,
traceId,
provider: "codex-stdio",
model: "codex-stdio",
backend: "local-source-fixture/codex-stdio-external-network-blocker",
projectId: stringOrFallback(body?.projectId, gateSummary.topology.projectId),
workspace: "/workspace/hwlab",
sandbox: "workspace-write",
runner: {
kind: "codex-mcp-stdio-runner",
writeCapable: true,
codexStdio: true,
longLivedSession: true,
durableSession: true
},
capabilityLevel: "blocked",
sessionMode: "codex-mcp-stdio-long-lived",
session: {
sessionId: conversationId,
conversationId,
status: "idle",
turn: 1,
lastTraceId: traceId
},
sessionReuse: {
conversationId,
sessionId: conversationId,
mapped: true,
reused: true,
turn: 1,
status: "idle"
},
error: {
code: "external_network_blocked",
category: "capability_unavailable",
layer: "network",
message: "SOURCE external network blocker: runtime policy disabled external network checks.",
userMessage: blocker.userMessage,
retryable: false,
blocker
},
blocker,
blockers: [blocker],
toolCalls: [
{
name: "external.network.check",
type: "network-check",
status: "blocked",
exitCode: 2,
traceId,
stderrSummary: "external_network_blocked",
targetUrl: "https://github.com/",
blocker
}
],
skills: {
status: "not_requested",
items: [],
count: 0,
totalCount: 0,
blockers: []
},
runnerTrace: {
traceId,
runnerKind: "codex-mcp-stdio-runner",
sessionMode: "codex-mcp-stdio-long-lived",
sessionId: conversationId,
sessionStatus: "idle",
status: "blocked",
elapsedMs: options.agentDelayMs ?? 0,
waitingFor: "network-policy",
events: [
{ seq: 1, traceId, type: "session", stage: "created", status: "completed", label: "session:created" },
{ seq: 2, traceId, type: "prompt", stage: "sent", status: "completed", label: "prompt:sent", toolName: "external.network.check" },
{ seq: 3, traceId, type: "network", stage: "policy", status: "started", label: "network:started", toolName: "external.network.check" },
blocker.lastEvent,
{ seq: 5, traceId, type: "tool_call", stage: "tool_call", status: "blocked", label: "tool:external.network.check:blocked", toolName: "external.network.check", errorCode: "external_network_blocked" }
],
lastEvent: { seq: 5, traceId, type: "tool_call", stage: "tool_call", status: "blocked", label: "tool:external.network.check:blocked", toolName: "external.network.check", errorCode: "external_network_blocked" }
},
conversationFacts: {
conversationId,
sessionId: conversationId,
sessionStatus: "idle",
sessionMode: "codex-mcp-stdio-long-lived",
capabilityLevel: "blocked",
runnerKind: "codex-mcp-stdio-runner",
workspace: "/workspace/hwlab",
sandbox: "workspace-write",
turnCount: 1,
latestTraceId: traceId,
traceIds: [traceId],
latestSkills: null,
recentToolCalls: [{ name: "external.network.check", status: "blocked" }],
facts: [],
valuesRedacted: true,
secretMaterialStored: false
},
providerTrace: {
source: "SOURCE-local-browser-fixture",
sourceKind: "SOURCE",
transport: "stdio+controlled-network",
responseId: "rsp_source_fixture_external_network_blocker",
redacted: true
}
});
return true;
}
if (options.agentFixtureMode === "slow-blocker") {
jsonResponse(response, 200, {
conversationId,