* fix(agents): skip bootstrap files with undefined path buildBootstrapContextFiles() called file.path.replace() without checking that path was defined. If a hook pushed a bootstrap file using 'filePath' instead of 'path', the function threw TypeError and crashed every agent session — not just the misconfigured hook. Fix: add a null-guard before the path.replace() call. Files with undefined path are skipped with a warning so one bad hook can't take down all agents. Also adds a test covering the undefined-path case. Fixes #22693 * fix: harden bootstrap path validation and report guards (#22698) (thanks @arosstale) --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { buildSystemPromptReport } from "./system-prompt-report.js";
|
|
import type { WorkspaceBootstrapFile } from "./workspace.js";
|
|
|
|
function makeBootstrapFile(overrides: Partial<WorkspaceBootstrapFile>): WorkspaceBootstrapFile {
|
|
return {
|
|
name: "AGENTS.md",
|
|
path: "/tmp/workspace/AGENTS.md",
|
|
content: "alpha",
|
|
missing: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("buildSystemPromptReport", () => {
|
|
const makeReport = (params: {
|
|
file: WorkspaceBootstrapFile;
|
|
injectedPath: string;
|
|
injectedContent: string;
|
|
bootstrapMaxChars?: number;
|
|
bootstrapTotalMaxChars?: number;
|
|
}) =>
|
|
buildSystemPromptReport({
|
|
source: "run",
|
|
generatedAt: 0,
|
|
bootstrapMaxChars: params.bootstrapMaxChars ?? 20_000,
|
|
bootstrapTotalMaxChars: params.bootstrapTotalMaxChars,
|
|
systemPrompt: "system",
|
|
bootstrapFiles: [params.file],
|
|
injectedFiles: [{ path: params.injectedPath, content: params.injectedContent }],
|
|
skillsPrompt: "",
|
|
tools: [],
|
|
});
|
|
|
|
it("counts injected chars when injected file paths are absolute", () => {
|
|
const file = makeBootstrapFile({ path: "/tmp/workspace/policies/AGENTS.md" });
|
|
const report = makeReport({
|
|
file,
|
|
injectedPath: "/tmp/workspace/policies/AGENTS.md",
|
|
injectedContent: "trimmed",
|
|
});
|
|
|
|
expect(report.injectedWorkspaceFiles[0]?.injectedChars).toBe("trimmed".length);
|
|
});
|
|
|
|
it("keeps legacy basename matching for injected files", () => {
|
|
const file = makeBootstrapFile({ path: "/tmp/workspace/policies/AGENTS.md" });
|
|
const report = makeReport({
|
|
file,
|
|
injectedPath: "AGENTS.md",
|
|
injectedContent: "trimmed",
|
|
});
|
|
|
|
expect(report.injectedWorkspaceFiles[0]?.injectedChars).toBe("trimmed".length);
|
|
});
|
|
|
|
it("marks workspace files truncated when injected chars are smaller than raw chars", () => {
|
|
const file = makeBootstrapFile({
|
|
path: "/tmp/workspace/policies/AGENTS.md",
|
|
content: "abcdefghijklmnopqrstuvwxyz",
|
|
});
|
|
const report = makeReport({
|
|
file,
|
|
injectedPath: "/tmp/workspace/policies/AGENTS.md",
|
|
injectedContent: "trimmed",
|
|
});
|
|
|
|
expect(report.injectedWorkspaceFiles[0]?.truncated).toBe(true);
|
|
});
|
|
|
|
it("includes both bootstrap caps in the report payload", () => {
|
|
const file = makeBootstrapFile({ path: "/tmp/workspace/policies/AGENTS.md" });
|
|
const report = makeReport({
|
|
file,
|
|
injectedPath: "AGENTS.md",
|
|
injectedContent: "trimmed",
|
|
bootstrapMaxChars: 11_111,
|
|
bootstrapTotalMaxChars: 22_222,
|
|
});
|
|
|
|
expect(report.bootstrapMaxChars).toBe(11_111);
|
|
expect(report.bootstrapTotalMaxChars).toBe(22_222);
|
|
});
|
|
|
|
it("reports injectedChars=0 when injected file does not match by path or basename", () => {
|
|
const file = makeBootstrapFile({ path: "/tmp/workspace/policies/AGENTS.md" });
|
|
const report = makeReport({
|
|
file,
|
|
injectedPath: "/tmp/workspace/policies/OTHER.md",
|
|
injectedContent: "trimmed",
|
|
});
|
|
|
|
expect(report.injectedWorkspaceFiles[0]?.injectedChars).toBe(0);
|
|
expect(report.injectedWorkspaceFiles[0]?.truncated).toBe(true);
|
|
});
|
|
|
|
it("ignores malformed injected file paths and still matches valid entries", () => {
|
|
const file = makeBootstrapFile({ path: "/tmp/workspace/policies/AGENTS.md" });
|
|
const report = buildSystemPromptReport({
|
|
source: "run",
|
|
generatedAt: 0,
|
|
bootstrapMaxChars: 20_000,
|
|
systemPrompt: "system",
|
|
bootstrapFiles: [file],
|
|
injectedFiles: [
|
|
{ path: 123 as unknown as string, content: "bad" },
|
|
{ path: "/tmp/workspace/policies/AGENTS.md", content: "trimmed" },
|
|
],
|
|
skillsPrompt: "",
|
|
tools: [],
|
|
});
|
|
|
|
expect(report.injectedWorkspaceFiles[0]?.injectedChars).toBe("trimmed".length);
|
|
});
|
|
});
|