docs: redesign HWLAB Agent showcase
This commit is contained in:
@@ -1,317 +1,475 @@
|
||||
const STAGES = [
|
||||
const INITIAL_EVENT = {
|
||||
id: "E00",
|
||||
phase: "observe",
|
||||
actor: "AGENT",
|
||||
title: "任务接管与控制边界绑定",
|
||||
summary: "ALTA-7 接收目标,并绑定工程、目标板、探针与证据空间。",
|
||||
input: "任务目标、CONSTAR_workspace、STM32F103、CMSIS-DAP",
|
||||
decision: "先读取工程事实,不把预设代码差异当作问题结论",
|
||||
tool: "task.bind",
|
||||
output: "控制边界建立;下一步读取工程与时钟树",
|
||||
log: "$ task.bind freq-1khz-accuracy\ncontext workspace=CONSTAR_workspace\ntarget board=STM32F103 probe=CMSIS-DAP\nresult session boundary ready",
|
||||
observed: "任务、工程和真实硬件已完成上下文绑定",
|
||||
hypothesis: "待验证:TIM2 时钟推导与板级配置不一致",
|
||||
action: "等待授权读取工程事实",
|
||||
next: "检查 Keil 工程与 TIM2 配置",
|
||||
confidence: 42,
|
||||
target: "none",
|
||||
};
|
||||
|
||||
const RUN_EVENTS = [
|
||||
{
|
||||
title: "分析工程",
|
||||
summary: "识别 TIM2 输入捕获路径与 APB 时钟倍频条件。",
|
||||
log: "scan TIM2 capture path · APB1 prescaler = 2",
|
||||
id: "E01",
|
||||
phase: "observe",
|
||||
actor: "AGENT",
|
||||
title: "读取工程事实,而不是直接改代码",
|
||||
summary: "扫描 Keil 工程、时钟树和 TIM2 输入捕获链路。",
|
||||
input: ".uvprojx、system_stm32f1xx.c、tim.c、frequency.c",
|
||||
decision: "优先确认运行时钟来源,避免围绕错误前提修改换算公式",
|
||||
tool: "workspace.read",
|
||||
output: "APB1 prescaler=2;TIM2 时钟需应用定时器倍频规则",
|
||||
log: "$ workspace.read --scope clock,capture\nread 71-FREQ.uvprojx\nread Core/Src/system_stm32f1xx.c\nread Core/Src/tim.c\nfound APB1 prescaler = 2\nfound TIM2 input capture on CH1",
|
||||
observed: "APB1 分频为 2;TIM2 位于 APB1,输入捕获使用 CH1",
|
||||
hypothesis: "固定 TIMER_CLOCK_HZ 可能忽略定时器倍频规则",
|
||||
action: "对照时钟树与频率换算实现",
|
||||
next: "建立可证伪的误差假设",
|
||||
confidence: 58,
|
||||
target: "none",
|
||||
},
|
||||
{
|
||||
title: "修改代码",
|
||||
summary: "修正定时器时钟计算,并注入实板标定偏移。",
|
||||
log: "patch Core/Src/frequency.c · +5 -3",
|
||||
id: "E02",
|
||||
phase: "hypothesize",
|
||||
actor: "AGENT",
|
||||
title: "形成可证伪的根因假设",
|
||||
summary: "把时钟推导错误与捕获偏移拆成两个可独立验证的变量。",
|
||||
input: "TIM2 实际时钟规则、固定常量、历史 1 kHz 偏差",
|
||||
decision: "先修正时钟来源,再应用已有板级 capture offset;不改变滤波策略",
|
||||
tool: "decision.record",
|
||||
output: "假设 H1:时钟推导是主因;预期修复后误差进入 ±0.10%",
|
||||
log: "$ decision.record H1\nfact TIM2 clock derives from APB1 x2 rule\nconflict fixed TIMER_CLOCK_HZ bypasses board clock tree\nscope clock derivation + calibrated capture offset\nexclude input filter policy",
|
||||
observed: "代码使用固定时钟常量,板级配置包含可用标定偏移",
|
||||
hypothesis: "H1:时钟推导错误是主因,capture offset 是剩余偏差",
|
||||
action: "记录假设与验收阈值",
|
||||
next: "制定最小修改与实板验证计划",
|
||||
confidence: 71,
|
||||
target: "none",
|
||||
},
|
||||
{
|
||||
title: "Keil 编译",
|
||||
summary: "使用受控工程配置生成可追溯固件产物。",
|
||||
log: "build 0 error(s), 0 warning(s) · freq.axf",
|
||||
id: "E03",
|
||||
phase: "plan",
|
||||
actor: "AGENT",
|
||||
title: "提交最小可验证计划",
|
||||
summary: "代码修改、构建、下载、UART 与独立物理测量形成闭环。",
|
||||
input: "假设 H1、受控工具清单、真实 HWPOD 资源",
|
||||
decision: "任何软件输出都不能替代 ioProbe 的独立物理事实",
|
||||
tool: "plan.commit",
|
||||
output: "P1 Patch → P2 Build → P3 Flash → P4 UART → P5 ioProbe",
|
||||
log: "$ plan.commit\nP1 apply minimal clock/capture patch\nP2 build Keil target 71-FREQ\nP3 flash through CMSIS-DAP\nP4 inspect UART runtime facts\nP5 measure FREQ_OUT with ioProbe",
|
||||
observed: "代码工具和真实硬件工具均已就绪",
|
||||
hypothesis: "H1 可通过单次受控闭环验证",
|
||||
action: "锁定计划和证据边界",
|
||||
next: "生成最小代码变更",
|
||||
confidence: 74,
|
||||
target: "none",
|
||||
},
|
||||
{
|
||||
title: "下载固件",
|
||||
summary: "通过 CMSIS-DAP 将本次构建产物写入目标板。",
|
||||
log: "flash 32.18 KiB · verify OK · reset target",
|
||||
id: "E04",
|
||||
phase: "act",
|
||||
actor: "AGENT",
|
||||
title: "基于假设生成最小代码变更",
|
||||
summary: "动态读取定时器时钟,并应用已有板级捕获偏移。",
|
||||
input: "frequency.c、board_clock、calibration.capture_offset",
|
||||
decision: "只修改频率换算边界,不触碰输入滤波与其他模块",
|
||||
tool: "apply_patch",
|
||||
output: "frequency.c +5 −3;变更绑定假设 H1",
|
||||
log: "$ apply_patch Core/Src/frequency.c\nremove TIMER_CLOCK_HZ assumption\nadd get_timer_clock_hz()\nadd calibration.capture_offset\nresult +5 -3 · patch_sha=8a91c2d",
|
||||
observed: "修改范围可限制在 capture_to_hz()",
|
||||
hypothesis: "最小变更足以消除主偏差,且不会改变滤波行为",
|
||||
action: "生成并审查代码 Diff",
|
||||
next: "调用 Keil 构建验证编译事实",
|
||||
confidence: 79,
|
||||
target: "none",
|
||||
patch: true,
|
||||
},
|
||||
{
|
||||
title: "UART 采集",
|
||||
summary: "读取固件运行状态,确认输入捕获链路稳定。",
|
||||
log: "uart capture stable · sample_count = 128",
|
||||
id: "E05",
|
||||
phase: "act",
|
||||
actor: "TOOL",
|
||||
title: "AGENT 调用 Keil 构建固件",
|
||||
summary: "构建工具返回可追溯产物,AGENT 检查错误、警告与摘要。",
|
||||
input: "71-FREQ.uvprojx、E04 patch_sha=8a91c2d",
|
||||
decision: "只有构建产物与当前 Patch 一致,才能进入真实板下载",
|
||||
tool: "keil.build",
|
||||
output: "0 errors、0 warnings;freq.axf 32.18 KiB",
|
||||
log: "$ keil.build --target 71-FREQ\ncompile frequency.c\nlink freq.axf\nProgram Size: Code=28964 RO-data=1984 RW-data=312 ZI-data=4168\n0 Error(s), 0 Warning(s)\nartifact sha256:4f7c...9b2a",
|
||||
observed: "当前 Patch 已生成唯一固件产物",
|
||||
hypothesis: "软件修复可进入目标板运行验证",
|
||||
action: "审查构建结果与产物摘要",
|
||||
next: "通过 CMSIS-DAP 写入目标板",
|
||||
confidence: 84,
|
||||
target: "none",
|
||||
evidence: 3,
|
||||
},
|
||||
{
|
||||
title: "物理测量",
|
||||
summary: "由 ioProbe 独立测量 FREQ_OUT,形成物理事实。",
|
||||
log: "probe CH1 frequency · 999.8 Hz · error -0.02%",
|
||||
id: "E06",
|
||||
phase: "act",
|
||||
actor: "TOOL",
|
||||
title: "AGENT 操作 CMSIS-DAP 写入固件",
|
||||
summary: "目标板擦除、下载、校验和复位均由当前 Session 记录。",
|
||||
input: "freq.axf sha256:4f7c...9b2a、STM32F103 target",
|
||||
decision: "Flash verify 必须通过,且写入摘要必须匹配 E05 产物",
|
||||
tool: "cmsis-dap.flash",
|
||||
output: "32.18 KiB 写入并校验通过;目标板已复位",
|
||||
log: "$ cmsis-dap.flash freq.axf\nconnect STM32F103 · SWD 4MHz\nerase sectors 0-31\nwrite 32.18 KiB\nverify OK · artifact matched\nreset target",
|
||||
observed: "真实目标板已运行 E05 对应固件",
|
||||
hypothesis: "若 H1 正确,UART 频率输出应接近 1 kHz",
|
||||
action: "控制调试探针完成下载与校验",
|
||||
next: "读取 UART 运行事实",
|
||||
confidence: 87,
|
||||
target: "probe",
|
||||
evidence: 4,
|
||||
},
|
||||
{
|
||||
id: "E07",
|
||||
phase: "verify",
|
||||
actor: "HWPOD",
|
||||
title: "AGENT 读取 UART 运行事实",
|
||||
summary: "连续采样确认捕获链路稳定,但不把软件自报值当作最终证据。",
|
||||
input: "UART2 115200、当前目标板运行态",
|
||||
decision: "UART 可证明固件链路正常;最终精度仍交给 ioProbe 独立判断",
|
||||
tool: "uart.capture",
|
||||
output: "128 samples;capture stable;overflow=0",
|
||||
log: "$ uart.capture --port UART2 --samples 128\n[boot] 71-FREQ rev.C\n[timer] clock=72000000Hz\n[capture] stable samples=128 overflow=0\n[reported] frequency=1000.1Hz",
|
||||
observed: "固件稳定运行,软件自报频率 1000.1 Hz",
|
||||
hypothesis: "H1 获得运行态支持,但仍需外部测量排除自证循环",
|
||||
action: "读取并界定 UART 事实",
|
||||
next: "调用 ioProbe 测量 FREQ_OUT",
|
||||
confidence: 91,
|
||||
target: "uart",
|
||||
evidence: 5,
|
||||
},
|
||||
{
|
||||
id: "E08",
|
||||
phase: "verify",
|
||||
actor: "HWPOD",
|
||||
title: "AGENT 调用 ioProbe 独立测量",
|
||||
summary: "物理测量不依赖固件自报值,直接采集 FREQ_OUT。",
|
||||
input: "ioProbe CH1、FREQ_OUT、目标 1000.0 Hz",
|
||||
decision: "以独立物理测量决定是否接受 H1 与当前 Patch",
|
||||
tool: "ioprobe.measure",
|
||||
output: "等待物理采样结果",
|
||||
log: "$ ioprobe.measure --channel CH1 --metric frequency\narm FREQ_OUT\nsample physical waveform\nwindow stable\nresult pending",
|
||||
observed: "UART 链路通过,物理输出已接入 ioProbe CH1",
|
||||
hypothesis: "物理频率应落入 1000.0 Hz ±0.10%",
|
||||
action: "采集真实波形并计算频率",
|
||||
next: "根据物理事实接受或拒绝封存",
|
||||
confidence: 93,
|
||||
target: "ioprobe",
|
||||
evidence: 6,
|
||||
},
|
||||
];
|
||||
|
||||
const EVIDENCE = [
|
||||
const SUCCESS_EVENT = {
|
||||
id: "E09",
|
||||
phase: "reflect",
|
||||
actor: "AGENT",
|
||||
title: "汇总证据并接受当前能力",
|
||||
summary: "物理结果支持 H1;AGENT 仅在完整证据链通过后允许封存。",
|
||||
input: "Patch、Build、Flash、UART、ioProbe 999.8 Hz",
|
||||
decision: "误差 −0.02% 满足阈值,H1 被证据支持,当前 Patch 可复用",
|
||||
tool: "evidence.aggregate",
|
||||
output: "Aggregate PASS;允许封存 freq-1khz-accuracy",
|
||||
log: "$ evidence.aggregate\npatch PASS · E04\nbuild PASS · E05\nflash PASS · E06\nuart PASS · E07\nphysical PASS · 999.8Hz (-0.02%)\nverdict Aggregate PASS",
|
||||
observed: "ioProbe 实测 999.8 Hz,误差 −0.02%",
|
||||
hypothesis: "H1 已被软件、运行态和独立物理证据共同支持",
|
||||
action: "形成 Aggregate PASS 裁决",
|
||||
next: "封存为可重复回归 Case",
|
||||
confidence: 99,
|
||||
target: "none",
|
||||
};
|
||||
|
||||
const FAILURE_EVENT = {
|
||||
id: "E09",
|
||||
phase: "reflect",
|
||||
actor: "AGENT",
|
||||
title: "拒绝封存,并返回诊断循环",
|
||||
summary: "物理事实推翻当前完成假设;AGENT 阻断结果并生成下一假设。",
|
||||
input: "Patch、Build、Flash、UART 通过,但 ioProbe 仅测得 972.4 Hz",
|
||||
decision: "软件链路通过不能覆盖物理失败;拒绝封存并重新打开诊断",
|
||||
tool: "plan.revise",
|
||||
output: "Aggregate BLOCKED;新假设 H2:滤波窗口或 capture offset 仍有偏差",
|
||||
log: "$ evidence.aggregate\nsoftware chain PASS\nphysical FAIL · 972.4Hz (-2.76%)\nverdict Aggregate BLOCKED\n$ plan.revise\nnext H2 inspect filter window + capture offset samples",
|
||||
observed: "ioProbe 实测 972.4 Hz,与 UART 自报值冲突",
|
||||
hypothesis: "H2:输入滤波窗口或 capture offset 仍存在板级偏差",
|
||||
action: "拒绝封存,计划回到工程诊断",
|
||||
next: "读取滤波窗口与原始 capture 样本",
|
||||
confidence: 56,
|
||||
target: "none",
|
||||
};
|
||||
|
||||
const EVIDENCE_DETAILS = [
|
||||
{
|
||||
idle: "任务尚未进入受控执行。",
|
||||
passed: "任务 freq-1khz-accuracy 已绑定 Workspace、Board、Probe 与目标指标。",
|
||||
idle: "E00 尚未进入执行态。",
|
||||
passed: "E00:任务、Workspace、Board 和 Probe 已绑定当前 Session。",
|
||||
},
|
||||
{
|
||||
idle: "尚无 Agent 执行 trace。",
|
||||
active: "Agent 正在受控工程上下文中分析、修改与执行。",
|
||||
passed: "Agent trace 已封存:工程分析与代码变更均可回放。",
|
||||
idle: "尚无 Agent 行动 Trace。",
|
||||
active: "E01–E09:AGENT 正在持续记录观察、决策、工具与结果。",
|
||||
passed: "E01–E09:Agent Trace 完整,可回放每次决策与工具调用。",
|
||||
},
|
||||
{
|
||||
idle: "尚无本次构建产物。",
|
||||
passed: "Keil 构建成功:0 error(s),0 warning(s),产物摘要已记录。",
|
||||
idle: "E04 尚未生成代码变更。",
|
||||
passed: "E04:Patch +5 −3,与根因假设 H1 建立明确关联。",
|
||||
},
|
||||
{
|
||||
idle: "目标板尚未写入本次固件。",
|
||||
passed: "CMSIS-DAP 下载与校验通过,固件摘要与构建产物一致。",
|
||||
idle: "E05 尚无构建产物。",
|
||||
passed: "E05:Keil 0 errors、0 warnings,产物摘要已封存。",
|
||||
},
|
||||
{
|
||||
idle: "UART 运行事实尚未采集。",
|
||||
passed: "UART 连续采样 128 次,输入捕获状态稳定,无溢出。",
|
||||
idle: "E06 尚未操作真实目标板。",
|
||||
passed: "E06:CMSIS-DAP 下载、校验和复位通过,产物摘要一致。",
|
||||
},
|
||||
{
|
||||
idle: "ioProbe 物理测量尚未开始。",
|
||||
active: "ioProbe 正在独立采集 FREQ_OUT 波形。",
|
||||
passed: "ioProbe 实测 999.8 Hz,误差 −0.02%,满足 ±0.10% 阈值。",
|
||||
failed: "ioProbe 实测 972.4 Hz,误差 −2.76%,超出 ±0.10% 阈值。",
|
||||
idle: "E07 尚未采集运行事实。",
|
||||
passed: "E07:UART 连续 128 samples,捕获稳定且 overflow=0。",
|
||||
},
|
||||
{
|
||||
idle: "Aggregate 尚未汇总,不能保存为回归 Case。",
|
||||
passed: "代码、构建、下载、运行与物理测量证据完整:Aggregate PASS。",
|
||||
blocked: "物理测量未通过:Aggregate BLOCKED,阻止错误结果进入回归集。",
|
||||
idle: "E08 尚未获得独立物理事实。",
|
||||
active: "E08:AGENT 正在调用 ioProbe 采集 FREQ_OUT。",
|
||||
passed: "E08:ioProbe 999.8 Hz,误差 −0.02%,满足阈值。",
|
||||
failed: "E08:ioProbe 972.4 Hz,误差 −2.76%,推翻完成假设。",
|
||||
},
|
||||
{
|
||||
idle: "E09 尚未形成聚合裁决。",
|
||||
passed: "E09:完整证据链 Aggregate PASS,可封存回归 Case。",
|
||||
blocked: "E09:Aggregate BLOCKED;AGENT 已拒绝封存并返回诊断。",
|
||||
},
|
||||
];
|
||||
|
||||
const stageButtons = [...document.querySelectorAll("[data-stage]")];
|
||||
const evidenceButtons = [...document.querySelectorAll("[data-evidence]")];
|
||||
const PATCH_HTML = `
|
||||
<div class="diff-line"><span>118</span><code>static float capture_to_hz(uint32_t ticks)</code></div>
|
||||
<div class="diff-line"><span>119</span><code>{</code></div>
|
||||
<div class="diff-line is-remove"><span>120</span><code>- return TIMER_CLOCK_HZ / ticks;</code></div>
|
||||
<div class="diff-line is-remove"><span>121</span><code>- // APB prescaler assumed to be 1</code></div>
|
||||
<div class="diff-line is-add"><span>120</span><code>+ const uint32_t timer_clock = get_timer_clock_hz();</code></div>
|
||||
<div class="diff-line is-add"><span>121</span><code>+ const float calibrated_ticks =</code></div>
|
||||
<div class="diff-line is-add"><span>122</span><code>+ (float)ticks - calibration.capture_offset;</code></div>
|
||||
<div class="diff-line is-add"><span>123</span><code>+ return timer_clock / calibrated_ticks;</code></div>
|
||||
<div class="diff-line"><span>124</span><code>}</code></div>
|
||||
<div class="diff-line is-add"><span>128</span><code>+ // Evidence link: hypothesis H1 / action E04</code></div>
|
||||
`;
|
||||
|
||||
const app = document.querySelector("#app");
|
||||
const runButton = document.querySelector("#runButton");
|
||||
const resetButton = document.querySelector("#resetButton");
|
||||
const failureMode = document.querySelector("#failureMode");
|
||||
const saveCaseButton = document.querySelector("#saveCaseButton");
|
||||
const runState = document.querySelector("#runState");
|
||||
const stageDetail = document.querySelector("#stageDetail");
|
||||
const runLog = document.querySelector("#runLog");
|
||||
const traceCounter = document.querySelector("#traceCounter");
|
||||
const evidenceDetail = document.querySelector("#evidenceDetail");
|
||||
const actualFrequency = document.querySelector("#actualFrequency");
|
||||
const frequencyError = document.querySelector("#frequencyError");
|
||||
const scopeStatus = document.querySelector("#scopeStatus");
|
||||
const benchState = document.querySelector("#benchState");
|
||||
const uartOutput = document.querySelector("#uartOutput");
|
||||
const uartIndicator = document.querySelector("#uartIndicator");
|
||||
const toast = document.querySelector("#toast");
|
||||
const agentState = document.querySelector("#agentState");
|
||||
const observedFact = document.querySelector("#observedFact");
|
||||
const currentHypothesis = document.querySelector("#currentHypothesis");
|
||||
const currentAction = document.querySelector("#currentAction");
|
||||
const nextAction = document.querySelector("#nextAction");
|
||||
const confidenceMeter = document.querySelector("#confidenceMeter");
|
||||
const confidenceLabel = document.querySelector("#confidenceLabel");
|
||||
const loopButtons = [...document.querySelectorAll("[data-loop]")];
|
||||
const timeline = document.querySelector("#agentTimeline");
|
||||
const eventCounter = document.querySelector("#eventCounter");
|
||||
const tabButtons = [...document.querySelectorAll("[data-tab]")];
|
||||
const inspectorViews = [...document.querySelectorAll("[data-view]")];
|
||||
const detailEventId = document.querySelector("#detailEventId");
|
||||
const detailTitle = document.querySelector("#detailTitle");
|
||||
const detailSummary = document.querySelector("#detailSummary");
|
||||
const detailInput = document.querySelector("#detailInput");
|
||||
const detailDecision = document.querySelector("#detailDecision");
|
||||
const detailTool = document.querySelector("#detailTool");
|
||||
const detailOutput = document.querySelector("#detailOutput");
|
||||
const patchAction = document.querySelector("#patchAction");
|
||||
const patchScore = document.querySelector("#patchScore");
|
||||
const codeDiff = document.querySelector("#codeDiff");
|
||||
const activeTool = document.querySelector("#activeTool");
|
||||
const toolStatus = document.querySelector("#toolStatus");
|
||||
const toolOutput = document.querySelector("#toolOutput");
|
||||
const hardwareCommand = document.querySelector("#hardwareCommand");
|
||||
const benchVerdict = document.querySelector("#benchVerdict");
|
||||
const scopeState = document.querySelector("#scopeState");
|
||||
const scopeCanvas = document.querySelector("#scopeCanvas");
|
||||
const scopeContext = scopeCanvas.getContext("2d");
|
||||
|
||||
const actualFrequency = document.querySelector("#actualFrequency");
|
||||
const frequencyError = document.querySelector("#frequencyError");
|
||||
const boardFact = document.querySelector("#boardFact");
|
||||
const uartFact = document.querySelector("#uartFact");
|
||||
const evidenceButtons = [...document.querySelectorAll("[data-evidence]")];
|
||||
const evidenceDetail = document.querySelector("#evidenceDetail");
|
||||
const toast = document.querySelector("#toast");
|
||||
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
|
||||
let runToken = 0;
|
||||
let currentState = "idle";
|
||||
let runState = "idle";
|
||||
let shownEvents = [];
|
||||
let selectedEventId = "E00";
|
||||
let selectedEvidence = -1;
|
||||
let selectedStage = -1;
|
||||
let currentLoop = "observe";
|
||||
let patchGenerated = false;
|
||||
let activeTab = "decision";
|
||||
let scopeMode = "idle";
|
||||
let scopeAnimationFrame = 0;
|
||||
let toastTimer = 0;
|
||||
|
||||
function delay(milliseconds, token) {
|
||||
const duration = reducedMotion.matches ? Math.min(milliseconds, 120) : milliseconds;
|
||||
function wait(milliseconds, token) {
|
||||
const duration = reducedMotion.matches ? Math.min(milliseconds, 90) : milliseconds;
|
||||
return new Promise((resolve) => {
|
||||
window.setTimeout(() => resolve(token === runToken), duration);
|
||||
});
|
||||
}
|
||||
|
||||
function setRunState(state, label) {
|
||||
currentState = state;
|
||||
runState = state;
|
||||
app.dataset.runState = state;
|
||||
runState.textContent = label;
|
||||
agentState.textContent = label;
|
||||
runButton.disabled = state === "running";
|
||||
failureMode.disabled = state === "running";
|
||||
saveCaseButton.disabled = state !== "passed";
|
||||
|
||||
if (state === "running" && !scopeAnimationFrame) {
|
||||
animateScope();
|
||||
}
|
||||
}
|
||||
|
||||
function setStageStatus(index, status) {
|
||||
const button = stageButtons[index];
|
||||
if (!button) return;
|
||||
if (status === "idle") {
|
||||
button.removeAttribute("data-status");
|
||||
return;
|
||||
function applyDecisionBoard(event) {
|
||||
observedFact.textContent = event.observed;
|
||||
currentHypothesis.textContent = event.hypothesis;
|
||||
currentAction.textContent = event.action;
|
||||
nextAction.textContent = event.next;
|
||||
confidenceMeter.style.width = `${event.confidence}%`;
|
||||
confidenceMeter.style.background = event.confidence >= 90 ? "var(--green)" : "var(--amber)";
|
||||
confidenceLabel.textContent = `${event.confidence >= 90 ? "证据" : "当前"} ${event.confidence}%`;
|
||||
}
|
||||
|
||||
function setLoop(name, status = "active") {
|
||||
if (currentLoop && currentLoop !== name) {
|
||||
const previous = loopButtons.find((button) => button.dataset.loop === currentLoop);
|
||||
if (previous?.dataset.status === "active") previous.dataset.status = "passed";
|
||||
}
|
||||
button.dataset.status = status;
|
||||
currentLoop = name;
|
||||
const button = loopButtons.find((item) => item.dataset.loop === name);
|
||||
if (button) button.dataset.status = status;
|
||||
}
|
||||
|
||||
function completeLoop(name, status = "passed") {
|
||||
const button = loopButtons.find((item) => item.dataset.loop === name);
|
||||
if (button) button.dataset.status = status;
|
||||
}
|
||||
|
||||
function setHardwareTarget(target, command = "等待工具调用") {
|
||||
app.dataset.hardwareTarget = target;
|
||||
hardwareCommand.textContent = command;
|
||||
}
|
||||
|
||||
function eventClass(status) {
|
||||
if (status === "active") return "is-active";
|
||||
if (status === "failed" || status === "blocked") return "is-failed";
|
||||
if (status === "passed") return "is-passed";
|
||||
return "";
|
||||
}
|
||||
|
||||
function appendEvent(event, status = "active") {
|
||||
const eventState = { ...event, status };
|
||||
shownEvents.push(eventState);
|
||||
|
||||
const item = document.createElement("li");
|
||||
item.className = `timeline-event ${eventClass(status)}`.trim();
|
||||
item.dataset.eventId = event.id;
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.innerHTML = `
|
||||
<span class="event-id">${event.id} · ${event.actor}</span>
|
||||
<span></span>
|
||||
<span class="event-time">+${String(shownEvents.length - 1).padStart(2, "0")}:${String((shownEvents.length - 1) * 2).padStart(2, "0")}</span>
|
||||
<strong class="event-title">${event.title}</strong>
|
||||
<span class="event-summary">${event.summary}</span>
|
||||
<span class="event-tool">${event.tool}</span>
|
||||
`;
|
||||
button.addEventListener("click", () => selectEvent(event.id));
|
||||
item.append(button);
|
||||
timeline.append(item);
|
||||
eventCounter.textContent = `${String(shownEvents.length).padStart(2, "0")} EVENTS`;
|
||||
selectEvent(event.id, false);
|
||||
timeline.scrollTop = timeline.scrollHeight;
|
||||
}
|
||||
|
||||
function updateEventStatus(eventId, status) {
|
||||
const event = shownEvents.find((item) => item.id === eventId);
|
||||
if (event) event.status = status;
|
||||
const item = timeline.querySelector(`[data-event-id="${eventId}"]`);
|
||||
if (!item) return;
|
||||
item.classList.remove("is-active", "is-passed", "is-failed");
|
||||
const className = eventClass(status);
|
||||
if (className) item.classList.add(className);
|
||||
}
|
||||
|
||||
function selectEvent(eventId, switchView = true) {
|
||||
const event = shownEvents.find((item) => item.id === eventId);
|
||||
if (!event) return;
|
||||
selectedEventId = eventId;
|
||||
timeline.querySelectorAll("button").forEach((button) => {
|
||||
button.classList.toggle("is-selected", button.parentElement?.dataset.eventId === eventId);
|
||||
});
|
||||
|
||||
detailEventId.textContent = `${event.id} · ${event.actor} · ${event.phase.toUpperCase()}`;
|
||||
detailTitle.textContent = event.title;
|
||||
detailSummary.textContent = event.summary;
|
||||
detailInput.textContent = event.input;
|
||||
detailDecision.textContent = event.decision;
|
||||
detailTool.textContent = event.tool;
|
||||
detailOutput.textContent = event.output;
|
||||
activeTool.textContent = event.tool;
|
||||
toolStatus.textContent = event.status === "active" ? "RUNNING" : event.status.toUpperCase();
|
||||
toolOutput.textContent = event.log;
|
||||
|
||||
if (switchView) selectTab(event.patch ? "patch" : event.actor === "AGENT" ? "decision" : "tool");
|
||||
}
|
||||
|
||||
function selectTab(tab) {
|
||||
activeTab = tab;
|
||||
tabButtons.forEach((button) => {
|
||||
const active = button.dataset.tab === tab;
|
||||
button.classList.toggle("is-active", active);
|
||||
button.setAttribute("aria-selected", String(active));
|
||||
});
|
||||
inspectorViews.forEach((view) => {
|
||||
const active = view.dataset.view === tab;
|
||||
view.classList.toggle("is-active", active);
|
||||
view.hidden = !active;
|
||||
});
|
||||
}
|
||||
|
||||
function revealPatch() {
|
||||
patchGenerated = true;
|
||||
patchAction.textContent = "E04 · H1 · frequency.c";
|
||||
patchScore.textContent = "+5 −3 · REVIEWED";
|
||||
codeDiff.innerHTML = PATCH_HTML;
|
||||
}
|
||||
|
||||
function setEvidenceStatus(index, status, label) {
|
||||
const button = evidenceButtons[index];
|
||||
if (!button) return;
|
||||
if (status === "idle") {
|
||||
button.removeAttribute("data-status");
|
||||
} else {
|
||||
button.dataset.status = status;
|
||||
}
|
||||
if (label) {
|
||||
button.querySelector("span").textContent = label;
|
||||
}
|
||||
if (selectedEvidence === index) {
|
||||
showEvidence(index);
|
||||
}
|
||||
}
|
||||
|
||||
function selectStage(index) {
|
||||
selectedStage = index;
|
||||
stageButtons.forEach((button, buttonIndex) => {
|
||||
button.classList.toggle("is-selected", buttonIndex === index);
|
||||
});
|
||||
|
||||
const stage = STAGES[index];
|
||||
if (!stage) {
|
||||
stageDetail.innerHTML = `
|
||||
<span class="detail-index">READY</span>
|
||||
<h3>等待开始工程闭环</h3>
|
||||
<p>执行过程将绑定代码、构建、下载、串口与物理测量事实。</p>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const status = stageButtons[index].dataset.status || "idle";
|
||||
const statusLabel = {
|
||||
idle: "PENDING",
|
||||
active: "RUNNING",
|
||||
passed: "VERIFIED",
|
||||
failed: "FAILED",
|
||||
}[status];
|
||||
|
||||
stageDetail.innerHTML = `
|
||||
<span class="detail-index">${String(index + 1).padStart(2, "0")} · ${statusLabel}</span>
|
||||
<h3>${stage.title}</h3>
|
||||
<p>${stage.summary}</p>
|
||||
`;
|
||||
if (status === "idle") button.removeAttribute("data-status");
|
||||
else button.dataset.status = status;
|
||||
if (label) button.querySelector("span").textContent = label;
|
||||
if (selectedEvidence === index) showEvidence(index);
|
||||
}
|
||||
|
||||
function showEvidence(index) {
|
||||
selectedEvidence = index;
|
||||
evidenceButtons.forEach((button, buttonIndex) => {
|
||||
button.classList.toggle("is-selected", buttonIndex === index);
|
||||
button.classList.toggle("is-selected", index === buttonIndex);
|
||||
});
|
||||
|
||||
const status = evidenceButtons[index]?.dataset.status || "idle";
|
||||
const detail = EVIDENCE[index]?.[status] || EVIDENCE[index]?.idle;
|
||||
evidenceDetail.textContent = detail || "暂无证据详情。";
|
||||
evidenceDetail.textContent = EVIDENCE_DETAILS[index]?.[status] || EVIDENCE_DETAILS[index]?.idle;
|
||||
}
|
||||
|
||||
function appendLog(message, tone = "") {
|
||||
const line = document.createElement("span");
|
||||
line.className = tone ? `log-${tone}` : "";
|
||||
line.textContent = `${runLog.textContent.trim() ? "\n" : ""}${message}`;
|
||||
runLog.append(line);
|
||||
runLog.scrollTop = runLog.scrollHeight;
|
||||
}
|
||||
|
||||
function resetDemo({ keepFailure = true } = {}) {
|
||||
runToken += 1;
|
||||
function setScopeMode(mode) {
|
||||
scopeMode = mode;
|
||||
window.cancelAnimationFrame(scopeAnimationFrame);
|
||||
scopeAnimationFrame = 0;
|
||||
if (!keepFailure) failureMode.checked = false;
|
||||
|
||||
stageButtons.forEach((button) => {
|
||||
button.removeAttribute("data-status");
|
||||
button.classList.remove("is-selected");
|
||||
});
|
||||
evidenceButtons.forEach((button, index) => {
|
||||
button.removeAttribute("data-status");
|
||||
button.classList.remove("is-selected");
|
||||
if (index === 5) button.querySelector("span").textContent = "ioProbe 待测";
|
||||
if (index === 6) button.querySelector("span").textContent = "Aggregate 待定";
|
||||
});
|
||||
|
||||
selectedStage = -1;
|
||||
selectedEvidence = -1;
|
||||
traceCounter.textContent = "0 / 6";
|
||||
runLog.innerHTML = '<span class="log-muted">$ hwlab case run freq-1khz-accuracy</span>\n<span class="log-muted"> 尚未执行</span>';
|
||||
evidenceDetail.textContent = "选择证据节点查看边界事实。";
|
||||
actualFrequency.innerHTML = "— <small>Hz</small>";
|
||||
frequencyError.textContent = "—";
|
||||
scopeStatus.textContent = "等待采集";
|
||||
benchState.textContent = "设备待命";
|
||||
uartOutput.textContent = "[idle] waiting for firmware\n[idle] measurement channel ready";
|
||||
uartIndicator.classList.remove("is-active");
|
||||
setRunState("idle", "等待指令");
|
||||
selectStage(-1);
|
||||
drawScope(0);
|
||||
}
|
||||
|
||||
async function runDemo() {
|
||||
if (currentState === "running") return;
|
||||
|
||||
resetDemo();
|
||||
const token = runToken;
|
||||
const shouldFail = failureMode.checked;
|
||||
setRunState("running", "闭环执行中");
|
||||
benchState.textContent = "HWPOD 已锁定";
|
||||
scopeStatus.textContent = "同步设备";
|
||||
runLog.innerHTML = "";
|
||||
appendLog("$ hwlab case run freq-1khz-accuracy", "muted");
|
||||
setEvidenceStatus(0, "passed");
|
||||
setEvidenceStatus(1, "active");
|
||||
|
||||
for (let index = 0; index < STAGES.length; index += 1) {
|
||||
if (token !== runToken) return;
|
||||
|
||||
setStageStatus(index, "active");
|
||||
selectStage(index);
|
||||
traceCounter.textContent = `${index} / 6`;
|
||||
appendLog(`→ ${STAGES[index].title}`, "active");
|
||||
|
||||
if (index === 3) benchState.textContent = "正在写入固件";
|
||||
if (index === 4) {
|
||||
benchState.textContent = "固件运行中";
|
||||
uartIndicator.classList.add("is-active");
|
||||
uartOutput.textContent = "[boot] 71-FREQ rev.C\n[capture] timer input ready";
|
||||
}
|
||||
if (index === 5) {
|
||||
benchState.textContent = "ioProbe 采集中";
|
||||
scopeStatus.textContent = "采集中";
|
||||
setEvidenceStatus(5, "active", "ioProbe 采集中");
|
||||
}
|
||||
|
||||
const shouldContinue = await delay(index === 5 ? 850 : 650, token);
|
||||
if (!shouldContinue) return;
|
||||
|
||||
if (index === 5 && shouldFail) {
|
||||
setStageStatus(index, "failed");
|
||||
appendLog("probe CH1 frequency · 972.4 Hz · outside tolerance", "fail");
|
||||
traceCounter.textContent = "5 / 6";
|
||||
setEvidenceStatus(5, "failed", "ioProbe 972.4 Hz");
|
||||
setEvidenceStatus(6, "blocked", "Aggregate BLOCKED");
|
||||
actualFrequency.innerHTML = "972.4 <small>Hz</small>";
|
||||
frequencyError.textContent = "−2.76%";
|
||||
scopeStatus.textContent = "阈值超限";
|
||||
benchState.textContent = "物理验证未通过";
|
||||
uartOutput.textContent = "[capture] stable, samples=128\n[result] freq=972.4Hz, tolerance=FAIL";
|
||||
setRunState("failed", "证据阻断");
|
||||
selectStage(index);
|
||||
showEvidence(6);
|
||||
drawScope(0);
|
||||
return;
|
||||
}
|
||||
|
||||
setStageStatus(index, "passed");
|
||||
appendLog(STAGES[index].log, "pass");
|
||||
traceCounter.textContent = `${index + 1} / 6`;
|
||||
selectStage(index);
|
||||
|
||||
if (index === 1) setEvidenceStatus(1, "passed");
|
||||
if (index === 2) setEvidenceStatus(2, "passed");
|
||||
if (index === 3) setEvidenceStatus(3, "passed");
|
||||
if (index === 4) {
|
||||
setEvidenceStatus(4, "passed");
|
||||
uartOutput.textContent = "[capture] stable, samples=128\n[timer] clock=72000000Hz, overflow=0";
|
||||
}
|
||||
}
|
||||
|
||||
setEvidenceStatus(5, "passed", "ioProbe 999.8 Hz");
|
||||
setEvidenceStatus(6, "passed", "Aggregate PASS");
|
||||
actualFrequency.innerHTML = "999.8 <small>Hz</small>";
|
||||
frequencyError.textContent = "−0.02%";
|
||||
scopeStatus.textContent = "测量通过";
|
||||
benchState.textContent = "验证完成 · PASS";
|
||||
uartOutput.textContent = "[capture] stable, samples=128\n[result] freq=999.8Hz, tolerance=PASS";
|
||||
appendLog("verdict Aggregate PASS · evidence sealed", "pass");
|
||||
setRunState("passed", "Aggregate PASS");
|
||||
showEvidence(6);
|
||||
drawScope(0);
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
window.clearTimeout(toastTimer);
|
||||
toast.textContent = message;
|
||||
toast.classList.add("is-visible");
|
||||
toastTimer = window.setTimeout(() => toast.classList.remove("is-visible"), 2600);
|
||||
if (mode === "sampling") animateScope();
|
||||
else drawScope(0);
|
||||
}
|
||||
|
||||
function drawScope(phase = 0) {
|
||||
@@ -329,12 +487,12 @@ function drawScope(phase = 0) {
|
||||
const cssWidth = width / ratio;
|
||||
const cssHeight = height / ratio;
|
||||
scopeContext.clearRect(0, 0, cssWidth, cssHeight);
|
||||
scopeContext.fillStyle = "#0c1418";
|
||||
scopeContext.fillStyle = "#0b1418";
|
||||
scopeContext.fillRect(0, 0, cssWidth, cssHeight);
|
||||
|
||||
scopeContext.strokeStyle = "rgba(94, 132, 143, 0.18)";
|
||||
scopeContext.strokeStyle = "rgba(95, 135, 145, 0.18)";
|
||||
scopeContext.lineWidth = 1;
|
||||
for (let x = 0; x <= cssWidth; x += cssWidth / 10) {
|
||||
|
||||
for (let x = 0; x <= cssWidth; x += cssWidth / 9) {
|
||||
scopeContext.beginPath();
|
||||
scopeContext.moveTo(x, 0);
|
||||
scopeContext.lineTo(x, cssHeight);
|
||||
@@ -347,32 +505,30 @@ function drawScope(phase = 0) {
|
||||
scopeContext.stroke();
|
||||
}
|
||||
|
||||
const amplitude = cssHeight * 0.27;
|
||||
const idle = scopeMode === "idle";
|
||||
const failed = scopeMode === "failed";
|
||||
const amplitude = cssHeight * 0.28;
|
||||
const center = cssHeight * 0.5;
|
||||
const period = currentState === "failed" ? 51 : 43;
|
||||
const waveColor = currentState === "failed" ? "#ff766e" : "#53dce1";
|
||||
const muted = currentState === "idle";
|
||||
|
||||
const period = failed ? 52 : 43;
|
||||
scopeContext.beginPath();
|
||||
for (let x = 0; x <= cssWidth; x += 2) {
|
||||
const signal = muted
|
||||
? Math.sin(x / 25) * 1.2
|
||||
: Math.sin((x + phase) * ((Math.PI * 2) / period)) * amplitude;
|
||||
const noise = currentState === "running" ? Math.sin((x + phase * 1.9) / 5) * 0.8 : 0;
|
||||
const signal = idle ? Math.sin(x / 24) * 1.1 : Math.sin((x + phase) * ((Math.PI * 2) / period)) * amplitude;
|
||||
const noise = scopeMode === "sampling" ? Math.sin((x + phase * 1.7) / 5) * 0.8 : 0;
|
||||
const y = center + signal + noise;
|
||||
if (x === 0) scopeContext.moveTo(x, y);
|
||||
else scopeContext.lineTo(x, y);
|
||||
}
|
||||
scopeContext.strokeStyle = muted ? "#496169" : waveColor;
|
||||
scopeContext.lineWidth = 1.4;
|
||||
scopeContext.shadowColor = muted ? "transparent" : waveColor;
|
||||
scopeContext.shadowBlur = muted ? 0 : 5;
|
||||
const color = failed ? "#ff766e" : idle ? "#486169" : "#50dbe0";
|
||||
scopeContext.strokeStyle = color;
|
||||
scopeContext.lineWidth = 1.3;
|
||||
scopeContext.shadowColor = idle ? "transparent" : color;
|
||||
scopeContext.shadowBlur = idle ? 0 : 5;
|
||||
scopeContext.stroke();
|
||||
scopeContext.shadowBlur = 0;
|
||||
}
|
||||
|
||||
function animateScope(timestamp = 0) {
|
||||
if (currentState !== "running") {
|
||||
if (scopeMode !== "sampling") {
|
||||
scopeAnimationFrame = 0;
|
||||
return;
|
||||
}
|
||||
@@ -380,8 +536,171 @@ function animateScope(timestamp = 0) {
|
||||
scopeAnimationFrame = window.requestAnimationFrame(animateScope);
|
||||
}
|
||||
|
||||
stageButtons.forEach((button, index) => {
|
||||
button.addEventListener("click", () => selectStage(index));
|
||||
function resetDemo({ keepFailure = true } = {}) {
|
||||
runToken += 1;
|
||||
if (!keepFailure) failureMode.checked = false;
|
||||
shownEvents = [];
|
||||
selectedEventId = "E00";
|
||||
selectedEvidence = -1;
|
||||
currentLoop = "observe";
|
||||
patchGenerated = false;
|
||||
timeline.innerHTML = "";
|
||||
loopButtons.forEach((button) => button.removeAttribute("data-status"));
|
||||
loopButtons.find((button) => button.dataset.loop === "observe").dataset.status = "active";
|
||||
evidenceButtons.forEach((button, index) => {
|
||||
button.removeAttribute("data-status");
|
||||
button.classList.remove("is-selected");
|
||||
if (index === 7) button.querySelector("span").textContent = "Verdict 待定";
|
||||
});
|
||||
|
||||
patchAction.textContent = "尚未生成变更";
|
||||
patchScore.textContent = "WAITING";
|
||||
codeDiff.innerHTML = '<div class="empty-change"><span>NO PATCH YET</span><p>AGENT 必须先获得工程事实并验证假设,代码变更才会出现。</p></div>';
|
||||
evidenceDetail.textContent = "每个证据节点都引用产生它的 AGENT 行动。";
|
||||
actualFrequency.innerHTML = "— <small>Hz</small>";
|
||||
frequencyError.textContent = "—";
|
||||
scopeState.textContent = "等待 AGENT 调用";
|
||||
benchVerdict.textContent = "SESSION BOUND";
|
||||
boardFact.textContent = "已绑定 · 待操作";
|
||||
uartFact.textContent = "等待固件";
|
||||
setHardwareTarget("none");
|
||||
setScopeMode("idle");
|
||||
setRunState("idle", "已接管 · 等待执行");
|
||||
applyDecisionBoard(INITIAL_EVENT);
|
||||
appendEvent(INITIAL_EVENT, "idle");
|
||||
selectTab("decision");
|
||||
}
|
||||
|
||||
async function runDemo() {
|
||||
if (runState === "running") return;
|
||||
resetDemo();
|
||||
const token = runToken;
|
||||
const shouldFail = failureMode.checked;
|
||||
|
||||
setRunState("running", "AGENT 正在观察工程");
|
||||
updateEventStatus("E00", "passed");
|
||||
setEvidenceStatus(0, "passed");
|
||||
setEvidenceStatus(1, "active");
|
||||
|
||||
for (const event of RUN_EVENTS) {
|
||||
if (token !== runToken) return;
|
||||
setLoop(event.phase);
|
||||
applyDecisionBoard(event);
|
||||
appendEvent(event, "active");
|
||||
setHardwareTarget(event.target, event.target === "none" ? `${event.tool} · local action` : `${event.tool} · command dispatched`);
|
||||
agentState.textContent = `${event.id} · ${event.title}`;
|
||||
|
||||
if (event.patch) {
|
||||
revealPatch();
|
||||
selectTab("patch");
|
||||
} else if (event.actor !== "AGENT") {
|
||||
selectTab("tool");
|
||||
} else {
|
||||
selectTab("decision");
|
||||
}
|
||||
|
||||
if (event.id === "E05") boardFact.textContent = "固件已构建 · 待写入";
|
||||
if (event.id === "E06") boardFact.textContent = "正在写入固件";
|
||||
if (event.id === "E07") {
|
||||
boardFact.textContent = "固件运行中";
|
||||
uartFact.textContent = "采集 128 samples";
|
||||
}
|
||||
if (event.id === "E08") {
|
||||
scopeState.textContent = "AGENT 正在采样";
|
||||
setEvidenceStatus(6, "active");
|
||||
setScopeMode("sampling");
|
||||
}
|
||||
|
||||
const continued = await wait(event.id === "E08" ? 720 : 520, token);
|
||||
if (!continued) return;
|
||||
|
||||
if (event.id === "E08" && shouldFail) {
|
||||
updateEventStatus(event.id, "failed");
|
||||
const failedMeasurement = shownEvents.find((item) => item.id === event.id);
|
||||
failedMeasurement.output = "ioProbe 972.4 Hz;误差 −2.76%;超出阈值";
|
||||
failedMeasurement.log = "$ ioprobe.measure --channel CH1 --metric frequency\narm FREQ_OUT\nsample physical waveform\nactual 972.4 Hz\nerror -2.76%\nresult OUTSIDE TOLERANCE";
|
||||
setEvidenceStatus(6, "failed");
|
||||
actualFrequency.innerHTML = "972.4 <small>Hz</small>";
|
||||
frequencyError.textContent = "−2.76%";
|
||||
scopeState.textContent = "物理阈值超限";
|
||||
benchVerdict.textContent = "PHYSICAL FAIL";
|
||||
setScopeMode("failed");
|
||||
selectEvent(event.id, false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (event.id === "E08") {
|
||||
const passedMeasurement = shownEvents.find((item) => item.id === event.id);
|
||||
passedMeasurement.output = "ioProbe 999.8 Hz;误差 −0.02%;满足阈值";
|
||||
passedMeasurement.log = "$ ioprobe.measure --channel CH1 --metric frequency\narm FREQ_OUT\nsample physical waveform\nactual 999.8 Hz\nerror -0.02%\nresult WITHIN TOLERANCE";
|
||||
actualFrequency.innerHTML = "999.8 <small>Hz</small>";
|
||||
frequencyError.textContent = "−0.02%";
|
||||
scopeState.textContent = "独立测量通过";
|
||||
setScopeMode("passed");
|
||||
}
|
||||
|
||||
updateEventStatus(event.id, "passed");
|
||||
if (event.id === "E04") setEvidenceStatus(2, "passed");
|
||||
if (event.evidence !== undefined) setEvidenceStatus(event.evidence, "passed");
|
||||
selectEvent(event.id, false);
|
||||
}
|
||||
|
||||
if (token !== runToken) return;
|
||||
const verdict = shouldFail ? FAILURE_EVENT : SUCCESS_EVENT;
|
||||
setLoop("reflect");
|
||||
applyDecisionBoard(verdict);
|
||||
appendEvent(verdict, "active");
|
||||
selectTab("decision");
|
||||
setHardwareTarget("none", shouldFail ? "physical fact rejected completion" : "evidence aggregation");
|
||||
agentState.textContent = shouldFail ? "AGENT 正在拒绝错误结果" : "AGENT 正在聚合证据";
|
||||
|
||||
const continued = await wait(620, token);
|
||||
if (!continued) return;
|
||||
|
||||
updateEventStatus(verdict.id, "passed");
|
||||
completeLoop("reflect", "passed");
|
||||
setEvidenceStatus(1, "passed");
|
||||
|
||||
if (shouldFail) {
|
||||
setEvidenceStatus(7, "blocked", "Verdict BLOCKED");
|
||||
setLoop("hypothesize", "active");
|
||||
applyDecisionBoard(FAILURE_EVENT);
|
||||
benchVerdict.textContent = "BLOCKED · BACK TO DIAGNOSIS";
|
||||
boardFact.textContent = "保持现场 · 等待新计划";
|
||||
uartFact.textContent = "软件事实保留";
|
||||
setRunState("failed", "拒绝封存 · 已返回诊断");
|
||||
showEvidence(7);
|
||||
} else {
|
||||
setEvidenceStatus(7, "passed", "Verdict PASS");
|
||||
actualFrequency.innerHTML = "999.8 <small>Hz</small>";
|
||||
frequencyError.textContent = "−0.02%";
|
||||
scopeState.textContent = "独立测量通过";
|
||||
benchVerdict.textContent = "AGGREGATE PASS";
|
||||
boardFact.textContent = "验证完成 · 固件保留";
|
||||
uartFact.textContent = "稳定 · 128 samples";
|
||||
setScopeMode("passed");
|
||||
setRunState("passed", "证据完整 · Aggregate PASS");
|
||||
showEvidence(7);
|
||||
}
|
||||
selectEvent(verdict.id, false);
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
window.clearTimeout(toastTimer);
|
||||
toast.textContent = message;
|
||||
toast.classList.add("is-visible");
|
||||
toastTimer = window.setTimeout(() => toast.classList.remove("is-visible"), 2500);
|
||||
}
|
||||
|
||||
tabButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => selectTab(button.dataset.tab));
|
||||
});
|
||||
|
||||
loopButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const matching = [...shownEvents].reverse().find((event) => event.phase === button.dataset.loop);
|
||||
if (matching) selectEvent(matching.id);
|
||||
});
|
||||
});
|
||||
|
||||
evidenceButtons.forEach((button, index) => {
|
||||
@@ -391,14 +710,14 @@ evidenceButtons.forEach((button, index) => {
|
||||
runButton.addEventListener("click", runDemo);
|
||||
resetButton.addEventListener("click", () => resetDemo({ keepFailure: true }));
|
||||
failureMode.addEventListener("change", () => {
|
||||
if (currentState !== "idle") resetDemo({ keepFailure: true });
|
||||
showToast(failureMode.checked ? "失败演示已开启:物理测量将阻断聚合结果" : "已恢复通过路径");
|
||||
if (runState !== "idle") resetDemo({ keepFailure: true });
|
||||
showToast(failureMode.checked ? "失败演示开启:AGENT 将拒绝封存并返回诊断" : "已恢复通过路径");
|
||||
});
|
||||
saveCaseButton.addEventListener("click", () => {
|
||||
if (currentState !== "passed") return;
|
||||
showToast("回归 Case 已保存:freq-1khz-accuracy / evidence sealed");
|
||||
if (runState !== "passed") return;
|
||||
showToast("回归 Case 已封存:freq-1khz-accuracy · Agent Trace + Physical Evidence");
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => drawScope(0));
|
||||
window.addEventListener("load", () => drawScope(0));
|
||||
|
||||
resetDemo({ keepFailure: false });
|
||||
|
||||
@@ -5,220 +5,253 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="HWLAB 第一性原理概念展示:从工程任务到真实硬件证据闭环。"
|
||||
content="HWLAB AGENT 主导的真实硬件研发闭环概念展示。"
|
||||
/>
|
||||
<title>HWLAB · 真实硬件研发闭环</title>
|
||||
<title>HWLAB · AGENT 真实硬件研发闭环</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell" id="app" data-run-state="idle">
|
||||
<div class="app-shell" id="app" data-run-state="idle" data-hardware-target="none">
|
||||
<header class="topbar">
|
||||
<div class="brand-block" aria-label="HWLAB">
|
||||
<div class="brand" aria-label="HWLAB">
|
||||
<span class="brand-mark" aria-hidden="true">HW</span>
|
||||
<span class="brand-name">HWLAB</span>
|
||||
<span class="brand-divider" aria-hidden="true"></span>
|
||||
<span class="brand-tagline">云端真实硬件研发闭环</span>
|
||||
<strong>HWLAB</strong>
|
||||
<span class="brand-rule" aria-hidden="true"></span>
|
||||
<span class="brand-caption">真实硬件研发闭环</span>
|
||||
</div>
|
||||
|
||||
<div class="target-context" aria-label="当前目标">
|
||||
<span class="context-label">TARGET</span>
|
||||
<div class="top-target">
|
||||
<span>ACTIVE TARGET</span>
|
||||
<strong>71-FREQ / STM32F103</strong>
|
||||
</div>
|
||||
|
||||
<div class="system-status" aria-label="系统状态">
|
||||
<span class="status-pill"><i class="status-dot"></i>NC01 · v03</span>
|
||||
<span class="status-pill status-pill--hardware"><i class="status-dot"></i>HWPOD 已占用</span>
|
||||
<span class="status-pill"><i class="status-dot"></i>Session 就绪</span>
|
||||
<div class="top-status" aria-label="运行面状态">
|
||||
<span><i></i>NC01 · v03</span>
|
||||
<span><i></i>HWPOD LOCKED</span>
|
||||
<span><i></i>SESSION READY</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="workspace">
|
||||
<aside class="task-panel" aria-label="项目与任务上下文">
|
||||
<div class="task-panel-heading">
|
||||
<span class="eyebrow">PROJECT</span>
|
||||
<div class="project-copy">
|
||||
<h1>71-FREQ</h1>
|
||||
<aside class="context-rail" aria-label="任务上下文">
|
||||
<section class="project-heading">
|
||||
<span class="section-kicker">PROJECT</span>
|
||||
<div class="project-name">
|
||||
<h2>71-FREQ</h2>
|
||||
<p>信号调理模块</p>
|
||||
</div>
|
||||
<span class="project-code" aria-hidden="true">71</span>
|
||||
</div>
|
||||
<span class="project-monogram" aria-hidden="true">71</span>
|
||||
</section>
|
||||
|
||||
<nav class="task-list" aria-label="研发任务">
|
||||
<button class="task-item task-item--active" type="button" title="修复 1 kHz 输入频率误差">
|
||||
<span class="task-marker">01</span>
|
||||
<span class="task-text">
|
||||
<strong>修复 1 kHz 输入频率误差</strong>
|
||||
<small><i class="state-dot state-dot--active"></i>等待验证</small>
|
||||
</span>
|
||||
</button>
|
||||
<button class="task-item" type="button" title="校准输入增益曲线">
|
||||
<span class="task-marker">02</span>
|
||||
<span class="task-text">
|
||||
<strong>校准输入增益曲线</strong>
|
||||
<small><i class="state-dot state-dot--done"></i>已完成</small>
|
||||
</span>
|
||||
</button>
|
||||
<button class="task-item" type="button" title="验证低频抗抖动策略">
|
||||
<span class="task-marker">03</span>
|
||||
<span class="task-text">
|
||||
<strong>验证低频抗抖动策略</strong>
|
||||
<small><i class="state-dot"></i>待排期</small>
|
||||
</span>
|
||||
</button>
|
||||
</nav>
|
||||
<section class="active-task">
|
||||
<span class="task-index">T-01</span>
|
||||
<div>
|
||||
<span class="section-kicker">ACTIVE TASK</span>
|
||||
<h3>修复 1 kHz 输入频率误差</h3>
|
||||
<p><i></i>已交由 Engineering Agent</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="context-facts">
|
||||
<span class="eyebrow">BOUND CONTEXT</span>
|
||||
<section class="brief-block">
|
||||
<span class="section-kicker">MISSION BRIEF</span>
|
||||
<p>修复频率换算偏差,并在真实板上形成独立物理证据。</p>
|
||||
</section>
|
||||
|
||||
<section class="bound-context">
|
||||
<span class="section-kicker">BOUND CONTEXT</span>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Workspace</dt>
|
||||
<dd>CONSTAR_workspace</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Board</dt>
|
||||
<dd>STM32F103</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Probe</dt>
|
||||
<dd>CMSIS-DAP</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Case</dt>
|
||||
<dd>freq-1khz-accuracy</dd>
|
||||
</div>
|
||||
<div><dt>Workspace</dt><dd>CONSTAR_workspace</dd></div>
|
||||
<div><dt>Board</dt><dd>STM32F103</dd></div>
|
||||
<div><dt>Probe</dt><dd>CMSIS-DAP</dd></div>
|
||||
<div><dt>Case</dt><dd>freq-1khz-accuracy</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="control-boundary">
|
||||
<span class="boundary-icon" aria-hidden="true">⌁</span>
|
||||
<div>
|
||||
<span class="section-kicker">CONTROL BOUNDARY</span>
|
||||
<p>代码、工具、设备和证据均绑定本次 Session。</p>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<section class="agent-panel" aria-labelledby="agent-title">
|
||||
<div class="panel-title-row">
|
||||
<div>
|
||||
<span class="eyebrow">ENGINEERING AGENT</span>
|
||||
<h2 id="agent-title">受控工程工作台</h2>
|
||||
<section class="agent-stage" aria-labelledby="agent-title">
|
||||
<header class="agent-presence">
|
||||
<div class="agent-avatar" aria-hidden="true">
|
||||
<span>A7</span>
|
||||
<i></i>
|
||||
</div>
|
||||
<span class="run-state" id="runState" aria-live="polite">等待指令</span>
|
||||
</div>
|
||||
<div class="agent-identity">
|
||||
<span class="section-kicker">ENGINEERING AGENT · ONLINE</span>
|
||||
<h1 id="agent-title">ALTA-7 <small>Embedded Systems</small></h1>
|
||||
</div>
|
||||
<div class="session-identity">
|
||||
<span>SESSION</span>
|
||||
<strong>agt_71freq_024</strong>
|
||||
</div>
|
||||
<span class="agent-state" id="agentState" aria-live="polite">已接管 · 等待执行</span>
|
||||
</header>
|
||||
|
||||
<div class="command-bar" aria-label="工程指令">
|
||||
<span class="command-symbol" aria-hidden="true">›_</span>
|
||||
<p>修复 1 kHz 输入下的频率误差,并在真实板上完成验证</p>
|
||||
<label class="failure-toggle" title="演示物理测量未通过时的阻断状态">
|
||||
<section class="mission-console" aria-label="Agent 任务控制">
|
||||
<div class="mission-command">
|
||||
<span class="prompt-glyph" aria-hidden="true">›_</span>
|
||||
<div>
|
||||
<span>OBJECTIVE</span>
|
||||
<strong>修复 1 kHz 输入频率误差,并在真实板上完成验证</strong>
|
||||
</div>
|
||||
</div>
|
||||
<label class="failure-switch" title="演示物理测量失败后的 Agent 重新诊断">
|
||||
<input id="failureMode" type="checkbox" />
|
||||
<span class="toggle-track" aria-hidden="true"><i></i></span>
|
||||
<span class="switch-track" aria-hidden="true"><i></i></span>
|
||||
<span>失败演示</span>
|
||||
</label>
|
||||
<button class="icon-button" id="resetButton" type="button" title="重置演示" aria-label="重置演示">
|
||||
↻
|
||||
</button>
|
||||
<button class="reset-button" id="resetButton" type="button" title="重置演示" aria-label="重置演示">↻</button>
|
||||
<button class="run-button" id="runButton" type="button">
|
||||
<span aria-hidden="true">▶</span>
|
||||
运行验证
|
||||
开始 AGENT RUN
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ol class="stage-strip" aria-label="执行阶段">
|
||||
<li><button type="button" data-stage="0"><span>01</span><strong>分析工程</strong><i></i></button></li>
|
||||
<li><button type="button" data-stage="1"><span>02</span><strong>修改代码</strong><i></i></button></li>
|
||||
<li><button type="button" data-stage="2"><span>03</span><strong>Keil 编译</strong><i></i></button></li>
|
||||
<li><button type="button" data-stage="3"><span>04</span><strong>下载固件</strong><i></i></button></li>
|
||||
<li><button type="button" data-stage="4"><span>05</span><strong>UART 采集</strong><i></i></button></li>
|
||||
<li><button type="button" data-stage="5"><span>06</span><strong>物理测量</strong><i></i></button></li>
|
||||
</ol>
|
||||
|
||||
<div class="work-surface">
|
||||
<section class="editor-pane" aria-label="代码变更">
|
||||
<header class="surface-header">
|
||||
<div class="file-tabs">
|
||||
<button class="file-tab file-tab--active" type="button">frequency.c <i></i></button>
|
||||
<button class="file-tab" type="button">timer_capture.h</button>
|
||||
</div>
|
||||
<span class="change-summary"><b>+5</b> <em>−3</em></span>
|
||||
</header>
|
||||
<div class="code-view" role="region" aria-label="frequency.c 代码差异" tabindex="0">
|
||||
<div class="code-line"><span>118</span><code>static float capture_to_hz(uint32_t ticks)</code></div>
|
||||
<div class="code-line"><span>119</span><code>{</code></div>
|
||||
<div class="code-line code-line--removed"><span>120</span><code>- return TIMER_CLOCK_HZ / ticks;</code></div>
|
||||
<div class="code-line code-line--removed"><span>121</span><code>- // APB prescaler assumed to be 1</code></div>
|
||||
<div class="code-line code-line--added"><span>120</span><code>+ const uint32_t timer_clock = get_timer_clock_hz();</code></div>
|
||||
<div class="code-line code-line--added"><span>121</span><code>+ const float calibrated_ticks =</code></div>
|
||||
<div class="code-line code-line--added"><span>122</span><code>+ (float)ticks - capture_offset_ticks;</code></div>
|
||||
<div class="code-line code-line--added"><span>123</span><code>+ return timer_clock / calibrated_ticks;</code></div>
|
||||
<div class="code-line"><span>124</span><code>}</code></div>
|
||||
<div class="code-line"><span>125</span><code></code></div>
|
||||
<div class="code-line code-line--context"><span>126</span><code>float frequency_read(void)</code></div>
|
||||
<div class="code-line code-line--context"><span>127</span><code>{</code></div>
|
||||
<div class="code-line code-line--added"><span>128</span><code>+ capture_offset_ticks = calibration.capture_offset;</code></div>
|
||||
<div class="code-line code-line--context"><span>129</span><code> return capture_to_hz(last_capture_ticks);</code></div>
|
||||
<div class="code-line code-line--context"><span>130</span><code>}</code></div>
|
||||
<section class="decision-board" aria-label="Agent 当前决策面">
|
||||
<article>
|
||||
<span>OBSERVED FACT</span>
|
||||
<p id="observedFact">任务、工程和真实硬件已完成上下文绑定</p>
|
||||
</article>
|
||||
<article class="decision-hypothesis">
|
||||
<span>CURRENT HYPOTHESIS</span>
|
||||
<p id="currentHypothesis">待验证:TIM2 时钟推导与板级配置不一致</p>
|
||||
<div class="confidence" title="假设置信状态">
|
||||
<i id="confidenceMeter"></i><small id="confidenceLabel">初始 42%</small>
|
||||
</div>
|
||||
</article>
|
||||
<article>
|
||||
<span>CURRENT ACTION</span>
|
||||
<p id="currentAction">等待授权读取工程事实</p>
|
||||
</article>
|
||||
<article>
|
||||
<span>NEXT ACTION</span>
|
||||
<p id="nextAction">检查 Keil 工程与 TIM2 配置</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<nav class="agent-loop" aria-label="Agent 工作循环">
|
||||
<button type="button" data-loop="observe"><i>01</i><span>观察</span><small>Observe</small></button>
|
||||
<button type="button" data-loop="hypothesize"><i>02</i><span>假设</span><small>Hypothesize</small></button>
|
||||
<button type="button" data-loop="plan"><i>03</i><span>计划</span><small>Plan</small></button>
|
||||
<button type="button" data-loop="act"><i>04</i><span>行动</span><small>Act</small></button>
|
||||
<button type="button" data-loop="verify"><i>05</i><span>验证</span><small>Verify</small></button>
|
||||
<button type="button" data-loop="reflect"><i>06</i><span>反思</span><small>Reflect</small></button>
|
||||
</nav>
|
||||
|
||||
<div class="agent-workbench">
|
||||
<section class="timeline-pane" aria-labelledby="timeline-title">
|
||||
<header class="pane-header">
|
||||
<div>
|
||||
<span class="section-kicker">LIVE AGENT TRACE</span>
|
||||
<h2 id="timeline-title">行动时间线</h2>
|
||||
</div>
|
||||
<span id="eventCounter">01 EVENT</span>
|
||||
</header>
|
||||
<ol class="agent-timeline" id="agentTimeline" aria-live="polite"></ol>
|
||||
</section>
|
||||
|
||||
<aside class="trace-pane" aria-label="Agent 执行记录">
|
||||
<header class="surface-header">
|
||||
<strong>执行记录</strong>
|
||||
<span id="traceCounter">0 / 6</span>
|
||||
<section class="inspector-pane" aria-labelledby="inspector-title">
|
||||
<header class="inspector-header">
|
||||
<div>
|
||||
<span class="section-kicker">ACTION INSPECTOR</span>
|
||||
<h2 id="inspector-title">AGENT 决策摘要</h2>
|
||||
</div>
|
||||
<div class="inspector-tabs" role="tablist" aria-label="行动详情视图">
|
||||
<button class="is-active" type="button" role="tab" aria-selected="true" data-tab="decision">决策</button>
|
||||
<button type="button" role="tab" aria-selected="false" data-tab="patch">变更</button>
|
||||
<button type="button" role="tab" aria-selected="false" data-tab="tool">工具输出</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="stage-detail" id="stageDetail">
|
||||
<span class="detail-index">READY</span>
|
||||
<h3>等待开始工程闭环</h3>
|
||||
<p>执行过程将绑定代码、构建、下载、串口与物理测量事实。</p>
|
||||
|
||||
<div class="inspector-view is-active" data-view="decision">
|
||||
<div class="decision-statement">
|
||||
<span id="detailEventId">RUN BRIEF</span>
|
||||
<h3 id="detailTitle">任务已由 ALTA-7 接管</h3>
|
||||
<p id="detailSummary">AGENT 将先获取工程事实,再决定是否修改代码。</p>
|
||||
</div>
|
||||
<dl class="action-facts">
|
||||
<div><dt>输入事实</dt><dd id="detailInput">Workspace、Board、Probe 与任务目标已绑定</dd></div>
|
||||
<div><dt>决策摘要</dt><dd id="detailDecision">不能直接假设代码错误,先检查时钟树和输入捕获配置</dd></div>
|
||||
<div><dt>工具调用</dt><dd id="detailTool">等待授权</dd></div>
|
||||
<div><dt>输出 / 下一步</dt><dd id="detailOutput">读取 .uvprojx、system_stm32f1xx.c 与 frequency.c</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
<pre class="run-log" id="runLog" aria-live="polite"><span class="log-muted">$ hwlab case run freq-1khz-accuracy</span>
|
||||
<span class="log-muted"> 尚未执行</span></pre>
|
||||
</aside>
|
||||
|
||||
<div class="inspector-view" data-view="patch" hidden>
|
||||
<div class="patch-meta">
|
||||
<div><span>AGENT ACTION</span><strong id="patchAction">尚未生成变更</strong></div>
|
||||
<span class="patch-score" id="patchScore">WAITING</span>
|
||||
</div>
|
||||
<div class="code-diff" id="codeDiff" aria-label="Agent 代码变更">
|
||||
<div class="empty-change">
|
||||
<span>NO PATCH YET</span>
|
||||
<p>AGENT 必须先获得工程事实并验证假设,代码变更才会出现。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inspector-view" data-view="tool" hidden>
|
||||
<div class="tool-call-heading">
|
||||
<div><span>ACTIVE TOOL</span><strong id="activeTool">none</strong></div>
|
||||
<span id="toolStatus">IDLE</span>
|
||||
</div>
|
||||
<pre class="tool-output" id="toolOutput"><span>$ agent.run --task freq-1khz-accuracy</span>
|
||||
waiting for authorized execution...</pre>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="hwpod-panel" aria-labelledby="hwpod-title">
|
||||
<div class="hwpod-heading">
|
||||
<aside class="hwpod-stage" aria-labelledby="hwpod-title">
|
||||
<header class="hwpod-header">
|
||||
<div>
|
||||
<span class="eyebrow">REAL HARDWARE</span>
|
||||
<h2 id="hwpod-title">HWPOD 现场</h2>
|
||||
<span class="section-kicker">AGENT-CONTROLLED HWPOD</span>
|
||||
<h2 id="hwpod-title">真实硬件现场</h2>
|
||||
</div>
|
||||
<span class="live-indicator"><i></i>LIVE</span>
|
||||
<span class="hardware-live"><i></i>LIVE</span>
|
||||
</header>
|
||||
|
||||
<div class="agent-link" id="agentLink">
|
||||
<span>AGENT LINK</span>
|
||||
<strong id="hardwareCommand">等待工具调用</strong>
|
||||
<i aria-hidden="true">→</i>
|
||||
</div>
|
||||
|
||||
<figure class="bench-view">
|
||||
<img
|
||||
src="./assets/hwpod-bench.png"
|
||||
alt="STM32 开发板、调试探针与 ioProbe 测量设备组成的真实硬件台架"
|
||||
alt="由 STM32 开发板、CMSIS-DAP 和 ioProbe 组成的真实硬件台架"
|
||||
/>
|
||||
<figcaption class="sr-only">当前任务绑定的真实 HWPOD 硬件现场</figcaption>
|
||||
<span class="hardware-label hardware-label--board"><i></i>STM32F103</span>
|
||||
<span class="hardware-label hardware-label--probe"><i></i>CMSIS-DAP</span>
|
||||
<span class="hardware-label hardware-label--ioprobe"><i></i>ioProbe</span>
|
||||
<span class="bench-state" id="benchState">设备待命</span>
|
||||
<figcaption class="sr-only">当前 Agent Session 绑定的真实硬件现场</figcaption>
|
||||
<span class="hardware-tag hardware-tag--board" data-device="board"><i></i>STM32F103</span>
|
||||
<span class="hardware-tag hardware-tag--probe" data-device="probe"><i></i>CMSIS-DAP</span>
|
||||
<span class="hardware-tag hardware-tag--uart" data-device="uart"><i></i>UART2</span>
|
||||
<span class="hardware-tag hardware-tag--ioprobe" data-device="ioprobe"><i></i>ioProbe</span>
|
||||
<span class="bench-verdict" id="benchVerdict">SESSION BOUND</span>
|
||||
</figure>
|
||||
|
||||
<section class="measurement-panel" aria-label="物理测量结果">
|
||||
<div class="scope-header">
|
||||
<span>CH1 · FREQ_OUT</span>
|
||||
<strong id="scopeStatus">等待采集</strong>
|
||||
</div>
|
||||
<canvas id="scopeCanvas" aria-label="ioProbe 输出波形"></canvas>
|
||||
<dl class="measurement-grid">
|
||||
<div>
|
||||
<dt>目标频率</dt>
|
||||
<dd>1000.0 <small>Hz</small></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>实测频率</dt>
|
||||
<dd id="actualFrequency">— <small>Hz</small></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>测量误差</dt>
|
||||
<dd id="frequencyError">—</dd>
|
||||
</div>
|
||||
<section class="scope-panel" aria-label="ioProbe 物理测量">
|
||||
<header>
|
||||
<span>ioProbe · CH1 · FREQ_OUT</span>
|
||||
<strong id="scopeState">等待 AGENT 调用</strong>
|
||||
</header>
|
||||
<canvas id="scopeCanvas" aria-label="ioProbe 采集波形"></canvas>
|
||||
<dl class="measurements">
|
||||
<div><dt>目标</dt><dd>1000.0 <small>Hz</small></dd></div>
|
||||
<div><dt>实测</dt><dd id="actualFrequency">— <small>Hz</small></dd></div>
|
||||
<div><dt>误差</dt><dd id="frequencyError">—</dd></div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="uart-panel" aria-label="UART 现场输出">
|
||||
<header>
|
||||
<span>UART2 · 115200</span>
|
||||
<i id="uartIndicator"></i>
|
||||
</header>
|
||||
<pre id="uartOutput">[idle] waiting for firmware
|
||||
[idle] measurement channel ready</pre>
|
||||
<section class="device-facts" aria-label="设备事实">
|
||||
<div><span>BOARD</span><strong id="boardFact">已绑定 · 待操作</strong></div>
|
||||
<div><span>UART</span><strong id="uartFact">等待固件</strong></div>
|
||||
</section>
|
||||
</aside>
|
||||
</main>
|
||||
@@ -226,21 +259,22 @@
|
||||
<footer class="evidence-band" aria-labelledby="evidence-title">
|
||||
<div class="evidence-heading">
|
||||
<div>
|
||||
<span class="eyebrow">HARNESSRL EVIDENCE</span>
|
||||
<h2 id="evidence-title">可审计证据链</h2>
|
||||
<span class="section-kicker">AUDITABLE EVIDENCE</span>
|
||||
<h2 id="evidence-title">AGENT 行动证据链</h2>
|
||||
</div>
|
||||
<p id="evidenceDetail">选择证据节点查看边界事实。</p>
|
||||
<button id="saveCaseButton" type="button" disabled>保存为回归 Case</button>
|
||||
<p id="evidenceDetail">每个证据节点都引用产生它的 AGENT 行动。</p>
|
||||
<button id="saveCaseButton" type="button" disabled>封存为回归 Case</button>
|
||||
</div>
|
||||
|
||||
<ol class="evidence-chain">
|
||||
<li><button type="button" data-evidence="0"><i></i><span>任务接收</span><small>Task</small></button></li>
|
||||
<li><button type="button" data-evidence="1"><i></i><span>Agent 执行</span><small>Trace</small></button></li>
|
||||
<li><button type="button" data-evidence="2"><i></i><span>Build 通过</span><small>Artifact</small></button></li>
|
||||
<li><button type="button" data-evidence="3"><i></i><span>Flash 通过</span><small>Probe</small></button></li>
|
||||
<li><button type="button" data-evidence="4"><i></i><span>UART 正常</span><small>Runtime</small></button></li>
|
||||
<li><button type="button" data-evidence="5"><i></i><span>ioProbe 待测</span><small>Physical</small></button></li>
|
||||
<li class="evidence-final"><button type="button" data-evidence="6"><i></i><span>Aggregate 待定</span><small>Verdict</small></button></li>
|
||||
<li><button type="button" data-evidence="0"><i></i><span>任务</span><small>E00 · Task</small></button></li>
|
||||
<li><button type="button" data-evidence="1"><i></i><span>Agent Trace</span><small>E01–E09</small></button></li>
|
||||
<li><button type="button" data-evidence="2"><i></i><span>Patch</span><small>E04 · Diff</small></button></li>
|
||||
<li><button type="button" data-evidence="3"><i></i><span>Build</span><small>E05 · Artifact</small></button></li>
|
||||
<li><button type="button" data-evidence="4"><i></i><span>Flash</span><small>E06 · Probe</small></button></li>
|
||||
<li><button type="button" data-evidence="5"><i></i><span>UART</span><small>E07 · Runtime</small></button></li>
|
||||
<li><button type="button" data-evidence="6"><i></i><span>Physical</span><small>E08 · ioProbe</small></button></li>
|
||||
<li class="verdict-evidence"><button type="button" data-evidence="7"><i></i><span>Verdict 待定</span><small>E09 · Aggregate</small></button></li>
|
||||
</ol>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user