Live CLI verification at 74.48.78.17:19667 showed that
workspace.evidence dispatched correctly through cloud-api and
the executor (executor returned text with the actual error),
but the device-host-cli threw unsupported command because
the new evidence subcommand was nested under command=build
instead of being its own top-level command.
The args shape for workspace.evidence from deviceHostArgs is
[workspace, evidence, kind, ...options], so the main() match
in device-host-cli should hit
at the workspace group level, not
under . Same fix for debug-probe.
After the move, readJobEvidence is dispatched with the right
kindPrefix (keil-build for workspace.evidence kind=build,
keil-download for debug-probe.evidence kind=download) and
the optional jobId from rest[1]. Tail defaults to 200 lines,
--full for the entire log.
Tracked-by: pikasTech/HWLAB#773
Tracks pikasTech/HWLAB#773. PR #765 fixed selector confusion but did
not touch cloud-api evidence propagation. This change closes the
real root cause and adds the read-only evidence selectors that #760
follow-up called for.
cloud-api (internal/cloud/access-control.ts):
- DEVICE_JOB_INTENTS adds workspace.evidence and debug.evidence.
- DEVICE_JOB_READ_ONLY_SUB_ACTIONS = { status, output, wait, cancel,
evidence } and DEVICE_JOB_ACTIONABLE_INTENTS = { workspace.build,
debug.download } make the mutating-intent / sub-action matrix
explicit.
- _deviceJobRequiresReason(intent, args, reason) returns false when
the caller already provided reason OR when the actionable mutating
intent is paired with a read-only sub-action. debug.reset and other
non-actionable mutating intents still always require reason.
- executorOutputPayload now also surfaces output.summary,
nestedOutput.summary, evidence.text, evidence.logTail and
evidence.summary as body.output.text, so a dispatcher that
includes the host logTail / buildSummary in its result becomes
visible to the Code Agent without a separate bootsharp dance.
device-pod executor (cmd/hwlab-device-pod/main.ts):
- gatewayDispatchText also walks result.evidence.{text,logTail,
summary}, dispatch.message (only when dispatchStatus=completed),
dispatch.summary, result.summary and dispatch.buildSummary before
falling back to JSON.stringify.
- deviceHostArgs maps workspace.evidence / debug.evidence to the
host device-host-cli evidence subcommands.
device-host-cli (skills/device-pod-cli/assets/device-host-cli.mjs):
- new readJobEvidence(kindPrefix, requestedId, { tail, full,
target }) reads the most recent (or specified) keil-build /
keil-download job log file and returns it as keil-build.evidence /
keil-download.evidence. tail defaults to 200, --full for entire
log. Missing job returns ok=false but does not throw.
- workspace build evidence and debug-probe download evidence now
wired in main() and help text.
device-pod-cli (tools/src/device-pod-cli-lib.ts):
- selectorCheatSheet adds the evidence row and clarifies that
build/download status/output/wait/cancel/evidence are read-only
and do NOT need --reason. build/download start still require
--reason. usage examples now include the two evidence selectors.
- failed-fast selectors are unchanged; existing 26 tests still pass.
SKILL.md (skills/device-pod-cli/SKILL.md):
- Selector Cheat Sheet adds workspace.evidence and
debug.evidence rows.
- #773 follow-up note: --reason is now required only for
mutating sub-actions, not for the read-only ones.
cloud-api tests (internal/cloud/access-control.test.ts):
- workspace.evidence and debug.evidence must be in DEVICE_JOB_INTENTS.
- _deviceJobRequiresReason signature and the read-only / actionable
branch table.
- executorOutputPayload must look at evidence and summary fields
in addition to text. 3 new tests; pre-existing 4 workbench
failures are unrelated to this change and reproduce on the
unchanged v0.2 base.
Verification (host-side, G14 /root/hwlab-v02, source=0cf9a8c6):
- bun test tools/device-pod-cli.test.ts → 26/26 pass.
- bun test internal/cloud/access-control.test.ts → 23 pass,
4 pre-existing workbench failures (unchanged on v0.2 base).
- bun test cmd/hwlab-device-pod/main.test.ts → 7/7 pass.
- node --check skills/device-pod-cli/assets/device-host-cli.mjs
→ syntax OK.
Hot probe: live v0.2 cloud-api at 74.48.78.17:19667 confirmed
job_devicepod_804c5db4... (workspace.build) returned
text="", bytes=0, output={} before this change. After the
executor text-extraction update and the new evidence selector,
a follow-up workspace.build start + build evidence will surface
the host logTail / buildSummary in body.output.text without
requiring bootsharp + host file fallback.
Tracked-by: pikasTech/HWLAB#773
Fixes HWLAB #775 round-1 regression vs pre-React `app.ts` /
`app-conversation.ts` / `app-device-pod.ts` / `app-trace.ts` /
`code-agent-status.ts`. This PR lays the foundation for rounds 2-7
by restoring three of the three explicitly named regressions:
1. **Inactivity timeout** (the "应该是无活动超时,而非全超时" line)
- `services/api/client.ts` `fetchJson` / `fetchText` now accept
`activityRef`; an internal `schedule()` re-evaluates
`remainingMs = timeoutMs - (now - lastActivityAt)` every 1s, so
long-running fetches stay alive while upstream keeps emitting
trace events. The AbortError now distinguishes "总超时" vs
"无活动超时(idle Ns;waitingFor=…;lastEventLabel=…)".
- Mirrors pre-React `app-device-pod.ts:1219-1293` `fetchJson` contract.
2. **Code Agent status 23 rows** (the "裸 JSON 出现" line)
- New `state/code-agent-status.ts` (`classifyCodeAgentStatusSummary`,
`codeAgentRuntimePathFromMessage`, `codeAgentSummaryRows`) lifts
the 23-row pre-React `codeAgentSummaryRows` into React; rows cover
codeAgent.status, provider/mode/backend, capabilityLevel, session,
sessionId/status, 会话提示, workspace, sandbox, runnerKind,
protocol, implementationType, providerTrace.{command,terminalStatus,
failureKind}, 运行路径语义, toolCalls, skills, conversation facts,
runnerTrace, readiness blockers, last traceId, 当前部署 revision.
- ConversationPanel's `<CodeAgentSummary>` now renders the 23 rows
through `codeAgentSummaryRows(summary)`. Blockers render through a
collapsible "展开 readiness 原始 JSON" disclosure so unmodeled
blockers stay inspectable without dumping bare JSON inline.
3. **Rich trace panel** (the "Trace 过程事件实时滚动,事件渲染等全丢了" line)
- New `components/conversation/MessageTracePanel.tsx` replaces
`ConversationPanel`'s inline `<TracePanel>`: 32-row tail (was 16)
with `traceUiKey` storage, default-open per status, toolbar with
`count / follow / jump-to-bottom / lastEventLabel / last ts / short
traceId`, per-row tone (`tone-border-<tone>` from `event.status`),
follow-on-scroll, follow-on-`pre` body with markdown harden hook,
"等待后端事件" placeholder when events is empty, and
`markTraceScrollIntent`-equivalent scroll handler.
- Pre-React `app-conv.ts:1337-1500` semantics are now rendered through
the same `traceUiKey` storage and a follow/pause toggle.
4. **Beijing time formatting** (the "formatBeijingTime" regression)
- `utils.ts` adds `formatBeijingTime(value, {withSeconds, includeYear})`
using a shared `Intl.DateTimeFormat` Asia/Shanghai formatter; the
pre-existing `formatTimestamp` now delegates to it.
- `MessageCard` and `MessageTracePanel` now render `formatBeijingTime`
in `withSeconds: true` mode so users see `2026-06-03 19:56:20` not
raw ISO `2026-06-03T11:56:20.731Z`.
5. **`api.gateDiagnostics` and `api.cancelAgentMessage` clients** are now
declared alongside the existing `sendAgentMessage` / `getAgentChatResult`
so round 6 can wire `GateView` and round 3 can wire 取消当前请求
without another client surface churn.
## Verification
- `cd web/hwlab-cloud-web && bun run check` passes (Vite build + 2/2 dist
freshness test pass + 12 fresh dist files)
- `bun run scripts/tsc-check.ts` passes (strict React TSX, 0 explicit any)
- `bun test` 2 pass / 0 fail
- `bun run build` 9 dist files verified fresh; `app.js` grew 198671 →
213154 bytes; `index.css` grew 13483 → 15324 bytes
- new `app.js` contains: `activityRef`, `inactivityDetail`,
`MessageTracePanel`, `codeAgentSummaryRows`,
`classifyCodeAgentStatusSummary`, `formatBeijingTime`
- new `index.css` contains: `.message-trace-toolbar`,
`.message-trace-events`, `.message-trace-list`, `.message-trace-row`,
`.message-trace-body`, `.message-trace-empty`, `.message-trace-last`,
`.message-trace-time`, `.message-trace-tone`, `.message-trace-label`,
`.code-agent-summary-rows`, `.code-agent-summary-row`,
`.code-agent-summary-key`, `.code-agent-summary-value`,
`.code-agent-summary-raw`
- `web/hwlab-cloud-web/src/state/code-agent-status.ts` 398 lines, the
other new/edited files are also within the 400-line frontend guard.
Refs: pikasTech/HWLAB#775 (round 1 of 7)
Co-authored-by: HWLAB <ci@hwlab.local>
Fixes HWLAB #759 round-3 v0.2 WebUI issues introduced by the React migration:
1. device-pod right sidebar could not be re-expanded because the toggle button
was nested inside the right sidebar which is fully hidden when collapsed.
Move the toggle button to be a direct child of the right sidebar via a
new anchor button + content wrapper. Add --right-collapsed-width and a
vertical-rl text rotation so the toggle stays visible and accessible
when the right sidebar is collapsed.
2. The Session sidebar and right sidebar resize handles had no event
handlers after the React migration; dragging did nothing. Add a new
useSidebarResize hook with pointer/keyboard handlers, dynamic bounds
based on viewport and opposite sidebar width, localStorage
persistence, and CSS variable injection on the workbench shell.
3. document.querySelector("#command-form > div") (the input shell) was on
the same row as the timeout selects. Restore the original layout by
switching the command-bar to grid-template-areas with the input shell
on its own row and the three selects on a second row.
4. Code Agent was unusable from the React UI because the 202 response has
status=running and no reply text; the React UI marked the message as
completed with the fallback empty body. Add a polling loop in the
workbench store that follows resultUrl until status reaches a terminal
value, and surface assistantText / reply.content from the polled
AgentChatResultResponse. Add AgentChatResultResponse, AgentChatReply
and AgentRunProvenance types plus a getAgentChatResult API method.
Verified locally on G14:web/hwlab-cloud-web: bun run check (12 fresh
dist files), bun run scripts/tsc-check.ts (strict React TSX, 0 explicit
any), bun test (2 pass / 0 fail).
Co-authored-by: HWLAB <ci@hwlab.local>
Address the friction in #760 where Code Agent picked <pod>:workspace:/ keil
download/flash and <pod>:debug-probe flash for firmware flashing, all of
which are not SOP. Add to SKILL.md:
- Selector Cheat Sheet: explicit per-surface allowed operations table
- Common selector mistakes table: wrong selector -> error you would hit
-> correct selector
- Pitfalls Observed in Real Runs: --reason required for build/download
even on read-only sub-actions, cloud-api job output is empty for
build/download, workspace rg --pattern shell-pipe collision
In tools/src/device-pod-cli-lib.ts:
- workspace.keil now fail-fasts on any action that is not add-source or
remove-source, with code unsupported_workspace_keil_action and a
selector hint pointing to debug-probe download start
- debug-probe flash now has a dedicated error code path with the same
selector hint instead of the generic unsupported_debug_operation
- catch-all debug-probe error message now includes the allowed list
- help() payload exposes a selectorCheatSheet array so hwpod --help
callers see the SOP table without reading the SKILL.md file
Tests in tools/device-pod-cli.test.ts: 6 new cases lock in the new
error codes, the selector cheat sheet, and that add-source still
dispatches correctly. 26/26 pass.
- Replace monolithic app.ts / app-conversation.ts / app-device-pod.ts /
app-skills.ts / app-helpers.ts / app-session-tabs.ts / auth.ts and the
364-line index.html with a Vite-built React + TypeScript SPA.
- New src/ module tree: main.tsx, App.tsx, components/{layout,auth,
conversation,command-bar,device-pod,skills,settings,help},
hooks/, services/api, services/auth, state, types, logic/ (pure
helpers from the old tree), styles/.
- Pure-logic modules (composer-policy, code-agent-facts, code-agent-
status, live-status, message-markdown, app-trace, app-session-tabs,
runtime) move to src/logic/ with their tests preserved; tests still
auto-discover.
- Build pipeline switches from Bun.build to Vite: vite.config.ts
inlines everything into a single app.js + app-assets/ for the dist;
scripts/dist-contract.ts now runs vite build and asserts the dist
against a fresh build instead of comparing dist to source.
- scripts/check.ts now verifies the React entry, the mount-only
index.html, Vite dist, the composer / session / device pod / skills
/ settings / help contracts, the React state shape, and the new
test file locations. It also refuses to run if any legacy app-*.ts
or auth.ts survives at the top level.
- New scripts/frontend-guard.ts enforces the 400-line migration target,
refuses the legacy el map and document.getElementById markers,
refuses to keep app-*.ts / auth.ts at the top level, and verifies
the required module directory layout.
- Update scripts/src/dev-cloud-workbench-smoke-lib.mjs to read the new
src/services/, src/logic/ paths and refresh cloudWebModuleSourceFiles.
- index.html is now a mount shell only (no login shell, no workbench
shell, no device pod sidebar markup). React renders the full UI from
src/main.tsx, satisfying the issue-756 migration goal of catching
unclosed tags at the TypeScript/JSX compile step instead of relying
on browser DOM healing.
- web:check (now: check + frontend-guard + tsc-check + bun test) is
green: 51 pass / 0 fail, Vite build produces dist/app.js (379 kB)
+ dist/index.html + dist/app-assets/. Vite build is reproducible;
dist freshness is verified against a fresh Vite build.
- Grandfathered pure-logic files (> 600 lines): src/logic/app-trace.ts
(1367), src/logic/live-status.ts (1006), src/logic/code-agent-facts.ts
(548), src/logic/code-agent-status.ts (572). Reason: each is a tested
pure-logic classifier with a public contract consumed by both the
CLI renderer and the Web renderer; splitting now would fork the
public function shape across two PRs and the migration target is the
React entry + module split, not a pure-logic refactor.
Refs #756. Closes#756 once merged + deployed to hwlab-v02.
Co-authored-by: Codex <codex@local>
PR #746 (#744) removed the duplicate `command-new-session` button from index.html and the `el.commandNewSession` field from the `el` literal, but left `el.commandNewSession.addEventListener("click", ...)` inside `initCommandBar()`. The resulting undefined reference crashed every Cloud Workbench init with
app.js:5414 Uncaught TypeError: Cannot read properties of undefined (reading 'addEventListener')
at initCommandBar (app.js:5414:24)
at app.js:3538:1
The new session entry point is `#session-create` in the session sidebar (added in #740/#743), so this listener is no longer needed. Removal is the minimal fix; all other command-bar bindings (`commandInput`, `commandForm`, `commandClear`) are untouched.
Verified locally:
- web:check (54 tests pass, all static contracts pass)
- web:build (12 dist files fresh)
- dist/app.js: 0 references to commandNewSession
Refs: pikasTech/HWLAB#748
Co-authored-by: Codex <codex@local>
- dev-issue-722-trace-integration.mjs: drives hwlab-cli agent trace --render web
with a 4151-char final response and asserts the rendered terminal row body
equals the full text without any 内容已截断 marker
- dev-issue-722-streaming-integration.mjs: drives the same path with a long
non-terminal streaming message and asserts the CLI preview cap still fires
at 1200 chars + the ... marker, while the terminal row in the same trace
keeps the full text
- package.json: expose the two scripts as dev:issue-722:* for repeatable
CLI verification on the v0.2 runtime
Refs: pikasTech/HWLAB#722
- HWLAB v0.2 Final Response was being truncated to 5000 characters in
traceAssistantSummaryRow and then again to 1200 characters in the CLI
compactTraceRenderRow, even when the response was the assistant’s
terminal (last) message. The same Final Response is what
/v1/agent/chat/result returns as reply.content, so the trace view
should show the full text and stay consistent with the CLI
assistantText path.
- Mark each rendered row with a terminal flag (true only for the
terminal Final Response) and lift the body cap to
Number.POSITIVE_INFINITY for terminal rows. The CLI Web-render path
now passes the full body through when row.terminal is true and keeps
the 1200 char preview for non-terminal streaming rows.
- Add unit tests in web/hwlab-cloud-web/app-trace.test.ts and
tools/hwlab-cli/client.test.ts to lock the behavior in for the
terminal/no-truncate path and the streaming/2200 cap path.
Refs: pikasTech/HWLAB#722