diff --git a/CHANGELOG.md b/CHANGELOG.md index 932da28009..8254508d10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Docs: https://docs.openclaw.ai - Gateway/Chat: harden `chat.send` inbound message handling by rejecting null bytes, stripping unsafe control characters, and normalizing Unicode to NFC before dispatch. (#8593) Thanks @fr33d3m0n. - Gateway/Send: return an actionable error when `send` targets internal-only `webchat`, guiding callers to use `chat.send` or a deliverable channel. (#15703) Thanks @rodrigouroz. - Control UI: prevent stored XSS via assistant name/avatar by removing inline script injection, serving bootstrap config as JSON, and enforcing `script-src 'self'`. Thanks @Adam55A-code. +- Agents/Context: make bootstrap prompt limits opt-in (`bootstrapMaxChars`/`bootstrapTotalMaxChars`), preserve full core bootstrap content by default, and surface both per-file and total bootstrap caps in `/context` reports. - Agents/Security: sanitize workspace paths before embedding into LLM prompts (strip Unicode control/format chars) to prevent instruction injection via malicious directory names. Thanks @aether-ai-agent. - Agents/Sandbox: clarify system prompt path guidance so sandbox `bash/exec` uses container paths (for example `/workspace`) while file tools keep host-bridge mapping, avoiding first-attempt path misses from host-only absolute paths in sandbox command execution. (#17693) Thanks @app/juniordevbot. - Agents/Context: apply configured model `contextWindow` overrides after provider discovery so `lookupContextTokens()` honors operator config values (including discovery-failure paths). (#17404) Thanks @michaelbship and @vignesh07. diff --git a/docs/concepts/agent-workspace.md b/docs/concepts/agent-workspace.md index 79e1647e8f..8decbcf777 100644 --- a/docs/concepts/agent-workspace.md +++ b/docs/concepts/agent-workspace.md @@ -116,7 +116,8 @@ See [Memory](/concepts/memory) for the workflow and automatic memory flush. If any bootstrap file is missing, OpenClaw injects a "missing file" marker into the session and continues. Large bootstrap files are truncated when injected; -adjust the limit with `agents.defaults.bootstrapMaxChars` (default: 20000). +adjust limits with `agents.defaults.bootstrapMaxChars` and/or +`agents.defaults.bootstrapTotalMaxChars` (unset = unlimited). `openclaw setup` can recreate missing defaults without overwriting existing files. diff --git a/docs/concepts/context.md b/docs/concepts/context.md index c06b7b7f3d..3203007de5 100644 --- a/docs/concepts/context.md +++ b/docs/concepts/context.md @@ -112,7 +112,11 @@ By default, OpenClaw injects a fixed set of workspace files (if present): - `HEARTBEAT.md` - `BOOTSTRAP.md` (first-run only) -Large files are truncated per-file using `agents.defaults.bootstrapMaxChars` (default `20000` chars). OpenClaw also enforces a total bootstrap injection cap across files with `agents.defaults.bootstrapTotalMaxChars` (default `24000` chars). `/context` shows **raw vs injected** sizes and whether truncation happened. +Large files are truncated only when limits are configured. Use +`agents.defaults.bootstrapMaxChars` for per-file limits and +`agents.defaults.bootstrapTotalMaxChars` for total bootstrap limits (unset = +unlimited). `/context` shows **raw vs injected** sizes and whether truncation +happened. ## Skills: what’s injected vs loaded on-demand diff --git a/docs/concepts/system-prompt.md b/docs/concepts/system-prompt.md index e74cea5b56..4eda3efac7 100644 --- a/docs/concepts/system-prompt.md +++ b/docs/concepts/system-prompt.md @@ -70,10 +70,11 @@ compaction. > are accessed on demand via the `memory_search` and `memory_get` tools, so they > do not count against the context window unless the model explicitly reads them. -Large files are truncated with a marker. The max per-file size is controlled by -`agents.defaults.bootstrapMaxChars` (default: 20000). Total injected bootstrap -content across files is capped by `agents.defaults.bootstrapTotalMaxChars` -(default: 24000). Missing files inject a short missing-file marker. +Large files are truncated with a marker only when limits are configured. Use +`agents.defaults.bootstrapMaxChars` for a per-file cap and +`agents.defaults.bootstrapTotalMaxChars` for a total cap across all injected +bootstrap files. If both are unset, bootstrap injection is unlimited. Missing +files inject a short missing-file marker. Sub-agent sessions only inject `AGENTS.md` and `TOOLS.md` (other bootstrap files are filtered out to keep the sub-agent context small). diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md index a74d3257a7..c814a94ba4 100644 --- a/docs/gateway/configuration-reference.md +++ b/docs/gateway/configuration-reference.md @@ -579,7 +579,7 @@ Disables automatic creation of workspace bootstrap files (`AGENTS.md`, `SOUL.md` ### `agents.defaults.bootstrapMaxChars` -Max characters per workspace bootstrap file before truncation. Default: `20000`. +Optional max characters per workspace bootstrap file before truncation. If unset, per-file bootstrap injection is unlimited. ```json5 { @@ -589,7 +589,7 @@ Max characters per workspace bootstrap file before truncation. Default: `20000`. ### `agents.defaults.bootstrapTotalMaxChars` -Max total characters injected across all workspace bootstrap files. Default: `24000`. +Optional max total characters injected across all workspace bootstrap files. If unset, total bootstrap injection is unlimited. ```json5 { diff --git a/docs/reference/token-use.md b/docs/reference/token-use.md index 5b64774664..9e63790552 100644 --- a/docs/reference/token-use.md +++ b/docs/reference/token-use.md @@ -18,7 +18,7 @@ OpenClaw assembles its own system prompt on every run. It includes: - Tool list + short descriptions - Skills list (only metadata; instructions are loaded on demand with `read`) - Self-update instructions -- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus `MEMORY.md` and/or `memory.md` when present). Large files are truncated by `agents.defaults.bootstrapMaxChars` (default: 20000), and total bootstrap injection is capped by `agents.defaults.bootstrapTotalMaxChars` (default: 24000). `memory/*.md` files are on-demand via memory tools and are not auto-injected. +- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus `MEMORY.md` and/or `memory.md` when present). Per-file and total bootstrap truncation only apply when configured via `agents.defaults.bootstrapMaxChars` and/or `agents.defaults.bootstrapTotalMaxChars` (unset = unlimited). `memory/*.md` files are on-demand via memory tools and are not auto-injected. - Time (UTC + user timezone) - Reply tags + heartbeat behavior - Runtime metadata (host/OS/model/thinking) diff --git a/src/agents/pi-embedded-helpers.buildbootstrapcontextfiles.e2e.test.ts b/src/agents/pi-embedded-helpers.buildbootstrapcontextfiles.e2e.test.ts index e4e852e69e..0b6bc5335d 100644 --- a/src/agents/pi-embedded-helpers.buildbootstrapcontextfiles.e2e.test.ts +++ b/src/agents/pi-embedded-helpers.buildbootstrapcontextfiles.e2e.test.ts @@ -2,8 +2,6 @@ import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { buildBootstrapContextFiles, - DEFAULT_BOOTSTRAP_MAX_CHARS, - DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS, resolveBootstrapMaxChars, resolveBootstrapTotalMaxChars, } from "./pi-embedded-helpers.js"; @@ -50,15 +48,15 @@ describe("buildBootstrapContextFiles", () => { expect(warnings[0]).toContain("TOOLS.md"); expect(warnings[0]).toContain("limit 200"); }); - it("keeps content under the default limit", () => { - const long = "a".repeat(DEFAULT_BOOTSTRAP_MAX_CHARS - 10); + it("does not truncate bootstrap content by default", () => { + const long = "a".repeat(30_000); const files = [makeFile({ content: long })]; const [result] = buildBootstrapContextFiles(files); expect(result?.content).toBe(long); expect(result?.content).not.toContain("[...truncated, read AGENTS.md for full content...]"); }); - it("caps total injected bootstrap characters across files", () => { + it("does not cap total injected bootstrap characters by default", () => { const files = [ makeFile({ name: "AGENTS.md", content: "a".repeat(10_000) }), makeFile({ name: "SOUL.md", path: "/tmp/SOUL.md", content: "b".repeat(10_000) }), @@ -66,7 +64,20 @@ describe("buildBootstrapContextFiles", () => { ]; const result = buildBootstrapContextFiles(files); const totalChars = result.reduce((sum, entry) => sum + entry.content.length, 0); - expect(totalChars).toBeLessThanOrEqual(DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS); + expect(totalChars).toBe(30_000); + expect(result).toHaveLength(3); + expect(result[2]?.content).toBe("c".repeat(10_000)); + }); + + it("caps total injected bootstrap characters when totalMaxChars is configured", () => { + const files = [ + makeFile({ name: "AGENTS.md", content: "a".repeat(10_000) }), + makeFile({ name: "SOUL.md", path: "/tmp/SOUL.md", content: "b".repeat(10_000) }), + makeFile({ name: "USER.md", path: "/tmp/USER.md", content: "c".repeat(10_000) }), + ]; + const result = buildBootstrapContextFiles(files, { totalMaxChars: 24_000 }); + const totalChars = result.reduce((sum, entry) => sum + entry.content.length, 0); + expect(totalChars).toBeLessThanOrEqual(24_000); expect(result).toHaveLength(3); expect(result[2]?.content).toContain("[...truncated, read USER.md for full content...]"); }); @@ -105,8 +116,8 @@ describe("buildBootstrapContextFiles", () => { }); describe("resolveBootstrapMaxChars", () => { - it("returns default when unset", () => { - expect(resolveBootstrapMaxChars()).toBe(DEFAULT_BOOTSTRAP_MAX_CHARS); + it("returns undefined when unset", () => { + expect(resolveBootstrapMaxChars()).toBeUndefined(); }); it("uses configured value when valid", () => { const cfg = { @@ -114,17 +125,17 @@ describe("resolveBootstrapMaxChars", () => { } as OpenClawConfig; expect(resolveBootstrapMaxChars(cfg)).toBe(12345); }); - it("falls back when invalid", () => { + it("returns undefined when invalid", () => { const cfg = { agents: { defaults: { bootstrapMaxChars: -1 } }, } as OpenClawConfig; - expect(resolveBootstrapMaxChars(cfg)).toBe(DEFAULT_BOOTSTRAP_MAX_CHARS); + expect(resolveBootstrapMaxChars(cfg)).toBeUndefined(); }); }); describe("resolveBootstrapTotalMaxChars", () => { - it("returns default when unset", () => { - expect(resolveBootstrapTotalMaxChars()).toBe(DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS); + it("returns undefined when unset", () => { + expect(resolveBootstrapTotalMaxChars()).toBeUndefined(); }); it("uses configured value when valid", () => { const cfg = { @@ -132,10 +143,10 @@ describe("resolveBootstrapTotalMaxChars", () => { } as OpenClawConfig; expect(resolveBootstrapTotalMaxChars(cfg)).toBe(12345); }); - it("falls back when invalid", () => { + it("returns undefined when invalid", () => { const cfg = { agents: { defaults: { bootstrapTotalMaxChars: -1 } }, } as OpenClawConfig; - expect(resolveBootstrapTotalMaxChars(cfg)).toBe(DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS); + expect(resolveBootstrapTotalMaxChars(cfg)).toBeUndefined(); }); }); diff --git a/src/agents/pi-embedded-helpers.ts b/src/agents/pi-embedded-helpers.ts index 5c45fb0509..e81b9cda16 100644 --- a/src/agents/pi-embedded-helpers.ts +++ b/src/agents/pi-embedded-helpers.ts @@ -1,7 +1,5 @@ export { buildBootstrapContextFiles, - DEFAULT_BOOTSTRAP_MAX_CHARS, - DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS, ensureSessionHeader, resolveBootstrapMaxChars, resolveBootstrapTotalMaxChars, diff --git a/src/agents/pi-embedded-helpers/bootstrap.ts b/src/agents/pi-embedded-helpers/bootstrap.ts index 9e589fc15a..168f9b7de4 100644 --- a/src/agents/pi-embedded-helpers/bootstrap.ts +++ b/src/agents/pi-embedded-helpers/bootstrap.ts @@ -82,8 +82,6 @@ export function stripThoughtSignatures( }) as T; } -export const DEFAULT_BOOTSTRAP_MAX_CHARS = 20_000; -export const DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS = 24_000; const MIN_BOOTSTRAP_FILE_BUDGET_CHARS = 64; const BOOTSTRAP_HEAD_RATIO = 0.7; const BOOTSTRAP_TAIL_RATIO = 0.2; @@ -91,32 +89,38 @@ const BOOTSTRAP_TAIL_RATIO = 0.2; type TrimBootstrapResult = { content: string; truncated: boolean; - maxChars: number; + maxChars?: number; originalLength: number; }; -export function resolveBootstrapMaxChars(cfg?: OpenClawConfig): number { - const raw = cfg?.agents?.defaults?.bootstrapMaxChars; +function resolveConfiguredBootstrapLimit(raw: unknown): number | undefined { if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) { return Math.floor(raw); } - return DEFAULT_BOOTSTRAP_MAX_CHARS; + return undefined; } -export function resolveBootstrapTotalMaxChars(cfg?: OpenClawConfig): number { - const raw = cfg?.agents?.defaults?.bootstrapTotalMaxChars; - if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) { - return Math.floor(raw); - } - return DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS; +export function resolveBootstrapMaxChars(cfg?: OpenClawConfig): number | undefined { + return resolveConfiguredBootstrapLimit(cfg?.agents?.defaults?.bootstrapMaxChars); +} + +export function resolveBootstrapTotalMaxChars(cfg?: OpenClawConfig): number | undefined { + return resolveConfiguredBootstrapLimit(cfg?.agents?.defaults?.bootstrapTotalMaxChars); } function trimBootstrapContent( content: string, fileName: string, - maxChars: number, + maxChars?: number, ): TrimBootstrapResult { const trimmed = content.trimEnd(); + if (typeof maxChars !== "number") { + return { + content: trimmed, + truncated: false, + originalLength: trimmed.length, + }; + } if (trimmed.length <= maxChars) { return { content: trimmed, @@ -188,48 +192,71 @@ export function buildBootstrapContextFiles( files: WorkspaceBootstrapFile[], opts?: { warn?: (message: string) => void; maxChars?: number; totalMaxChars?: number }, ): EmbeddedContextFile[] { - const maxChars = opts?.maxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS; - const totalMaxChars = Math.max( - 1, - Math.floor(opts?.totalMaxChars ?? Math.max(maxChars, DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS)), - ); + const maxChars = resolveConfiguredBootstrapLimit(opts?.maxChars); + const totalMaxChars = resolveConfiguredBootstrapLimit(opts?.totalMaxChars); let remainingTotalChars = totalMaxChars; const result: EmbeddedContextFile[] = []; for (const file of files) { - if (remainingTotalChars <= 0) { + if (typeof remainingTotalChars === "number" && remainingTotalChars <= 0) { break; } if (file.missing) { const missingText = `[MISSING] Expected at: ${file.path}`; - const cappedMissingText = clampToBudget(missingText, remainingTotalChars); + const cappedMissingText = + typeof remainingTotalChars === "number" + ? clampToBudget(missingText, remainingTotalChars) + : missingText; if (!cappedMissingText) { break; } - remainingTotalChars = Math.max(0, remainingTotalChars - cappedMissingText.length); + if (typeof remainingTotalChars === "number") { + remainingTotalChars = Math.max(0, remainingTotalChars - cappedMissingText.length); + } result.push({ path: file.path, content: cappedMissingText, }); continue; } - if (remainingTotalChars < MIN_BOOTSTRAP_FILE_BUDGET_CHARS) { + if ( + typeof remainingTotalChars === "number" && + remainingTotalChars < MIN_BOOTSTRAP_FILE_BUDGET_CHARS + ) { opts?.warn?.( `remaining bootstrap budget is ${remainingTotalChars} chars (<${MIN_BOOTSTRAP_FILE_BUDGET_CHARS}); skipping additional bootstrap files`, ); break; } - const fileMaxChars = Math.max(1, Math.min(maxChars, remainingTotalChars)); + const fileMaxChars = (() => { + if (typeof maxChars === "number" && typeof remainingTotalChars === "number") { + return Math.max(1, Math.min(maxChars, remainingTotalChars)); + } + if (typeof maxChars === "number") { + return Math.max(1, maxChars); + } + if (typeof remainingTotalChars === "number") { + return Math.max(1, remainingTotalChars); + } + return undefined; + })(); const trimmed = trimBootstrapContent(file.content ?? "", file.name, fileMaxChars); - const contentWithinBudget = clampToBudget(trimmed.content, remainingTotalChars); + const contentWithinBudget = + typeof remainingTotalChars === "number" + ? clampToBudget(trimmed.content, remainingTotalChars) + : trimmed.content; if (!contentWithinBudget) { continue; } if (trimmed.truncated || contentWithinBudget.length < trimmed.content.length) { + const limitLabel = + typeof trimmed.maxChars === "number" ? trimmed.maxChars : "configured total budget"; opts?.warn?.( - `workspace bootstrap file ${file.name} is ${trimmed.originalLength} chars (limit ${trimmed.maxChars}); truncating in injected context`, + `workspace bootstrap file ${file.name} is ${trimmed.originalLength} chars (limit ${limitLabel}); truncating in injected context`, ); } - remainingTotalChars = Math.max(0, remainingTotalChars - contentWithinBudget.length); + if (typeof remainingTotalChars === "number") { + remainingTotalChars = Math.max(0, remainingTotalChars - contentWithinBudget.length); + } result.push({ path: file.path, content: contentWithinBudget, diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 7a040c0eb6..75c2c7673a 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -39,6 +39,7 @@ import { createOllamaStreamFn, OLLAMA_NATIVE_BASE_URL } from "../../ollama-strea import { isCloudCodeAssistFormatError, resolveBootstrapMaxChars, + resolveBootstrapTotalMaxChars, validateAnthropicTurns, validateGeminiTurns, } from "../../pi-embedded-helpers.js"; @@ -462,6 +463,7 @@ export async function runEmbeddedAttempt( model: params.modelId, workspaceDir: effectiveWorkspace, bootstrapMaxChars: resolveBootstrapMaxChars(params.config), + bootstrapTotalMaxChars: resolveBootstrapTotalMaxChars(params.config), sandbox: (() => { const runtime = resolveSandboxRuntimeStatus({ cfg: params.config, diff --git a/src/agents/system-prompt-report.test.ts b/src/agents/system-prompt-report.test.ts index c2737865b6..bb22204347 100644 --- a/src/agents/system-prompt-report.test.ts +++ b/src/agents/system-prompt-report.test.ts @@ -44,4 +44,40 @@ describe("buildSystemPromptReport", () => { 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 = buildSystemPromptReport({ + source: "run", + generatedAt: 0, + systemPrompt: "system", + bootstrapFiles: [file], + injectedFiles: [{ path: "/tmp/workspace/policies/AGENTS.md", content: "trimmed" }], + skillsPrompt: "", + tools: [], + }); + + 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 = buildSystemPromptReport({ + source: "run", + generatedAt: 0, + bootstrapMaxChars: 11_111, + bootstrapTotalMaxChars: 22_222, + systemPrompt: "system", + bootstrapFiles: [file], + injectedFiles: [{ path: "AGENTS.md", content: "trimmed" }], + skillsPrompt: "", + tools: [], + }); + + expect(report.bootstrapMaxChars).toBe(11_111); + expect(report.bootstrapTotalMaxChars).toBe(22_222); + }); }); diff --git a/src/agents/system-prompt-report.ts b/src/agents/system-prompt-report.ts index 5783202e10..51521c2995 100644 --- a/src/agents/system-prompt-report.ts +++ b/src/agents/system-prompt-report.ts @@ -39,7 +39,6 @@ function parseSkillBlocks(skillsPrompt: string): Array<{ name: string; blockChar function buildInjectedWorkspaceFiles(params: { bootstrapFiles: WorkspaceBootstrapFile[]; injectedFiles: EmbeddedContextFile[]; - bootstrapMaxChars: number; }): SessionSystemPromptReport["injectedWorkspaceFiles"] { const injectedByPath = new Map(params.injectedFiles.map((f) => [f.path, f.content])); const injectedByBaseName = new Map(); @@ -57,7 +56,7 @@ function buildInjectedWorkspaceFiles(params: { injectedByPath.get(file.name) ?? injectedByBaseName.get(file.name); const injectedChars = injected ? injected.length : 0; - const truncated = !file.missing && rawChars > params.bootstrapMaxChars; + const truncated = !file.missing && injectedChars < rawChars; return { name: file.name, path: file.path, @@ -118,7 +117,8 @@ export function buildSystemPromptReport(params: { provider?: string; model?: string; workspaceDir?: string; - bootstrapMaxChars: number; + bootstrapMaxChars?: number; + bootstrapTotalMaxChars?: number; sandbox?: SessionSystemPromptReport["sandbox"]; systemPrompt: string; bootstrapFiles: WorkspaceBootstrapFile[]; @@ -148,6 +148,7 @@ export function buildSystemPromptReport(params: { model: params.model, workspaceDir: params.workspaceDir, bootstrapMaxChars: params.bootstrapMaxChars, + bootstrapTotalMaxChars: params.bootstrapTotalMaxChars, sandbox: params.sandbox, systemPrompt: { chars: systemPrompt.length, @@ -157,7 +158,6 @@ export function buildSystemPromptReport(params: { injectedWorkspaceFiles: buildInjectedWorkspaceFiles({ bootstrapFiles: params.bootstrapFiles, injectedFiles: params.injectedFiles, - bootstrapMaxChars: params.bootstrapMaxChars, }), skills: { promptChars: params.skillsPrompt.length, diff --git a/src/auto-reply/reply/commands-context-report.ts b/src/auto-reply/reply/commands-context-report.ts index 833964523d..7ae2782df9 100644 --- a/src/auto-reply/reply/commands-context-report.ts +++ b/src/auto-reply/reply/commands-context-report.ts @@ -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, @@ -249,7 +254,11 @@ export async function buildContextReply(params: HandleCommandsParams): Promise = { "auth.cooldowns.billingMaxHours": "Cap (hours) for billing backoff (default: 24).", "auth.cooldowns.failureWindowHours": "Failure window (hours) for backoff counters (default: 24).", "agents.defaults.bootstrapMaxChars": - "Max characters of each workspace bootstrap file injected into the system prompt before truncation (default: 20000).", + "Optional max characters of each workspace bootstrap file injected into the system prompt before truncation.", "agents.defaults.bootstrapTotalMaxChars": - "Max total characters across all injected workspace bootstrap files (default: 24000).", + "Optional max total characters across all injected workspace bootstrap files.", "agents.defaults.repoRoot": "Optional repository root shown in the system prompt runtime line (overrides auto-detect).", "agents.defaults.envelopeTimezone": diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index 012d59f728..809af7709c 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -159,6 +159,7 @@ export type SessionSystemPromptReport = { model?: string; workspaceDir?: string; bootstrapMaxChars?: number; + bootstrapTotalMaxChars?: number; sandbox?: { mode?: string; sandboxed?: boolean; diff --git a/src/config/types.agent-defaults.ts b/src/config/types.agent-defaults.ts index 2774105fb2..a09aa998f2 100644 --- a/src/config/types.agent-defaults.ts +++ b/src/config/types.agent-defaults.ts @@ -134,9 +134,9 @@ export type AgentDefaultsConfig = { repoRoot?: string; /** Skip bootstrap (BOOTSTRAP.md creation, etc.) for pre-configured deployments. */ skipBootstrap?: boolean; - /** Max chars for injected bootstrap files before truncation (default: 20000). */ + /** Optional max chars for each injected bootstrap file before truncation. */ bootstrapMaxChars?: number; - /** Max total chars across all injected bootstrap files (default: 24000). */ + /** Optional max total chars across all injected bootstrap files. */ bootstrapTotalMaxChars?: number; /** Optional IANA timezone for the user (used in system prompt; defaults to host timezone). */ userTimezone?: string;