merge: integrate wired and wireless CAN into N7Web

This commit is contained in:
minghanchen
2026-08-19 10:21:27 +08:00
34 changed files with 4740 additions and 246 deletions
+194 -42
View File
@@ -39,10 +39,16 @@ function loadHardwareClientId() {
return clientId;
}
function loadControlTransport() {
const stored = localStorage.getItem("cognitive-agent.control-transport");
return ["wired_can", "wireless_mqtt"].includes(stored) ? stored : "wired_can";
}
const state = {
provider: "openai",
reasoningEffort: localStorage.getItem("cognitive-agent.reasoning-effort") || "medium",
executionMode: "dry_run",
controlTransport: loadControlTransport(),
conversationId: loadConversationId(),
vehicleId: null,
runtime: null,
@@ -61,6 +67,7 @@ const state = {
hardwareAccessPoller: null,
physicalStatePoller: null,
physicalStateSyncPromise: null,
passengerStateSyncPromise: null,
pendingModeProfileId: null,
modeRunTimer: null,
modeDraftCommand: "",
@@ -76,11 +83,18 @@ const apiRequest = (input, options) => window.CognitiveAgentNetwork.request(inpu
// workflow so the UI does not report failure while the seat is still moving.
const HARDWARE_REQUEST_TIMEOUT_MS = 15 * 60 * 1000;
const REQUEST_TIMEOUT_MS = 45 * 1000;
const DEFAULT_PHYSICAL_STATE_POLL_INTERVAL_MS = 5000;
function requestTimeoutForExecutionMode(mode) {
return mode === "hardware" ? HARDWARE_REQUEST_TIMEOUT_MS : REQUEST_TIMEOUT_MS;
}
function physicalStatePollIntervalMs() {
const configured = Number(selectedVehicleTransport()?.state_sync?.poll_interval_ms);
if (!Number.isFinite(configured)) return DEFAULT_PHYSICAL_STATE_POLL_INTERVAL_MS;
return Math.max(250, Math.min(DEFAULT_PHYSICAL_STATE_POLL_INTERVAL_MS, configured));
}
function refreshIcons() {
if (window.lucide) {
window.lucide.createIcons({ attrs: { "aria-hidden": "true" } });
@@ -4222,12 +4236,80 @@ function isLocalDecisionHost() {
return ["127.0.0.1", "localhost", "::1"].includes(window.location.hostname);
}
function vehicleTransportOptions() {
return Array.isArray(state.runtime?.vehicle_transports?.options)
? state.runtime.vehicle_transports.options
: [];
}
function selectedVehicleTransport() {
return vehicleTransportOptions().find((item) => item.id === state.controlTransport)
|| (state.controlTransport === "wireless_mqtt"
? state.runtime?.vehicle_mqtt
: state.runtime?.vehicle_can)
|| null;
}
function isDriverOnlyCh1Runtime() {
return state.runtime?.vehicle_can?.configured === true
&& state.runtime?.vehicle_can?.target_policy === "ch1_body_can_driver_only"
const transport = selectedVehicleTransport();
return transport?.configured === true
&& transport?.target_policy === "ch1_body_can_driver_only"
&& state.runtime?.hardware?.configured !== true;
}
function renderControlTransport() {
const options = vehicleTransportOptions();
$$('[data-control-transport]').forEach((button) => {
const id = button.dataset.controlTransport;
const runtime = options.find((item) => item.id === id)
|| (id === "wireless_mqtt" ? state.runtime?.vehicle_mqtt : state.runtime?.vehicle_can);
const selected = id === state.controlTransport;
button.classList.toggle("active", selected);
button.setAttribute("aria-pressed", String(selected));
// Transport choice is intentionally manual. An unavailable connection is
// shown as offline, but it must never disable or silently change the
// operator's wired/wireless selection.
button.disabled = false;
button.dataset.connectionState = runtime?.state_sync?.state === "online" ? "online" : "offline";
button.title = runtime?.state_configured === true
? runtime?.state_sync?.last_error || ""
: `${id === "wireless_mqtt" ? "无线 MQTT" : "有线 CAN"} 当前未配置(仍可手动选择)`;
});
const selected = selectedVehicleTransport();
const note = $("#vehicleTransportNote");
if (!note) return;
if (selected?.state_configured !== true) {
note.textContent = state.controlTransport === "wireless_mqtt"
? "已手动选择无线控制 · MQTT 当前未配置或不可用"
: "已手动选择有线控制 · ZLG CAN 当前未配置或不可用";
return;
}
if (state.controlTransport === "wireless_mqtt") {
const connection = selected?.state_sync?.state === "online" ? "已连接" : "未连接";
note.textContent = `MQTT ${connection} · 状态 ${selected?.condition_topic || "n7/n7-headunit/can/state"} · 控制 ${selected?.control_topic || "n7/can_control"}`;
} else {
const connection = selected?.state_sync?.state === "online" ? "已连接" : "未连接";
note.textContent = `ZLG 有线 ${connection} · CH${selected?.channel ?? 1} · CAN FD 500k/2M`;
}
}
function selectControlTransport(transport) {
if (!["wired_can", "wireless_mqtt"].includes(transport)) return;
const changed = state.controlTransport !== transport;
state.controlTransport = transport;
localStorage.setItem("cognitive-agent.control-transport", transport);
if (changed) {
state.conversationId = createConversationId();
sessionStorage.setItem("cognitive-agent.conversation-id", state.conversationId);
state.seatFeedbackSources.passenger = "unavailable";
state.seatFeedbackSources.driver = "unavailable";
}
state.vehicleStateSnapshot = null;
state.vehicleStateTurn = -1;
renderControlTransport();
applyRuntimeTargetPolicy(state.runtime);
}
function renderHardwareAccess(status) {
state.hardwareAccess = status;
}
@@ -4258,6 +4340,8 @@ function updateSeatFeedbackModeLabel() {
physical_can: "实车已同步",
physical_partial: "物理反馈不完整",
physical_can_ch1: "实车已同步",
wireless_mqtt_ch0: "MQTT 已同步",
wireless_mqtt_ch1: "MQTT 已同步",
simulated_hardware: "模拟 HIL",
simulated: "虚拟反馈",
unavailable: "不可用",
@@ -4266,6 +4350,8 @@ function updateSeatFeedbackModeLabel() {
physical_can: "实车",
physical_partial: "部分",
physical_can_ch1: "实车",
wireless_mqtt_ch0: "MQTT",
wireless_mqtt_ch1: "MQTT",
simulated_hardware: "模拟",
simulated: "虚拟",
unavailable: "未连接",
@@ -4283,7 +4369,11 @@ function updateSeatFeedbackModeLabel() {
}
function selectExecutionMode(mode) {
if (mode === "hardware" && !state.runtime?.hardware_execution_allowed) return;
const selectedTransport = selectedVehicleTransport();
const hardwareExecutionReady = Boolean(
state.runtime?.hardware?.configured || selectedTransport?.configured,
);
if (mode === "hardware" && !hardwareExecutionReady) return;
if (mode === "simulated_hardware" && !state.runtime?.simulated_hardware?.enabled) return;
state.executionMode = mode;
$$('[data-execution-mode]').forEach((button) => {
@@ -4291,7 +4381,8 @@ function selectExecutionMode(mode) {
});
const hardware = mode === "hardware";
const simulatedHardware = mode === "simulated_hardware";
const vehicleCanReady = Boolean(state.runtime?.vehicle_can?.configured);
const vehicleCanReady = Boolean(selectedTransport?.configured);
const mqttSelected = state.controlTransport === "wireless_mqtt";
const ch1DriverOnly = isDriverOnlyCh1Runtime();
$("#simulationFaultField").hidden = !simulatedHardware;
$("#executionBadge").className = `status-badge ${hardware ? "" : simulatedHardware ? "warning" : "neutral"}`.trim();
@@ -4305,7 +4396,7 @@ function selectExecutionMode(mode) {
: simulatedHardware ? "模拟台架隔离边界" : "虚拟执行边界";
$("#safetyText").textContent = hardware
? ch1DriverOnly && vehicleCanReady
? "仅使用 ZLG CH1 控制主驾座椅与已验证车身设备;每次动作前必须确认 P 挡和 0 km/h,并以 CH1 状态帧回读为准。CH0、副驾 T2G 与 IAR 路径均不参与。"
? `仅使用${mqttSelected ? "本地 MQTT 无线链路" : "ZLG 有线 CH1"}控制主驾座椅与已验证车身设备;每次动作前必须确认 P 挡和 0 km/h,并以 CH1 状态帧回读为准。CH0、副驾 T2G 与 IAR 路径均不参与。`
: vehicleCanReady
? "同一 CAN 盒由单进程持有:CH0 控制副驾四电机并校验板端反馈;CH1 控制主驾四电机与车身设备,每次动作前校验 P 挡、零车速和对应状态帧。"
: "当前仅通道 0 连接副驾座椅台架并校验板端反馈;车端 CAN 未启用,模式中的其他车舱领域保持虚拟执行。"
@@ -4315,7 +4406,9 @@ function selectExecutionMode(mode) {
$("#executeStageLabel").textContent = hardware ? "台架执行" : simulatedHardware ? "故障仿真" : "虚拟执行";
$("#feedbackModeLabel").textContent = hardware
? (ch1DriverOnly
? "CH1 BODY CAN · AUTHORITATIVE PHYSICAL FEEDBACK"
? (mqttSelected
? "MQTT CH1 · AUTHORITATIVE CAN FEEDBACK"
: "CH1 BODY CAN · AUTHORITATIVE PHYSICAL FEEDBACK")
: "T2G BENCH · PHYSICAL CAN FEEDBACK")
: simulatedHardware
? "SIMULATED HIL · FAULT INJECTION"
@@ -5019,13 +5112,18 @@ async function saveCurrentModeProfile() {
}
function applyRuntimeTargetPolicy(runtime) {
const passengerReady = runtime?.hardware?.configured === true;
const driverReady = runtime?.vehicle_can?.configured === true;
const selectedTransport = selectedVehicleTransport();
const wirelessDualChannel = state.controlTransport === "wireless_mqtt"
&& selectedTransport?.target_policy === "dual_channel_mqtt_can0_passenger_can1_vehicle";
const passengerReady = wirelessDualChannel
? selectedTransport?.configured === true
: runtime?.hardware?.configured === true;
const driverReady = selectedTransport?.configured === true;
const driverOnly = driverReady && !passengerReady;
const stage = $("#seatTwin");
if (stage) {
stage.dataset.passengerChannel = "CH0";
stage.dataset.driverChannel = "CH1";
stage.dataset.driverChannel = state.controlTransport === "wireless_mqtt" ? "MQTT" : "CH1";
stage.dataset.passengerConfigured = String(passengerReady);
stage.dataset.driverConfigured = String(driverReady);
}
@@ -5072,6 +5170,7 @@ async function loadRuntime() {
const response = await apiRequest("/api/runtime", { cache: "no-store" });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
state.runtime = await response.json();
renderControlTransport();
applyRuntimeTargetPolicy(state.runtime);
populateVehicleSelector();
populateModeChips();
@@ -5080,12 +5179,17 @@ async function loadRuntime() {
state.reasoningEffort = configuredEffort;
}
const hardware = state.runtime.hardware || {};
const vehicleCan = state.runtime.vehicle_can || {};
const linkedHardwareReady = Boolean(hardware.configured && vehicleCan.configured);
const seatHardwareReady = Boolean(hardware.configured);
const vehicleCan = selectedVehicleTransport() || {};
const mqttSelected = state.controlTransport === "wireless_mqtt";
const mqttDualChannel = mqttSelected
&& vehicleCan.target_policy === "dual_channel_mqtt_can0_passenger_can1_vehicle";
const seatHardwareReady = Boolean(
mqttDualChannel ? vehicleCan.configured : hardware.configured,
);
const vehicleHardwareReady = Boolean(vehicleCan.configured);
const linkedHardwareReady = seatHardwareReady && vehicleHardwareReady;
const ch1DriverOnly = vehicleHardwareReady && !seatHardwareReady;
const hardwareExecutionReady = Boolean(state.runtime.hardware_execution_allowed);
const hardwareExecutionReady = Boolean(hardware.configured || vehicleCan.configured);
const badge = $("#modelBadge");
const gptButton = $('[data-provider="openai"]');
if (state.runtime.openai_available) {
@@ -5120,32 +5224,35 @@ async function loadRuntime() {
&& hardware.access_policy === "local_approval_idle_lease"
? ` · 本地审批 · 空闲 ${hardware.access_inactivity_timeout_seconds || 180} 秒自动停止`
: "";
const vehicleRouteText = mqttSelected
? `MQTT ${vehicleCan.host || "127.0.0.1"}:${vehicleCan.port || 1883} · ${vehicleCan.condition_topic || "n7/n7-headunit/can/state"} / ${vehicleCan.control_topic || "n7/can_control"}`
: `${vehicleCan.backend || "zlgcan"} · CH${vehicleCan.channel ?? 1} 车身 CAN FD ${Number(vehicleCan.arbitration?.bitrate || 500000) / 1000}k/${Number(vehicleCan.data?.bitrate || 2000000) / 1000000}M`;
$("#hardwareNote").textContent = ch1DriverOnly && vehicleHardwareReady && !seatHardwareReady
? `${vehicleCan.backend} · CH${vehicleCan.channel} 车身 CAN FD ${vehicleCan.arbitration.bitrate / 1000}k/${vehicleCan.data.bitrate / 1000000}M · 仅主驾/车端白名单 · CH0/T2G/IAR 已禁用${accessText}${concurrencyText}`
? `${vehicleRouteText} · 仅主驾/车端白名单 · CH0/T2G/IAR 已禁用${accessText}${concurrencyText}`
: linkedHardwareReady
? `${hardware.backend} · CH${hardware.channel} 座椅 ${hardware.bitrate / 1000} kbit/s · CH${vehicleCan.channel} 车身 CAN FD ${vehicleCan.arbitration.bitrate / 1000}k/${vehicleCan.data.bitrate / 1000000}M · ${profileText} · ${flashText}${accessText}${concurrencyText}`
? `${mqttDualChannel ? "MQTT CAN0 副驾座椅 · MQTT CAN1 其余车辆" : `${hardware.backend} · CH${hardware.channel} 座椅 ${hardware.bitrate / 1000} kbit/s`} · ${vehicleRouteText} · ${profileText}${mqttDualChannel ? " · 远端自动烧录禁用" : ` · ${flashText}`}${accessText}${concurrencyText}`
: seatHardwareReady
? `${hardware.backend} · CH${hardware.channel} 座椅 ${hardware.bitrate / 1000} kbit/s · ${profileText} · ${flashText} · 车端 CAN 未启用,其他领域保持虚拟执行${accessText}${concurrencyText}`
: `联动真实执行已锁定:${profileText};请完成座椅固件、双通道 ZLG 适配器和车身 CAN FD 配置`;
: `真实执行已锁定:${profileText};当前${mqttSelected ? "无线 MQTT" : "有线 CAN"}控制未通过后端审批配置`;
$("#hardwareAliasNote").textContent = ch1DriverOnly && vehicleHardwareReady && !seatHardwareReady
? "CH1 → 车身 CAN FD / 主驾座椅 · CH0 与副驾 T2G 路径禁止"
? `${mqttSelected ? "MQTT" : "CH1"} → 车身 CAN / 主驾座椅 · CH0 与副驾 T2G 路径禁止`
: linkedHardwareReady
? "CH0 → 副驾座椅控制器 · CH1 → 车身 CAN FD"
? `${mqttDualChannel ? "MQTT CAN0" : "CH0"} → 副驾座椅控制器 · ${mqttDualChannel ? "MQTT CAN1" : "CH1"} → 其余车辆 CAN`
: seatHardwareReady
? `CH${hardware.channel} → 副驾座椅控制器 · 车身控制暂用虚拟 CAN`
: "尚未配置可执行的物理 CAN 通道";
$("#hardwareBadge").className = `status-badge ${hardwareExecutionReady ? "" : "danger"}`.trim();
$("#hardwareBadge").innerHTML = ch1DriverOnly && vehicleHardwareReady && !seatHardwareReady
? '<i data-lucide="shield-check"></i>CH1 车端已配置'
? `<i data-lucide="shield-check"></i>${mqttSelected ? "MQTT" : "CH1"} 车端已配置`
: linkedHardwareReady
? '<i data-lucide="shield-check"></i>双通道已配置'
: seatHardwareReady
? '<i data-lucide="shield-check"></i>座椅台架已配置'
: '<i data-lucide="shield-off"></i>硬件锁定';
$("#hardwareCoverage").textContent = ch1DriverOnly && vehicleHardwareReady && !seatHardwareReady
? `CH1 主驾 + 车端设备 · 物理字段回读${accessText}${concurrencyText}`
? `${mqttSelected ? "MQTT" : "CH1"} 主驾 + 车端设备 · CAN 字段回读${accessText}${concurrencyText}`
: linkedHardwareReady
? `CH0 座椅 + CH${vehicleCan.channel} 车身${accessText}${concurrencyText}`
? `CH0 座椅 + ${mqttSelected ? "MQTT" : `CH${vehicleCan.channel}`} 车身${accessText}${concurrencyText}`
: seatHardwareReady
? `CH${hardware.channel} 座椅 · 物理反馈${accessText}${concurrencyText}`
: "已阻断";
@@ -5166,10 +5273,16 @@ async function loadRuntime() {
}
async function synchronizePassengerSeatState() {
if (!state.runtime?.hardware?.configured) return false;
const requestedTransport = state.controlTransport;
const mqttSelected = requestedTransport === "wireless_mqtt";
const stateReady = mqttSelected
? selectedVehicleTransport()?.state_configured
: state.runtime?.hardware?.configured;
if (!stateReady) return false;
const query = new URLSearchParams({
conversation_id: state.conversationId,
vehicle_id: state.vehicleId || state.runtime.default_vehicle_id || "",
control_transport: requestedTransport,
hardware_client_id: state.hardwareClientId,
});
try {
@@ -5182,15 +5295,16 @@ async function synchronizePassengerSeatState() {
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const payload = await response.json();
if (requestedTransport !== state.controlTransport) return false;
renderSeatState({ passenger: payload.actuator_state?.passenger || {} });
state.seatFeedbackSources.passenger = payload.complete
? "physical_can"
? payload.source === "wireless_mqtt_ch0" ? "wireless_mqtt_ch0" : "physical_can"
: "physical_partial";
state.seatFeedbackSource = state.seatFeedbackSources.passenger;
updateSeatFeedbackModeLabel();
return true;
} catch (_error) {
if (!state.seatFeedbackSources.passenger.startsWith("physical")) {
if (!["physical_can", "wireless_mqtt_ch0"].includes(state.seatFeedbackSources.passenger)) {
state.seatFeedbackSources.passenger = "unavailable";
updateSeatFeedbackModeLabel();
}
@@ -5199,10 +5313,12 @@ async function synchronizePassengerSeatState() {
}
async function synchronizeDriverSeatState() {
if (!state.runtime?.vehicle_can?.configured) return false;
if (!selectedVehicleTransport()?.state_configured) return false;
const requestedTransport = state.controlTransport;
const query = new URLSearchParams({
conversation_id: state.conversationId,
vehicle_id: state.vehicleId || state.runtime.default_vehicle_id || "",
control_transport: requestedTransport,
hardware_client_id: state.hardwareClientId,
});
try {
@@ -5213,13 +5329,16 @@ async function synchronizeDriverSeatState() {
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const payload = await response.json();
if (requestedTransport !== state.controlTransport) return false;
applyAuthoritativeVehicleState(payload);
state.seatFeedbackSources.driver = payload.complete ? "physical_can_ch1" : "physical_partial";
state.seatFeedbackSources.driver = payload.complete
? payload.source === "wireless_mqtt_ch1" ? "wireless_mqtt_ch1" : "physical_can_ch1"
: "physical_partial";
state.seatFeedbackSource = state.seatFeedbackSources.driver;
updateSeatFeedbackModeLabel();
return true;
} catch (_error) {
if (!state.seatFeedbackSources.driver.startsWith("physical")) {
if (!["physical_can_ch1", "wireless_mqtt_ch1"].includes(state.seatFeedbackSources.driver)) {
state.seatFeedbackSources.driver = "unavailable";
updateSeatFeedbackModeLabel();
}
@@ -5230,7 +5349,22 @@ async function synchronizeDriverSeatState() {
async function synchronizePhysicalSeatState() {
if (state.physicalStateSyncPromise) return state.physicalStateSyncPromise;
state.physicalStateSyncPromise = (async () => {
// Both channels share one adapter owner; keep passive reads serialized.
if (state.controlTransport === "wireless_mqtt") {
// CAN0 may legitimately be absent and wait for its bounded seat-status
// timeout. Keep that wait single-flight in the background so cached
// CAN1 vehicle state can still refresh at the MQTT polling cadence.
if (!state.passengerStateSyncPromise) {
let passengerPromise;
passengerPromise = synchronizePassengerSeatState().finally(() => {
if (state.passengerStateSyncPromise === passengerPromise) {
state.passengerStateSyncPromise = null;
}
});
state.passengerStateSyncPromise = passengerPromise;
}
return synchronizeDriverSeatState();
}
// The wired channels share one adapter owner; keep passive reads serialized.
const passenger = await synchronizePassengerSeatState();
const driver = await synchronizeDriverSeatState();
return passenger || driver;
@@ -5242,6 +5376,16 @@ async function synchronizePhysicalSeatState() {
}
}
function restartPhysicalStatePolling() {
if (state.physicalStatePoller) window.clearInterval(state.physicalStatePoller);
state.physicalStatePoller = window.setInterval(() => {
const activeModule = document.body.dataset.module;
if (document.visibilityState !== "visible") return;
if (!["self-orchestration", "self-extension"].includes(activeModule)) return;
synchronizePhysicalSeatState();
}, physicalStatePollIntervalMs());
}
async function loadAuthStatus() {
const response = await apiRequest("/api/auth/status", { cache: "no-store" });
if (!response.ok) return;
@@ -5542,7 +5686,7 @@ function renderDomainState(domainState) {
if (vehicleSystems) {
vehicleSystems.targets.climateLevel = Math.max(0, Math.min(7, Number(climate.fan_level) || 0));
vehicleSystems.targets.climateTemperature = Number(
state.runtime?.vehicle_can?.target_policy === "ch1_body_can_driver_only"
selectedVehicleTransport()?.target_policy === "ch1_body_can_driver_only"
? driverTemperature
: passengerTemperature,
) || 24;
@@ -6170,8 +6314,8 @@ function renderExecution(result) {
: simulatedHil
? `${executedDomains.join(" / ")} 已完成隔离 HIL 仿真,未访问物理 CAN`
: physical
? state.runtime?.vehicle_can?.target_policy === "ch1_body_can_driver_only"
? `${executedDomains.join(" / ")} 已通过 CH1 下发,并以车端状态字段确认 ${operationResults.filter((item) => item.status === "succeeded").length} 个步骤`
? selectedVehicleTransport()?.target_policy === "ch1_body_can_driver_only"
? `${executedDomains.join(" / ")} 已通过${result.control_transport === "wireless_mqtt" ? " MQTT" : " CH1"}下发,并以车端状态字段确认 ${operationResults.filter((item) => item.status === "succeeded").length} 个步骤`
: `${executedDomains.join(" / ")} 已下发真实 CAN,并以 IAR 固件实际反馈确认 ${operationResults.filter((item) => item.status === "succeeded").length} 个步骤`
: `${executedDomains.join(" / ")} 共 ${report.spec.results.length} 个操作已完成 CAN 编码与虚拟执行`;
if (partiallySucceeded) {
@@ -6306,8 +6450,12 @@ function applyAuthoritativeVehicleState(payload) {
domainState: domainPatch,
};
if (actuatorPatch?.driver) {
state.seatFeedbackSource = "physical_can_ch1";
state.seatFeedbackSources.driver = "physical_can_ch1";
const source = payload.control_transport === "wireless_mqtt"
|| payload.source === "wireless_mqtt_ch1"
? "wireless_mqtt_ch1"
: "physical_can_ch1";
state.seatFeedbackSource = source;
state.seatFeedbackSources.driver = source;
}
renderSeatState(actuatorPatch);
renderDomainState(domainPatch);
@@ -6346,8 +6494,10 @@ function renderResult(result) {
if (result.actuator_state?.driver) state.seatFeedbackSources.driver = "simulated_hardware";
} else if (feedbackSource === "physical_can_ch1"
&& isAuthoritativeVehicleStatePayload(result)) {
state.seatFeedbackSource = "physical_can_ch1";
state.seatFeedbackSources.driver = "physical_can_ch1";
state.seatFeedbackSource = result.control_transport === "wireless_mqtt"
? "wireless_mqtt_ch1"
: "physical_can_ch1";
state.seatFeedbackSources.driver = state.seatFeedbackSource;
} else if (reportMode === "hil") {
state.seatFeedbackSource = result.execution_report?.spec?.physical_state?.complete
? "physical_can"
@@ -6393,7 +6543,7 @@ function renderResult(result) {
renderTrace(result.trace_events || []);
renderValidation(result.validation || []);
const ch1HardwareResult = reportMode === "hil"
&& state.runtime?.vehicle_can?.target_policy === "ch1_body_can_driver_only";
&& selectedVehicleTransport()?.target_policy === "ch1_body_can_driver_only";
if (ch1HardwareResult) {
applyAuthoritativeVehicleState(result);
} else {
@@ -6457,6 +6607,7 @@ async function submitRequest(event) {
conversation_id: state.conversationId,
vehicle_id: state.vehicleId || state.runtime?.default_vehicle_id,
execution_mode: state.executionMode,
control_transport: state.controlTransport,
...(state.provider === "openai" ? { reasoning_effort: state.reasoningEffort } : {}),
...(state.executionMode === "hardware" && !driverOnlyCh1
? { hardware_client_id: state.hardwareClientId }
@@ -6716,6 +6867,12 @@ document.addEventListener("DOMContentLoaded", async () => {
$$(".segment[data-provider]").forEach((button) => button.addEventListener("click", () => selectProvider(button.dataset.provider)));
$$('[data-reasoning-effort]').forEach((button) => button.addEventListener("click", () => selectReasoningEffort(button.dataset.reasoningEffort)));
$$('[data-execution-mode]').forEach((button) => button.addEventListener("click", () => selectExecutionMode(button.dataset.executionMode)));
$$('[data-control-transport]').forEach((button) => button.addEventListener("click", async () => {
selectControlTransport(button.dataset.controlTransport);
await loadRuntime();
restartPhysicalStatePolling();
await synchronizePhysicalSeatState();
}));
$$(".example-command").forEach((button) => button.addEventListener("click", () => {
$("#utterance").value = button.dataset.example;
}));
@@ -6732,12 +6889,7 @@ document.addEventListener("DOMContentLoaded", async () => {
);
}
await synchronizePhysicalSeatState();
state.physicalStatePoller = window.setInterval(() => {
const activeModule = document.body.dataset.module;
if (document.visibilityState !== "visible") return;
if (!["self-orchestration", "self-extension"].includes(activeModule)) return;
synchronizePhysicalSeatState();
}, 5000);
restartPhysicalStatePolling();
if (new URLSearchParams(window.location.search).get("autorun") === "1") {
$("#requestForm").requestSubmit();
}