From 58df53bce54e3fe5c882df4fb8dc88158aef8e06 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 2 Jun 2026 20:56:36 +0800 Subject: [PATCH] feat: assemble AgentRun prompts and skills --- .../reference/agentrun-code-agent-dispatch.md | 7 ++- internal/agent/agentrun-dispatch.mjs | 56 +++++++++++++++++++ internal/agent/agentrun-dispatch.test.mjs | 27 ++++++++- internal/agent/prompts/hwlab-v02-runtime.md | 21 +++++++ internal/cloud/code-agent-agentrun-adapter.ts | 11 +++- internal/cloud/server-agent-chat.test.ts | 7 +++ skills/device-pod-cli/SKILL.md | 20 +++---- 7 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 internal/agent/prompts/hwlab-v02-runtime.md diff --git a/docs/reference/agentrun-code-agent-dispatch.md b/docs/reference/agentrun-code-agent-dispatch.md index 42ccd51d..d93c5be1 100644 --- a/docs/reference/agentrun-code-agent-dispatch.md +++ b/docs/reference/agentrun-code-agent-dispatch.md @@ -23,11 +23,14 @@ - AgentRun run 必须使用 Git-only `resourceBundleRef`,默认 repo 是 `http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git`。 - `commitId` 必须是完整 40 字符小写 SHA,不接受 branch、tag、`HEAD` 或短 SHA。 - `workspaceRef` 只描述目标 repo/branch,默认 branch 是 `v0.2`;实际 checkout 身份以 `resourceBundleRef.repoUrl + commitId` 为准。 -- 默认 `toolAliases` 会把 `tools/unidesk-ssh.mjs` 暴露为 runner shell 内的 `unidesk-ssh` 命令;它只实现 AgentRun Code Agent 所需的短连接 UniDesk SSH passthrough,例如 `unidesk-ssh G14:/root/hwlab-v02 script -- 'pwd'`,完整文件传输和 patch 仍走 UniDesk CLI 原入口。 +- 默认 `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 正式路径。 +- 默认 `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`。 ## 验收 - 源码合同测试:`node --test internal/agent/agentrun-dispatch.test.mjs`。 - 语法检查:`node --check internal/agent/agentrun-dispatch.mjs && node --check internal/agent/agentrun-dispatch.test.mjs`。 -- 合同必须证明 `UNIDESK_SSH_CLIENT_TOKEN` 不出现在 `transientEnv`,GitHub/UniDesk SSH 能力都通过 AgentRun `toolCredentials` SecretRef 装配,且 runner resource bundle 默认暴露 `unidesk-ssh` 可执行别名。 +- 合同必须证明 `UNIDESK_SSH_CLIENT_TOKEN` 不出现在 `transientEnv`,GitHub/UniDesk SSH 能力都通过 AgentRun `toolCredentials` SecretRef 装配,且 runner resource bundle 默认暴露 `hwpod`、`unidesk-ssh`、`promptRefs` 和 `skillRefs`。 +- 真实 CLI 验收必须使用短 prompt 走 `backendProfile=deepseek`:首轮 prompt “不调用工具的情况下,你可见的 skill 有哪些?”应能回答 HWLAB bundle skill;同一会话 continuation 应显示 AgentRun 原生 resume 语义且不重复注入 initial prompt;“编译 D601-F103-V2”应触发 `hwpod` Device Pod 路径,若失败则报告正式 blocker,不切换 fallback。 diff --git a/internal/agent/agentrun-dispatch.mjs b/internal/agent/agentrun-dispatch.mjs index 16f75981..fc8deddc 100644 --- a/internal/agent/agentrun-dispatch.mjs +++ b/internal/agent/agentrun-dispatch.mjs @@ -7,8 +7,16 @@ export const DEFAULT_HWLAB_AGENTRUN_BRANCH = "v0.2"; export const DEFAULT_HWLAB_AGENTRUN_BACKEND_PROFILE = "deepseek"; export const DEFAULT_UNIDESK_MAIN_SERVER_ENV = "UNIDESK_MAIN_SERVER_IP"; export const DEFAULT_HWLAB_AGENTRUN_TOOL_ALIASES = Object.freeze([ + { name: "hwpod", path: "tools/device-pod-cli.mjs", kind: "node-script" }, { name: "unidesk-ssh", path: "tools/unidesk-ssh.mjs", kind: "bun-script" } ]); +export const DEFAULT_HWLAB_AGENTRUN_PROMPT_REFS = Object.freeze([ + { name: "hwlab-v02-runtime", path: "internal/agent/prompts/hwlab-v02-runtime.md", inject: "thread-start", required: true } +]); +export const DEFAULT_HWLAB_AGENTRUN_SKILL_REFS = Object.freeze([ + { name: "device-pod-cli", path: "skills/device-pod-cli/SKILL.md", required: true, aggregateAs: "device-pod-cli" }, + { name: "hwlab-agent-runtime", path: "skills/hwlab-agent-runtime/SKILL.md", required: true, aggregateAs: "hwlab-agent-runtime" } +]); export const AGENTRUN_REUSABLE_CREDENTIAL_ENV_NAMES = Object.freeze([ "AUTH_PASSWORD", @@ -86,6 +94,8 @@ export function createHwlabAgentRunDispatchAssembly(options = {}) { const timeoutMs = positiveInteger(options.timeoutMs ?? 600000, "timeoutMs"); const unideskMainServerIp = optionalString(options.unideskMainServerIp ?? env.UNIDESK_MAIN_SERVER_IP ?? env.UNIDESK_MAIN_SERVER_HOST); const toolAliases = normalizeResourceToolAliases(options.toolAliases ?? DEFAULT_HWLAB_AGENTRUN_TOOL_ALIASES); + const promptRefs = normalizeResourcePromptRefs(options.promptRefs ?? DEFAULT_HWLAB_AGENTRUN_PROMPT_REFS); + const skillRefs = normalizeResourceSkillRefs(options.skillRefs ?? DEFAULT_HWLAB_AGENTRUN_SKILL_REFS); if (includeUnideskSshToolCredential && !unideskMainServerIp) { throw new Error("unideskMainServerIp is required when UniDesk SSH passthrough is enabled"); } @@ -104,6 +114,8 @@ export function createHwlabAgentRunDispatchAssembly(options = {}) { ...(options.subdir ? { subdir: nonEmptyString(options.subdir, "subdir") } : {}), ...(Array.isArray(options.sparsePaths) ? { sparsePaths: options.sparsePaths.map((item, index) => nonEmptyString(item, `sparsePaths[${index}]`)) } : {}), ...(toolAliases.length > 0 ? { toolAliases } : {}), + ...(promptRefs.length > 0 ? { promptRefs } : {}), + ...(skillRefs.length > 0 ? { skillRefs } : {}), ...(options.resourceCredentialRef ? { credentialRef: options.resourceCredentialRef } : {}) }; @@ -165,6 +177,8 @@ export function createHwlabAgentRunDispatchAssembly(options = {}) { unideskSshCredentialSource: includeUnideskSshToolCredential ? "toolCredentials" : null, unideskMainServerSource: unideskMainServerIp ? "transientEnv" : null, toolAliases: toolAliases.map((item) => item.name), + promptRefs: promptRefs.map((item) => item.name), + skillRefs: skillRefs.map((item) => item.name), transientEnvNames: transientEnv.map((item) => item.name), forbiddenTransientEnvNames: AGENTRUN_REUSABLE_CREDENTIAL_ENV_NAMES.slice() } @@ -259,6 +273,48 @@ function normalizeResourceToolAliases(items) { }); } +function normalizeResourcePromptRefs(items) { + if (!Array.isArray(items)) throw new Error("promptRefs must be an array"); + const seen = new Set(); + return items.map((item, index) => { + if (!item || typeof item !== "object") throw new Error(`promptRefs[${index}] must be an object`); + const name = resourceName(item.name, `promptRefs[${index}].name`); + if (seen.has(name)) throw new Error(`promptRefs name ${name} is duplicated`); + seen.add(name); + const refPath = resourcePath(item.path, `promptRefs[${index}].path`); + const inject = optionalString(item.inject) ?? "thread-start"; + if (inject !== "thread-start") throw new Error(`promptRefs[${index}].inject must be thread-start`); + return { name, path: refPath, inject: "thread-start", required: item.required !== false }; + }); +} + +function normalizeResourceSkillRefs(items) { + if (!Array.isArray(items)) throw new Error("skillRefs must be an array"); + const seen = new Set(); + return items.map((item, index) => { + if (!item || typeof item !== "object") throw new Error(`skillRefs[${index}] must be an object`); + const name = resourceName(item.name, `skillRefs[${index}].name`); + if (seen.has(name)) throw new Error(`skillRefs name ${name} is duplicated`); + seen.add(name); + const refPath = resourcePath(item.path, `skillRefs[${index}].path`); + if (!refPath.endsWith("SKILL.md")) throw new Error(`skillRefs[${index}].path must point to SKILL.md`); + const aggregateAs = optionalString(item.aggregateAs); + return { name, path: refPath, required: item.required !== false, ...(aggregateAs ? { aggregateAs: resourceName(aggregateAs, `skillRefs[${index}].aggregateAs`) } : {}) }; + }); +} + +function resourceName(value, fieldName) { + const name = nonEmptyString(value, fieldName); + if (!/^[a-z][a-z0-9._-]{0,62}$/u.test(name)) throw new Error(`${fieldName} must be a lowercase resource name`); + return name; +} + +function resourcePath(value, fieldName) { + const refPath = nonEmptyString(value, fieldName); + if (refPath.startsWith("/") || refPath.includes("..")) throw new Error(`${fieldName} must stay within the checkout`); + return refPath; +} + async function postJson(fetchImpl, managerUrl, path, payload) { const response = await fetchImpl(`${managerUrl}${path}`, { method: "POST", diff --git a/internal/agent/agentrun-dispatch.test.mjs b/internal/agent/agentrun-dispatch.test.mjs index e4feb8f5..d967e2ad 100644 --- a/internal/agent/agentrun-dispatch.test.mjs +++ b/internal/agent/agentrun-dispatch.test.mjs @@ -26,8 +26,18 @@ test("HWLAB AgentRun assembly grants GitHub and UniDesk SSH through toolCredenti assert.equal(assembly.runPayload.backendProfile, "deepseek"); assert.equal(assembly.runPayload.workspaceRef.branch, "v0.2"); assert.equal(assembly.runPayload.resourceBundleRef.commitId, commitId); - assert.deepEqual(assembly.runPayload.resourceBundleRef.toolAliases.map((item) => item.name), ["unidesk-ssh"]); - assert.equal(assembly.runPayload.resourceBundleRef.toolAliases[0].path, "tools/unidesk-ssh.mjs"); + assert.deepEqual(assembly.runPayload.resourceBundleRef.toolAliases.map((item) => item.name), ["hwpod", "unidesk-ssh"]); + assert.equal(assembly.runPayload.resourceBundleRef.toolAliases[0].path, "tools/device-pod-cli.mjs"); + assert.deepEqual(assembly.runPayload.resourceBundleRef.promptRefs, [ + { name: "hwlab-v02-runtime", path: "internal/agent/prompts/hwlab-v02-runtime.md", inject: "thread-start", required: true } + ]); + assert.deepEqual(assembly.runPayload.resourceBundleRef.skillRefs, [ + { name: "device-pod-cli", path: "skills/device-pod-cli/SKILL.md", required: true, aggregateAs: "device-pod-cli" }, + { name: "hwlab-agent-runtime", path: "skills/hwlab-agent-runtime/SKILL.md", required: true, aggregateAs: "hwlab-agent-runtime" } + ]); + assert.deepEqual(assembly.boundaries.promptRefs, ["hwlab-v02-runtime"]); + assert.deepEqual(assembly.boundaries.skillRefs, ["device-pod-cli", "hwlab-agent-runtime"]); + assert.equal(assembly.boundaries.toolAliases.includes("hwpod"), true); assert.equal(assembly.boundaries.toolAliases.includes("unidesk-ssh"), true); assert.equal(assembly.commandPayload.payload.traceId, "trc_hwlab_agentrun_001"); assert.equal(assembly.commandPayload.payload.threadId, "thread_001"); @@ -71,6 +81,19 @@ test("HWLAB AgentRun assembly allows explicit resource tool aliases", () => { assert.deepEqual(assembly.runPayload.resourceBundleRef.toolAliases, [{ name: "status-tool", path: "tools/status.mjs", kind: "node-script" }]); }); +test("HWLAB AgentRun assembly allows explicit resource prompt and skill refs", () => { + const assembly = createHwlabAgentRunDispatchAssembly({ + traceId: "trc_hwlab_agentrun_prompt_skill_override", + prompt: "prompt skill override", + commitId, + unideskMainServerIp: "https://unidesk.example.test", + promptRefs: [{ name: "custom-prompt", path: "internal/agent/prompts/custom.md", inject: "thread-start", required: true }], + skillRefs: [{ name: "custom-skill", path: "skills/custom-skill/SKILL.md", required: true, aggregateAs: "custom-skill" }] + }); + assert.deepEqual(assembly.runPayload.resourceBundleRef.promptRefs, [{ name: "custom-prompt", path: "internal/agent/prompts/custom.md", inject: "thread-start", required: true }]); + assert.deepEqual(assembly.runPayload.resourceBundleRef.skillRefs, [{ name: "custom-skill", path: "skills/custom-skill/SKILL.md", required: true, aggregateAs: "custom-skill" }]); +}); + test("HWLAB AgentRun assembly does not duplicate explicit UniDesk main-server env", () => { const assembly = createHwlabAgentRunDispatchAssembly({ traceId: "trc_hwlab_agentrun_explicit_unidesk", diff --git a/internal/agent/prompts/hwlab-v02-runtime.md b/internal/agent/prompts/hwlab-v02-runtime.md new file mode 100644 index 00000000..4a3d91f0 --- /dev/null +++ b/internal/agent/prompts/hwlab-v02-runtime.md @@ -0,0 +1,21 @@ +# HWLAB v0.2 AgentRun Runtime Prompt + +You are running inside the HWLAB v0.2 Code Agent runtime assembled by AgentRun. + +Use the repo-local resource bundle as the source of runtime rules: + +- Required skills are mounted from `ResourceBundleRef.skillRefs` into `.agents/skills` for this run. +- The expected HWLAB skills are `device-pod-cli` and `hwlab-agent-runtime`. +- Use `hwpod` from PATH for Device Pod work, including D601-F103-V2 build, status, job polling, output inspection, debug-probe, and UART operations. +- Use `unidesk-ssh` only for UniDesk passthrough tasks that are not covered by Device Pod APIs. + +Do not use fallback execution paths: + +- Do not rely on Codex default system skills as a substitute for HWLAB bundle skills. +- Do not read `/app/skills`, host skill directories, ConfigMaps, or user-provided long prompts as a substitute for `ResourceBundleRef.skillRefs`. +- Do not call generic gateway shell, old diagnostic images, local `.device-pod/*.json`, or direct Windows host commands when `hwpod` can reach the formal Device Pod path. +- Do not manually concatenate prior user/assistant messages. Conversation continuity must come from Codex stdio `thread/resume` only. + +When the user asks what skills are visible, mention the HWLAB bundle skills by name and manifest path before any generic model/system skill list. + +When the user asks to compile or operate `D601-F103-V2`, start from the Device Pod path with `hwpod bootsharp --pod-id D601-F103-V2`, then use `hwpod` job/status/output commands as needed. If the Device Pod path returns a named blocker, report that blocker and do not switch to fallback paths. diff --git a/internal/cloud/code-agent-agentrun-adapter.ts b/internal/cloud/code-agent-agentrun-adapter.ts index 78ab76cc..28bef8f5 100644 --- a/internal/cloud/code-agent-agentrun-adapter.ts +++ b/internal/cloud/code-agent-agentrun-adapter.ts @@ -37,6 +37,13 @@ const HWLAB_RESOURCE_TOOL_ALIASES = Object.freeze([ Object.freeze({ name: "hwpod", path: "tools/device-pod-cli.mjs", kind: "node-script" }), Object.freeze({ name: "unidesk-ssh", path: "tools/unidesk-ssh.mjs", kind: "bun-script" }) ]); +const HWLAB_RESOURCE_PROMPT_REFS = Object.freeze([ + Object.freeze({ name: "hwlab-v02-runtime", path: "internal/agent/prompts/hwlab-v02-runtime.md", inject: "thread-start", required: true }) +]); +const HWLAB_RESOURCE_SKILL_REFS = Object.freeze([ + Object.freeze({ name: "device-pod-cli", path: "skills/device-pod-cli/SKILL.md", required: true, aggregateAs: "device-pod-cli" }), + Object.freeze({ name: "hwlab-agent-runtime", path: "skills/hwlab-agent-runtime/SKILL.md", required: true, aggregateAs: "hwlab-agent-runtime" }) +]); export function codeAgentAgentRunAdapterEnabled(env = process.env) { const value = String(env.HWLAB_CODE_AGENT_ADAPTER ?? env.HWLAB_CODE_AGENT_PROVIDER ?? "").trim().toLowerCase(); @@ -465,7 +472,9 @@ function buildAgentRunCreateRunInput({ params, env, traceId, backendProfile, ses commitId, submodules: false, lfs: false, - toolAliases: HWLAB_RESOURCE_TOOL_ALIASES + toolAliases: HWLAB_RESOURCE_TOOL_ALIASES, + promptRefs: HWLAB_RESOURCE_PROMPT_REFS, + skillRefs: HWLAB_RESOURCE_SKILL_REFS } : null; return { diff --git a/internal/cloud/server-agent-chat.test.ts b/internal/cloud/server-agent-chat.test.ts index fc1edffa..3a4c656a 100644 --- a/internal/cloud/server-agent-chat.test.ts +++ b/internal/cloud/server-agent-chat.test.ts @@ -176,6 +176,13 @@ test("cloud api /v1/agent/chat delegates v0.2 turns to AgentRun v0.1 over adapte { name: "hwpod", path: "tools/device-pod-cli.mjs", kind: "node-script" }, { name: "unidesk-ssh", path: "tools/unidesk-ssh.mjs", kind: "bun-script" } ]); + assert.deepEqual(body.resourceBundleRef.promptRefs, [ + { name: "hwlab-v02-runtime", path: "internal/agent/prompts/hwlab-v02-runtime.md", inject: "thread-start", required: true } + ]); + assert.deepEqual(body.resourceBundleRef.skillRefs, [ + { name: "device-pod-cli", path: "skills/device-pod-cli/SKILL.md", required: true, aggregateAs: "device-pod-cli" }, + { name: "hwlab-agent-runtime", path: "skills/hwlab-agent-runtime/SKILL.md", required: true, aggregateAs: "hwlab-agent-runtime" } + ]); const toolCredentials = body.executionPolicy.secretScope.toolCredentials; assert.equal(toolCredentials.some((item) => item.tool === "github" && item.projection.envName === "GH_TOKEN" && item.secretRef.name === "agentrun-v01-tool-github-pr"), true); assert.equal(toolCredentials.some((item) => item.tool === "unidesk-ssh" && item.projection.envName === "UNIDESK_SSH_CLIENT_TOKEN" && item.secretRef.name === "agentrun-v01-tool-unidesk-ssh"), true); diff --git a/skills/device-pod-cli/SKILL.md b/skills/device-pod-cli/SKILL.md index 71b3d7e6..94eb08f6 100644 --- a/skills/device-pod-cli/SKILL.md +++ b/skills/device-pod-cli/SKILL.md @@ -8,16 +8,16 @@ version: 0.2.0-rest Skill(cli-spec) -Scope: this skill is only for HWLAB-internal code agent runners inside the HWLAB runtime, such as `/workspace/hwlab` sessions running from the G14 DEV, PROD, or v0.2 images. It does not apply to external UniDesk developer workspaces or third-party machines. +Scope: this skill is only for HWLAB-internal code agent runners assembled from a HWLAB `ResourceBundleRef`, such as AgentRun v0.1 jobs for the G14 DEV, PROD, or v0.2 lanes. It does not apply to external UniDesk developer workspaces or third-party machines. Use this skill when the task mentions `device-pod-cli`, `device-pod`, `device-host-cli`, Keil build/download through a Device Pod, debug probe control, workspace operations, or I/O probe reads such as UART boot logs. -Canonical skill location: `/app/skills/device-pod-cli/SKILL.md`. Do not create duplicate skill copies under `/root/.agents/skills`, `/home/ubuntu/.agents/skills`, or `/workspace/hwlab/skills`; those locations cause stale instructions. +Canonical AgentRun skill location: `.agents/skills/device-pod-cli/SKILL.md` inside the current materialized workspace. The source manifest is `skills/device-pod-cli/SKILL.md` in the same Git commit as the run. Do not use `/app/skills`, `/root/.agents/skills`, `/home/ubuntu/.agents/skills`, ConfigMaps, host paths, or copied workspace skill directories as substitutes; those locations cause stale instructions. ## Runtime Contract -- The stable runner entrypoint is `hwpod`. Use `node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs` only as a compatibility fallback when `hwpod` is missing from PATH, and report that as a runner image/package friction point. -- The canonical implementation is `/app/tools/device-pod-cli.ts`; `hwpod`, `/app/tools/device-pod-cli.mjs`, and the skill script are wrappers that launch the Bun+TypeScript CLI. +- The stable runner entrypoint is `hwpod`. If `hwpod` is missing from PATH, report `hwpod_unavailable` as a runner assembly blocker; do not switch to `/app/skills`, generic shell, or an old diagnostic image. +- 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. @@ -177,24 +177,24 @@ Do not upload or recreate runner-side `.device-pod/*.json` as the formal profile ## T1 -阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:在未设置 `HWLAB_RUNTIME_*` / `HWLAB_DEVICE_POD_*` 装配环境时运行 `profile list`,确认输出 JSON 且错误码为 `runtime_endpoint_required`,没有静默 fallback 到旧 DEV 入口。 +阅读 `.agents/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:在未设置 `HWLAB_RUNTIME_*` / `HWLAB_DEVICE_POD_*` 装配环境时运行 `profile list`,确认输出 JSON 且错误码为 `runtime_endpoint_required`,没有静默 fallback 到旧 DEV 入口。 ## T2 -阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:在 WEB / AgentRun 已装配 `HWLAB_RUNTIME_API_URL`、`HWLAB_RUNTIME_NAMESPACE`、`HWLAB_RUNTIME_LANE`、`HWLAB_RUNTIME_ENDPOINT_LOCKED=1` 和 `HWLAB_DEVICE_POD_SESSION_TOKEN` 的真实 runner 中运行 `profile list`、`profile show --pod-id `、`doctor --pod-id `,确认 CLI 自动定位当前 runtime,输出来自 cloud-api authority,并且没有泄露 `gatewaySessionId`、`hostWorkspaceRoot`、probe UID 或 UART port。 +阅读 `.agents/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:在 WEB / AgentRun 已装配 `HWLAB_RUNTIME_API_URL`、`HWLAB_RUNTIME_NAMESPACE`、`HWLAB_RUNTIME_LANE`、`HWLAB_RUNTIME_ENDPOINT_LOCKED=1` 和 `HWLAB_DEVICE_POD_SESSION_TOKEN` 的真实 runner 中运行 `profile list`、`profile show --pod-id `、`doctor --pod-id `,确认 CLI 自动定位当前 runtime,输出来自 cloud-api authority,并且没有泄露 `gatewaySessionId`、`hostWorkspaceRoot`、probe UID 或 UART port。 ## T3 -阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:创建本地 `.device-pod/.json`,写入假的 `gatewaySessionId`,再运行 `profile list` 和 selector `--dry-run`;确认 JSON 输出标记 `localProfileAuthority=false` 且没有读取本地 fake route。 +阅读 `.agents/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:创建本地 `.device-pod/.json`,写入假的 `gatewaySessionId`,再运行 `profile list` 和 selector `--dry-run`;确认 JSON 输出标记 `localProfileAuthority=false` 且没有读取本地 fake route。 ## T4 -阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:运行 `profile create --pod-id `,确认返回 `legacy_profile_create_removed`,并提示使用 cloud-api admin API 管理服务端 profile/grant。 +阅读 `.agents/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:运行 `profile create --pod-id `,确认返回 `legacy_profile_create_removed`,并提示使用 cloud-api admin API 管理服务端 profile/grant。 ## T5 -阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:运行 `workspace put/rm/rmdir/keil` 和 `io-probe jsonrpc` 的 `--dry-run`,确认 JSON plan 中 intent 分别为 `workspace.put`、`workspace.rm`、`workspace.rmdir`、`workspace.keil`、`io.uart.jsonrpc`,且 `contentB64`、`text`、`patch` 不以原文泄露。 +阅读 `.agents/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:运行 `workspace put/rm/rmdir/keil` 和 `io-probe jsonrpc` 的 `--dry-run`,确认 JSON plan 中 intent 分别为 `workspace.put`、`workspace.rm`、`workspace.rmdir`、`workspace.keil`、`io.uart.jsonrpc`,且 `contentB64`、`text`、`patch` 不以原文泄露。 ## T6 -阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:在 `HWLAB_RUNTIME_ENDPOINT_LOCKED=1` 的真实 runner 中额外传入 `--api-base-url`、`--api-url`、`--cloud-api-url` 或 `--base-url`,确认返回 `runtime_endpoint_manual_url_forbidden`,从而证明 DS、旧 trace 或人工命令不能覆盖 WEB / AgentRun 装配的当前集群路径。 +阅读 `.agents/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:在 `HWLAB_RUNTIME_ENDPOINT_LOCKED=1` 的真实 runner 中额外传入 `--api-base-url`、`--api-url`、`--cloud-api-url` 或 `--base-url`,确认返回 `runtime_endpoint_manual_url_forbidden`,从而证明 DS、旧 trace 或人工命令不能覆盖 WEB / AgentRun 装配的当前集群路径。