import { randomUUID } from "node:crypto"; import { CLOUD_API_SERVICE_ID } from "../audit/index.mjs"; export const HARDWARE_INVOKE_SHELL_METHOD = "hardware.invoke.shell"; export const CODE_AGENT_HARDWARE_PROVIDER = "hwlab-hardware-capability"; export const CODE_AGENT_HARDWARE_MODEL = "controlled-hardware-capability"; export const CODE_AGENT_HARDWARE_BACKEND = "hwlab-cloud-api/hardware.invoke.shell"; export const CODE_AGENT_HARDWARE_RUNNER_KIND = "hwlab-hardware-capability-runner"; export const CODE_AGENT_HARDWARE_SANDBOX = "cloud-api-capability-route-only"; export const CODE_AGENT_HARDWARE_SESSION_MODE = "controlled-hardware-capability-route"; export const CODE_AGENT_HARDWARE_IMPLEMENTATION_TYPE = "cloud-api-hardware-capability-runner"; export const CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED = "pc-gateway-shell-executed"; export const CODE_AGENT_HARDWARE_CAPABILITY_BLOCKED = "blocked"; const OUTPUT_LIMIT = 4000; const COMMAND_SUMMARY_LIMIT = 320; const DEFAULT_DEV_MVP_PC_GATEWAY_TOPOLOGY = Object.freeze([Object.freeze({ projectId: "prj_mvp_topology", gatewaySessionId: "gws_DESKTOP-1MHOD9I", resourceId: "res_windows_host", capabilityId: "cap_windows_cmd_exec", capabilityName: "shell.exec", gatewayLabel: "DESKTOP-1MHOD9I", topologySource: "dev-mvp-pc-gateway-topology" })]); const HARDWARE_LIMITATION_FLAGS = Object.freeze([ "cloud-api-hardware-capability-only", "no-direct-gateway-link", "no-direct-box-link", "no-direct-patch-panel-link", "no-openai-text-fallback", "registered-capability-required" ]); export function detectHardwareCapabilityIntent(message, { params = {}, env = process.env, topology } = {}) { const text = String(message ?? "").trim(); if (!mentionsPcGatewayShellCapability(text, params)) return null; const command = normalizeWindowsCommand( params.input?.command ?? params.command ?? extractWindowsCommand(text) ); const explicit = { projectId: cleanProtocolId(params.projectId, "prj") ?? extractProtocolField(text, "projectId", "prj"), gatewaySessionId: cleanProtocolId(params.gatewaySessionId, "gws") ?? extractProtocolField(text, "gatewaySessionId", "gws") ?? extractProtocolField(text, "session", "gws"), resourceId: cleanProtocolId(params.resourceId, "res") ?? extractProtocolField(text, "resourceId", "res"), capabilityId: cleanProtocolId(params.capabilityId, "cap") ?? extractProtocolField(text, "capabilityId", "cap") }; const resolution = resolveHardwareTopology(explicit, { topology, env }); if (resolution.blocker) { return { kind: "hardware_shell", toolName: HARDWARE_INVOKE_SHELL_METHOD, action: "shell.exec", command, request: null, topology: resolution, blocker: resolution.blocker }; } if (!command) { return { kind: "hardware_shell", toolName: HARDWARE_INVOKE_SHELL_METHOD, action: "shell.exec", request: resolution.request, topology: resolution, blocker: hardwareBlocker({ code: "hardware_command_missing", layer: "intent", category: "needs_command", summary: "PC gateway shell.exec request did not include a Windows cmd command.", userMessage: "PC gateway shell.exec 请求缺少 Windows cmd 命令;请明确给出 cmd /c ...。", retryable: false }) }; } if (!isAllowedWindowsCmd(command)) { return { kind: "hardware_shell", toolName: HARDWARE_INVOKE_SHELL_METHOD, action: "shell.exec", command, request: resolution.request, topology: resolution, blocker: hardwareBlocker({ code: "hardware_command_unsupported", layer: "security", category: "security_boundary", summary: "Only explicit Windows cmd /c commands are allowed for the registered PC gateway shell.exec capability.", userMessage: "该能力只允许已登记 PC gateway 的 Windows cmd /c 命令;不会执行 bash、sh、kubectl、PowerShell 或任意 shell。", retryable: false }) }; } return { kind: "hardware_shell", toolName: HARDWARE_INVOKE_SHELL_METHOD, action: "shell.exec", command, request: resolution.request, topology: resolution }; } export async function callCodeAgentHardwareRunner({ intent, conversationId, sessionId, traceId, now, workspace, sessionRegistry, gatewayRegistry, invokeHardwareShellRpc, traceRecorder } = {}) { const startedAt = nowIso(now); const resolvedWorkspace = workspace ?? process.cwd(); const request = intent?.request ?? null; const requestId = `req_code_agent_hw_shell_${randomUUID()}`; const actorId = "usr_code_agent"; const acquire = acquireHardwareSession(sessionRegistry, { conversationId, sessionId, traceId, workspace: resolvedWorkspace, now }); let session = acquire.session; const preflightBlocker = intent?.blocker ?? acquire.blocker ?? hardwareRegistryBlocker({ request, gatewayRegistry, traceId }); traceRecorder?.append({ type: "tool_call", status: "started", label: "tool:hardware.invoke.shell:started", toolName: HARDWARE_INVOKE_SHELL_METHOD, waitingFor: HARDWARE_INVOKE_SHELL_METHOD }); if (preflightBlocker) { session = releaseHardwareSession(sessionRegistry, session, { now, traceId, conversationId }); const finishedAt = nowIso(now); const runnerResult = hardwareBlockedRunnerResult({ request, command: intent?.command, blocker: preflightBlocker, traceId, requestId, actorId, startedAt, finishedAt, workspace: resolvedWorkspace, session, topology: intent?.topology }); appendHardwareTraceCompletion(traceRecorder, runnerResult); return runnerResult; } if (typeof invokeHardwareShellRpc !== "function") { session = releaseHardwareSession(sessionRegistry, session, { now, traceId, conversationId }); const finishedAt = nowIso(now); const runnerResult = hardwareBlockedRunnerResult({ request, command: intent?.command, blocker: hardwareBlocker({ code: "hardware_dispatch_unavailable", layer: "dispatch", category: "needs_config", summary: "Code Agent hardware runner has no cloud-api JSON-RPC dispatcher.", userMessage: "Code Agent 硬件能力 runner 缺少 cloud-api 内部 dispatch 入口;本次未执行。", retryable: false, traceId }), traceId, requestId, actorId, startedAt, finishedAt, workspace: resolvedWorkspace, session, topology: intent?.topology }); appendHardwareTraceCompletion(traceRecorder, runnerResult); return runnerResult; } let rpcResponse; try { rpcResponse = await invokeHardwareShellRpc({ method: HARDWARE_INVOKE_SHELL_METHOD, params: { projectId: request.projectId, gatewaySessionId: request.gatewaySessionId, resourceId: request.resourceId, capabilityId: request.capabilityId, input: { command: intent.command } }, traceId, requestId, actorId, serviceId: CLOUD_API_SERVICE_ID }); } catch (error) { rpcResponse = { error: { code: "hardware_dispatch_exception", message: "hardware.invoke.shell dispatch threw before returning JSON-RPC", data: { reason: error?.message ?? "dispatch exception" } } }; } session = releaseHardwareSession(sessionRegistry, session, { now, traceId, conversationId }); const finishedAt = nowIso(now); const runnerResult = hardwareRunnerResultFromRpc({ request, command: intent.command, rpcResponse, traceId, requestId, actorId, startedAt, finishedAt, workspace: resolvedWorkspace, session, topology: intent?.topology }); appendHardwareTraceCompletion(traceRecorder, runnerResult); return runnerResult; } export function createCodeAgentHardwareInvokeShellRpc({ handleJsonRpcRequest, runtimeStore, env, dbProbe, gatewayRegistry } = {}) { if (typeof handleJsonRpcRequest !== "function") return null; return async ({ method = HARDWARE_INVOKE_SHELL_METHOD, params = {}, traceId, requestId, actorId, serviceId = CLOUD_API_SERVICE_ID } = {}) => handleJsonRpcRequest( { jsonrpc: "2.0", id: requestId || `req_code_agent_hw_shell_${randomUUID()}`, method, params, meta: { traceId: traceId || `trc_code_agent_hw_shell_${randomUUID()}`, actorId, serviceId, environment: "dev" } }, { adapter: "code-agent-internal", runtimeStore, env, dbProbe, gatewayRegistry } ); } export function codeAgentHardwareRunnerDescriptor({ workspace, session = null, capabilityLevel = CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED } = {}) { return { kind: CODE_AGENT_HARDWARE_RUNNER_KIND, provider: CODE_AGENT_HARDWARE_PROVIDER, backend: CODE_AGENT_HARDWARE_BACKEND, workspace: workspace ?? process.cwd(), sandbox: CODE_AGENT_HARDWARE_SANDBOX, session: CODE_AGENT_HARDWARE_SESSION_MODE, sessionMode: CODE_AGENT_HARDWARE_SESSION_MODE, sessionId: session?.sessionId ?? null, turn: session?.turn ?? null, sessionReused: session?.reused ?? false, implementationType: CODE_AGENT_HARDWARE_IMPLEMENTATION_TYPE, codexStdio: false, longLivedSession: false, durableSession: false, writeCapable: true, readOnly: false, capabilityLevel, toolPolicy: { allowed: [`internal-json-rpc ${HARDWARE_INVOKE_SHELL_METHOD}`], blocked: ["direct-gateway-link", "direct-box-link", "direct-patch-panel-link", "openai-text-fallback", "unregistered-shell"] }, runnerLimitations: [...HARDWARE_LIMITATION_FLAGS], safety: { secretsRead: false, secretValuesPrinted: false, kubeconfigRead: false, cloudApiRouteOnly: true, sourceServiceId: CLOUD_API_SERVICE_ID, directGatewayCallsAllowed: false, directBoxCallsAllowed: false, directPatchPanelCallsAllowed: false, openAiFallbackAllowed: false, outputLimitBytes: OUTPUT_LIMIT } }; } function hardwareRunnerResultFromRpc({ request, command, rpcResponse, traceId, requestId, actorId, startedAt, finishedAt, workspace, session, topology }) { const rpcError = rpcResponse?.error ?? null; if (rpcError) { const reason = rpcError.data?.reason ?? rpcError.message ?? "JSON-RPC dispatch failed"; return hardwareBlockedRunnerResult({ request, command, blocker: hardwareBlocker({ code: String(reason).includes("unknown serviceId") ? "hardware_rpc_service_id_invalid" : "hardware_json_rpc_invalid", layer: "json-rpc", category: "json_rpc_meta", summary: reason, userMessage: String(reason).includes("unknown serviceId") ? "cloud-api 内部 JSON-RPC serviceId 不在冻结列表中,本次未执行硬件能力。" : "cloud-api 内部 JSON-RPC 请求未通过协议校验,本次未执行硬件能力。", retryable: false, traceId, reason }), traceId, requestId, actorId, startedAt, finishedAt, workspace, session, topology, rpcError }); } const result = rpcResponse?.result ?? {}; const dispatch = result.dispatch ?? {}; const dispatchStatus = dispatch.dispatchStatus ?? result.status ?? "unknown"; const exitCode = Number.isInteger(dispatch.exitCode) ? dispatch.exitCode : null; const stdout = safeOutput(dispatch.stdout ?? dispatch.output?.stdout ?? ""); const stderr = safeOutput(dispatch.stderr ?? dispatch.output?.stderr ?? ""); const shellExecuted = dispatch.shellExecuted === true; const succeeded = shellExecuted && exitCode === 0 && ["succeeded", "completed"].includes(String(dispatchStatus).toLowerCase()); const operationId = result.operationId ?? dispatch.operationId ?? null; if (!succeeded) { return hardwareBlockedRunnerResult({ request, command, blocker: hardwareFailureBlocker({ dispatchStatus, exitCode, result, dispatch, traceId }), traceId, requestId, actorId, startedAt, finishedAt, workspace, session, topology, result, dispatch, operationId, dispatchStatus, exitCode, stdout, stderr }); } const stdoutSummary = summarizeOutput(stdout); const stderrSummary = summarizeOutput(stderr); const toolCall = hardwareToolCall({ request, command, status: "completed", traceId, requestId, actorId, operationId, dispatchStatus, exitCode, stdout, stderr, stdoutSummary, stderrSummary, capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED, startedAt, finishedAt }); const runnerTrace = hardwareRunnerTrace({ traceId, startedAt, finishedAt, workspace, session, request, operationId, dispatchStatus, exitCode, status: "completed", capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED, events: ["request:accepted", "runner:hardware-capability:selected", "tool:hardware.invoke.shell:started", "tool:hardware.invoke.shell:completed"] }); return { provider: CODE_AGENT_HARDWARE_PROVIDER, model: CODE_AGENT_HARDWARE_MODEL, backend: CODE_AGENT_HARDWARE_BACKEND, content: hardwareReply({ request, operationId, dispatchStatus, exitCode, stdoutSummary, stderrSummary, blocked: false }), workspace, sandbox: CODE_AGENT_HARDWARE_SANDBOX, session, sessionMode: CODE_AGENT_HARDWARE_SESSION_MODE, sessionReuse: session ? sessionReuseEvidence(session) : null, implementationType: CODE_AGENT_HARDWARE_IMPLEMENTATION_TYPE, runnerLimitations: [...HARDWARE_LIMITATION_FLAGS], codexStdioFeasibility: skippedCodexStdioFeasibility({ workspace, reason: "hardware_capability_deterministic_route" }), longLivedSessionGate: hardwareLongLivedGate(session), toolCalls: [toolCall], skills: hardwareSkills({ status: "used", blockers: [] }), runner: codeAgentHardwareRunnerDescriptor({ workspace, session, capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED }), runnerTrace, capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED, blockers: [], route: HARDWARE_INVOKE_SHELL_METHOD, toolName: HARDWARE_INVOKE_SHELL_METHOD, operationId, dispatchStatus, exitCode, stdoutSummary, stderrSummary, hardware: hardwareStructuredResult({ request, command, result, dispatch, operationId, dispatchStatus, exitCode, stdoutSummary, stderrSummary, topology }), providerTrace: { runnerKind: CODE_AGENT_HARDWARE_RUNNER_KIND, codexStdio: false, transport: "internal-json-rpc", serviceId: CLOUD_API_SERVICE_ID, method: HARDWARE_INVOKE_SHELL_METHOD, traceId, operationId, dispatchStatus, exitCode, capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED, fallbackUsed: false } }; } function hardwareBlockedRunnerResult({ request, command, blocker, traceId, requestId, actorId, startedAt, finishedAt, workspace, session, topology, result = null, dispatch = {}, operationId = null, dispatchStatus = null, exitCode = null, stdout = "", stderr = "", rpcError = null }) { const normalizedBlocker = { ...blocker, traceId, route: HARDWARE_INVOKE_SHELL_METHOD, toolName: HARDWARE_INVOKE_SHELL_METHOD }; const stdoutSummary = summarizeOutput(stdout); const stderrSummary = summarizeOutput(stderr || normalizedBlocker.summary || normalizedBlocker.code); const toolCall = hardwareToolCall({ request, command, status: "blocked", traceId, requestId, actorId, operationId, dispatchStatus, exitCode, stdout, stderr, stdoutSummary, stderrSummary, capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_BLOCKED, blocker: normalizedBlocker, startedAt, finishedAt }); const runnerTrace = hardwareRunnerTrace({ traceId, startedAt, finishedAt, workspace, session, request, operationId, dispatchStatus, exitCode, status: "blocked", capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_BLOCKED, blocker: normalizedBlocker, events: ["request:accepted", "runner:hardware-capability:selected", "tool:hardware.invoke.shell:blocked", `blocker:${normalizedBlocker.code}`] }); return { provider: CODE_AGENT_HARDWARE_PROVIDER, model: CODE_AGENT_HARDWARE_MODEL, backend: CODE_AGENT_HARDWARE_BACKEND, content: hardwareReply({ request, operationId, dispatchStatus, exitCode, stdoutSummary, stderrSummary, blocker: normalizedBlocker, blocked: true }), workspace, sandbox: CODE_AGENT_HARDWARE_SANDBOX, session, sessionMode: CODE_AGENT_HARDWARE_SESSION_MODE, sessionReuse: session ? sessionReuseEvidence(session) : null, implementationType: CODE_AGENT_HARDWARE_IMPLEMENTATION_TYPE, runnerLimitations: [...HARDWARE_LIMITATION_FLAGS], codexStdioFeasibility: skippedCodexStdioFeasibility({ workspace, reason: "hardware_capability_deterministic_route" }), longLivedSessionGate: hardwareLongLivedGate(session), toolCalls: [toolCall], skills: hardwareSkills({ status: "blocked", blockers: [normalizedBlocker] }), runner: codeAgentHardwareRunnerDescriptor({ workspace, session, capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_BLOCKED }), runnerTrace, capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_BLOCKED, blockers: [normalizedBlocker], blocker: normalizedBlocker, route: HARDWARE_INVOKE_SHELL_METHOD, toolName: HARDWARE_INVOKE_SHELL_METHOD, operationId, dispatchStatus, exitCode, stdoutSummary, stderrSummary, hardware: hardwareStructuredResult({ request, command, result, dispatch, operationId, dispatchStatus, exitCode, stdoutSummary, stderrSummary, topology, blocker: normalizedBlocker, rpcError }), providerTrace: { runnerKind: CODE_AGENT_HARDWARE_RUNNER_KIND, codexStdio: false, transport: "internal-json-rpc", serviceId: CLOUD_API_SERVICE_ID, method: HARDWARE_INVOKE_SHELL_METHOD, traceId, operationId, dispatchStatus, exitCode, capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_BLOCKED, fallbackUsed: false, blocker: normalizedBlocker } }; } function hardwareToolCall({ request, command, status, traceId, requestId, actorId, operationId, dispatchStatus, exitCode, stdout, stderr, stdoutSummary, stderrSummary, capabilityLevel, blocker = null, startedAt, finishedAt }) { return { id: `tool_${randomUUID()}`, type: "cloud-api-rpc", name: HARDWARE_INVOKE_SHELL_METHOD, status, method: HARDWARE_INVOKE_SHELL_METHOD, route: HARDWARE_INVOKE_SHELL_METHOD, serviceId: CLOUD_API_SERVICE_ID, sourceServiceId: CLOUD_API_SERVICE_ID, requestId, actorId, traceId, projectId: request?.projectId ?? null, gatewaySessionId: request?.gatewaySessionId ?? null, resourceId: request?.resourceId ?? null, capabilityId: request?.capabilityId ?? null, capabilityName: "shell.exec", command: command ? redactCommand(command) : null, operationId, dispatchStatus, exitCode, stdout: boundOutput(stdout).text, stdoutSummary, stderrSummary, outputTruncated: boundOutput(stdout).truncated || boundOutput(stderr).truncated, capabilityLevel, blocker, blockers: blocker ? [blocker] : [], startedAt, finishedAt, safety: { cloudApiRouteOnly: true, directGatewayCalls: false, directBoxCalls: false, directPatchPanelCalls: false, openAiFallbackUsed: false, serviceIdFrozen: true } }; } function hardwareRunnerTrace({ traceId, startedAt, finishedAt, workspace, session, request, operationId, dispatchStatus, exitCode, status, capabilityLevel, events, blocker = null }) { return { traceId, runnerKind: CODE_AGENT_HARDWARE_RUNNER_KIND, workspace, sandbox: CODE_AGENT_HARDWARE_SANDBOX, sessionMode: CODE_AGENT_HARDWARE_SESSION_MODE, sessionId: session?.sessionId ?? null, sessionStatus: session?.status ?? null, turn: session?.turn ?? null, sessionReused: session?.reused ?? false, implementationType: CODE_AGENT_HARDWARE_IMPLEMENTATION_TYPE, limitations: [...HARDWARE_LIMITATION_FLAGS], startedAt, finishedAt, events, eventLabels: events, method: HARDWARE_INVOKE_SHELL_METHOD, serviceId: CLOUD_API_SERVICE_ID, projectId: request?.projectId ?? null, gatewaySessionId: request?.gatewaySessionId ?? null, resourceId: request?.resourceId ?? null, capabilityId: request?.capabilityId ?? null, operationId, dispatchStatus, exitCode, accepted: status === "completed", status, capabilityLevel, blocker, blockers: blocker ? [blocker] : [], outputTruncated: false, valuesPrinted: false, directGatewayCalls: false, directBoxCalls: false, directPatchPanelCalls: false, fallbackUsed: false, note: "Controlled hardware capability route uses cloud-api internal JSON-RPC hardware.invoke.shell with frozen serviceId; Code Agent does not call gateway directly." }; } function hardwareStructuredResult({ request, command, result, dispatch = {}, operationId, dispatchStatus, exitCode, stdoutSummary, stderrSummary, topology, blocker = null, rpcError = null }) { return { type: blocker ? "hardware_capability_blocker" : "hardware_capability_result", status: blocker ? "blocked" : "succeeded", method: HARDWARE_INVOKE_SHELL_METHOD, serviceId: CLOUD_API_SERVICE_ID, projectId: request?.projectId ?? null, gatewaySessionId: request?.gatewaySessionId ?? null, resourceId: request?.resourceId ?? null, capabilityId: request?.capabilityId ?? null, capabilityName: "shell.exec", capabilityLevel: blocker ? CODE_AGENT_HARDWARE_CAPABILITY_BLOCKED : CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED, operationId, dispatchStatus, exitCode, stdoutSummary, stderrSummary, command: command ? redactCommand(command) : null, topology: { source: topology?.source ?? null, fallbackUsed: topology?.fallbackUsed === true, candidates: topology?.candidateCount ?? null }, dispatch, result, rpcError: rpcError ? { code: rpcError.code, message: rpcError.message, reason: rpcError.data?.reason ?? null } : null, blocker, path: { summary: "Code Agent -> cloud-api internal JSON-RPC -> hardware.invoke.shell -> registered PC gateway", segments: ["Code Agent", "cloud-api", "hardware.invoke.shell", "PC gateway"], cloudApiOnly: true, directGatewayCalls: false, directBoxCalls: false, directPatchPanelCalls: false, openAiFallbackUsed: false } }; } function hardwareReply({ request, operationId, dispatchStatus, exitCode, stdoutSummary, stderrSummary, blocker = null, blocked }) { const lines = []; if (blocked) { lines.push(`PC gateway shell.exec 阻塞:${blocker.userMessage ?? blocker.summary ?? blocker.code}。`); lines.push(`Blocker: ${blocker.code};layer=${blocker.layer ?? "unknown"};reason=${blocker.reason ?? blocker.summary ?? blocker.code}。`); } else { lines.push("PC gateway shell.exec 已通过 cloud-api 受控能力路由返回。"); } lines.push(`路径:Code Agent -> cloud-api internal JSON-RPC -> hardware.invoke.shell;未直连 gateway/box/patch-panel,未使用 OpenAI text fallback。`); lines.push(`目标:projectId=${request?.projectId ?? "null"};gatewaySessionId=${request?.gatewaySessionId ?? "null"};resourceId=${request?.resourceId ?? "null"};capabilityId=${request?.capabilityId ?? "null"}。`); lines.push(`结果:operationId=${operationId ?? "null"};dispatchStatus=${dispatchStatus ?? "null"};exitCode=${exitCode ?? "null"}。`); if (stdoutSummary) lines.push(`stdout 摘要:${stdoutSummary}`); if (stderrSummary) lines.push(`stderr 摘要:${stderrSummary}`); return boundOutput(lines.join("\n")).text; } function hardwareFailureBlocker({ dispatchStatus, exitCode, result, dispatch, traceId }) { const status = String(dispatchStatus ?? "").toLowerCase(); if (status === "not_connected") { return hardwareBlocker({ code: "hardware_gateway_offline", layer: "gateway", category: "gateway_offline", summary: dispatch.message ?? "gateway is not connected", userMessage: "目标 PC gateway 当前不在线或已过期,本次未执行。", retryable: true, traceId }); } if (status === "timed_out") { return hardwareBlocker({ code: "hardware_dispatch_failed", layer: "dispatch", category: "timeout", summary: dispatch.message ?? "gateway dispatch timed out", userMessage: "cloud-api 已派发到 gateway,但等待回传超时。", retryable: true, traceId }); } if (Number.isInteger(exitCode) && exitCode !== 0) { return hardwareBlocker({ code: "hardware_shell_failed", layer: "gateway-shell", category: "command_failed", summary: `Windows cmd exited with ${exitCode}`, userMessage: `Windows cmd 已执行但退出码为 ${exitCode}。`, retryable: true, traceId }); } return hardwareBlocker({ code: "hardware_dispatch_failed", layer: "dispatch", category: "dispatch_failed", summary: dispatch.message ?? result?.status ?? "hardware.invoke.shell dispatch did not report a successful shell execution", userMessage: "hardware.invoke.shell 未返回成功执行结果,本次按 dispatch failed 处理。", retryable: true, traceId }); } function hardwareRegistryBlocker({ request, gatewayRegistry, traceId }) { if (!request || !gatewayRegistry || typeof gatewayRegistry.getSession !== "function") return null; const session = gatewayRegistry.getSession(request.gatewaySessionId); if (!session) { return hardwareBlocker({ code: "hardware_session_missing", layer: "gateway-registry", category: "session_missing", summary: `gateway session ${request.gatewaySessionId} is not registered in the cloud-api gateway registry`, userMessage: "目标 gateway session 未在 cloud-api gateway registry 中登记。", retryable: true, traceId }); } if (typeof gatewayRegistry.isOnline === "function" && !gatewayRegistry.isOnline(request.gatewaySessionId)) { return hardwareBlocker({ code: "hardware_gateway_offline", layer: "gateway-registry", category: "gateway_offline", summary: `gateway session ${request.gatewaySessionId} is registered but offline or stale`, userMessage: "目标 gateway 已登记但当前离线或心跳过期。", retryable: true, traceId }); } if (session.resourceId && session.resourceId !== request.resourceId) { return hardwareBlocker({ code: "hardware_resource_missing", layer: "gateway-registry", category: "resource_missing", summary: `resource ${request.resourceId} is not registered under gateway session ${request.gatewaySessionId}`, userMessage: "目标 resourceId 不属于当前 gateway session。", retryable: false, traceId }); } if (Array.isArray(session.capabilities) && session.capabilities.length > 0) { const found = session.capabilities.some((capability) => { if (typeof capability === "string") return capability === request.capabilityId || capability === "shell.exec"; return capability?.capabilityId === request.capabilityId || capability?.id === request.capabilityId || capability?.name === "shell.exec"; }); if (!found) { return hardwareBlocker({ code: "hardware_capability_missing", layer: "gateway-registry", category: "capability_missing", summary: `capability ${request.capabilityId} is not registered on gateway session ${request.gatewaySessionId}`, userMessage: "目标 shell.exec capability 未在该 gateway session 上登记。", retryable: false, traceId }); } } return null; } function resolveHardwareTopology(explicit, { topology, env = process.env } = {}) { const explicitComplete = ["projectId", "gatewaySessionId", "resourceId", "capabilityId"].every((field) => explicit[field]); const candidates = hardwareTopologyCandidates({ topology, env }); const matches = candidates.filter((candidate) => topologyMatchesExplicit(candidate, explicit)); if (explicitComplete && (candidates.length === 0 || matches.length <= 1)) { const matched = matches[0] ?? {}; return { ok: true, source: Object.keys(explicit).every((key) => explicit[key] === matched[key]) ? matched.topologySource ?? "configured-topology" : "explicit-fields", fallbackUsed: false, candidateCount: matches.length, request: { projectId: explicit.projectId, gatewaySessionId: explicit.gatewaySessionId, resourceId: explicit.resourceId, capabilityId: explicit.capabilityId } }; } if (matches.length === 1) { const match = matches[0]; return { ok: true, source: match.topologySource ?? "configured-topology", fallbackUsed: !explicitComplete, candidateCount: 1, request: { projectId: explicit.projectId ?? match.projectId, gatewaySessionId: explicit.gatewaySessionId ?? match.gatewaySessionId, resourceId: explicit.resourceId ?? match.resourceId, capabilityId: explicit.capabilityId ?? match.capabilityId } }; } const missing = Object.entries(explicit).filter(([, value]) => !value).map(([key]) => key); if (matches.length > 1) { return { ok: false, source: "ambiguous-topology", fallbackUsed: false, candidateCount: matches.length, blocker: hardwareBlocker({ code: "hardware_topology_ambiguous", layer: "topology", category: "topology_ambiguous", summary: `PC gateway shell.exec topology is not unique; ${matches.length} candidates match the request.`, userMessage: "PC gateway shell.exec 拓扑不唯一,请显式提供 projectId/gatewaySessionId/resourceId/capabilityId。", retryable: false, missing }) }; } const explicitAny = Object.values(explicit).some(Boolean); return { ok: false, source: "missing-topology", fallbackUsed: false, candidateCount: 0, blocker: hardwareBlocker({ code: explicitAny ? "hardware_capability_missing" : "hardware_topology_missing", layer: "topology", category: explicitAny ? "capability_missing" : "topology_missing", summary: explicitAny ? "No registered PC gateway shell.exec topology matches the explicit request." : "PC gateway shell.exec request does not include enough identifiers and no unique topology is available.", userMessage: explicitAny ? "显式请求的 PC gateway shell.exec capability 未登记或不匹配。" : "缺少 PC gateway shell.exec 拓扑字段,且当前没有唯一可解析拓扑。", retryable: false, missing }) }; } function hardwareTopologyCandidates({ topology, env = process.env } = {}) { if (Array.isArray(topology)) return topology.map(normalizeTopologyCandidate).filter(Boolean); if (Array.isArray(topology?.candidates)) return topology.candidates.map(normalizeTopologyCandidate).filter(Boolean); if (Array.isArray(topology?.capabilities)) return topology.capabilities.map(normalizeTopologyCandidate).filter(Boolean); if (topology?.pcGatewayShell) return [normalizeTopologyCandidate(topology.pcGatewayShell)].filter(Boolean); const envCandidate = normalizeTopologyCandidate({ projectId: env.HWLAB_CODE_AGENT_PC_GATEWAY_PROJECT_ID, gatewaySessionId: env.HWLAB_CODE_AGENT_PC_GATEWAY_SESSION_ID, resourceId: env.HWLAB_CODE_AGENT_PC_GATEWAY_RESOURCE_ID, capabilityId: env.HWLAB_CODE_AGENT_PC_GATEWAY_CAPABILITY_ID, topologySource: "env:HWLAB_CODE_AGENT_PC_GATEWAY_*" }); if (envCandidate) return [envCandidate]; return DEFAULT_DEV_MVP_PC_GATEWAY_TOPOLOGY.map(normalizeTopologyCandidate).filter(Boolean); } function normalizeTopologyCandidate(candidate = {}) { const normalized = { projectId: cleanProtocolId(candidate.projectId, "prj"), gatewaySessionId: cleanProtocolId(candidate.gatewaySessionId, "gws"), resourceId: cleanProtocolId(candidate.resourceId, "res"), capabilityId: cleanProtocolId(candidate.capabilityId, "cap"), capabilityName: candidate.capabilityName ?? candidate.name ?? "shell.exec", topologySource: candidate.topologySource ?? candidate.source ?? "configured-topology" }; if (!normalized.projectId || !normalized.gatewaySessionId || !normalized.resourceId || !normalized.capabilityId) return null; if (normalized.capabilityName !== "shell.exec" && normalized.capabilityId !== "cap_windows_cmd_exec") return null; return normalized; } function topologyMatchesExplicit(candidate, explicit) { return Object.entries(explicit).every(([field, value]) => !value || candidate[field] === value); } function mentionsPcGatewayShellCapability(text, params = {}) { if (params.capabilityId || params.gatewaySessionId || params.resourceId || params.input?.command || params.command) { return true; } return /PC\s*gateway|Windows\s+cmd|cmd(?:\.exe)?\s*\/c|shell\.exec|hardware\.invoke\.shell|cap_windows_cmd_exec|res_windows_host|gws_DESKTOP/iu.test(text); } function extractProtocolField(text, field, prefix) { const aliases = field === "gatewaySessionId" ? "(?:gatewaySessionId|gatewaySession|session)" : field; const match = String(text ?? "").match(new RegExp(`${aliases}\\s*[:=]\\s*([A-Za-z][A-Za-z0-9._:-]+)`, "iu")); return cleanProtocolId(match?.[1], prefix); } function extractWindowsCommand(text) { const match = String(text ?? "").match(/\bcmd(?:\.exe)?\s*\/c\s+[\s\S]+$/iu); if (!match) return null; return trimCommandTail(match[0]); } function trimCommandTail(command) { return String(command ?? "") .replace(/(?:。|;)\s*(?:返回|请返回|输出|并返回|$)[\s\S]*$/u, "") .replace(/\s+(?:返回|请返回|输出|并返回)\s+(?:stdout|stderr|exitCode|退出码)[\s\S]*$/iu, "") .replace(/[。;]\s*$/u, "") .trim(); } function normalizeWindowsCommand(command) { if (typeof command !== "string") return null; const trimmed = trimCommandTail(command); return trimmed || null; } function isAllowedWindowsCmd(command) { const value = String(command ?? "").trim(); if (!/^cmd(?:\.exe)?\s+\/c\s+\S/iu.test(value)) return false; return !/\b(?:bash|sh|kubectl|powershell|pwsh)\b|sh\s+-c/iu.test(value); } function acquireHardwareSession(sessionRegistry, { conversationId, sessionId, traceId, workspace, now } = {}) { if (!sessionRegistry || typeof sessionRegistry.acquire !== "function") return { session: null, blocker: null }; const acquired = sessionRegistry.acquire({ conversationId, sessionId, workspace, sandbox: CODE_AGENT_HARDWARE_SANDBOX, runnerKind: CODE_AGENT_HARDWARE_RUNNER_KIND, sessionMode: CODE_AGENT_HARDWARE_SESSION_MODE, capabilityLevel: CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED, implementationType: CODE_AGENT_HARDWARE_IMPLEMENTATION_TYPE, traceId, now }); if (acquired.ok) return { session: acquired.session, blocker: null }; return { session: acquired.session ?? null, blocker: hardwareBlocker({ code: acquired.code ?? "hardware_session_blocked", layer: "session", category: "session_blocked", summary: acquired.message ?? "Code Agent hardware capability session could not be acquired.", userMessage: "Code Agent 硬件能力 session 当前不可用。", retryable: true, traceId }) }; } function releaseHardwareSession(sessionRegistry, session, { now, traceId, conversationId } = {}) { if (!session || !sessionRegistry || typeof sessionRegistry.release !== "function") return session; return sessionRegistry.release(session.sessionId, { now, traceId, conversationId, reused: session.reused, status: "idle" }) ?? session; } function appendHardwareTraceCompletion(traceRecorder, runnerResult) { traceRecorder?.append({ type: "tool_call", status: runnerResult.capabilityLevel === CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED ? "completed" : "blocked", label: runnerResult.capabilityLevel === CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED ? "tool:hardware.invoke.shell:completed" : "tool:hardware.invoke.shell:blocked", toolName: HARDWARE_INVOKE_SHELL_METHOD, outputSummary: `dispatchStatus=${runnerResult.dispatchStatus ?? "null"}; exitCode=${runnerResult.exitCode ?? "null"}; operationId=${runnerResult.operationId ?? "null"}`, terminal: false }); } function skippedCodexStdioFeasibility({ workspace, reason }) { return { checked: true, skipped: true, reason, sourceIssue: "pikasTech/HWLAB#460", status: "skipped", ready: false, canStartLongLivedCodexStdio: false, commandProbe: { ready: false, skipped: true, reason }, workspace: workspace ?? process.cwd(), sandbox: CODE_AGENT_HARDWARE_SANDBOX, blockers: [], blockerCodes: [], summary: "Registered hardware capability requests use deterministic cloud-api JSON-RPC route; Codex stdio and text fallback are not part of this control decision." }; } function hardwareLongLivedGate(session) { return { status: "skipped", ready: false, provider: CODE_AGENT_HARDWARE_PROVIDER, runnerKind: CODE_AGENT_HARDWARE_RUNNER_KIND, sessionId: session?.sessionId ?? null, sessionMode: CODE_AGENT_HARDWARE_SESSION_MODE, implementationType: CODE_AGENT_HARDWARE_IMPLEMENTATION_TYPE, blockers: [], reason: "hardware_capability_deterministic_route" }; } function hardwareSkills({ status, blockers }) { return { status, items: [{ name: HARDWARE_INVOKE_SHELL_METHOD, summary: "Controlled cloud-api hardware capability route for registered PC gateway shell.exec.", capabilityLevel: status === "used" ? CODE_AGENT_HARDWARE_CAPABILITY_EXECUTED : CODE_AGENT_HARDWARE_CAPABILITY_BLOCKED }], count: 1, blockers }; } function hardwareBlocker({ code, layer, category, summary, userMessage, retryable, traceId = null, reason = null, missing = [] }) { return { code, layer, category, retryable: Boolean(retryable), summary: redactText(summary), message: redactText(summary), userMessage, reason: reason ? redactText(reason) : undefined, traceId, route: HARDWARE_INVOKE_SHELL_METHOD, toolName: HARDWARE_INVOKE_SHELL_METHOD, missing }; } function sessionReuseEvidence(session) { return { conversationId: session.conversationId, sessionId: session.sessionId, mapped: true, reused: session.reused, turn: session.turn, previousTurns: Math.max(0, session.turn - 1), workspace: session.workspace, createdAt: session.createdAt, updatedAt: session.updatedAt, idleTimeoutMs: session.idleTimeoutMs, expiresAt: session.expiresAt, lastTraceId: session.lastTraceId, status: session.status }; } function safeOutput(value) { return redactText(value); } function summarizeOutput(value) { const text = String(value ?? "").trim().replace(/\s+/gu, " "); return text.length > COMMAND_SUMMARY_LIMIT ? `${text.slice(0, COMMAND_SUMMARY_LIMIT)}...` : text; } function boundOutput(value, maxLength = OUTPUT_LIMIT) { const text = String(value ?? ""); if (text.length <= maxLength) return { text, truncated: false }; return { text: `${text.slice(0, maxLength)}\n[output truncated at ${maxLength} bytes]`, truncated: true }; } function redactCommand(value) { return summarizeOutput(redactText(value)); } function redactText(value) { return String(value ?? "") .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gu, "Bearer ***") .replace(/sk-[A-Za-z0-9._-]+/gu, "sk-***") .replace(/([A-Za-z0-9_]*TOKEN[A-Za-z0-9_]*=)[^\s]+/giu, "$1***") .replace(/([A-Za-z0-9_]*KEY[A-Za-z0-9_]*=)[^\s]+/giu, "$1***"); } function cleanProtocolId(value, prefix) { if (typeof value !== "string") return null; const trimmed = value.trim(); if (!trimmed) return null; if (/^[a-z][a-z0-9]*_[A-Za-z0-9._:-]+$/u.test(trimmed)) return trimmed; return `${prefix}_${trimmed.replace(/[^A-Za-z0-9._:-]/gu, "-").slice(0, 80)}`; } function nowIso(now) { return typeof now === "function" ? now() : new Date().toISOString(); }