


























@@ -96,6 +96,11 @@ const MIN_BOOTSTRAP_FILE_BUDGET_CHARS = 64;
9696const BOOTSTRAP_HEAD_RATIO = 0.75;
9797const BOOTSTRAP_TAIL_RATIO = 0.25;
9898const MIN_BOOTSTRAP_TRIMMED_CONTENT_CHARS = 16;
99+const AGENTS_BOOTSTRAP_FILENAME = "AGENTS.md";
100+const AGENTS_POLICY_DIGEST_RATIO = 0.35;
101+const AGENTS_POLICY_HEAD_RATIO = 0.45;
102+const AGENTS_POLICY_TAIL_RATIO = 0.15;
103+const AGENTS_POLICY_DIGEST_MAX_LINE_CHARS = 240;
99104100105type TrimBootstrapResult = {
101106content: string;
@@ -104,6 +109,11 @@ type TrimBootstrapResult = {
104109originalLength: number;
105110};
106111112+type PolicyDigest = {
113+text: string;
114+omittedLines: number;
115+};
116+107117export function resolveBootstrapMaxChars(cfg?: OpenClawConfig, agentId?: string | null): number {
108118const raw =
109119cfg && agentId
@@ -141,6 +151,120 @@ export function resolveBootstrapPromptTruncationWarningMode(
141151return DEFAULT_BOOTSTRAP_PROMPT_TRUNCATION_WARNING_MODE;
142152}
143153154+function isAgentsBootstrapFile(fileName: string): boolean {
155+return fileName.toLowerCase() === AGENTS_BOOTSTRAP_FILENAME.toLowerCase();
156+}
157+158+function isPolicyDigestCandidate(line: string): boolean {
159+if (/^(?:#{1,6}|\s*[-*+]|\s*\d+[.)])\s+\S/u.test(line)) {
160+return true;
161+}
162+return /\b(?:AGENTS\.md|scoped|required|must|never|do not|before subtree|read scoped|owner|security|secret|credential|test|validation|command|commit|push|github|pr)\b/iu.test(
163+line,
164+);
165+}
166+167+function normalizePolicyDigestLine(line: string): string {
168+const normalized = line.trim().replace(/\s+/gu, " ");
169+if (normalized.length <= AGENTS_POLICY_DIGEST_MAX_LINE_CHARS) {
170+return normalized;
171+}
172+return `${truncateUtf16Safe(normalized, AGENTS_POLICY_DIGEST_MAX_LINE_CHARS - 1)}…`;
173+}
174+175+function buildAgentsPolicyDigest(content: string, budget: number): PolicyDigest {
176+if (budget <= 0) {
177+return { text: "", omittedLines: 0 };
178+}
179+180+const candidates = content
181+.split(/\r?\n/u)
182+.map((line, index) => ({ index, line: normalizePolicyDigestLine(line) }))
183+.filter(({ line }) => line.length > 0 && isPolicyDigestCandidate(line));
184+const highPriorityPattern =
185+/\b(?:AGENTS\.md|scoped|required|must|never|do not|before subtree|read scoped|security|secret|credential)\b/iu;
186+const selected = new Set<number>();
187+let used = 0;
188+const trySelect = (candidate: { index: number; line: string }) => {
189+const separatorChars = selected.size > 0 ? 1 : 0;
190+if (used + separatorChars + candidate.line.length > budget) {
191+return;
192+}
193+selected.add(candidate.index);
194+used += separatorChars + candidate.line.length;
195+};
196+197+for (const candidate of candidates) {
198+if (highPriorityPattern.test(candidate.line)) {
199+trySelect(candidate);
200+}
201+}
202+for (const candidate of candidates) {
203+if (!selected.has(candidate.index)) {
204+trySelect(candidate);
205+}
206+}
207+208+const lines = candidates
209+.filter((candidate) => selected.has(candidate.index))
210+.toSorted((a, b) => a.index - b.index)
211+.map((candidate) => candidate.line);
212+return {
213+text: lines.join("\n"),
214+omittedLines: Math.max(0, candidates.length - lines.length),
215+};
216+}
217+218+function trimAgentsBootstrapContent(content: string, maxChars: number): TrimBootstrapResult {
219+const trimmed = content.trimEnd();
220+if (trimmed.length <= maxChars) {
221+return {
222+content: trimmed,
223+truncated: false,
224+ maxChars,
225+originalLength: trimmed.length,
226+};
227+}
228+229+let headChars = Math.floor(maxChars * AGENTS_POLICY_HEAD_RATIO);
230+let tailChars = Math.floor(maxChars * AGENTS_POLICY_TAIL_RATIO);
231+let digestBudget = Math.floor(maxChars * AGENTS_POLICY_DIGEST_RATIO);
232+let digest = buildAgentsPolicyDigest(trimmed, digestBudget);
233+const render = () =>
234+[
235+trimmed.slice(0, headChars),
236+`[...truncated, read ${AGENTS_BOOTSTRAP_FILENAME} for full content...]`,
237+digest.text ? "[Policy digest from AGENTS.md]" : "",
238+digest.text,
239+digest.omittedLines > 0 ? `[...${digest.omittedLines} more policy lines omitted...]` : "",
240+`…(truncated ${AGENTS_BOOTSTRAP_FILENAME}: kept ${headChars}+policy ${digest.text.length}+${tailChars} chars of ${trimmed.length})…`,
241+tailChars > 0 ? trimmed.slice(-tailChars) : "",
242+]
243+.filter((part) => part.length > 0)
244+.join("\n");
245+246+let rendered = render();
247+while (rendered.length > maxChars && (tailChars > 0 || headChars > 1 || digestBudget > 0)) {
248+const overflow = rendered.length - maxChars;
249+if (tailChars > 0) {
250+tailChars = Math.max(0, tailChars - overflow);
251+} else if (headChars > 1) {
252+headChars = Math.max(1, headChars - overflow);
253+} else {
254+digestBudget = Math.max(0, digestBudget - overflow);
255+digest = buildAgentsPolicyDigest(trimmed, digestBudget);
256+}
257+rendered = render();
258+}
259+260+return {
261+content: rendered.length > maxChars ? truncateUtf16Safe(rendered, maxChars) : rendered,
262+truncated: true,
263+ maxChars,
264+originalLength: trimmed.length,
265+};
266+}
267+144268function trimBootstrapContent(
145269content: string,
146270fileName: string,
@@ -155,6 +279,9 @@ function trimBootstrapContent(
155279originalLength: trimmed.length,
156280};
157281}
282+if (isAgentsBootstrapFile(fileName)) {
283+return trimAgentsBootstrapContent(content, maxChars);
284+}
158285159286const markerTemplate = (headChars: number, tailChars: number) =>
160287[
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。