feat: assemble AgentRun prompts and skills

This commit is contained in:
Codex
2026-06-02 20:56:36 +08:00
parent 9c58faf3e3
commit 58df53bce5
7 changed files with 134 additions and 15 deletions
@@ -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/<name>/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。
+56
View File
@@ -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",
+25 -2
View File
@@ -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",
@@ -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.
+10 -1
View File
@@ -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 {
+7
View File
@@ -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);
+10 -10
View File
@@ -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 <devicePodId>``doctor --pod-id <devicePodId>`,确认 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 <devicePodId>``doctor --pod-id <devicePodId>`,确认 CLI 自动定位当前 runtime,输出来自 cloud-api authority,并且没有泄露 `gatewaySessionId``hostWorkspaceRoot`、probe UID 或 UART port。
## T3
阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:创建本地 `.device-pod/<devicePodId>.json`,写入假的 `gatewaySessionId`,再运行 `profile list` 和 selector `--dry-run`;确认 JSON 输出标记 `localProfileAuthority=false` 且没有读取本地 fake route。
阅读 `.agents/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:创建本地 `.device-pod/<devicePodId>.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 <devicePodId>`,确认返回 `legacy_profile_create_removed`,并提示使用 cloud-api admin API 管理服务端 profile/grant。
阅读 `.agents/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:运行 `profile create --pod-id <devicePodId>`,确认返回 `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 装配的当前集群路径。