docs: redesign HWLAB Agent showcase
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / unidesk-host- Success

This commit is contained in:
Codex
2026-07-10 18:47:51 +02:00
parent 7e2e8fbbc3
commit e0288b4348
3 changed files with 2199 additions and 1295 deletions
@@ -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=2TIM2 时钟需应用定时器倍频规则",
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 分频为 2TIM2 位于 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 warningsfreq.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 samplescapture stableoverflow=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、BoardProbe 与目标指标。",
idle: "E00 尚未进入执行。",
passed: "E00:任务、Workspace、BoardProbe 已绑定当前 Session。",
},
{
idle: "尚无 Agent 执行 trace。",
active: "Agent 正在受控工程上下文中分析、修改与执行。",
passed: "Agent trace 已封存:工程分析与代码变更均可回放。",
idle: "尚无 Agent 行动 Trace。",
active: "E01–E09:AGENT 正在持续记录观察、决策、工具与结果。",
passed: "E01E09Agent Trace 完整,可回放每次决策与工具调用。",
},
{
idle: "尚无本次构建产物。",
passed: "Keil 构建成功:0 error(s)0 warning(s),产物摘要已记录。",
idle: "E04 尚未生成代码变更。",
passed: "E04Patch +5 −3,与根因假设 H1 建立明确关联。",
},
{
idle: "目标板尚未写入本次固件。",
passed: "CMSIS-DAP 下载与校验通过,固件摘要与构建产物一致。",
idle: "E05 尚无构建产物。",
passed: "E05Keil 0 errors、0 warnings,产物摘要已封存。",
},
{
idle: "UART 运行事实尚未采集。",
passed: "UART 连续采样 128 次,输入捕获状态稳定,无溢出。",
idle: "E06 尚未操作真实目标板。",
passed: "E06CMSIS-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: "E07UART 连续 128 samples,捕获稳定且 overflow=0。",
},
{
idle: "Aggregate 尚未汇总,不能保存为回归 Case。",
passed: "代码、构建、下载、运行与物理测量证据完整:Aggregate PASS。",
blocked: "物理测量未通过:Aggregate BLOCKED,阻止错误结果进入回归集。",
idle: "E08 尚未获得独立物理事实。",
active: "E08AGENT 正在调用 ioProbe 采集 FREQ_OUT。",
passed: "E08ioProbe 999.8 Hz,误差 0.02%,满足阈值。",
failed: "E08ioProbe 972.4 Hz,误差 2.76%,推翻完成假设。",
},
{
idle: "E09 尚未形成聚合裁决。",
passed: "E09:完整证据链 Aggregate PASS,可封存回归 Case。",
blocked: "E09Aggregate BLOCKEDAGENT 已拒绝封存并返回诊断。",
},
];
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>E01E09</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