diff --git a/docs/reference/code-agent-chat-readiness.md b/docs/reference/code-agent-chat-readiness.md index 3e3124bb..66a6c392 100644 --- a/docs/reference/code-agent-chat-readiness.md +++ b/docs/reference/code-agent-chat-readiness.md @@ -98,6 +98,13 @@ PS1 PowerShell 默认使用 wrapper 的 `-EncodedCommand` 路径;不要手写 `cmd /c powershell ...` 的管道、引号或中文转义。简单 `cmd` 命令仍可用 `--command "cmd /d /s /c ..."`,但涉及目录枚举、Unicode、管道、排序或 JSON 输出时优先用 `--powershell-stdin`。 +Windows 侧 skill、编译器、脚本工具和多参数命令都应走同一个通用传输模式,不新增某个工具的专用 wrapper 子命令: + +- 工作目录优先用 wrapper 的 `--cwd ` 或脚本内 `Set-Location -LiteralPath ` 表达,不要在 prompt 里拼 `cmd /c "cd ... && ..."`。 +- 命令参数在 PowerShell 脚本里用数组或显式变量传递,例如 `$args = @("subcommand", "-p", $projectPath)` 后 `& $exe @args`,避免让模型手动嵌套引号。 +- 对所有 Windows skill 都先复用 `C:\Users\liang\.agents\skills\\SKILL.md` 和该 skill 自带 CLI;Code Agent 只负责通过 repo wrapper 调用,不把 skill 行为复制到 cloud-api 或 wrapper。 +- 一次探测命令失败后,只做一次更窄、更结构化的修正;若仍失败,返回失败 `operationId`、stderr 摘要和下一步,而不是连续试错。 + Windows 文件系统探测必须是有界小输出: - 对 `F:\work`、`F:\work\ConStart` 或同类目录先做顶层目录/项目标记探测,不要读取 Secret、env、kubeconfig、DB URL 或完整源码内容。 diff --git a/internal/cloud/codex-stdio-session.mjs b/internal/cloud/codex-stdio-session.mjs index 4813f2f3..db06fd5a 100644 --- a/internal/cloud/codex-stdio-session.mjs +++ b/internal/cloud/codex-stdio-session.mjs @@ -95,10 +95,11 @@ const CODEX_STDIO_BOUNDARY_INSTRUCTIONS = [ "Do not read or print secrets, tokens, kubeconfig files, DB URLs, private keys, or raw environment values.", "Do not call gateway, box-simu, or patch-panel directly.", "Hardware control requests must go through cloud-api/HWLAB API/skill CLI controlled paths.", - "For registered PC gateway Windows cmd or Keil requests, invoke the repo-owned wrapper with the Codex exec tool: node /app/tools/hwlab-gateway-shell.mjs --json --timeout-ms --command \"cmd /c ...\".", - "For PowerShell through the PC gateway, do not hand-escape cmd pipes or quotes; use node /app/tools/hwlab-gateway-shell.mjs --json --timeout-ms --powershell-stdin with a single-quoted heredoc. The wrapper sends powershell.exe -EncodedCommand for Unicode-safe execution.", + "For registered PC gateway Windows command or skill requests, invoke the repo-owned wrapper with the Codex exec tool: node /app/tools/hwlab-gateway-shell.mjs --json --timeout-ms --powershell-stdin. Put scripts on stdin and set working directory with wrapper --cwd or PowerShell Set-Location -LiteralPath; do not hand-build compound cmd /c, cd &&, pipes, or nested quotes.", + "For PowerShell through the PC gateway, do not hand-escape cmd pipes or quotes; use a single-quoted heredoc with --powershell-stdin. The wrapper sends powershell.exe -EncodedCommand for Unicode-safe execution.", "For Windows filesystem inventory, start with one small bounded PowerShell stdin script: use -LiteralPath, Select-Object -First, ConvertTo-Json -Compress, and keep stdout under about 12 KB. Do not dump full directory JSON and then retry.", - "For F:\\work or F:\\work\\ConStart discovery, first list top-level project markers and *.uvprojx candidates with bounded output. For Keil build/program requests, use the Windows skill CLI at C:\\Users\\liang\\.agents\\skills\\keil with py -3 keil-cli.py; do not reimplement Keil build logic.", + "For Windows-side skills under C:\\Users\\liang\\.agents\\skills\\, read the skill manifest when needed and call its CLI from a PowerShell stdin script using explicit paths or argument arrays. Do not reimplement skill logic, and do not use shell working-directory tricks like cd &&.", + "For F:\\work or F:\\work\\ConStart discovery, first list top-level project markers and *.uvprojx candidates with bounded output. For Keil build/program requests, use the Windows skill CLI at C:\\Users\\liang\\.agents\\skills\\keil with py -3 keil-cli.py from the generic PowerShell stdin wrapper path; do not reimplement Keil build logic.", "If a Windows gateway command reaches the gateway but fails due script syntax or output size, simplify once and report the failed operationId plus the corrected bounded command evidence. Do not spend multiple turns on exploratory rewrites.", "Use the Windows-side skill CLIs under C:\\Users\\liang\\.agents\\skills\\ from that cmd command when they exist; do not reimplement those tools in the prompt.", "Do not satisfy PC gateway requests by string matching, assistant-only claims, internal shortcut dispatch, or direct gateway URLs; the Codex turn must run the wrapper and report command evidence.", @@ -1225,9 +1226,11 @@ function tomlString(value) { export function createCodexAppServerJsonLineClient({ command = DEFAULT_CODEX_STDIO_COMMAND, args = null, env = process.env, cwd = repoRoot, onNotification = null } = {}) { const spawnArgs = Array.isArray(args) ? args : codexAppServerArgs(env); + const childDetached = process.platform !== "win32"; const child = spawn(command, spawnArgs, { cwd, env, + detached: childDetached, stdio: ["pipe", "pipe", "pipe"] }); let nextId = 1; @@ -1402,7 +1405,7 @@ export function createCodexAppServerJsonLineClient({ command = DEFAULT_CODEX_STD function close() { if (closed) return; - child.kill("SIGTERM"); + terminateChildProcess(child, { detached: childDetached }); } return { @@ -1416,6 +1419,35 @@ export function createCodexAppServerJsonLineClient({ command = DEFAULT_CODEX_STD }; } +function terminateChildProcess(child, { detached = false } = {}) { + if (!child || child.killed) return; + try { + if (detached && Number.isInteger(child.pid)) { + process.kill(-child.pid, "SIGTERM"); + } else { + child.kill("SIGTERM"); + } + } catch { + try { + child.kill("SIGTERM"); + } catch { + return; + } + } + setTimeout(() => { + if (child.killed) return; + try { + if (detached && Number.isInteger(child.pid)) { + process.kill(-child.pid, "SIGKILL"); + } else { + child.kill("SIGKILL"); + } + } catch { + // Process already exited or the runtime does not allow group signaling. + } + }, 2000).unref?.(); +} + async function runAppServerTurn({ client, session, diff --git a/internal/cloud/server.mjs b/internal/cloud/server.mjs index b6bbe945..f0b79e13 100644 --- a/internal/cloud/server.mjs +++ b/internal/cloud/server.mjs @@ -1337,6 +1337,7 @@ function submitCodeAgentChatTurn({ params, options, traceId }) { const run = async () => { try { const payload = await runCodeAgentChat(params, options); + if (isCodeAgentResultCanceled(results.get(traceId))) return; results.set(traceId, payload); traceStore.append(traceId, { type: "result", @@ -1350,6 +1351,7 @@ function submitCodeAgentChatTurn({ params, options, traceId }) { terminal: true }); } catch (error) { + if (isCodeAgentResultCanceled(results.get(traceId))) return; const payload = { status: "failed", traceId, @@ -1575,7 +1577,7 @@ async function handleCodeAgentCancelHttp(request, response, options) { ...runnerTraceSnapshot, sessionLifecycleStatus: sessionSummary.status }; - sendJson(response, 200, { + const payload = { accepted: true, canceled: true, status: "canceled", @@ -1589,8 +1591,26 @@ async function handleCodeAgentCancelHttp(request, response, options) { runnerTrace, lastTraceEvent: runnerTrace.lastEvent, retryable: true, - userMessage: "当前 Codex stdio 请求已取消;输入、sessionId、traceId 和最后 trace event 已保留,可重试上一条消息。" - }); + error: { + code: "codex_stdio_canceled", + layer: "session", + category: "canceled", + retryable: true, + message: "user canceled current Code Agent request", + userMessage: "当前 Codex stdio 请求已取消;输入、sessionId、traceId 和最后 trace event 已保留,可重试上一条消息。", + traceId, + route: "/v1/agent/chat/cancel", + toolName: "codex-stdio.cancel" + }, + userMessage: "当前 Codex stdio 请求已取消;输入、sessionId、traceId 和最后 trace event 已保留,可重试上一条消息。", + updatedAt: new Date().toISOString() + }; + options.codeAgentChatResults?.set(traceId, payload); + sendJson(response, 200, payload); +} + +function isCodeAgentResultCanceled(result) { + return result?.status === "canceled" || result?.canceled === true || result?.error?.code === "codex_stdio_canceled"; } function cancelBlockedPayload({ diff --git a/internal/dev-entrypoint/cloud-web-routes.mjs b/internal/dev-entrypoint/cloud-web-routes.mjs index 61568031..05d7c04e 100644 --- a/internal/dev-entrypoint/cloud-web-routes.mjs +++ b/internal/dev-entrypoint/cloud-web-routes.mjs @@ -7,7 +7,8 @@ const POST_PROXY_ROUTES = new Set([ "/v1/m3/io" ]); const PUBLIC_PROXY_ROUTES = new Set([ - "POST /v1/agent/chat" + "POST /v1/agent/chat", + "POST /v1/agent/chat/cancel" ]); export function cloudWebProxyRoutePolicy(method, pathname) { diff --git a/internal/dev-entrypoint/http.test.mjs b/internal/dev-entrypoint/http.test.mjs index 8c04bc11..3b60eff9 100644 --- a/internal/dev-entrypoint/http.test.mjs +++ b/internal/dev-entrypoint/http.test.mjs @@ -14,8 +14,8 @@ test("cloud web route policy proxies public Code Agent chat without gating other }); assert.deepEqual(cloudWebProxyRoutePolicy("POST", "/v1/agent/chat/cancel"), { proxy: true, - authRequired: true, - publicRoute: false, + authRequired: false, + publicRoute: true, routeKey: "POST /v1/agent/chat/cancel" }); assert.deepEqual(cloudWebProxyRoutePolicy("POST", "/v1/m3/io"), { diff --git a/tools/hwlab-gateway-shell.mjs b/tools/hwlab-gateway-shell.mjs index 45234f97..1d390335 100644 --- a/tools/hwlab-gateway-shell.mjs +++ b/tools/hwlab-gateway-shell.mjs @@ -99,6 +99,7 @@ function printHelp() { " --powershell-file PATH Read a local PS1 file, encode it, and run through the gateway", " --powershell-stdin Read PS1 from stdin, encode it, and run through the gateway", " --powershell-exe NAME Default powershell.exe", + " --cwd WINDOWS_PATH Gateway-side working directory; prefer this over shell-level cd &&", " --timeout-ms N Short dispatch timeout in milliseconds", " --project-id ID Default prj_mvp_topology", " --gateway-session-id ID Default gws_DESKTOP-1MHOD9I", diff --git a/web/hwlab-cloud-web/app.mjs b/web/hwlab-cloud-web/app.mjs index 27ee5839..d5000d79 100644 --- a/web/hwlab-cloud-web/app.mjs +++ b/web/hwlab-cloud-web/app.mjs @@ -20,13 +20,14 @@ import { const DEFAULT_API_TIMEOUT_MS = 4500; const DEFAULT_CODE_AGENT_TIMEOUT_MS = 600000; const DEFAULT_CODE_AGENT_SUBMIT_TIMEOUT_MS = 60000; +const DEFAULT_CODE_AGENT_CANCEL_TIMEOUT_MS = 30000; const CODE_AGENT_TIMEOUT_STORAGE_KEY = "hwlab.workbench.codeAgentTimeoutMs.v1"; const TRACE_STREAM_FALLBACK_MS = 2500; const TRACE_POLL_INTERVAL_MS = 1000; const API_TIMEOUT_MS = resolveTimeoutMs("apiTimeoutMs", DEFAULT_API_TIMEOUT_MS, { min: 500, max: 30000 }); let CODE_AGENT_TIMEOUT_MS = resolveUserCodeAgentTimeoutMs(); const CODE_AGENT_SUBMIT_TIMEOUT_MS = resolveTimeoutMs("codeAgentSubmitTimeoutMs", DEFAULT_CODE_AGENT_SUBMIT_TIMEOUT_MS, { min: 5000, max: 120000 }); -const CODE_AGENT_TRACE_PREVIEW_LIMIT = 14; +const CODE_AGENT_CANCEL_TIMEOUT_MS = resolveTimeoutMs("codeAgentCancelTimeoutMs", DEFAULT_CODE_AGENT_CANCEL_TIMEOUT_MS, { min: 5000, max: 120000 }); const rpcReadMethods = Object.freeze([ "system.health", "cloud.adapter.describe", @@ -174,6 +175,7 @@ const state = { codeAgentAvailability: null, chatMessages: [], traceStreams: new Map(), + traceDetailsOpen: new Map(), canceledTraces: new Set(), currentRequest: null, sessionStatus: null, @@ -760,6 +762,7 @@ function initCommandBar() { el.commandClear.addEventListener("click", () => { for (const close of state.traceStreams.values()) close(); state.traceStreams.clear(); + state.traceDetailsOpen.clear(); state.chatMessages = []; state.conversationId = null; state.sessionId = null; @@ -3692,6 +3695,7 @@ async function cancelAgentMessage(messageId) { const runnerTrace = payload.runnerTrace ?? latestTraceSnapshot(traceId); if (response.ok && payload.canceled === true) { state.canceledTraces.add(traceId); + stopRunningTraceStream(traceId); state.chatPending = false; state.currentRequest = null; state.sessionId = payload.sessionId || sessionId || state.sessionId; @@ -3743,14 +3747,23 @@ async function cancelAgentMessage(messageId) { renderRecords(state.liveSurface); } +function stopRunningTraceStream(traceId) { + const id = nonEmptyString(traceId); + if (!id) return; + const close = state.traceStreams.get(id); + if (typeof close === "function") close(); + state.traceStreams.delete(id); +} + async function cancelAgentRequest({ traceId, conversationId, sessionId, threadId }) { return fetchJson("/v1/agent/chat/cancel", { method: "POST", headers: { "Content-Type": "application/json", - "X-Trace-Id": traceId + "X-Trace-Id": traceId, + "Prefer": "respond-async" }, - timeoutMs: Math.min(API_TIMEOUT_MS, 5000), + timeoutMs: CODE_AGENT_CANCEL_TIMEOUT_MS, timeoutName: "Code Agent cancel", body: JSON.stringify({ traceId, @@ -4011,7 +4024,14 @@ function messageTracePanel(message) { if (!trace && !message.traceId) return null; const details = document.createElement("details"); details.className = "message-trace"; - details.open = ["running", "completed", "source", "failed", "timeout", "canceled", "error"].includes(message.status); + const traceUiKey = messageTraceUiKey(message); + const storedOpen = traceUiKey ? state.traceDetailsOpen.get(traceUiKey) : undefined; + details.open = typeof storedOpen === "boolean" ? storedOpen : defaultTraceDetailsOpen(message); + if (traceUiKey) { + details.addEventListener("toggle", () => { + state.traceDetailsOpen.set(traceUiKey, details.open); + }); + } const summary = document.createElement("summary"); summary.className = "message-meta"; summary.textContent = runnerTraceHeadline(message, trace); @@ -4024,9 +4044,9 @@ function messageTracePanel(message) { item.textContent = `trace=${message.traceId ?? "pending"};等待后端事件。`; list.append(item); } else { - const toolbar = messageTraceToolbar(message, trace, events, rows, list); - list.dataset.traceMode = rows.length > CODE_AGENT_TRACE_PREVIEW_LIMIT ? "tail" : "all"; - renderTraceEventList(list, tracePreviewRows(rows)); + const toolbar = messageTraceToolbar(message, trace, events, rows); + list.dataset.traceMode = "all"; + renderTraceEventList(list, rows); details.append(summary, toolbar, list); return details; } @@ -4034,35 +4054,29 @@ function messageTracePanel(message) { return details; } -function messageTraceToolbar(message, trace, events, rows, list) { +function messageTraceUiKey(message) { + const traceId = nonEmptyString(message.traceId); + const messageId = nonEmptyString(message.id ?? message.messageId); + if (!traceId && !messageId) return null; + return `${messageId ?? "message"}:${traceId ?? "trace"}`; +} + +function defaultTraceDetailsOpen(message) { + return ["running", "completed", "source", "failed", "timeout", "canceled", "error"].includes(message.status); +} + +function messageTraceToolbar(message, trace, events, rows) { const toolbar = document.createElement("div"); toolbar.className = "message-trace-toolbar"; - const count = textSpan(messageTraceCountText(events.length, rows.length, false), "message-trace-count"); + const count = textSpan(messageTraceCountText(events.length, rows.length), "message-trace-count"); toolbar.append(count); - if (rows.length > CODE_AGENT_TRACE_PREVIEW_LIMIT) { - let expanded = false; - const toggle = traceActionButton("展开全部", () => { - expanded = !expanded; - renderTraceEventList(list, expanded ? rows : tracePreviewRows(rows)); - list.classList.toggle("message-trace-events-full", expanded); - list.dataset.traceMode = expanded ? "all" : "tail"; - count.textContent = messageTraceCountText(events.length, rows.length, expanded); - toggle.textContent = expanded ? "收起为最近 14" : "展开全部"; - }, "在面板内查看完整 trace 时间线"); - toolbar.append(toggle); - } toolbar.append(traceActionButton("复制 JSON", () => copyTextToClipboard(messageTraceJson(message, trace, events)), "复制完整 trace JSON")); toolbar.append(traceActionButton("下载 trace", () => downloadTraceJson(message, trace, events), "下载完整 trace JSON 文件")); return toolbar; } -function tracePreviewRows(rows) { - return rows.length > CODE_AGENT_TRACE_PREVIEW_LIMIT ? rows.slice(-CODE_AGENT_TRACE_PREVIEW_LIMIT) : rows; -} - -function messageTraceCountText(rawTotal, displayTotal, expanded) { - if (displayTotal <= CODE_AGENT_TRACE_PREVIEW_LIMIT || expanded) return `显示全部 ${displayTotal} / 原始 ${rawTotal}`; - return `显示最近 ${CODE_AGENT_TRACE_PREVIEW_LIMIT} / 可读 ${displayTotal} / 原始 ${rawTotal}`; +function messageTraceCountText(rawTotal, displayTotal) { + return `显示全部 ${displayTotal} / 原始 ${rawTotal}`; } function renderTraceEventList(list, rows) { diff --git a/web/hwlab-cloud-web/scripts/check.mjs b/web/hwlab-cloud-web/scripts/check.mjs index 4aa861e1..756308d2 100644 --- a/web/hwlab-cloud-web/scripts/check.mjs +++ b/web/hwlab-cloud-web/scripts/check.mjs @@ -66,6 +66,7 @@ const markedLicense = fs.readFileSync(path.resolve(rootDir, "third_party/marked/ const artifactPublisher = fs.readFileSync(path.resolve(repoRoot, "scripts/dev-artifact-publish.mjs"), "utf8"); const cloudWebRoutes = fs.readFileSync(path.resolve(repoRoot, "internal/dev-entrypoint/cloud-web-routes.mjs"), "utf8"); const cloudWebProxy = fs.readFileSync(path.resolve(repoRoot, "internal/dev-entrypoint/cloud-web-proxy.mjs"), "utf8"); +const cloudApiServer = fs.readFileSync(path.resolve(repoRoot, "internal/cloud/server.mjs"), "utf8"); const codexStdioSession = fs.readFileSync(path.resolve(repoRoot, "internal/cloud/codex-stdio-session.mjs"), "utf8"); const gatewayShellTool = fs.readFileSync(path.resolve(repoRoot, "tools/hwlab-gateway-shell.mjs"), "utf8"); const frontendSource = `${html}\n${auth}\n${app}\n${artifactPublisher}`; @@ -790,14 +791,27 @@ assert.match(app, /codexStdioFeasibility:\s*result\.codexStdioFeasibility/); assert.match(app, /longLivedSessionGate:\s*result\.longLivedSessionGate/); assert.match(app, /conversationFacts:\s*result\.conversationFacts/); assert.match(app, /"Prefer":\s*"respond-async"/); +assert.match(app, /DEFAULT_CODE_AGENT_CANCEL_TIMEOUT_MS\s*=\s*30000/); +assert.match(app, /CODE_AGENT_CANCEL_TIMEOUT_MS/); +assert.match(functionBody(app, "cancelAgentRequest"), /timeoutMs:\s*CODE_AGENT_CANCEL_TIMEOUT_MS/); +assert.match(app, /function stopRunningTraceStream/); +assert.match(functionBody(app, "cancelAgentMessage"), /stopRunningTraceStream\(traceId\)/); assert.match(app, /function waitForAgentMessageResult/); assert.match(app, /\/v1\/agent\/chat\/result\/\$\{encodeURIComponent\(traceId\)\}/); assert.match(app, /function messageTracePanel/); -assert.match(app, /CODE_AGENT_TRACE_PREVIEW_LIMIT\s*=\s*14/); assert.match(app, /function messageTraceToolbar/); assert.match(app, /function messageTraceCountText/); -assert.match(app, /显示最近\s+\$\{CODE_AGENT_TRACE_PREVIEW_LIMIT\}\s+\/\s+可读\s+\$\{displayTotal\}\s+\/\s+原始\s+\$\{rawTotal\}/); -assert.match(app, /展开全部/); +assert.match(app, /显示全部\s+\$\{displayTotal\}\s+\/\s+原始\s+\$\{rawTotal\}/); +assert.match(app, /traceDetailsOpen:\s*new Map\(\)/); +assert.match(app, /state\.traceDetailsOpen\.clear\(\)/); +assert.match(app, /function messageTraceUiKey/); +assert.match(app, /function defaultTraceDetailsOpen/); +assert.match(functionBody(app, "messageTracePanel"), /state\.traceDetailsOpen\.get\(traceUiKey\)/); +assert.match(functionBody(app, "messageTracePanel"), /defaultTraceDetailsOpen\(message\)/); +assert.match(functionBody(app, "messageTracePanel"), /state\.traceDetailsOpen\.set\(traceUiKey,\s*details\.open\)/); +assert.match(functionBody(app, "messageTracePanel"), /list\.dataset\.traceMode\s*=\s*"all"/); +assert.match(functionBody(app, "messageTracePanel"), /renderTraceEventList\(list,\s*rows\)/); +assert.doesNotMatch(app, /CODE_AGENT_TRACE_PREVIEW_LIMIT|tracePreviewRows|收起为最近|展开全部|data-trace-mode="tail"/); assert.match(app, /复制 JSON/); assert.match(app, /下载 trace/); assert.match(app, /function renderTraceEventList/); @@ -813,7 +827,7 @@ assert.match(app, /function downloadTraceJson/); assert.match(styles, /\.message-trace-toolbar\s*\{/); assert.match(styles, /\.message-trace-count\s*\{/); assert.match(styles, /\.message-trace-action\s*\{/); -assert.match(styles, /\.message-trace-events-full\s*\{[\s\S]*?max-height:\s*min\(360px,\s*42vh\)[\s\S]*?overflow:\s*auto/); +assert.doesNotMatch(styles, /\.message-trace-events-full/); assert.match(app, /function messageRuntimePathPanel/); assert.match(app, /codeAgentRuntimePathFromMessage/); assert.match(app, /function runnerTraceHeadline/); @@ -1063,6 +1077,7 @@ assert.match(cloudWebRoutes, /"\/v1\/agent\/chat"/); assert.match(cloudWebRoutes, /"\/v1\/agent\/chat\/cancel"/); assert.match(cloudWebRoutes, /"\/v1\/m3\/io"/); assert.match(cloudWebRoutes, /"POST \/v1\/agent\/chat"/); +assert.match(cloudWebRoutes, /"POST \/v1\/agent\/chat\/cancel"/); assert.match(cloudWebRoutes, /isPublicCodeAgentPollRoute/); assert.match(cloudWebRoutes, /\/v1\/agent\/chat\/result\//); assert.match(cloudWebRoutes, /\/v1\/agent\/chat\/trace\//); @@ -1079,6 +1094,8 @@ assert.match(codexStdioSession, /\["assistant-message", "turn\/completed"\]\.inc assert.match(codexStdioSession, /does not reset the backend idle timer/); assert.match(codexStdioSession, /--powershell-stdin/); assert.match(codexStdioSession, /-EncodedCommand/); +assert.match(codexStdioSession, /do not hand-build compound cmd \/c, cd &&, pipes, or nested quotes/); +assert.match(codexStdioSession, /Set-Location -LiteralPath/); assert.match(codexStdioSession, /Windows filesystem inventory/); assert.match(codexStdioSession, /Select-Object -First/); assert.match(codexStdioSession, /ConvertTo-Json -Compress/); @@ -1086,7 +1103,16 @@ assert.match(codexStdioSession, /C:\\\\Users\\\\liang\\\\\.agents\\\\skills\\\\k assert.match(gatewayShellTool, /function powershellEncodedCommand/); assert.match(gatewayShellTool, /Buffer\.from\(`\$\{prologue\}\$\{source\}`,\s*"utf16le"\)\.toString\("base64"\)/); assert.match(gatewayShellTool, /--powershell-stdin/); +assert.match(gatewayShellTool, /--cwd WINDOWS_PATH/); assert.match(gatewayShellTool, /powershell\.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand/); +assert.doesNotMatch(gatewayShellTool, /keil-find|keil-build|keil-job-status/); +assert.match(codexStdioSession, /detached:\s*childDetached/); +assert.match(codexStdioSession, /function terminateChildProcess/); +assert.match(codexStdioSession, /process\.kill\(-child\.pid,\s*"SIGTERM"\)/); +assert.match(codexStdioSession, /process\.kill\(-child\.pid,\s*"SIGKILL"\)/); +assert.match(cloudApiServer, /function isCodeAgentResultCanceled/); +assert.match(cloudApiServer, /options\.codeAgentChatResults\?\.set\(traceId,\s*payload\)/); +assert.match(cloudApiServer, /if \(isCodeAgentResultCanceled\(results\.get\(traceId\)\)\) return;/); assert.match(artifactPublisher, /"system\.health"/); assert.match(artifactPublisher, /"cloud\.adapter\.describe"/); assert.match(artifactPublisher, /"audit\.event\.query"/); diff --git a/web/hwlab-cloud-web/styles.css b/web/hwlab-cloud-web/styles.css index 779a1bee..0a451060 100644 --- a/web/hwlab-cloud-web/styles.css +++ b/web/hwlab-cloud-web/styles.css @@ -1416,13 +1416,6 @@ h3 { list-style: none; } -.message-trace-events-full { - max-height: min(360px, 42vh); - overflow: auto; - overscroll-behavior: contain; - padding-right: 4px; -} - .message-trace-row { min-width: 0; display: grid;