feat: 接入 D601 F103 v2 device-pod
This commit is contained in:
@@ -356,6 +356,11 @@ test("device-pod executor maps absorbed G14 device-pod operations to host CLI ar
|
||||
};
|
||||
|
||||
try {
|
||||
await submitAndWait(service.port, "workspace.bootsharp", {
|
||||
path: ".",
|
||||
depth: 2,
|
||||
limit: 120
|
||||
}, "job_map_bootsharp", baseBody);
|
||||
await submitAndWait(service.port, "workspace.put", {
|
||||
path: "User/new.c",
|
||||
contentB64: Buffer.from("int x;\n").toString("base64"),
|
||||
@@ -375,12 +380,13 @@ test("device-pod executor maps absorbed G14 device-pod operations to host CLI ar
|
||||
expectResultField: ["pin", "value"]
|
||||
}, "job_map_jsonrpc", baseBody);
|
||||
|
||||
assert.match(commands[0], / workspace put User\/new\.c --content-b64 /u);
|
||||
assert.match(commands[0], / --create-dirs$/u);
|
||||
assert.match(commands[1], / workspace keil add-source User\/new\.c --base projects\/app --group User$/u);
|
||||
assert.match(commands[2], / io-probe uart\/1 jsonrpc gpio\.read /u);
|
||||
assert.match(commands[2], / --params "\{\\"pin\\":\\"PB5\\"\}" /u);
|
||||
assert.match(commands[2], / --require-jsonrpc-result --expect-result-field pin --expect-result-field value$/u);
|
||||
assert.match(commands[0], / workspace bootsharp \. --depth 2 --limit 120$/u);
|
||||
assert.match(commands[1], / workspace put User\/new\.c --content-b64 /u);
|
||||
assert.match(commands[1], / --create-dirs$/u);
|
||||
assert.match(commands[2], / workspace keil add-source User\/new\.c --base projects\/app --group User$/u);
|
||||
assert.match(commands[3], / io-probe uart\/1 jsonrpc gpio\.read /u);
|
||||
assert.match(commands[3], / --params "\{\\"pin\\":\\"PB5\\"\}" /u);
|
||||
assert.match(commands[3], / --require-jsonrpc-result --expect-result-field pin --expect-result-field value$/u);
|
||||
} finally {
|
||||
await service.stop();
|
||||
await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve())));
|
||||
|
||||
@@ -369,7 +369,7 @@ function gatewayAdapterBlocker(summary) {
|
||||
}
|
||||
|
||||
function gatewayDispatchFailedBlocker(summary) {
|
||||
return { code: "gateway_dispatch_failed", layer: "device-pod", retryable: true, summary, userMessage: "Device Pod gateway/device-host-cli dispatch failed." };
|
||||
return { code: "gateway_dispatch_failed", layer: "device-pod", retryable: true, summary: String(summary || "gateway/device-host-cli dispatch failed"), userMessage: "Device Pod gateway/device-host-cli dispatch failed." };
|
||||
}
|
||||
|
||||
function gatewayDispatchPreflightBlocker(route, command) {
|
||||
@@ -392,9 +392,11 @@ function jobFromGatewayDispatch(job, payload, httpStatus) {
|
||||
}
|
||||
const result = normalizeObject(payload.result);
|
||||
const dispatch = normalizeObject(result.dispatch);
|
||||
const dispatchText = gatewayDispatchText(result, dispatch);
|
||||
const blocker = payload.blocker ?? result.blocker ?? (payload.error ? gatewayDispatchFailedBlocker(payload.error.message ?? "gateway dispatch failed") : null);
|
||||
const completed = !blocker && httpStatus < 400 && (result.status === "completed" || result.status === "succeeded" || dispatch.dispatchStatus === "succeeded" || dispatch.dispatchStatus === "completed");
|
||||
const status = completed ? "completed" : blocker?.code === "gateway_dispatch_unavailable" ? "blocked" : "failed";
|
||||
const failureSummary = blocker?.summary || dispatch.message || dispatch.stderr || dispatchText || result.status || "gateway/device-host-cli dispatch failed";
|
||||
const now = new Date().toISOString();
|
||||
return {
|
||||
...job,
|
||||
@@ -402,15 +404,15 @@ function jobFromGatewayDispatch(job, payload, httpStatus) {
|
||||
updatedAt: now,
|
||||
completedAt: now,
|
||||
output: boundedOutput({
|
||||
text: gatewayDispatchText(result, dispatch),
|
||||
text: dispatchText,
|
||||
httpStatus,
|
||||
dispatch,
|
||||
gateway: result.gateway ?? null,
|
||||
auditId: result.auditId ?? null,
|
||||
evidenceId: result.evidenceId ?? null,
|
||||
summary: completed ? "gateway/device-host-cli dispatch completed" : blocker?.summary ?? "gateway/device-host-cli dispatch failed"
|
||||
summary: completed ? "gateway/device-host-cli dispatch completed" : failureSummary
|
||||
}),
|
||||
blocker: completed ? null : blocker ?? gatewayDispatchFailedBlocker(dispatch.message ?? dispatch.stderr ?? result.status ?? "gateway dispatch failed")
|
||||
blocker: completed ? null : (blocker?.summary ? blocker : gatewayDispatchFailedBlocker(failureSummary))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -418,7 +420,8 @@ function gatewayDispatchText(result, dispatch) {
|
||||
if (typeof dispatch.stdout === "string" && dispatch.stdout) return dispatch.stdout;
|
||||
if (typeof dispatch.stderr === "string" && dispatch.stderr) return dispatch.stderr;
|
||||
if (typeof result.text === "string") return result.text;
|
||||
return JSON.stringify(result);
|
||||
if (result && Object.keys(result).length > 0) return JSON.stringify(result);
|
||||
return "";
|
||||
}
|
||||
|
||||
function deviceHostCommand(profile, job) {
|
||||
@@ -468,6 +471,7 @@ function shellQuote(value) {
|
||||
}
|
||||
|
||||
function deviceHostArgs(intent, args = {}) {
|
||||
if (intent === "workspace.bootsharp") return ["workspace", "bootsharp", textOr(args.path, "."), ...hostOptionArgs(args, ["depth", "limit", "agentsLimit"])];
|
||||
if (intent === "workspace.ls") return ["workspace", "ls", textOr(args.path, ".")];
|
||||
if (intent === "workspace.cat") {
|
||||
const path = textOr(args.path, "");
|
||||
@@ -518,8 +522,8 @@ function deviceHostArgs(intent, args = {}) {
|
||||
"workspace",
|
||||
"build",
|
||||
textOr(args.action, "start"),
|
||||
...jobIdArgs(args),
|
||||
...hostOptionArgs(args, ["target", "timeoutMs", "clean", "dryRun"]),
|
||||
...jobIdArgs(args)
|
||||
];
|
||||
}
|
||||
if (intent === "debug.status") return ["debug-probe", "status"];
|
||||
@@ -529,6 +533,7 @@ function deviceHostArgs(intent, args = {}) {
|
||||
"debug-probe",
|
||||
"download",
|
||||
textOr(args.action, "start"),
|
||||
...jobIdArgs(args),
|
||||
...hostOptionArgs(args, [
|
||||
"target",
|
||||
"timeoutMs",
|
||||
@@ -537,8 +542,7 @@ function deviceHostArgs(intent, args = {}) {
|
||||
"durationMs",
|
||||
"port",
|
||||
"baudRate"
|
||||
]),
|
||||
...jobIdArgs(args)
|
||||
])
|
||||
];
|
||||
}
|
||||
if (intent === "debug.reset") return ["debug-probe", "reset"];
|
||||
|
||||
@@ -190,6 +190,7 @@ DELETE /v1/admin/device-pod-grants/{devicePodId}/{userId}
|
||||
第一阶段 intent 集合:
|
||||
|
||||
- `workspace.ls`
|
||||
- `workspace.bootsharp`
|
||||
- `workspace.cat`
|
||||
- `workspace.rg`
|
||||
- `workspace.apply-patch`
|
||||
@@ -263,6 +264,7 @@ manages: many devicePodId
|
||||
- `profile list/show` 调用 cloud-api `/v1/device-pods` 和 `/status`,只显示服务端脱敏 profile 摘要和 `profileHash`。
|
||||
- `setup first-admin` 和 `admin device-pod upsert/grant` 只作为 cloud-api REST wrapper,用于首次空库 seed 或 admin profile/grant 管理;它们接受显式 `--profile-json` 或 `--device-pod-json`,不得读取本地 `.device-pod/*.json` 作为权威 profile。
|
||||
- `devicePodId:workspace|debug-probe|io-probe...` selector 转换为 `POST /v1/device-pods/{devicePodId}/jobs` 或 job status/output/cancel REST,不直接调用 `/v1/rpc/hardware.invoke.shell`。
|
||||
- `bootsharp --pod-id <devicePodId>` 和 `<devicePodId>:workspace:/ bootsharp` 都转换为 `workspace.bootsharp` job,用于返回 workspace tree、AGENTS.md 摘要和当前路径提示;该入口是上下文恢复和 DS 派单的首个探测动作,不读取本地 profile。
|
||||
- workspace 写操作覆盖 `apply-patch`、`put`、`rm`、`rmdir`、`build` 和 `keil add-source/remove-source` 等 Keil 工程维护动作。
|
||||
- 源码局部编辑优先使用 `apply-patch`;`put` 只用于明确的整文件写入或新文件创建。
|
||||
- UART 业务覆盖 `read`、`write`、`read-after-launch-flash` 和 `jsonrpc`;JSON-RPC 请求必须由 device-host-cli 校验 response id,除非显式传入允许 id mismatch 的业务参数。
|
||||
@@ -274,6 +276,8 @@ manages: many devicePodId
|
||||
|
||||
D601 Windows F103 gateway 的稳定命名使用 `gws_D601_F103`。不要因为当前 `devicePodId` 或样例设备名是 `device-pod-71-freq` / `71-FREQ`,把 gateway session 改成旧的 `gws_d601_win_71_freq`;profile route 和 Windows gateway 运行脚本必须使用同一组 F103 命名。
|
||||
|
||||
当前正式接入的 F103 v2 逻辑设备名为 `D601-F103-V2`。它的 Windows workspace 固定为 `F:\Work\D601-HWLAB`,Keil 工程为 `projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx`,Keil target 为 `USART`,Keil 可执行文件为 `C:\Keil_v5\UV4\UV4.exe`,UART 为 `COM9`/`115200`。v0.2 服务端 profile 必须使用 `devicePodId=D601-F103-V2`,不得继续把该 workspace 暴露成 `device-pod-71-freq`。
|
||||
|
||||
Windows 侧固定入口:
|
||||
|
||||
```text
|
||||
@@ -298,15 +302,15 @@ set "HWLAB_GATEWAY_CMD_TIMEOUT_MS=120000"
|
||||
set "HWLAB_GATEWAY_PORT=7001"
|
||||
```
|
||||
|
||||
v0.2 `device-pod-71-freq` 的服务端 profile route 必须和 gateway 注册保持一致:
|
||||
v0.2 `D601-F103-V2` 的服务端 profile route 必须和 gateway 注册保持一致:
|
||||
|
||||
```json
|
||||
{
|
||||
"gatewaySessionId": "gws_D601_F103",
|
||||
"resourceId": "res_windows_host",
|
||||
"capabilityId": "cap_windows_cmd_exec",
|
||||
"hostWorkspaceRoot": "F:\\Work\\ConStart",
|
||||
"hostCli": "node C:\\Users\\liang\\AppData\\Local\\Temp\\hwlab-v02-device-host-cli\\device-host-cli.mjs"
|
||||
"hostWorkspaceRoot": "F:\\Work\\D601-HWLAB",
|
||||
"hostCli": "node tools\\device-host-cli.mjs"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -345,6 +349,40 @@ bun tools/device-pod-cli.ts job output --pod-id device-pod-71-freq "$JOB" \
|
||||
--cookie "$COOKIE"
|
||||
```
|
||||
|
||||
`D601-F103-V2` 的验收命令使用同一条 v0.2 CLI 链路,只替换 `devicePodId` 并从 `bootsharp` 开始:
|
||||
|
||||
```bash
|
||||
bun tools/hwlab-cli/bin/hwlab-cli.ts client device-pods status D601-F103-V2 \
|
||||
--base-url http://74.48.78.17:19666 \
|
||||
--full
|
||||
|
||||
COOKIE=$(node -pe 'require("./.state/hwlab-cli/session.json").cookie')
|
||||
bun tools/device-pod-cli.ts bootsharp --pod-id D601-F103-V2 \
|
||||
--api-base-url http://74.48.78.17:19667 \
|
||||
--cookie "$COOKIE"
|
||||
|
||||
LEASE=$(bun tools/device-pod-cli.ts lease acquire --pod-id D601-F103-V2 \
|
||||
--api-base-url http://74.48.78.17:19667 \
|
||||
--cookie "$COOKIE" \
|
||||
--reason "D601-F103-V2 build/download validation" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).body.leaseToken))')
|
||||
|
||||
bun tools/device-pod-cli.ts D601-F103-V2:workspace:/ build start \
|
||||
--api-base-url http://74.48.78.17:19667 \
|
||||
--cookie "$COOKIE" \
|
||||
--lease-token "$LEASE" \
|
||||
--reason "D601-F103-V2 Keil build validation" \
|
||||
--timeout-ms 120000
|
||||
|
||||
bun tools/device-pod-cli.ts D601-F103-V2:debug-probe download start \
|
||||
--api-base-url http://74.48.78.17:19667 \
|
||||
--cookie "$COOKIE" \
|
||||
--lease-token "$LEASE" \
|
||||
--reason "D601-F103-V2 Keil download validation" \
|
||||
--capture-uart uart/1 \
|
||||
--capture-duration-ms 8000 \
|
||||
--timeout-ms 120000
|
||||
```
|
||||
|
||||
验收通过条件:`client device-pods status` 返回 `status=ok` 且 `blocker=null`;gateway sessions 中 `gws_D601_F103` 为 online;`device-pod-cli` job 进入 `completed`,且 D601 Windows gateway `/status.lastDispatch.operationId` 与该 job 的 `operationId` 一致并有 `lastResultAt`。旧 `gws_d601_win_71_freq` session 只能作为历史 stale 线索,不能作为 v0.2 F103 gateway 的目标命名。
|
||||
|
||||
## 测试规格
|
||||
|
||||
@@ -8,6 +8,7 @@ const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 7;
|
||||
const CODE_AGENT_DEVICE_POD_SESSION_REFRESH_SKEW_SECONDS = 60 * 5;
|
||||
const DEVICE_JOB_CONTRACT_VERSION = "device-pod-job-v1";
|
||||
const DEVICE_JOB_INTENTS = new Set([
|
||||
"workspace.bootsharp",
|
||||
"workspace.ls",
|
||||
"workspace.cat",
|
||||
"workspace.rg",
|
||||
|
||||
@@ -204,7 +204,7 @@ export const CODEX_STDIO_BOUNDARY_INSTRUCTIONS = [
|
||||
"Use the provided workspace and repo-owned Codex stdio session only.",
|
||||
"Do not read or print secrets, tokens, kubeconfig files, DB URLs, private keys, or raw environment values.",
|
||||
"For hardware, gateway, box-simu, patch-panel, DAP, PWM, Keil, serial, and Windows skill requests, execute the requested work through the repo-owned Codex stdio session with the available tran wrapper, skill CLI, or project tool that actually reaches the target.",
|
||||
"For any request that mentions device-pod, device-pod-cli, device-host-cli, a device-pod profile, device-pod-71-freq, device-pod-71-00075-11, or a registered device-pod Keil build/download/UART/debug-probe operation, first use the HWLAB internal skill named device-pod-cli. The canonical skill location is /app/skills/device-pod-cli/SKILL.md; read that manifest and run node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs for the operation.",
|
||||
"For any request that mentions device-pod, device-pod-cli, device-host-cli, a device-pod profile, device-pod-71-freq, device-pod-71-00075-11, D601-F103-V2, or a registered device-pod Keil build/download/UART/debug-probe operation, first use the HWLAB internal skill named device-pod-cli. The canonical skill location is /app/skills/device-pod-cli/SKILL.md; read that manifest and run node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs for the operation. After selecting the target pod or resuming context, run bootsharp --pod-id <devicePodId> before edits, build, or download.",
|
||||
"When a matching device-pod profile exists, do not bypass device-pod-cli with direct hwlab-gateway-tran.mjs, Windows Keil skills, serial-monitor skills, keil-cli.py, or ad hoc path discovery. Only drop to tran or Windows-side skills when the device-pod-cli skill explicitly says to bootstrap, install, or repair the lower layer.",
|
||||
"For registered PC gateway Windows command or skill requests, use the preloaded tran wrapper: node /app/tools/hwlab-gateway-tran.mjs gws_DESKTOP-1MHOD9I:/f/work <cmd|ps|upload|download> [options] -- <args>. The locator before ':' is the gateway session and the path after ':' is the Windows workspace, with /f/work and f:/work both mapping to F:\\work.",
|
||||
"For Windows cmd, call tran as: node /app/tools/hwlab-gateway-tran.mjs gws_DESKTOP-1MHOD9I:/f/work cmd -- <command>. For PowerShell, call tran as: node /app/tools/hwlab-gateway-tran.mjs gws_DESKTOP-1MHOD9I:/f/work ps -- <script>. The wrapper owns UTF-8, quoting, cwd, EncodedCommand, and stdout/stderr passthrough; do not hand-build nested cmd /c, cd &&, pipes, or quoting workarounds.",
|
||||
|
||||
@@ -68,6 +68,15 @@ node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs profile show --pod-id
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs health --pod-id device-pod-71-freq --api-base-url <url> --session-token <token>
|
||||
```
|
||||
|
||||
Start every Device Pod task or resumed context with `bootsharp` so the agent sees the current workspace tree and `AGENTS.md` through the formal REST path:
|
||||
|
||||
```sh
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs bootsharp --pod-id <devicePodId> --api-base-url <url> --session-token <token>
|
||||
node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs <devicePodId>:workspace:/ bootsharp --api-base-url <url> --session-token <token>
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```sh
|
||||
@@ -165,6 +174,7 @@ Do not treat local files such as `.device-pod/device-pod-71-freq.json` or `DEVIC
|
||||
## Operation Rules
|
||||
|
||||
- Use `device-pod-cli` first for workspace, build, download, reset, UART, and debug-probe tasks.
|
||||
- After selecting a pod or resuming context, run `bootsharp --pod-id <devicePodId>` before edits, build, or download.
|
||||
- Prefer `workspace apply-patch` for source edits. Use `workspace put` only for intentional whole-file writes or new files, because it bypasses hunk-level conflict detection.
|
||||
- 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.
|
||||
|
||||
@@ -106,6 +106,21 @@ test("device-pod-cli selector creates REST job instead of gateway RPC", async ()
|
||||
assert.equal(JSON.stringify(seen[0].body).includes("gatewaySessionId"), false);
|
||||
});
|
||||
|
||||
test("device-pod-cli bootsharp creates a formal workspace bootsharp REST job", async () => {
|
||||
const seen: any[] = [];
|
||||
const result = await runDevicePodCli(["--api-base-url", "http://cloud.test", "--session-token", "session-a", "bootsharp", "--pod-id", "D601-F103-V2", "--depth", "2"], {
|
||||
fetchImpl: async (url, init) => {
|
||||
seen.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
|
||||
return jsonResponse(202, { ok: true, status: "running", job: { id: "job_bootsharp", status: "running" }, profileHash: "sha256:abc" });
|
||||
},
|
||||
now: () => "2026-05-31T00:00:00.000Z"
|
||||
});
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(seen[0].url, "http://cloud.test/v1/device-pods/D601-F103-V2/jobs");
|
||||
assert.deepEqual(seen[0].body, { intent: "workspace.bootsharp", args: { path: ".", depth: 2 } });
|
||||
assert.equal(result.payload.localProfileAuthority, false);
|
||||
});
|
||||
|
||||
test("device-pod-cli sends reason and lease token for mutating jobs", async () => {
|
||||
const seen: any[] = [];
|
||||
const result = await runDevicePodCli(["--api-base-url", "http://cloud.test", "--session-token", "session-a", "--lease-token", "lease-a", "--reason", "reset smoke", "device-pod-71-freq:debug-probe", "reset"], {
|
||||
@@ -211,6 +226,55 @@ test("device-pod-cli rejects legacy local profile create in formal mode", async
|
||||
assert.equal(result.payload.error.code, "legacy_profile_create_removed");
|
||||
});
|
||||
|
||||
test("device-pod-cli keeps debug-probe status as a probe operation, not job status", async () => {
|
||||
const seen: any[] = [];
|
||||
const result = await runDevicePodCli(["--api-base-url", "http://cloud.test", "--session-token", "session-a", "D601-F103-V2:debug-probe", "status"], {
|
||||
fetchImpl: async (url, init) => {
|
||||
seen.push({ url: String(url), init });
|
||||
return jsonResponse(200, { ok: true, devicePodId: "D601-F103-V2", status: "ok" });
|
||||
},
|
||||
now: () => "2026-05-31T00:00:00.000Z"
|
||||
});
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(seen[0].url, "http://cloud.test/v1/device-pods/D601-F103-V2/status");
|
||||
assert.equal(result.payload.action, "debug.status");
|
||||
});
|
||||
|
||||
test("device-pod-cli forwards host build and download job ids as named device-pod jobs", async () => {
|
||||
const seen: any[] = [];
|
||||
const fetchImpl = async (url, init) => {
|
||||
seen.push({ url: String(url), body: JSON.parse(String(init?.body ?? "{}")) });
|
||||
return jsonResponse(202, { ok: true, status: "running", job: { id: `job_${seen.length}`, status: "running" } });
|
||||
};
|
||||
const build = await runDevicePodCli([
|
||||
"--api-base-url", "http://cloud.test",
|
||||
"--session-token", "session-a",
|
||||
"D601-F103-V2:workspace:/",
|
||||
"build",
|
||||
"status",
|
||||
"20260531101208650-keil-build-030aab"
|
||||
], { fetchImpl, now: () => "2026-05-31T00:00:00.000Z" });
|
||||
assert.equal(build.exitCode, 0);
|
||||
assert.deepEqual(seen[0].body, {
|
||||
intent: "workspace.build",
|
||||
args: { action: "status", jobId: "20260531101208650-keil-build-030aab" }
|
||||
});
|
||||
|
||||
const download = await runDevicePodCli([
|
||||
"--api-base-url", "http://cloud.test",
|
||||
"--session-token", "session-a",
|
||||
"D601-F103-V2:debug-probe",
|
||||
"download",
|
||||
"status",
|
||||
"20260531103014862-keil-download-177dd9"
|
||||
], { fetchImpl, now: () => "2026-05-31T00:00:00.000Z" });
|
||||
assert.equal(download.exitCode, 0);
|
||||
assert.deepEqual(seen[1].body, {
|
||||
intent: "debug.download",
|
||||
args: { action: "status", jobId: "20260531103014862-keil-download-177dd9" }
|
||||
});
|
||||
});
|
||||
|
||||
function jsonResponse(status: number, body: unknown) {
|
||||
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ async function dispatch(target: string, context: any) {
|
||||
if (target === "login") return login(context);
|
||||
if (target === "doctor") return doctor(context);
|
||||
if (target === "health") return health(context);
|
||||
if (target === "bootsharp") return bootsharpCommand(context);
|
||||
if (target === "profile") return profileCommand(context);
|
||||
if (target === "lease" || target === "leases") return leaseCommand(context);
|
||||
if (target === "job" || target === "jobs") return jobCommand(context);
|
||||
@@ -60,6 +61,8 @@ function help() {
|
||||
"device-pod-cli admin grant --api-base-url URL --session-token TOKEN --pod-id device-pod-71-freq --user-id USER_ID",
|
||||
"device-pod-cli profile list --api-base-url URL --session-token TOKEN",
|
||||
"device-pod-cli profile show --pod-id device-pod-71-freq --api-base-url URL --session-token TOKEN",
|
||||
"device-pod-cli bootsharp --pod-id D601-F103-V2 --api-base-url URL --session-token TOKEN",
|
||||
"device-pod-cli D601-F103-V2:workspace:/ bootsharp --api-base-url URL --session-token TOKEN",
|
||||
"device-pod-cli device-pod-71-freq:workspace:/ ls --api-base-url URL --session-token TOKEN",
|
||||
"device-pod-cli device-pod-71-freq:workspace:/ put User/new.c --reason TEXT --lease-token TOKEN < file",
|
||||
"device-pod-cli device-pod-71-freq:workspace:/ rmdir User/empty --reason TEXT --lease-token TOKEN",
|
||||
@@ -68,7 +71,8 @@ function help() {
|
||||
"device-pod-cli device-pod-71-freq:io-probe:/uart/1 jsonrpc gpio.read --params-json '{\"pin\":\"PB5\'} --reason TEXT --lease-token TOKEN",
|
||||
"device-pod-cli job output --pod-id device-pod-71-freq <jobId> --api-base-url URL --session-token TOKEN",
|
||||
"device-pod-cli lease acquire --pod-id device-pod-71-freq --reason TEXT --api-base-url URL --session-token TOKEN"
|
||||
]
|
||||
],
|
||||
startupProbe: "Run bootsharp first after selecting a Device Pod or resuming context; it returns workspace tree and AGENTS.md hints through the formal REST job path."
|
||||
});
|
||||
}
|
||||
|
||||
@@ -138,6 +142,13 @@ async function health({ parsed, rest, env, fetchImpl }: any) {
|
||||
return responsePayload("health", response, { route: { method: "GET", path }, devicePodId: podId || null, localProfileAuthority: false });
|
||||
}
|
||||
|
||||
async function bootsharpCommand(context: any) {
|
||||
const podId = requiredText(context.parsed.podId ?? context.rest[0], "podId");
|
||||
const path = text(context.parsed.path ?? context.rest[1] ?? "/") || "/";
|
||||
const selector = { podId, surface: "workspace", path };
|
||||
return invokeSelector(selector, { ...context, rest: ["bootsharp"] });
|
||||
}
|
||||
|
||||
async function profileCommand({ parsed, rest, env, fetchImpl }: any) {
|
||||
const subcommand = rest[0] || "list";
|
||||
if (subcommand === "create") {
|
||||
@@ -211,7 +222,12 @@ async function buildJob(selector: any, operation: string, rest: string[], parsed
|
||||
let intent = "";
|
||||
if (selector.surface === "workspace") {
|
||||
const basePath = selector.path || ".";
|
||||
if (operation === "ls") {
|
||||
if (operation === "bootsharp") {
|
||||
intent = "workspace.bootsharp";
|
||||
args.path = joinPath(basePath, rest[1] || "");
|
||||
Object.assign(args, passthroughOptions(parsed, ["depth", "limit", "agentsLimit"]));
|
||||
}
|
||||
else if (operation === "ls") {
|
||||
intent = "workspace.ls";
|
||||
args.path = joinPath(basePath, rest[1] || "");
|
||||
}
|
||||
@@ -268,6 +284,7 @@ async function buildJob(selector: any, operation: string, rest: string[], parsed
|
||||
else if (operation === "build") {
|
||||
intent = "workspace.build";
|
||||
args.action = rest[1] || "start";
|
||||
if (["status", "wait"].includes(String(args.action))) args.jobId = text(rest[2] ?? parsed.jobId);
|
||||
Object.assign(args, passthroughOptions(parsed, ["target", "timeoutMs", "clean", "dryRun"]));
|
||||
}
|
||||
else throw cliError("unsupported_workspace_operation", `unsupported workspace operation: ${operation}`);
|
||||
@@ -276,6 +293,7 @@ async function buildJob(selector: any, operation: string, rest: string[], parsed
|
||||
else if (operation === "download") {
|
||||
intent = "debug.download";
|
||||
args.action = rest[1] || "start";
|
||||
if (["status", "wait"].includes(String(args.action))) args.jobId = text(rest[2] ?? parsed.jobId);
|
||||
Object.assign(args, passthroughOptions(parsed, [
|
||||
"target",
|
||||
"timeoutMs",
|
||||
@@ -328,10 +346,7 @@ async function buildJob(selector: any, operation: string, rest: string[], parsed
|
||||
}
|
||||
|
||||
function jobRouteFromSelector(selector: any, operation: string, rest: string[], parsed: ParsedArgs) {
|
||||
if (operation === "status" || operation === "output" || operation === "cancel") return [operation, requiredText(rest[1] ?? parsed.jobId, "jobId")];
|
||||
if ((selector.surface === "workspace" && operation === "build") || (selector.surface === "debug-probe" && operation === "download")) {
|
||||
if (["status", "output", "cancel"].includes(rest[1])) return [rest[1], requiredText(rest[2] ?? parsed.jobId, "jobId")];
|
||||
}
|
||||
if (selector.surface === "workspace" && (operation === "status" || operation === "output" || operation === "cancel")) return [operation, requiredText(rest[1] ?? parsed.jobId, "jobId")];
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user