diff --git a/cmd/hwlab-device-pod/main.test.ts b/cmd/hwlab-device-pod/main.test.ts index 83e5f527..07e63d46 100644 --- a/cmd/hwlab-device-pod/main.test.ts +++ b/cmd/hwlab-device-pod/main.test.ts @@ -346,6 +346,43 @@ test("device-pod executor maps v0.1 CLI options to device-host-cli argv", async } }); +test("device-pod executor forwards workspace rg patterns through base64 for Windows cmd", async () => { + let dispatchedBody = null; + const cloudApi = createServer(async (request, response) => { + dispatchedBody = await requestJson(request); + response.writeHead(200, { "content-type": "application/json; charset=utf-8" }); + response.end(JSON.stringify({ + jsonrpc: "2.0", + id: dispatchedBody.id, + result: { status: "completed", dispatch: { dispatchStatus: "succeeded", stdout: "ok", exitCode: 0 } } + })); + }); + await new Promise((resolve) => cloudApi.listen(0, "127.0.0.1", resolve)); + const cloudPort = cloudApi.address().port; + const service = await startDevicePod("device-pod-test", { + HWLAB_CLOUD_API_INTERNAL_URL: `http://127.0.0.1:${cloudPort}` + }); + + try { + await submitAndWait(service.port, "workspace.rg", { + pattern: "FREQ_Controller_FW\\.(axf|bin|hex)$", + path: "projects/71-00075-11/FirmWare/MDK-ARM", + nameOnly: true, + filesWithMatches: true + }, "job_rg_quote", { + profile: { devicePodId: "device-pod-test", target: { id: "target-test" }, route: { gatewaySessionId: "gws_test", hostCli: "node tools\\device-host-cli.mjs" } } + }); + const command = dispatchedBody.params.input.command; + const encoded = Buffer.from("FREQ_Controller_FW\\.(axf|bin|hex)$", "utf8").toString("base64"); + assert.match(command, /^node tools\\device-host-cli\.mjs --profile-json-b64 /u); + assert.match(command, new RegExp(` workspace rg --pattern-b64 ${encoded.replace(/[+]/gu, "\\+")} --path projects/71-00075-11/FirmWare/MDK-ARM --files-with-matches --name-only$`, "u")); + assert.equal(command.includes("FREQ_Controller_FW\\\\."), false); + } finally { + await service.stop(); + await new Promise((resolve, reject) => cloudApi.close((error) => (error ? reject(error) : resolve()))); + } +}); + test("device-pod executor returns build verify synchronously for one-call artifact verification (HWLAB #821)", async () => { let dispatchedBody = null; const cloudApi = createServer(async (request, response) => { diff --git a/cmd/hwlab-device-pod/main.ts b/cmd/hwlab-device-pod/main.ts index 7997fe81..004ce51e 100644 --- a/cmd/hwlab-device-pod/main.ts +++ b/cmd/hwlab-device-pod/main.ts @@ -523,7 +523,7 @@ function deviceHostArgs(intent, args = {}) { } if (intent === "workspace.rg") { const pattern = textOr(args.pattern ?? args.query, ""); - return pattern ? ["workspace", "rg", pattern, textOr(args.path, "."), ...hostOptionArgs(args, ["glob", "g", "filesWithMatches", "nameOnly", "names", "l", "ignoreCase", "i", "maxCount", "limit"])] : null; + return pattern ? ["workspace", "rg", "--pattern-b64", Buffer.from(pattern, "utf8").toString("base64"), "--path", textOr(args.path, "."), ...hostOptionArgs(args, ["glob", "g", "filesWithMatches", "nameOnly", "names", "l", "ignoreCase", "i", "maxCount", "limit"])] : null; } if (intent === "workspace.put") { const path = textOr(args.path, ""); diff --git a/skills/device-pod-cli/SKILL.md b/skills/device-pod-cli/SKILL.md index fad48866..263f851b 100644 --- a/skills/device-pod-cli/SKILL.md +++ b/skills/device-pod-cli/SKILL.md @@ -253,7 +253,7 @@ Do not treat local files such as `.device-pod/device-pod-71-freq.json` or `DEVIC - Use `hwpod`/`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 ` 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. -- For regex searches with spaces, pipes, or multiple path tokens, prefer explicit `rg --pattern --path ` or `--pattern-b64 ` instead of positional shell parsing. `rg` searches file contents by default; add `--name-only` / `--names` / `--files-with-matches` to search file names, and `--limit ` to raise the file walk ceiling for large Keil workspaces. +- For regex searches with spaces, pipes, or multiple path tokens, prefer explicit `rg --pattern --path ` or `--pattern-b64 ` instead of positional shell parsing. `rg` searches file contents by default; add `--name-only` / `--names` / `--files-with-matches` to search file names. The default file walk ceiling is 100000; use `--limit ` to lower it for narrow searches. - 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. diff --git a/skills/device-pod-cli/assets/device-host-cli.mjs b/skills/device-pod-cli/assets/device-host-cli.mjs index 4a4d0a6b..8b5432cb 100644 --- a/skills/device-pod-cli/assets/device-host-cli.mjs +++ b/skills/device-pod-cli/assets/device-host-cli.mjs @@ -603,7 +603,9 @@ function workspaceRm(rel, options = {}) { ok('workspace.rm', { path: path.relative(profile.__workspaceRoot, file), removed: true, bytes: content.length, sha256: createHash('sha256').update(content).digest('hex') }); } -function walkFiles(root, rel, results, limit = 20000) { +const DEFAULT_RG_SCAN_LIMIT = 100000; + +function walkFiles(root, rel, results, limit = DEFAULT_RG_SCAN_LIMIT) { if (results.length >= limit) return true; const dir = path.join(root, rel); let truncated = false; @@ -621,7 +623,7 @@ function rgWorkspace(pattern, rel, options = {}) { const profile = loadProfile(); const root = resolveWorkspacePath(profile, rel || '.'); const files = []; - const scanLimit = parsePositiveInt(optionValue(options, 'limit'), 20000, 'rg limit', 1, 100000); + const scanLimit = parsePositiveInt(optionValue(options, 'limit'), DEFAULT_RG_SCAN_LIMIT, 'rg limit', 1, DEFAULT_RG_SCAN_LIMIT); let filesTruncated = false; const stat = fs.statSync(root); if (stat.isFile()) files.push(path.relative(profile.__workspaceRoot, root)); diff --git a/tools/device-pod-cli.test.ts b/tools/device-pod-cli.test.ts index dabc86c5..ff56fcff 100644 --- a/tools/device-pod-cli.test.ts +++ b/tools/device-pod-cli.test.ts @@ -734,7 +734,7 @@ test("device-pod-cli forwards workspace rg filename search and scan limit option "D601-71-FREQ:workspace:/", "rg", "--pattern", - "FREQ_Controller_FW\\\\.(axf|bin|hex)$", + "FREQ_Controller_FW\\.(axf|bin|hex)$", "--path", "projects/71-00075-11/FirmWare/MDK-ARM", "--name-only", @@ -752,7 +752,7 @@ test("device-pod-cli forwards workspace rg filename search and scan limit option assert.deepEqual(seen[0].body, { intent: "workspace.rg", args: { - pattern: "FREQ_Controller_FW\\\\.(axf|bin|hex)$", + pattern: "FREQ_Controller_FW\\.(axf|bin|hex)$", path: "projects/71-00075-11/FirmWare/MDK-ARM", nameOnly: true, filesWithMatches: true,