Skip to content

Commit 23bf11c

Browse files
committed
fix(discord): validate realtime bootstrap files
1 parent 526e7d3 commit 23bf11c

2 files changed

Lines changed: 61 additions & 4 deletions

File tree

src/agents/realtime-bootstrap-context.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,29 @@ describe("resolveRealtimeBootstrapContextInstructions", () => {
5353
expect(instructions).not.toContain(workspaceDir);
5454
});
5555

56+
it("ignores unsupported file requests from unchecked callers", async () => {
57+
const workspaceDir = await makeWorkspace();
58+
const warnings: string[] = [];
59+
const uncheckedFiles = ["IDENTITY.md", "AGENTS.md"] as unknown as NonNullable<
60+
Parameters<typeof resolveRealtimeBootstrapContextInstructions>[0]["files"]
61+
>;
62+
await fs.writeFile(path.join(workspaceDir, "IDENTITY.md"), "Name: Wilfred\n", "utf8");
63+
await fs.writeFile(path.join(workspaceDir, "AGENTS.md"), "Do not load me here.\n", "utf8");
64+
65+
const instructions = await resolveRealtimeBootstrapContextInstructions({
66+
config: makeConfig(workspaceDir),
67+
agentId: "main",
68+
files: uncheckedFiles,
69+
warn: (message) => warnings.push(message),
70+
});
71+
72+
expect(instructions).toContain("### IDENTITY.md");
73+
expect(instructions).toContain("Name: Wilfred");
74+
expect(instructions).not.toContain("AGENTS.md");
75+
expect(instructions).not.toContain("Do not load me here.");
76+
expect(warnings).toContain('skipping unsupported realtime bootstrap context file "AGENTS.md"');
77+
});
78+
5679
it("keeps the complete injected instruction text within the default budget", async () => {
5780
const workspaceDir = await makeWorkspace();
5881
await fs.writeFile(

src/agents/realtime-bootstrap-context.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,20 @@ export const REALTIME_BOOTSTRAP_CONTEXT_FILE_NAMES = [
1919
export type RealtimeBootstrapContextFileName =
2020
(typeof REALTIME_BOOTSTRAP_CONTEXT_FILE_NAMES)[number];
2121

22+
const REALTIME_BOOTSTRAP_CONTEXT_FILE_NAME_SET: ReadonlySet<string> = new Set(
23+
REALTIME_BOOTSTRAP_CONTEXT_FILE_NAMES,
24+
);
2225
const DEFAULT_REALTIME_BOOTSTRAP_CONTEXT_MAX_CHARS = 4_000;
2326
const REALTIME_BOOTSTRAP_CONTEXT_TITLE = "OpenClaw realtime voice profile context:";
2427
const REALTIME_BOOTSTRAP_CONTEXT_GUIDANCE =
2528
"Use these profile files for identity, persona, and user grounding; do not mention them unless asked.";
2629

30+
function isRealtimeBootstrapContextFileName(
31+
value: string,
32+
): value is RealtimeBootstrapContextFileName {
33+
return REALTIME_BOOTSTRAP_CONTEXT_FILE_NAME_SET.has(value);
34+
}
35+
2736
function formatRealtimeBootstrapContextFileName(pathValue: string): string {
2837
return path.basename(pathValue.trim().replace(/\\/g, "/"));
2938
}
@@ -41,14 +50,32 @@ function resolveRealtimeBootstrapContextContentBudget(params: {
4150
return params.totalMaxChars - params.preamble.length - separatorChars - headingChars;
4251
}
4352

53+
function normalizeRealtimeBootstrapContextFileNames(
54+
files: readonly string[],
55+
warn?: (message: string) => void,
56+
): RealtimeBootstrapContextFileName[] {
57+
const normalized: RealtimeBootstrapContextFileName[] = [];
58+
for (const fileName of files) {
59+
if (isRealtimeBootstrapContextFileName(fileName)) {
60+
normalized.push(fileName);
61+
continue;
62+
}
63+
warn?.(`skipping unsupported realtime bootstrap context file "${fileName}"`);
64+
}
65+
return normalized;
66+
}
67+
4468
export async function resolveRealtimeBootstrapContextInstructions(params: {
4569
agentId: string;
4670
config: OpenClawConfig;
4771
files?: readonly RealtimeBootstrapContextFileName[];
4872
sessionKey?: string;
4973
warn?: (message: string) => void;
5074
}): Promise<string | undefined> {
51-
const requestedFiles = params.files ?? REALTIME_BOOTSTRAP_CONTEXT_FILE_NAMES;
75+
const requestedFiles = normalizeRealtimeBootstrapContextFileNames(
76+
params.files ?? REALTIME_BOOTSTRAP_CONTEXT_FILE_NAMES,
77+
params.warn,
78+
);
5279
if (requestedFiles.length === 0) {
5380
return undefined;
5481
}
@@ -63,11 +90,18 @@ export async function resolveRealtimeBootstrapContextInstructions(params: {
6390
});
6491
const selectedFiles = bootstrapFiles
6592
.filter(
66-
(file) => !file.missing && requestedOrder.has(file.name as RealtimeBootstrapContextFileName),
93+
(file) =>
94+
!file.missing &&
95+
isRealtimeBootstrapContextFileName(file.name) &&
96+
requestedOrder.has(file.name),
6797
)
6898
.toSorted((left, right) => {
69-
const leftOrder = requestedOrder.get(left.name as RealtimeBootstrapContextFileName) ?? 0;
70-
const rightOrder = requestedOrder.get(right.name as RealtimeBootstrapContextFileName) ?? 0;
99+
const leftOrder = isRealtimeBootstrapContextFileName(left.name)
100+
? (requestedOrder.get(left.name) ?? 0)
101+
: 0;
102+
const rightOrder = isRealtimeBootstrapContextFileName(right.name)
103+
? (requestedOrder.get(right.name) ?? 0)
104+
: 0;
71105
if (leftOrder !== rightOrder) {
72106
return leftOrder - rightOrder;
73107
}

0 commit comments

Comments
 (0)