Agents: raise bootstrap total cap and warn on /context truncation (#18229)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: f6620526df
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
Gustavo Madeira Santana
2026-02-16 12:04:53 -05:00
committed by GitHub
parent 5b185da366
commit 8a67016646
16 changed files with 188 additions and 14 deletions

View File

@@ -0,0 +1,79 @@
import { describe, expect, it } from "vitest";
import type { HandleCommandsParams } from "./commands-types.js";
import { buildContextReply } from "./commands-context-report.js";
function makeParams(commandBodyNormalized: string, truncated: boolean): HandleCommandsParams {
return {
command: {
commandBodyNormalized,
channel: "telegram",
senderIsOwner: true,
},
sessionKey: "agent:default:main",
workspaceDir: "/tmp/workspace",
contextTokens: null,
provider: "openai",
model: "gpt-5",
elevated: { allowed: false },
resolvedThinkLevel: "off",
resolvedReasoningLevel: "off",
sessionEntry: {
totalTokens: 123,
inputTokens: 100,
outputTokens: 23,
systemPromptReport: {
source: "run",
generatedAt: Date.now(),
workspaceDir: "/tmp/workspace",
bootstrapMaxChars: 20_000,
bootstrapTotalMaxChars: 150_000,
sandbox: { mode: "off", sandboxed: false },
systemPrompt: {
chars: 1_000,
projectContextChars: 500,
nonProjectContextChars: 500,
},
injectedWorkspaceFiles: [
{
name: "AGENTS.md",
path: "/tmp/workspace/AGENTS.md",
missing: false,
rawChars: truncated ? 200_000 : 10_000,
injectedChars: truncated ? 20_000 : 10_000,
truncated,
},
],
skills: {
promptChars: 10,
entries: [{ name: "checks", blockChars: 10 }],
},
tools: {
listChars: 10,
schemaChars: 20,
entries: [{ name: "read", summaryChars: 10, schemaChars: 20, propertiesCount: 1 }],
},
},
},
cfg: {},
ctx: {},
commandBody: "",
commandArgs: [],
resolvedElevatedLevel: "off",
} as unknown as HandleCommandsParams;
}
describe("buildContextReply", () => {
it("shows bootstrap truncation warning in list output when context exceeds configured limits", async () => {
const result = await buildContextReply(makeParams("/context list", true));
expect(result.text).toContain("Bootstrap max/total: 150,000 chars");
expect(result.text).toContain("⚠ Bootstrap context is over configured limits");
expect(result.text).toContain(
"Causes: 1 file(s) exceeded max/file; raw total exceeded max/total.",
);
});
it("does not show bootstrap truncation warning when there is no truncation", async () => {
const result = await buildContextReply(makeParams("/context list", false));
expect(result.text).not.toContain("Bootstrap context is over configured limits");
});
});

View File

@@ -4,7 +4,10 @@ import type { HandleCommandsParams } from "./commands-types.js";
import { resolveSessionAgentIds } from "../../agents/agent-scope.js";
import { resolveBootstrapContextForRun } from "../../agents/bootstrap-files.js";
import { resolveDefaultModelForAgent } from "../../agents/model-selection.js";
import { resolveBootstrapMaxChars } from "../../agents/pi-embedded-helpers.js";
import {
resolveBootstrapMaxChars,
resolveBootstrapTotalMaxChars,
} from "../../agents/pi-embedded-helpers.js";
import { createOpenClawCodingTools } from "../../agents/pi-tools.js";
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js";
@@ -59,6 +62,7 @@ async function resolveContextReport(
const workspaceDir = params.workspaceDir;
const bootstrapMaxChars = resolveBootstrapMaxChars(params.cfg);
const bootstrapTotalMaxChars = resolveBootstrapTotalMaxChars(params.cfg);
const { bootstrapFiles, contextFiles: injectedFiles } = await resolveBootstrapContextForRun({
workspaceDir,
config: params.cfg,
@@ -169,6 +173,7 @@ async function resolveContextReport(
model: params.model,
workspaceDir,
bootstrapMaxChars,
bootstrapTotalMaxChars,
sandbox: { mode: sandboxRuntime.mode, sandboxed: sandboxRuntime.sandboxed },
systemPrompt,
bootstrapFiles,
@@ -250,6 +255,37 @@ export async function buildContextReply(params: HandleCommandsParams): Promise<R
typeof report.bootstrapMaxChars === "number"
? `${formatInt(report.bootstrapMaxChars)} chars`
: "? chars";
const bootstrapTotalLabel =
typeof report.bootstrapTotalMaxChars === "number"
? `${formatInt(report.bootstrapTotalMaxChars)} chars`
: "? chars";
const bootstrapMaxChars = report.bootstrapMaxChars;
const bootstrapTotalMaxChars = report.bootstrapTotalMaxChars;
const nonMissingBootstrapFiles = report.injectedWorkspaceFiles.filter((f) => !f.missing);
const truncatedBootstrapFiles = nonMissingBootstrapFiles.filter((f) => f.truncated);
const rawBootstrapChars = nonMissingBootstrapFiles.reduce((sum, file) => sum + file.rawChars, 0);
const injectedBootstrapChars = nonMissingBootstrapFiles.reduce(
(sum, file) => sum + file.injectedChars,
0,
);
const perFileOverLimitCount =
typeof bootstrapMaxChars === "number"
? nonMissingBootstrapFiles.filter((f) => f.rawChars > bootstrapMaxChars).length
: 0;
const totalOverLimit =
typeof bootstrapTotalMaxChars === "number" && rawBootstrapChars > bootstrapTotalMaxChars;
const truncationCauseParts = [
perFileOverLimitCount > 0 ? `${perFileOverLimitCount} file(s) exceeded max/file` : null,
totalOverLimit ? "raw total exceeded max/total" : null,
].filter(Boolean);
const bootstrapWarningLines =
truncatedBootstrapFiles.length > 0
? [
`⚠ Bootstrap context is over configured limits: ${truncatedBootstrapFiles.length} file(s) truncated (${formatInt(rawBootstrapChars)} raw chars -> ${formatInt(injectedBootstrapChars)} injected chars).`,
...(truncationCauseParts.length ? [`Causes: ${truncationCauseParts.join("; ")}.`] : []),
"Tip: increase `agents.defaults.bootstrapMaxChars` and/or `agents.defaults.bootstrapTotalMaxChars` if this truncation is not intentional.",
]
: [];
const totalsLine =
session.totalTokens != null
@@ -280,8 +316,10 @@ export async function buildContextReply(params: HandleCommandsParams): Promise<R
"🧠 Context breakdown (detailed)",
`Workspace: ${workspaceLabel}`,
`Bootstrap max/file: ${bootstrapMaxLabel}`,
`Bootstrap max/total: ${bootstrapTotalLabel}`,
sandboxLine,
systemPromptLine,
...(bootstrapWarningLines.length ? ["", ...bootstrapWarningLines] : []),
"",
"Injected workspace files:",
...fileLines,
@@ -317,8 +355,10 @@ export async function buildContextReply(params: HandleCommandsParams): Promise<R
"🧠 Context breakdown",
`Workspace: ${workspaceLabel}`,
`Bootstrap max/file: ${bootstrapMaxLabel}`,
`Bootstrap max/total: ${bootstrapTotalLabel}`,
sandboxLine,
systemPromptLine,
...(bootstrapWarningLines.length ? ["", ...bootstrapWarningLines] : []),
"",
"Injected workspace files:",
...fileLines,