- authHeaders now reads HWLAB_API_KEY (env or --apiKey/--bearerToken) first; only when hwl_live_ prefix is present
- when both are set, HWLAB_API_KEY wins and x-hwlab-device-pod-api-key is suppressed
- new test asserts the precedence using a fake API key against a local http server
- help auth hint updated to mention the preferred credential
- requestJson now prefers HWLAB_API_KEY (env or --apiKey / --bearerToken) and sends Authorization: Bearer hwl_live_...
- new client auth whoami command returns actor + authMethod via /v1/users/me
- client auth status reports apiKey.source and apiKey.prefix
- authVisibility + authDiagnosis surface apiKeySource/Prefix; invalid key returns api_key_invalid diagnosis
- legacy cookie / auto-login paths still work when no HWLAB_API_KEY is set
The CLI was emitting workspace.build/debug.download with action=evidence
for "<pod>:workspace build evidence <jobId>" and "<pod>:debug-probe
download evidence <jobId>". Cloud-api accepted the sub-action form, but
the device-pod executor mapped it to device-host-cli argv as
"workspace build evidence" / "debug-probe download evidence" -- neither
of which exists in the host CLI dispatcher; the host returned
"unsupported command: --profile-json-b64 ..." and the evidence job was
marked failed with blocker.code gateway_dispatch_failed.
Per docs/reference/g14.md (v0.2 device-pod cloud-api architecture),
"The evidence sub-action on workspace.evidence / debug.evidence is a
first-class intent, not a workspace.build sub-action." The executor's
deviceHostArgs already produces the correct argv for the first-class
intent, so the fix lives in the CLI selector parser: when
operation === "build" and rest[1] === "evidence", emit
intent=workspace.evidence with kind=build and the explicit jobId; the
same shape for operation === "download" -> debug.evidence with
kind=download. Non-evidence sub-actions (start, status, wait, output,
cancel) are unchanged.
Live verification on G14 against http://74.48.78.17:19667 (D601-F103-V2,
profileHash sha256:0352cd14...):
- before: device-host-cli argv = "workspace build evidence"
-> gateway_dispatch_failed
- after: device-host-cli argv = "workspace evidence build <jobId> ..."
accepted by cloud-api with status=running, blocker=null
- the executor argv proves the fix; the D601 Windows host
F:\Work\ConStart\tools\device-host-cli.mjs is still a stale
D601 ops-side copy and is tracked separately in #779 (out of scope
for #801).
Adds:
- device-pod-cli.test.ts: evidence sub-action -> first-class intent
- device-pod-cli.test.ts: build start / download start regression
(non-evidence sub-actions keep workspace.build / debug.download)
Out of scope: COM9 -> COM3 UART port drift on the Windows device pod
host (separate issue from #801; no UART/profile port code touched).
P0 freezes the concept boundary between HWLAB (business session/workspace) and
AgentRun (tenant/policy/execution):
- HWLAB adapter now always uses DEFAULT_HWLAB_AGENTRUN_PROJECT_ID /
DEFAULT_HWLAB_AGENTRUN_PROVIDER_ID / DEFAULT_HWLAB_AGENTRUN_BACKEND_PROFILE on
AgentRun runPayload, regardless of caller options. Business projectId belongs
in workspaceRef only, not in AgentRun projectId.
- commandPayload.projectId is now always set to the HWLAB-fixed
DEFAULT_HWLAB_AGENTRUN_PROJECT_ID, never undefined and never derived from
caller options.
- client agent send --session-id <SES> --conversation-id <CONV> without
--provider-profile now looks up the explicit session record to inherit
providerProfile / threadId / conversationId. Mismatched --conversation-id
against the session record aborts with session_conversation_mismatch
before dispatching to AgentRun (prevents stale session state from being
used as a new conversation).
Contract tests added (all 4 passing):
- internal/agent/agentrun-dispatch.test.mjs: HWLAB AgentRun assembly fixes
tenantId/projectId/providerId/backendProfile regardless of options; command
payload does not leak business projectId into AgentRun command projectId.
- tools/hwlab-cli/client.test.ts: send --session-id + --conversation-id
inherits explicit session providerProfile; fails with
session_conversation_mismatch when the session points at a different
conversation.
P2 (CLI/trace result segregation) and P3 (state machine convergence) remain
for follow-up. Tracked via #792 comment chain.
Tracked-by: pikasTech/HWLAB#792
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
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.
- 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