mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
feat(pi-overflow): 新增统一的新工具结果展示截断扩展
此前截断分散在多层且互相冲突: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>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
a6d8416c76
commit
2a6bf8c929
@@ -7,7 +7,7 @@
|
||||
- `pi-compaction` 负责已经进入 Session 的历史投影,与 Context Mode 的输入隔离职责互补。首次 Session hydration 允许一次完整扫描;后续普通 `context` hot path 必须复用不可变 snapshot 或只处理 delta,不得调用 summarizer、写 Session 或重新构建完整 branch。
|
||||
- L2 遮蔽只用 Pi 原生 `context_edit`(`{ type: "context_edit", targetId, replacement }`)处理保护窗口外的旧 toolResult:成批提交、受 trigger/target 回差与 `minClearTokens` 约束,不逐条即时替换;不为遮蔽写 `segment-summary` 等 custom entry,原始条目、UI 与导出保持不变。
|
||||
- 不得改写最近 `keepRecentTurns` 轮;不得遮蔽 user/assistant 消息、`excludeTools` 中的工具、`SKILL.md` 的 `read` 结果、已遮蔽或已省略的条目、以及小于 `minEntryTokens` 的结果。
|
||||
- 遮蔽占位符必须确定性、单行、以 `MASK_PREFIX`(`"[pi-compaction] Earlier "`)开头,并在文本中指向 `context_tree_query` 恢复;`context_tree_query` 与 native checkpoint 的 portable summary 都必须有界。
|
||||
- 遮蔽占位符必须确定性、单行、以 `MASK_PREFIX`(`"[pi-compaction] Earlier "`)开头,并在文本中指向 `context_tree_query` 恢复;`context_tree_query` 与 native checkpoint 的 portable summary 都必须有界。被遮蔽的 toolResult 若带 `details.overflow.id`(pi-overflow 已存原文),占位符末尾追加 `overflow_query` 提示——只读该公开字段,不得 import pi-overflow 内部实现。
|
||||
- L3 整段摘要只能从空闲的 `agent_settled` 通过 `ctx.compact` 触发(`ctx.compact` 会中止当前运行,非空闲时调用会破坏运行);同一 leaf 不重复触发。阈值以绝对 token 计并受窗口比例约束(`effectiveThreshold = min(tokens, floor(ratio × contextWindow))`)。
|
||||
- `context` 钩子不得改写消息——非 checkpoint 路径只更新 snapshot 并返回 undefined;唯一例外是 native checkpoint 路径:checkpoint 之后的条目在投影时必须应用各 target 最新的 `context_edit`(null 省略、字符串规范成 text block),因为 `sessionEntryToContextMessages` 不处理 edits。
|
||||
- `session_before_compact` 只负责生成 portable/native 摘要,不得按自身阈值取消 Pi 的压缩(Pi 的 `contextTokens > window - reserveTokens` 是最终安全网)。
|
||||
|
||||
@@ -79,6 +79,7 @@ Compaction entries still carry `pi-compaction` version 1 details; native checkpo
|
||||
## Interfaces
|
||||
|
||||
- `context_tree_query`: recover the original output of a tool result that pi-compaction cleared from context, by tool call ID (1–20 ids per call, bounded output). Use it when a `[pi-compaction] Earlier` placeholder lacks evidence needed for the current task.
|
||||
- pi-overflow linkage: when the masked tool result carries `details.overflow.id` (pi-overflow stored the oversized original), the placeholder ends with `Full original stored: overflow_query {"id":"…"}`. pi-compaction only reads that public field and never imports pi-overflow internals.
|
||||
- `/compaction-status`: show raw/effective tokens, reclaimed tokens, confidence, segments, masked results, and generation.
|
||||
- Service `my-pi.pi-compaction.service.v1`: `getSnapshot`, `getMetrics`, and `query(sessionId, ids)` — `query` returns a placeholder record only for tool call IDs that are currently masked, so consumers such as the permission system's AutoReview see the same evidence the model does.
|
||||
|
||||
|
||||
@@ -33,3 +33,4 @@
|
||||
## 本地改动记录
|
||||
|
||||
- 上下文管理重构为 L2/L3:L2 在上下文压力下经 `turn_end` 提交 Pi 原生 `context_edit`,把保护窗口外的旧 toolResult 批量遮蔽为指向 `context_tree_query` 的确定性占位符(原始条目保留、不写 custom entry);L3 只在空闲 `agent_settled` 且达到绝对 token 阈值(受窗口比例约束)时调用一次 `ctx.compact`。删除逐条确定性存根、后台语义 worker、`softRatio`/`hardRatio`/`targetRatio` 配置与 `session_before_compact` 的自行阈值取消;Pi 原生阈值压缩保留为最终安全网。
|
||||
- L2 遮蔽占位符联动 pi-overflow:被遮蔽的 toolResult 若携带 `details.overflow.id`,占位符末尾追加 `overflow_query {"id":"…"}` 恢复提示。
|
||||
|
||||
@@ -52,14 +52,25 @@ function json(value: unknown): string {
|
||||
}
|
||||
}
|
||||
|
||||
function maskPlaceholder(toolName: string, args: unknown, toolCallId: string, originalTokens: number): string {
|
||||
function maskPlaceholder(
|
||||
toolName: string,
|
||||
args: unknown,
|
||||
toolCallId: string,
|
||||
originalTokens: number,
|
||||
overflowId?: string,
|
||||
): string {
|
||||
let argsText = json(args);
|
||||
if (argsText.length > MAX_PLACEHOLDER_ARGS_CHARS) {
|
||||
argsText = `${argsText.slice(0, MAX_PLACEHOLDER_ARGS_CHARS)}…`;
|
||||
}
|
||||
// `details.overflow.id` is pi-overflow's public contract — read the field
|
||||
// only, never import its implementation.
|
||||
const overflowHint = typeof overflowId === "string" && overflowId.length > 0
|
||||
? ` Full original stored: overflow_query {"id":"${overflowId}"}.`
|
||||
: "";
|
||||
return `${MASK_PREFIX}${toolName} result cleared to save context (~${originalTokens} tokens). ` +
|
||||
`Call args: ${argsText}. Re-run the call if still needed, or recover the original with ` +
|
||||
`context_tree_query {"toolCallIds":["${toolCallId}"]}.`;
|
||||
`context_tree_query {"toolCallIds":["${toolCallId}"]}.${overflowHint}`;
|
||||
}
|
||||
|
||||
function isSkillRead(toolName: string, args: unknown): boolean {
|
||||
@@ -136,7 +147,14 @@ export function planMasking(params: {
|
||||
if (contentText(message.content).startsWith(MASK_PREFIX)) continue;
|
||||
const originalTokens = params.estimate(message);
|
||||
if (originalTokens < masking.minEntryTokens) continue;
|
||||
const placeholder = maskPlaceholder(toolName, args, message.toolCallId, originalTokens);
|
||||
const overflowId = (message.details as { overflow?: { id?: unknown } } | undefined)?.overflow?.id;
|
||||
const placeholder = maskPlaceholder(
|
||||
toolName,
|
||||
args,
|
||||
message.toolCallId,
|
||||
originalTokens,
|
||||
typeof overflowId === "string" ? overflowId : undefined,
|
||||
);
|
||||
const placeholderTokens = params.estimate({ ...message, content: [{ type: "text", text: placeholder }] });
|
||||
clearedTokens += originalTokens - placeholderTokens;
|
||||
masked.push({
|
||||
|
||||
@@ -137,6 +137,27 @@ test("placeholder is deterministic, names the tool and call id, and truncates ar
|
||||
assert.ok(placeholder.includes("…"));
|
||||
});
|
||||
|
||||
test("a masked result carrying details.overflow.id gets an overflow_query hint in the placeholder", () => {
|
||||
const withOverflow = {
|
||||
...toolResultMessage("r1", "c1", "a".repeat(4_000)),
|
||||
details: { overflow: { id: "0123456789ab", totalLines: 5, totalBytes: 4000, truncated: true } },
|
||||
};
|
||||
const withoutOverflow = toolResultMessage("r2", "c2", "b".repeat(4_000));
|
||||
const entries = [
|
||||
entry("e1", assistantMessage("a1", [{ id: "c1", name: "bash" }, { id: "c2", name: "bash" }])),
|
||||
entry("e2", withOverflow),
|
||||
entry("e3", withoutOverflow),
|
||||
...protect(),
|
||||
];
|
||||
const masking = { ...MASKING, minClearTokens: 1 };
|
||||
const plan = planMasking({ contextEntries: entries, currentTokens: 3_500, contextWindow: 0, masking, estimate });
|
||||
assert.equal(plan.masked.length, 2);
|
||||
const first = plan.masked.find((m) => m.toolCallId === "c1")!.placeholder;
|
||||
const second = plan.masked.find((m) => m.toolCallId === "c2")!.placeholder;
|
||||
assert.ok(first.includes(` Full original stored: overflow_query {"id":"0123456789ab"}.`));
|
||||
assert.ok(!second.includes("overflow_query"));
|
||||
});
|
||||
|
||||
test("the small-window ratio caps both trigger and target", () => {
|
||||
const masking: MaskingConfig = {
|
||||
...MASKING,
|
||||
|
||||
Reference in New Issue
Block a user