Files
pikasTech-HWLAB/internal/cloud/code-agent-hardware-runner.mjs
T
2026-05-24 12:32:27 +00:00

1183 lines
40 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.
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();
}