From bf7a8bfd495624ef86de782c1f0896f498f21bb2 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 2 Jun 2026 21:30:33 +0800 Subject: [PATCH] fix: align hwpod lease guidance --- docs/reference/agentrun-code-agent-dispatch.md | 1 + skills/device-pod-cli/SKILL.md | 17 ++++++----------- tools/device-pod-cli.test.ts | 16 ++++++++++++++++ tools/src/device-pod-cli-lib.ts | 15 +++++++++++++++ 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/docs/reference/agentrun-code-agent-dispatch.md b/docs/reference/agentrun-code-agent-dispatch.md index d93c5be1..71329c0a 100644 --- a/docs/reference/agentrun-code-agent-dispatch.md +++ b/docs/reference/agentrun-code-agent-dispatch.md @@ -24,6 +24,7 @@ - `commitId` 必须是完整 40 字符小写 SHA,不接受 branch、tag、`HEAD` 或短 SHA。 - `workspaceRef` 只描述目标 repo/branch,默认 branch 是 `v0.2`;实际 checkout 身份以 `resourceBundleRef.repoUrl + commitId` 为准。 - 默认 `toolAliases` 会把 `tools/device-pod-cli.mjs` 暴露为 `hwpod`,并把 `tools/unidesk-ssh.mjs` 暴露为 runner shell 内的 `unidesk-ssh` 命令。Device Pod、D601-F103-V2、Keil build/download、job status/output 和 UART/debug-probe 操作必须优先走 `hwpod`;`unidesk-ssh` 只用于 UniDesk passthrough 任务,不能替代 Device Pod 正式路径。 +- Device Pod lease 不是独立 runner 控制命令;标准 `hwpod` surface 只提交命名 Device Pod job。需要 lease 时,token 只能作为 `--lease-token` 跟随实际 mutating job 请求转发给 cloud-api。Agent 不得调用 `hwpod lease ...`,也不得新增 gateway shell 或其他 fallback 来绕过 job authority。 - 默认 `promptRefs` 指向 `internal/agent/prompts/hwlab-v02-runtime.md`,`inject=thread-start` 且 `required=true`。该 prompt 只在 AgentRun/Codex stdio 新 thread 首轮注入;后续 turn 必须依赖原生 `thread/resume`,不得在 command payload 中拼接历史、旧 skill 列表或长业务 prompt。 - 默认 `skillRefs` 指向 `skills/device-pod-cli/SKILL.md` 和 `skills/hwlab-agent-runtime/SKILL.md`,都为 `required=true`,由 AgentRun 聚合到当前 workspace `.agents/skills//SKILL.md`。HWLAB 不再依赖 `/app/skills`、hostPath、默认 Codex skill registry、ConfigMap 或用户长 prompt 作为 skill 注入 fallback。 - AgentRun runtime image 已预装 `gh`,结合 `tool=github` 注入的 `GH_TOKEN` 即可访问 HWLAB/UniDesk PR 与 issue;不得把 GitHub token 放入 prompt 或 `transientEnv`。 diff --git a/skills/device-pod-cli/SKILL.md b/skills/device-pod-cli/SKILL.md index 94eb08f6..79684c8a 100644 --- a/skills/device-pod-cli/SKILL.md +++ b/skills/device-pod-cli/SKILL.md @@ -1,6 +1,6 @@ --- name: device-pod-cli -description: Use the HWLAB v0.2 internal device-pod CLI from a HWLAB code agent runner to operate authorized Device Pods through cloud-api REST, leases, hwlab-device-pod, gateway, and device-host-cli. +description: Use the HWLAB v0.2 internal device-pod CLI from a HWLAB code agent runner to operate authorized Device Pods through cloud-api REST jobs, hwlab-device-pod, gateway, and device-host-cli. version: 0.2.0-rest --- @@ -20,7 +20,7 @@ Canonical AgentRun skill location: `.agents/skills/device-pod-cli/SKILL.md` insi - The canonical implementation is `tools/device-pod-cli.mjs` from the same ResourceBundleRef commit, exposed through the `hwpod` tool alias. - All commands emit JSON. A command with no output is a failure. - Formal v0.2 mode is cloud-api authority: the CLI calls `/v1/device-pods...` REST APIs and must not read `.device-pod/*.json` as the source of truth for gateway session, workspace root, probe UID, UART port, or host CLI. -- Server-side profile, grant, lease, job authority, and profile hash are owned by `hwlab-cloud-api`; `hwlab-device-pod` and `device-host-cli` execute only after cloud-api authorization. +- Server-side profile, grant, job authority, profile hash, and any lease enforcement are owned by `hwlab-cloud-api`; `hwlab-device-pod` and `device-host-cli` execute only after cloud-api authorization. ## Configuration @@ -62,19 +62,13 @@ hwpod :workspace:/ bootsharp For the D601 F103 v2 board, the registered `devicePodId` is `D601-F103-V2`; its server-side profile points to `F:\Work\D601-HWLAB`, Keil project `projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx`, target `USART`, `C:\Keil_v5\UV4\UV4.exe`, and UART `COM9` at `115200`. -Diagnose auth, grant, lease, and gateway blockers without touching local profiles: +Diagnose auth, grant, job-authority, and gateway blockers without touching local profiles: ```sh hwpod doctor --pod-id device-pod-71-freq ``` -Acquire and inspect a lease for mutating jobs: - -```sh -hwpod lease acquire --pod-id device-pod-71-freq --reason "build smoke" -hwpod lease current --pod-id device-pod-71-freq -hwpod lease release --pod-id device-pod-71-freq --lease-token -``` +There is no standalone `hwpod lease acquire/current/release` command in the current v0.2 standard path. Do not preflight a Device Pod task by calling `hwpod lease ...`. When cloud-api requires a lease token, pass the provided token on the actual mutating job; otherwise omit `--lease-token`. Run read-only workspace, debug, and I/O jobs: @@ -157,6 +151,7 @@ Do not treat local files such as `.device-pod/device-pod-71-freq.json` or `DEVIC - If apply-patch fails, read the returned `patchHint`, re-read the current file with `workspace cat` or `workspace rg`, and retry a smaller exact-context hunk before using whole-file write. - Do not start by calling `/app/tools/hwlab-gateway-tran.mjs`, Windows-side Keil skills, serial-monitor skills, `keil-cli.py`, or generic shell when the Device Pod REST path is available. - If the CLI returns an auth, grant, lease, executor, or gateway blocker, fix that named blocker in the cloud-api/device-pod path; do not bypass the profile with generic gateway shell. +- If the CLI returns `standalone_lease_command_removed`, use the actual Device Pod job command with `--reason` and optional `--lease-token`; do not call `hwpod lease acquire/current/release` or switch to a fallback transport. - The server-side profile already carries `hostWorkspaceRoot`, `projectWorkspace`, `debugInterface.uv4Path`, probe UID, UART port, and gateway route. Let `cloud-api -> hwlab-device-pod -> device-host-cli` apply those values. - Keep I/O selectors as strict path tokens, for example `device-pod-71-freq:io-probe:/uart/1 read`; do not split `/uart/1` across argv. - Use `--dry-run` to inspect the REST job request without dispatching it. @@ -169,7 +164,7 @@ Do not upload or recreate runner-side `.device-pod/*.json` as the formal profile ## Safety -- Mutating operations require a human/business reason and may require an active lease. Cloud-api is the enforcement point; the CLI only forwards `reason` and `leaseToken`. +- Mutating operations require a human/business reason and may require a lease token supplied by the cloud-api/business flow. Cloud-api is the enforcement point; the CLI only forwards `reason` and `leaseToken` on the named job request. - `hwlab-gateway` is transport only. Do not add a generic `cmd` operation to `device-pod-cli`; add a named Device Pod operation instead. - `SOURCE`, `LOCAL`, `DRY-RUN`, fixture data, and local profile files cannot be reported as `DEV-LIVE` hardware evidence. diff --git a/tools/device-pod-cli.test.ts b/tools/device-pod-cli.test.ts index 97db02ba..a9bfcd86 100644 --- a/tools/device-pod-cli.test.ts +++ b/tools/device-pod-cli.test.ts @@ -322,6 +322,22 @@ test("device-pod-cli sends reason without extra token for mutating jobs", async assert.deepEqual(seen[0].body, { intent: "debug.reset", args: {}, reason: "reset smoke" }); }); +test("device-pod-cli rejects standalone lease command with standard path guidance", async () => { + let called = false; + const result = await runAssembledDevicePodCli(["lease", "current", "--pod-id", "D601-F103-V2"], { + fetchImpl: async () => { + called = true; + return jsonResponse(500, { ok: false }); + }, + now: () => "2026-06-02T00:00:00.000Z" + }); + assert.equal(result.exitCode, 1); + assert.equal(called, false); + assert.equal(result.payload.error.code, "standalone_lease_command_removed"); + assert.match(result.payload.error.message, /Device Pod jobs through cloud-api/); + assert.equal(JSON.stringify(result.payload.error.details.next).includes("hwpod lease acquire/current/release"), true); +}); + test("device-pod-cli preserves common v0.1 hardware options in REST job args", async () => { const seen: any[] = []; const result = await runAssembledDevicePodCli([ diff --git a/tools/src/device-pod-cli-lib.ts b/tools/src/device-pod-cli-lib.ts index 36e321a9..1c03897c 100644 --- a/tools/src/device-pod-cli-lib.ts +++ b/tools/src/device-pod-cli-lib.ts @@ -45,6 +45,7 @@ async function dispatch(target: string, context: any) { if (target === "health") return health(context); if (target === "bootsharp") return bootsharpCommand(context); if (target === "profile") return profileCommand(context); + if (target === "lease" || target === "leases") return removedStandaloneLeaseCommand(); if (target === "job" || target === "jobs") return jobCommand(context); const selector = parseSelector(target); if (!selector) throw cliError("invalid_target_selector", `invalid target selector: ${target}`); @@ -169,6 +170,20 @@ async function profileCommand({ parsed, rest, env, fetchImpl }: any) { throw cliError("unsupported_profile_command", `unsupported profile command: ${subcommand}`); } +function removedStandaloneLeaseCommand() { + throw cliError( + "standalone_lease_command_removed", + "standalone hwpod lease commands are not part of the v0.2 standard path; send named Device Pod jobs through cloud-api with --reason and, only when cloud-api requires it, --lease-token", + { + next: [ + "Run hwpod bootsharp --pod-id before operating a pod.", + "Run the actual Device Pod job, for example hwpod :workspace:/ build start --reason TEXT.", + "Do not call hwpod lease acquire/current/release or switch to a generic gateway shell." + ] + } + ); +} + async function jobCommand({ parsed, rest, env, fetchImpl }: any) { const subcommand = rest[0] || "status"; const podId = requiredText(parsed.podId, "podId");