mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45: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>
113 lines
5.5 KiB
TypeScript
113 lines
5.5 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { runOverflowQuery } from "../src/query.ts";
|
|
import { storeEntry } from "../src/store.ts";
|
|
import { readOverflowSettings, type OverflowSettings } from "../settings.ts";
|
|
|
|
function settings(overrides: Partial<OverflowSettings> = {}): OverflowSettings {
|
|
return { ...readOverflowSettings("/nonexistent/pi-overflow.json"), ...overrides };
|
|
}
|
|
|
|
async function store(text: string, id = "0123456789ab") {
|
|
const root = await mkdtemp(join(tmpdir(), "pi-overflow-query-"));
|
|
await storeEntry(root, id, { kind: "text", text }, { tool: "bash", args: { command: "run" } }, 1_000_000);
|
|
return root;
|
|
}
|
|
|
|
test("search returns hits with merged context", async () => {
|
|
const lines = Array.from({ length: 100 }, (_, i) => (i === 9 || i === 11 || i === 50 ? `line ${i + 1} FAIL` : `line ${i + 1}`));
|
|
const root = await store(lines.join("\n"));
|
|
const result = await runOverflowQuery(root, { id: "0123456789ab", search: "FAIL", context: 1 }, settings());
|
|
assert.ok(!result.error);
|
|
assert.match(result.text, /L10: line 10 FAIL/);
|
|
assert.match(result.text, /L12: line 12 FAIL/);
|
|
assert.match(result.text, /L51: line 51 FAIL/);
|
|
// hits at lines 10 and 12 with context 1 merge into one range 9-13
|
|
assert.match(result.text, /--- lines 9-13 ---/);
|
|
assert.match(result.text, /--- lines 50-52 ---/);
|
|
});
|
|
|
|
test("line-range retrieval returns numbered lines", async () => {
|
|
const lines = Array.from({ length: 50 }, (_, i) => `row ${i + 1}`);
|
|
const root = await store(lines.join("\n"));
|
|
const result = await runOverflowQuery(root, { id: "0123456789ab", lines: { start: 10, end: 15 } }, settings());
|
|
assert.match(result.text, /--- lines 10-15 ---/);
|
|
assert.match(result.text, /L10: row 10/);
|
|
assert.match(result.text, /L15: row 15/);
|
|
assert.ok(!result.text.includes("row 16"));
|
|
});
|
|
|
|
test("jsonPath extracts nested values", async () => {
|
|
const doc = JSON.stringify({ snapshot: { nodes: [{ name: "a" }, { name: "b" }, { name: "c" }] } });
|
|
const root = await store(doc);
|
|
const result = await runOverflowQuery(root, { id: "0123456789ab", jsonPath: "snapshot.nodes[2].name" }, settings());
|
|
assert.match(result.text, /"c"/);
|
|
});
|
|
|
|
test("invalid id, regex, JSON and path return clear errors without throwing", async () => {
|
|
const root = await store('{"a": {"b": 1}}');
|
|
assert.ok((await runOverflowQuery(root, { id: "../etc/passwd" }, settings())).error);
|
|
assert.ok((await runOverflowQuery(root, { id: "zzzzzzzzzzzz" }, settings())).error);
|
|
assert.ok((await runOverflowQuery(root, { id: "0123456789ab", search: "([" }, settings())).error);
|
|
const goodPath = await runOverflowQuery(root, { id: "0123456789ab", jsonPath: "a.b" }, settings());
|
|
assert.ok(!goodPath.error, "valid path on valid JSON must succeed");
|
|
const badPath = await runOverflowQuery(root, { id: "0123456789ab", jsonPath: "a.b.c!!" }, settings());
|
|
assert.ok(badPath.error);
|
|
const missing = await runOverflowQuery(root, { id: "ffffffffffff" }, settings());
|
|
assert.ok(missing.error);
|
|
});
|
|
|
|
test("non-JSON content + jsonPath reports the parse failure", async () => {
|
|
const root = await store("not json at all");
|
|
const result = await runOverflowQuery(root, { id: "0123456789ab", jsonPath: "a" }, settings());
|
|
assert.ok(result.error);
|
|
assert.match(result.text, /not valid JSON/);
|
|
});
|
|
|
|
test("output is bounded by thresholdChars and marked truncated", async () => {
|
|
const lines = Array.from({ length: 5000 }, (_, i) => `line ${i + 1} pad ${"p".repeat(40)}`);
|
|
const root = await store(lines.join("\n"));
|
|
const result = await runOverflowQuery(
|
|
root,
|
|
{ id: "0123456789ab", lines: { start: 1, end: 5000 } },
|
|
settings({ thresholdChars: 4000 }),
|
|
);
|
|
assert.ok(result.text.length <= 4000);
|
|
assert.equal(result.truncated, true);
|
|
assert.match(result.text, /truncated/);
|
|
});
|
|
|
|
test("lines and search output clip each line to maxLineChars", async () => {
|
|
const long = `row ${"q".repeat(2_000)}`;
|
|
const root = await store(`first\n${long}\n${long} ERROR\nlast`);
|
|
const s = settings({ maxLineChars: 60 });
|
|
const lines = await runOverflowQuery(root, { id: "0123456789ab", lines: { start: 2, end: 3 } }, s);
|
|
assert.ok(lines.text.includes("q".repeat(56) + "…"));
|
|
assert.ok(!lines.text.includes("q".repeat(61)));
|
|
const search = await runOverflowQuery(root, { id: "0123456789ab", search: "ERROR", context: 0 }, s);
|
|
assert.ok(search.text.includes("q".repeat(56) + "…"));
|
|
assert.ok(!search.text.includes("q".repeat(61)));
|
|
});
|
|
|
|
test("search only scans the first 10000 chars of each line", async () => {
|
|
const earlyHit = `hit ${"a".repeat(100)} FAIL`;
|
|
const lateHit = `${"a".repeat(10_500)} FAIL`;
|
|
const root = await store(`${earlyHit}\n${lateHit}\nplain`);
|
|
const result = await runOverflowQuery(root, { id: "0123456789ab", search: "FAIL", context: 0 }, settings());
|
|
assert.match(result.text, /L1: hit /);
|
|
assert.ok(!result.text.includes("L2:"), "match beyond char 10000 must not be reported");
|
|
});
|
|
|
|
test("default query returns metadata and a first-screen view", async () => {
|
|
const lines = Array.from({ length: 300 }, (_, i) => `row ${i + 1}`);
|
|
const root = await store(lines.join("\n"));
|
|
const result = await runOverflowQuery(root, { id: "0123456789ab" }, settings());
|
|
assert.ok(!result.error);
|
|
assert.match(result.text, /\[pi-overflow id=0123456789ab tool=bash \d+ lines \/ \d+ bytes\]/);
|
|
assert.match(result.text, /--- lines 1-40 ---/);
|
|
assert.ok(result.text.includes("row 300"), "tail of the stored file must appear");
|
|
});
|