fix: support device-pod filename rg

This commit is contained in:
Codex Agent
2026-06-04 15:56:08 +08:00
parent ff7f07434d
commit 37f1b6af22
8 changed files with 112 additions and 25 deletions
+9 -2
View File
@@ -120,12 +120,18 @@ host `logTail` + `buildSummary` directly through the formal REST job path.
### 3. `workspace rg --pattern "regex|pipes"` collides with local shell
When the regex contains `|` or other shell metacharacters, the local shell
interprets them before `hwpod` sees the pattern. Two safe forms:
interprets them before `hwpod` sees the pattern. `workspace rg` searches file
contents by default; use `--name-only`, `--names`, or `--files-with-matches`
when you need to match workspace-relative file names such as build artifacts.
Safe forms:
```sh
# shell-safe: pattern as one arg, but | still needs quoting
hwpod <pod>:workspace:/ rg --pattern "Programming Done|Verify OK" --path <relpath>
# filename search for build artifacts
hwpod <pod>:workspace:/ rg --pattern "FREQ_Controller_FW\\.(axf|bin|hex)$" --path projects/71-00075-11/FirmWare/MDK-ARM --name-only
# bullet-proof: pattern as base64
P=$(printf "%s" "Error\(s\)|Warning\(s\)|Erase Done|Programming Done|Verify OK|Application running" | base64 -w0)
hwpod <pod>:workspace:/ rg --pattern-b64 "$P" --path <relpath> --reason "log scan"
@@ -171,6 +177,7 @@ Run read-only workspace, debug, and I/O jobs:
hwpod device-pod-71-freq:workspace:/ ls
hwpod device-pod-71-freq:workspace:/src rg main
hwpod device-pod-71-freq:workspace:/ rg --pattern "Programming Done|Verify OK|Application running|Error" --path projects/01_baseline/captures
hwpod device-pod-71-freq:workspace:/ rg --pattern "FREQ_Controller_FW\\.(axf|bin|hex)$" --path projects/71-00075-11/FirmWare/MDK-ARM --name-only
hwpod device-pod-71-freq:debug-probe chip-id
hwpod device-pod-71-freq:io-probe:/uart/1 read --duration-ms 5000
```
@@ -242,7 +249,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 <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.
- For regex searches with spaces, pipes, or multiple path tokens, prefer explicit `rg --pattern <regex> --path <workspaceRel>` or `--pattern-b64 <base64>` instead of positional shell parsing.
- For regex searches with spaces, pipes, or multiple path tokens, prefer explicit `rg --pattern <regex> --path <workspaceRel>` or `--pattern-b64 <base64>` instead of positional shell parsing. `rg` searches file contents by default; add `--name-only` / `--names` / `--files-with-matches` to search file names, and `--limit <N>` to raise the file walk ceiling for large Keil workspaces.
- 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.
@@ -598,39 +598,48 @@ 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 = 5000) {
if (results.length >= limit) return;
function walkFiles(root, rel, results, limit = 20000) {
if (results.length >= limit) return true;
const dir = path.join(root, rel);
let truncated = false;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (entry.name === '.git' || entry.name === 'node_modules' || entry.name === '.state') continue;
const childRel = path.join(rel, entry.name);
if (entry.isDirectory()) walkFiles(root, childRel, results, limit);
if (entry.isDirectory()) truncated = walkFiles(root, childRel, results, limit) || truncated;
else if (entry.isFile()) results.push(childRel);
if (results.length >= limit) return;
if (results.length >= limit) return true;
}
return truncated;
}
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);
let filesTruncated = false;
const stat = fs.statSync(root);
if (stat.isFile()) files.push(path.relative(profile.__workspaceRoot, root));
else {
const nested = [];
const rootRel = path.relative(profile.__workspaceRoot, root) || '.';
walkFiles(root, '.', nested);
filesTruncated = walkFiles(root, '.', nested, scanLimit);
files.push(...nested.map((fileRel) => rootRel === '.' ? fileRel : path.join(rootRel, fileRel)));
}
const re = new RegExp(pattern, 'i');
const matches = [];
const fileMatches = [];
const filesWithMatches = Boolean(options.l || options.filesWithMatches || options['files-with-matches'] || options._?.includes('-l'));
const nameOnly = Boolean(filesWithMatches || options.nameOnly || options['name-only'] || options.names || options._?.includes('--name-only'));
const globs = normalizeList(options.g).concat(normalizeList(options.glob), normalizeList(options.iglob));
const types = normalizeList(options.t).concat(normalizeList(options.type));
for (const fileRel of files) {
if (!rgFileAllowed(fileRel, { globs, types })) continue;
if (!filesWithMatches && matches.length >= 100) break;
if (nameOnly) {
if (re.test(fileRel.replace(/\\/gu, '/'))) fileMatches.push(fileRel);
continue;
}
if (matches.length >= 100) break;
const full = resolveWorkspacePath(profile, fileRel);
const buffer = fs.readFileSync(full);
if (buffer.includes(0)) continue;
@@ -645,7 +654,7 @@ function rgWorkspace(pattern, rel, options = {}) {
if (matches.length < 100) matches.push({ path: fileRel, line: index + 1, text: line.slice(0, 500) });
}
}
ok('workspace.rg', { pattern, root: path.relative(profile.__workspaceRoot, root) || '.', count: filesWithMatches ? fileMatches.length : matches.length, files: filesWithMatches ? fileMatches : undefined, matches: filesWithMatches ? undefined : matches, options: { globs, types, filesWithMatches } });
ok('workspace.rg', { pattern, root: path.relative(profile.__workspaceRoot, root) || '.', count: nameOnly ? fileMatches.length : matches.length, files: nameOnly ? fileMatches : undefined, matches: nameOnly ? undefined : matches, options: { globs, types, filesWithMatches, nameOnly, scanLimit, filesScanned: files.length, filesTruncated } });
}
function resolveRgWorkspaceRequest(rest) {
@@ -654,7 +663,14 @@ function resolveRgWorkspaceRequest(rest) {
if (encodedPattern !== undefined) {
return {
pattern: decodeBase64Utf8(encodedPattern, '--pattern-b64'),
rel: parsed._[0] || '.',
rel: optionString(parsed, 'path') || parsed._[0] || '.',
options: parsed,
};
}
if (parsed.pattern !== undefined) {
return {
pattern: String(Array.isArray(parsed.pattern) ? parsed.pattern[0] : parsed.pattern),
rel: optionString(parsed, 'path') || parsed._[0] || '.',
options: parsed,
};
}