mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 18:55:22 +00:00
此前截断分散在多层且互相冲突:Pi bash 保留尾部 50KB,RTK 兜底截断又只留 开头 12000 字符(砍掉报错与完整输出路径),pi-chrome 截开头 30K,web_fetch 各 provider 不一致。新扩展统一负责"新结果给模型看多少": - capture.ts 最先加载暂存原文,index.ts 最后加载(system-prompt 之前) - 超过 24K 字符时原文落盘 <agentDir>/my-pi/pi-overflow/(700/600、原子写), 优先取 bash fullOutputPath 完整文件;模型看到 头 + 中段信号行 + 尾 的有界 视图,行号基于落盘原文、结尾永远可见 - overflow_query:按 id 正则搜索、行区间、jsonPath 只读查询,id 校验防穿越 - 收编:RTK truncate 默认关闭、删除 pi-chrome truncateText、web_fetch 默认 每页 20000(上限 100000)、web_search max_results 限定 1–20 - pi-compaction 遮蔽带 details.overflow.id 的结果时在占位符提示 overflow_query - 登记 tools.jsonc / routing / 权限 allow / tool-search 常驻与分组 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
101 lines
3.8 KiB
TypeScript
101 lines
3.8 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export interface OverflowSettings {
|
|
enabled: boolean;
|
|
thresholdChars: number;
|
|
headLines: number;
|
|
tailLines: number;
|
|
maxSignalLines: number;
|
|
signalPattern: string;
|
|
signalRegex: RegExp;
|
|
maxLineChars: number;
|
|
exemptTools: string[];
|
|
retentionDays: number;
|
|
maxStoreBytes: number;
|
|
maxEntryBytes: number;
|
|
}
|
|
|
|
export const DEFAULT_SIGNAL_PATTERN =
|
|
"\\b(error|errors|fail|failed|failure|warn|warning|exception|traceback|panic|fatal)\\b";
|
|
|
|
export const OVERFLOW_DEFAULTS = {
|
|
enabled: true,
|
|
thresholdChars: 24_000,
|
|
headLines: 40,
|
|
tailLines: 120,
|
|
maxSignalLines: 40,
|
|
signalPattern: DEFAULT_SIGNAL_PATTERN,
|
|
maxLineChars: 500,
|
|
exemptTools: ["read", "edit", "write", "overflow_query", "ask_user_question"],
|
|
retentionDays: 7,
|
|
maxStoreBytes: 1_073_741_824,
|
|
maxEntryBytes: 67_108_864,
|
|
} as const;
|
|
|
|
// Bundle-level `config/` is the single source of truth: the extension reads
|
|
// `config/pi-overflow.json` from inside the installed bundle (resolved
|
|
// relative to this package-root source file), never `settings.json`.
|
|
export const OVERFLOW_CONFIG_PATH = fileURLToPath(
|
|
new URL("../config/pi-overflow.json", import.meta.url),
|
|
);
|
|
|
|
function isObject(value: unknown): value is Record<string, unknown> {
|
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
function bool(value: unknown, fallback: boolean): boolean {
|
|
return typeof value === "boolean" ? value : fallback;
|
|
}
|
|
|
|
function intInRange(value: unknown, fallback: number, min: number, max: number): number {
|
|
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max
|
|
? value
|
|
: fallback;
|
|
}
|
|
|
|
function compileSignalPattern(value: unknown): { pattern: string; regex: RegExp } {
|
|
const pattern = typeof value === "string" && value.length > 0 ? value : DEFAULT_SIGNAL_PATTERN;
|
|
try {
|
|
return { pattern, regex: new RegExp(pattern, "i") };
|
|
} catch {
|
|
return { pattern: DEFAULT_SIGNAL_PATTERN, regex: new RegExp(DEFAULT_SIGNAL_PATTERN, "i") };
|
|
}
|
|
}
|
|
|
|
function stringList(value: unknown, fallback: readonly string[]): string[] {
|
|
if (!Array.isArray(value)) return [...fallback];
|
|
const list = value.filter((item): item is string => typeof item === "string" && item.length > 0);
|
|
return [...new Set(list)];
|
|
}
|
|
|
|
/**
|
|
* Read `config/pi-overflow.json`. Every field is validated independently and
|
|
* falls back to its bundle default when missing or invalid; an unreadable or
|
|
* non-object file yields all defaults. Tests inject an explicit path.
|
|
*/
|
|
export function readOverflowSettings(configPath: string = OVERFLOW_CONFIG_PATH): OverflowSettings {
|
|
let parsed: unknown = {};
|
|
try {
|
|
parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
} catch {
|
|
parsed = {};
|
|
}
|
|
const config = isObject(parsed) ? parsed : {};
|
|
const signal = compileSignalPattern(config.signalPattern);
|
|
return {
|
|
enabled: bool(config.enabled, OVERFLOW_DEFAULTS.enabled),
|
|
thresholdChars: intInRange(config.thresholdChars, OVERFLOW_DEFAULTS.thresholdChars, 1, 10_000_000),
|
|
headLines: intInRange(config.headLines, OVERFLOW_DEFAULTS.headLines, 0, 10_000),
|
|
tailLines: intInRange(config.tailLines, OVERFLOW_DEFAULTS.tailLines, 0, 10_000),
|
|
maxSignalLines: intInRange(config.maxSignalLines, OVERFLOW_DEFAULTS.maxSignalLines, 0, 1_000),
|
|
signalPattern: signal.pattern,
|
|
signalRegex: signal.regex,
|
|
maxLineChars: intInRange(config.maxLineChars, OVERFLOW_DEFAULTS.maxLineChars, 10, 100_000),
|
|
exemptTools: stringList(config.exemptTools, OVERFLOW_DEFAULTS.exemptTools),
|
|
retentionDays: intInRange(config.retentionDays, OVERFLOW_DEFAULTS.retentionDays, 0, 3_650),
|
|
maxStoreBytes: intInRange(config.maxStoreBytes, OVERFLOW_DEFAULTS.maxStoreBytes, 1_024, 1_099_511_627_776),
|
|
maxEntryBytes: intInRange(config.maxEntryBytes, OVERFLOW_DEFAULTS.maxEntryBytes, 1_024, 1_099_511_627_776),
|
|
};
|
|
}
|