fix(read): normalized recovery paths
Used the resolved cwd-relative path in summary recovery selectors, PDF image handles, and notebook diagnostics. Fixes #7788
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Normalized resolved file paths in `read` summary recovery selectors, PDF image handles, and notebook errors so suffix-matched input does not teach agents malformed follow-up paths ([#7788](https://github.com/can1357/oh-my-pi/issues/7788)).
|
||||
|
||||
## [17.2.9] - 2026-08-05
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -2486,6 +2486,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
||||
const imageMetadata = await readImageMetadata(absolutePath);
|
||||
const mimeType = imageMetadata?.mimeType;
|
||||
const ext = path.extname(absolutePath).toLowerCase();
|
||||
const resolvedDisplayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
|
||||
const shouldConvertWithMarkit = CONVERTIBLE_EXTENSIONS.has(ext);
|
||||
|
||||
// Profiler reports (macOS `sample` call trees, V8 `.cpuprofile` JSON):
|
||||
@@ -2530,7 +2531,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
||||
fileSize,
|
||||
}));
|
||||
} else if (isNotebookPath(absolutePath) && !isRawSelector(parsed)) {
|
||||
const notebookText = await readEditableNotebookText(absolutePath, localReadPath);
|
||||
const notebookText = await readEditableNotebookText(absolutePath, resolvedDisplayPath);
|
||||
if (isMultiRange(parsed) && parsed.kind === "lines") {
|
||||
return this.#buildInMemoryMultiRangeResult(notebookText, parsed.ranges, {
|
||||
details: { resolvedPath: absolutePath },
|
||||
@@ -2549,7 +2550,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
||||
const result = await convertFileWithMarkit(absolutePath, signal);
|
||||
if (result.ok) {
|
||||
const renderedContent =
|
||||
ext === ".pdf" ? rewritePdfImagePlaceholders(result.content, localReadPath) : result.content;
|
||||
ext === ".pdf" ? rewritePdfImagePlaceholders(result.content, resolvedDisplayPath) : result.content;
|
||||
// Route the converted markdown through the in-memory text builder
|
||||
// so line-range selectors (`file.pdf:50-100`, `:5-16,40-80`) and
|
||||
// raw mode apply against the converted output. Without this,
|
||||
@@ -2592,7 +2593,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
||||
return toolResult<ReadToolDetails>({ resolvedPath: absolutePath, suffixResolution })
|
||||
.text(
|
||||
prependSuffixResolutionNotice(
|
||||
`[Cannot read binary file '${formatPathRelativeToCwd(absolutePath, this.session.cwd)}' (${formatBytes(fileSize)}); not valid UTF-8 text. Use ':raw' to read bytes verbatim.]`,
|
||||
`[Cannot read binary file '${resolvedDisplayPath}' (${formatBytes(fileSize)}); not valid UTF-8 text. Use ':raw' to read bytes verbatim.]`,
|
||||
suffixResolution,
|
||||
),
|
||||
)
|
||||
@@ -2609,7 +2610,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
||||
if (summary?.parsed && summary.elided) {
|
||||
const renderedSummary = this.#renderSummary(summary);
|
||||
const footer = formatSummaryElisionFooter(
|
||||
localReadPath,
|
||||
resolvedDisplayPath,
|
||||
renderedSummary.elidedRanges,
|
||||
renderedSummary.elidedLines,
|
||||
);
|
||||
|
||||
@@ -82,6 +82,24 @@ describe("read summary", () => {
|
||||
expect(result.details?.summary?.elidedSpans).toBe(2);
|
||||
});
|
||||
|
||||
it("uses the resolved path in elision recovery selectors after suffix matching", async () => {
|
||||
const fixture = path.join(tmpDir, "project", "src", "fixture.ts");
|
||||
await fs.mkdir(path.dirname(fixture), { recursive: true });
|
||||
await fs.writeFile(
|
||||
fixture,
|
||||
"export function alpha(value: string): string {\n\tconst clean = value.trim();\n\tconst label = clean || 'alpha';\n\treturn label.toUpperCase();\n}\n\nexport function beta(): number {\n\tconst one = 1;\n\tconst two = 2;\n\treturn one + two;\n}\n",
|
||||
);
|
||||
const malformed = "src/fixture.ts";
|
||||
|
||||
const tool = new ReadTool(createSession(tmpDir));
|
||||
const result = await tool.execute("read-summary-suffix-path", { path: malformed });
|
||||
const text = textOutput(result);
|
||||
|
||||
expect(result.details?.suffixResolution?.to).toBe("project/src/fixture.ts");
|
||||
expect(text).toContain("with project/src/fixture.ts:1-5,7-11]");
|
||||
expect(text).not.toContain(`with ${malformed}:`);
|
||||
});
|
||||
|
||||
it("summarizes Markdown only when prose summaries are enabled", async () => {
|
||||
const fixture = path.join(tmpDir, "fixture.md");
|
||||
await fs.writeFile(
|
||||
@@ -316,9 +334,9 @@ describe("read summary", () => {
|
||||
expect(result.details?.summary?.elidedSpans).toBe(2);
|
||||
expect(result.details?.summary?.elidedLines).toBeGreaterThan(0);
|
||||
expect(text).toContain("ln elided");
|
||||
expect(text).toContain(`${fixture}:1-5,7-11`);
|
||||
expect(text).not.toContain(`${fixture}:raw`);
|
||||
expect(text).not.toContain(`${fixture}:1-9999`);
|
||||
expect(text).toContain("footer.ts:1-5,7-11");
|
||||
expect(text).not.toContain("footer.ts:raw");
|
||||
expect(text).not.toContain("footer.ts:1-9999");
|
||||
// Footer must be the LAST block of output so the recovery hint sits
|
||||
// next to the structural summary it describes.
|
||||
expect(text.trimEnd().endsWith("]")).toBe(true);
|
||||
|
||||
@@ -125,8 +125,8 @@ describe("read PDF image extraction", () => {
|
||||
.join("\n");
|
||||
|
||||
expect(text).not.toContain("<!-- image:");
|
||||
expect(text).toContain(`read \`${pdfPath}:p11-img0.png\``);
|
||||
expect(text).toContain(`read \`${pdfPath}:p11-img1.png\``);
|
||||
expect(text).toContain("read `doc.pdf:p11-img0.png`");
|
||||
expect(text).toContain("read `doc.pdf:p11-img1.png`");
|
||||
// Page/size metadata is preserved in the handle text.
|
||||
expect(text).toContain("page 11, 199x124pt");
|
||||
});
|
||||
@@ -144,7 +144,7 @@ describe("read PDF image extraction", () => {
|
||||
.join("\n");
|
||||
|
||||
expect(text).not.toContain("<!-- image:");
|
||||
expect(text).toContain(`read \`${pdfPath}:p3-img0.png\``);
|
||||
expect(text).toContain("read `doc.pdf:p3-img0.png`");
|
||||
});
|
||||
|
||||
it("extracts a PDF image member as an inline image block", async () => {
|
||||
|
||||
Reference in New Issue
Block a user