chore: initialize my-pi extension bundle

This commit is contained in:
云服务部-叶林立
2026-08-18 12:30:48 +08:00
parent 7b7b332912
commit 0637139a16
62 changed files with 11055 additions and 1 deletions
+680
View File
@@ -0,0 +1,680 @@
import { getAgentDir } from "@earendil-works/pi-coding-agent";
import { homedir } from "node:os";
import { dirname, join, resolve, sep } from "node:path";
import {
aggregateLinterOutput,
aggregateTestOutput,
compactGitOutput,
detectLanguage,
filterBuildOutput,
filterSourceCode,
smartTruncate,
stripAnsiFast,
truncate,
} from "./techniques/index.js";
import { trackOutputSavings } from "./output-metrics.js";
import { mapTextContentBlocks, toRecord } from "./record-utils.js";
import type { RtkIntegrationConfig } from "./types.js";
interface ToolResultLikeEvent {
toolName: string;
input?: unknown;
content?: unknown;
}
export interface ToolResultCompactionMetadata {
applied: boolean;
techniques: string[];
truncated: boolean;
originalCharCount: number;
compactedCharCount: number;
originalLineCount: number;
compactedLineCount: number;
}
export interface ToolResultCompactionOutcome {
changed: boolean;
content?: unknown[];
techniques: string[];
metadata?: ToolResultCompactionMetadata;
}
interface AnchoredReadLine {
lineNumber: number;
content: string;
originalLine: string;
}
interface AnchorSafeReadLine {
text: string;
content: string;
}
interface AnchorSafeReadParts {
prefixLines: string[];
anchoredLines: AnchoredReadLine[];
suffixLines: string[];
trailingNewline: boolean;
}
const LOSSY_TECHNIQUE_PREFIXES = [
"build",
"test",
"git",
"linter",
"search",
"truncate",
"smart-truncate",
"source:",
] as const;
const READ_EXACT_OUTPUT_LINE_THRESHOLD = 80;
const READ_COMPACTION_BANNER_PREFIX = "[RTK compacted output:";
const ANCHORED_READ_LINE_MIN_MATCHES = 2;
const ANCHORED_READ_LINE_MIN_RATIO = 0.5;
const ANCHORED_READ_LINE_SAMPLE_LIMIT = 200;
const ANCHORED_READ_LINE_PATTERNS = [
/^\s*(?:>>>|>>|[>+\-*]+)?\s*(\d+)\s*#\s*[A-Za-z0-9_-]{2,32}:(.*)$/,
/^\s*(?:>>>|>>|[>+\-*]+)?\s*(\d+)\s*:\s*[A-Za-z0-9_-]{1,32}\|(.*)$/,
/^\s*(?:>>>|>>|[>+\-*]+)?\s*(\d+)[a-z]{2}\|(.*)$/,
] as const;
const ANCHORED_READ_INFORMATIONAL_LINE_PATTERN = /^\s*(?:$|<\/?file>|\.{3}|\[[^\]]+\]|Read\s+.+:\s+\d+\s+lines\b)/;
const USER_SKILL_ROOTS = [join(getAgentDir(), "skills"), join(homedir(), ".agents", "skills")];
function normalizePathForComparison(path: string): string {
return process.platform === "win32" ? path.toLowerCase() : path;
}
function isPathUnderRoot(targetPath: string, rootPath: string): boolean {
const normalizedTarget = normalizePathForComparison(resolve(targetPath));
const normalizedRoot = normalizePathForComparison(resolve(rootPath));
if (normalizedTarget === normalizedRoot) {
return true;
}
const rootWithSeparator = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;
return normalizedTarget.startsWith(rootWithSeparator);
}
function isUnderAnyAncestorAgentsSkills(targetPath: string): boolean {
let currentDir = resolve(process.cwd());
while (true) {
if (isPathUnderRoot(targetPath, join(currentDir, ".agents", "skills"))) {
return true;
}
const parentDir = dirname(currentDir);
if (parentDir === currentDir) {
return false;
}
currentDir = parentDir;
}
}
function isSkillReadPath(filePath: string): boolean {
if (!filePath.trim()) {
return false;
}
const resolvedPath = resolve(filePath);
if (USER_SKILL_ROOTS.some((root) => isPathUnderRoot(resolvedPath, root))) {
return true;
}
if (isPathUnderRoot(resolvedPath, join(process.cwd(), ".pi", "skills"))) {
return true;
}
return isUnderAnyAncestorAgentsSkills(resolvedPath);
}
function toArray(value: unknown): unknown[] {
return Array.isArray(value) ? value : [];
}
function normalizeCommand(input: Record<string, unknown>): string | undefined {
const raw = input.command;
if (typeof raw === "string" && raw.trim()) {
return raw;
}
return undefined;
}
function normalizePath(input: Record<string, unknown>): string {
const raw = input.path;
if (typeof raw === "string") {
return raw;
}
return "";
}
function hasExplicitReadRange(input: Record<string, unknown>): boolean {
return input.offset !== undefined || input.limit !== undefined;
}
function splitReadLines(text: string): { lines: string[]; trailingNewline: boolean } {
if (!text) {
return { lines: [], trailingNewline: false };
}
const trailingNewline = text.endsWith("\n");
const lines = text.split(/\r?\n/);
if (trailingNewline) {
lines.pop();
}
return { lines, trailingNewline };
}
function joinReadLines(lines: string[], trailingNewline: boolean): string {
const joined = lines.join("\n");
return trailingNewline && joined ? `${joined}\n` : joined;
}
function parseAnchoredReadLine(line: string): AnchoredReadLine | undefined {
for (const pattern of ANCHORED_READ_LINE_PATTERNS) {
const match = line.match(pattern);
if (!match) {
continue;
}
const lineNumber = Number.parseInt(match[1] ?? "", 10);
if (!Number.isSafeInteger(lineNumber) || lineNumber <= 0) {
continue;
}
const content = match[2] ?? "";
return {
lineNumber,
content,
originalLine: line,
};
}
return undefined;
}
function parseAnchoredReadLineNumber(line: string): number | undefined {
return parseAnchoredReadLine(line)?.lineNumber;
}
function looksLikeAnchoredLineOutput(text: string, parseLineNumber: (line: string) => number | undefined): boolean {
let matchCount = 0;
let relevantLineCount = 0;
let previousMatchedLineNumber: number | undefined;
let hasIncreasingAnchors = false;
for (const line of splitReadLines(text).lines.slice(0, ANCHORED_READ_LINE_SAMPLE_LIMIT)) {
if (!ANCHORED_READ_INFORMATIONAL_LINE_PATTERN.test(line)) {
relevantLineCount += 1;
}
const lineNumber = parseLineNumber(line);
if (lineNumber === undefined) {
continue;
}
matchCount += 1;
if (previousMatchedLineNumber !== undefined && lineNumber > previousMatchedLineNumber) {
hasIncreasingAnchors = true;
}
previousMatchedLineNumber = lineNumber;
}
if (matchCount < ANCHORED_READ_LINE_MIN_MATCHES || !hasIncreasingAnchors) {
return false;
}
const ratioBase = Math.max(relevantLineCount, matchCount);
return matchCount / ratioBase >= ANCHORED_READ_LINE_MIN_RATIO;
}
function looksLikeAnchoredReadOutput(text: string): boolean {
return looksLikeAnchoredLineOutput(text, parseAnchoredReadLineNumber);
}
function shouldPreserveExactReadOutput(
text: string,
input: Record<string, unknown>,
config: RtkIntegrationConfig,
): boolean {
if (!config.outputCompaction.readCompaction.enabled) {
return true;
}
if (hasExplicitReadRange(input)) {
return true;
}
if (config.outputCompaction.preserveExactSkillReads && isSkillReadPath(normalizePath(input))) {
return true;
}
return countLines(text) <= READ_EXACT_OUTPUT_LINE_THRESHOLD;
}
function shouldApplyReadSourceFiltering(text: string, config: RtkIntegrationConfig): boolean {
const compaction = config.outputCompaction;
const lineCount = countLines(text);
return (
(compaction.smartTruncate.enabled && lineCount > compaction.smartTruncate.maxLines) ||
(compaction.truncate.enabled && text.length > compaction.truncate.maxChars)
);
}
function extractAnchoredReadParts(text: string): AnchorSafeReadParts | undefined {
if (!looksLikeAnchoredReadOutput(text)) {
return undefined;
}
const { lines, trailingNewline } = splitReadLines(text);
const parsedLines = lines.map((line) => parseAnchoredReadLine(line));
const firstAnchorIndex = parsedLines.findIndex((line) => line !== undefined);
if (firstAnchorIndex === -1) {
return undefined;
}
let lastAnchorIndex = firstAnchorIndex;
for (let index = parsedLines.length - 1; index >= firstAnchorIndex; index -= 1) {
if (parsedLines[index] !== undefined) {
lastAnchorIndex = index;
break;
}
}
const anchoredLines: AnchoredReadLine[] = [];
for (let index = firstAnchorIndex; index <= lastAnchorIndex; index += 1) {
const anchoredLine = parsedLines[index];
if (!anchoredLine) {
return undefined;
}
anchoredLines.push(anchoredLine);
}
return {
prefixLines: lines.slice(0, firstAnchorIndex),
anchoredLines,
suffixLines: lines.slice(lastAnchorIndex + 1),
trailingNewline,
};
}
function toAnchorSafeReadLines(anchoredLines: AnchoredReadLine[]): AnchorSafeReadLine[] {
return anchoredLines.map((line) => ({
text: line.originalLine,
content: line.content,
}));
}
function renderAnchorSafeReadBody(lines: AnchorSafeReadLine[]): string {
return lines.map((line) => line.text).join("\n");
}
function renderAnchorSafeReadText(parts: AnchorSafeReadParts, lines: AnchorSafeReadLine[]): string {
return joinReadLines(
[...parts.prefixLines, ...lines.map((line) => line.text), ...parts.suffixLines],
parts.trailingNewline,
);
}
function remapTransformedContentToAnchorSafeLines(
sourceLines: AnchorSafeReadLine[],
transformedContent: string,
): AnchorSafeReadLine[] {
const transformedLines = splitReadLines(transformedContent).lines;
const remappedLines: AnchorSafeReadLine[] = [];
let searchStartIndex = 0;
for (const transformedLine of transformedLines) {
let matchedIndex = -1;
for (let index = searchStartIndex; index < sourceLines.length; index += 1) {
if (sourceLines[index]?.content === transformedLine) {
matchedIndex = index;
break;
}
}
if (matchedIndex === -1) {
remappedLines.push({
text: transformedLine,
content: transformedLine,
});
continue;
}
remappedLines.push(sourceLines[matchedIndex]!);
searchStartIndex = matchedIndex + 1;
}
return remappedLines;
}
function truncateAnchorSafeReadLines(lines: AnchorSafeReadLine[], maxChars: number): AnchorSafeReadLine[] {
if (renderAnchorSafeReadBody(lines).length <= maxChars) {
return lines;
}
const marker = "[RTK anchor-safe truncate: remaining anchored read lines omitted to preserve complete anchors]";
const truncatedLines: AnchorSafeReadLine[] = [];
let charCount = 0;
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index]!;
const separatorLength = truncatedLines.length > 0 ? 1 : 0;
const nextCharCount = charCount + separatorLength + line.text.length;
const remainingAfter = lines.length - index - 1;
const markerLength = remainingAfter > 0 ? (nextCharCount > 0 ? 1 : 0) + marker.length : 0;
if (nextCharCount + markerLength > maxChars) {
const markerLine = { text: marker, content: marker };
return truncatedLines.length > 0 ? [...truncatedLines, markerLine] : [markerLine];
}
truncatedLines.push(line);
charCount = nextCharCount;
}
return truncatedLines;
}
function compactAnchoredReadText(
text: string,
filePath: string,
config: RtkIntegrationConfig,
): { text: string; techniques: string[] } {
const parts = extractAnchoredReadParts(text);
if (!parts) {
return { text, techniques: [] };
}
let lines = toAnchorSafeReadLines(parts.anchoredLines);
const techniques: string[] = [];
const compaction = config.outputCompaction;
const language = detectLanguage(filePath);
if (
compaction.sourceCodeFilteringEnabled &&
compaction.sourceCodeFiltering !== "none" &&
shouldApplyReadSourceFiltering(text, config)
) {
const currentSource = lines.map((line) => line.content).join("\n");
const filtered = normalizeTechniqueResult(
filterSourceCode(currentSource, language, compaction.sourceCodeFiltering),
currentSource,
);
const filteredLines = remapTransformedContentToAnchorSafeLines(lines, filtered);
if (renderAnchorSafeReadBody(filteredLines) !== renderAnchorSafeReadBody(lines)) {
lines = filteredLines;
techniques.push(`source:${compaction.sourceCodeFiltering}`);
}
}
if (compaction.smartTruncate.enabled && lines.length > compaction.smartTruncate.maxLines) {
const currentSource = lines.map((line) => line.content).join("\n");
const compacted = smartTruncate(currentSource, compaction.smartTruncate.maxLines, language);
const compactedLines = remapTransformedContentToAnchorSafeLines(lines, compacted);
if (renderAnchorSafeReadBody(compactedLines) !== renderAnchorSafeReadBody(lines)) {
lines = compactedLines;
techniques.push("smart-truncate");
}
}
if (compaction.truncate.enabled && renderAnchorSafeReadText(parts, lines).length > compaction.truncate.maxChars) {
const nonBodyOverhead = renderAnchorSafeReadText(parts, []).length;
const bodyMaxChars = Math.max(1, compaction.truncate.maxChars - nonBodyOverhead);
const truncatedLines = truncateAnchorSafeReadLines(lines, bodyMaxChars);
if (renderAnchorSafeReadBody(truncatedLines) !== renderAnchorSafeReadBody(lines)) {
lines = truncatedLines;
techniques.push("truncate");
}
}
return {
text: renderAnchorSafeReadText(parts, lines),
techniques,
};
}
function formatReadCompactionBanner(techniques: string[]): string {
return `${READ_COMPACTION_BANNER_PREFIX} ${techniques.join(", ")}]`;
}
function countLines(text: string): number {
if (!text) {
return 0;
}
const normalized = text.endsWith("\n") ? text.slice(0, -1) : text;
if (!normalized) {
return 1;
}
return normalized.split("\n").length;
}
function hasLossyCompaction(techniques: string[]): boolean {
return techniques.some((technique) =>
LOSSY_TECHNIQUE_PREFIXES.some((prefix) =>
prefix.endsWith(":") ? technique.startsWith(prefix) : technique === prefix,
),
);
}
function normalizeTechniqueResult(result: string | null, currentText: string): string {
return result === null ? currentText : result;
}
interface CompactionState {
text: string;
techniques: string[];
}
/** Strips ANSI escape codes when enabled, recording the "ansi" technique on change. */
function applyAnsiStripping(state: CompactionState, compaction: RtkIntegrationConfig["outputCompaction"]): void {
if (!compaction.stripAnsi) {
return;
}
const stripped = stripAnsiFast(state.text);
if (stripped !== state.text) {
state.text = stripped;
state.techniques.push("ansi");
}
}
/** Applies hard character truncation when enabled and the threshold is exceeded. */
function applyTruncation(state: CompactionState, compaction: RtkIntegrationConfig["outputCompaction"]): void {
if (compaction.truncate.enabled && state.text.length > compaction.truncate.maxChars) {
state.text = truncate(state.text, compaction.truncate.maxChars);
state.techniques.push("truncate");
}
}
/**
* Applies a single nullable-result compaction technique: runs `transform`, keeps
* its result when it differs from the current text, and records `technique`.
* Mirrors the `normalizeTechniqueResult(...) !== current → push` idiom shared
* across the bash/read compactors.
*/
function applyNullableTechnique(
state: CompactionState,
transform: (text: string) => string | null,
technique: string,
): void {
const compacted = normalizeTechniqueResult(transform(state.text), state.text);
if (compacted !== state.text) {
state.text = compacted;
state.techniques.push(technique);
}
}
function applyConditionalTechnique(
state: CompactionState,
enabled: boolean,
transform: (text: string) => string | null,
technique: string,
): void {
if (enabled) {
applyNullableTechnique(state, transform, technique);
}
}
function beginCompaction(
text: string,
config: RtkIntegrationConfig,
): { state: CompactionState; compaction: RtkIntegrationConfig["outputCompaction"] } {
const state: CompactionState = { text, techniques: [] };
const compaction = config.outputCompaction;
applyAnsiStripping(state, compaction);
return { state, compaction };
}
function applyReadCompactionBanner(state: CompactionState): void {
if (state.techniques.length > 0 && !state.text.startsWith(READ_COMPACTION_BANNER_PREFIX)) {
state.text = `${formatReadCompactionBanner(state.techniques)}\n${state.text}`;
}
}
function compactBashText(
text: string,
command: string | undefined,
config: RtkIntegrationConfig,
): { text: string; techniques: string[] } {
const { state, compaction } = beginCompaction(text, config);
applyConditionalTechnique(state, compaction.filterBuildOutput, (t) => filterBuildOutput(t, command), "build");
applyConditionalTechnique(state, compaction.aggregateTestOutput, (t) => aggregateTestOutput(t, command), "test");
applyConditionalTechnique(state, compaction.compactGitOutput, (t) => compactGitOutput(t, command), "git");
applyConditionalTechnique(state, compaction.aggregateLinterOutput, (t) => aggregateLinterOutput(t, command), "linter");
applyTruncation(state, compaction);
return { text: state.text, techniques: state.techniques };
}
function compactReadText(
text: string,
filePath: string,
config: RtkIntegrationConfig,
preserveExactReadOutput: boolean,
): { text: string; techniques: string[] } {
if (preserveExactReadOutput) {
return { text, techniques: [] };
}
const { state, compaction } = beginCompaction(text, config);
if (looksLikeAnchoredReadOutput(state.text)) {
const anchored = compactAnchoredReadText(state.text, filePath, config);
state.text = anchored.text;
state.techniques.push(...anchored.techniques);
applyReadCompactionBanner(state);
return { text: state.text, techniques: state.techniques };
}
const language = detectLanguage(filePath);
// Only apply lossy source filtering when a downstream line/char safeguard would otherwise trigger.
if (
compaction.sourceCodeFilteringEnabled &&
compaction.sourceCodeFiltering !== "none" &&
shouldApplyReadSourceFiltering(text, config)
) {
applyNullableTechnique(
state,
(t) => filterSourceCode(t, language, compaction.sourceCodeFiltering),
`source:${compaction.sourceCodeFiltering}`,
);
}
if (compaction.smartTruncate.enabled) {
const lineCount = state.text.split("\n").length;
if (lineCount > compaction.smartTruncate.maxLines) {
const compacted = smartTruncate(state.text, compaction.smartTruncate.maxLines, language);
if (compacted !== state.text) {
state.text = compacted;
state.techniques.push("smart-truncate");
}
}
}
applyTruncation(state, compaction);
applyReadCompactionBanner(state);
return { text: state.text, techniques: state.techniques };
}
export function compactToolResult(
event: ToolResultLikeEvent,
config: RtkIntegrationConfig,
): ToolResultCompactionOutcome {
if (!config.outputCompaction.enabled) {
return { changed: false, techniques: [] };
}
const input = toRecord(event.input);
const sourceContent = toArray(event.content);
if (sourceContent.length === 0) {
return { changed: false, techniques: [] };
}
const allTechniques = new Set<string>();
const originalChunks: string[] = [];
const filteredChunks: string[] = [];
const { changed, mapped: nextContent } = mapTextContentBlocks(sourceContent, (contentBlock) => {
let transformed = { text: contentBlock.text, techniques: [] as string[] };
if (event.toolName === "bash") {
transformed = compactBashText(contentBlock.text, normalizeCommand(input), config);
} else if (event.toolName === "read") {
const normalizedPath = normalizePath(input);
transformed = compactReadText(
contentBlock.text,
normalizedPath,
config,
shouldPreserveExactReadOutput(contentBlock.text, input, config),
);
}
for (const technique of transformed.techniques) {
allTechniques.add(technique);
}
originalChunks.push(contentBlock.text);
filteredChunks.push(transformed.text);
return transformed.text !== contentBlock.text ? transformed.text : null;
});
if (!changed) {
return { changed: false, techniques: [] };
}
const techniques = Array.from(allTechniques);
const originalText = originalChunks.join("\n");
const compactedText = filteredChunks.join("\n");
if (config.outputCompaction.trackSavings) {
trackOutputSavings(originalText, compactedText, event.toolName, techniques);
}
const metadata: ToolResultCompactionMetadata = {
applied: true,
techniques,
truncated: hasLossyCompaction(techniques),
originalCharCount: originalText.length,
compactedCharCount: compactedText.length,
originalLineCount: countLines(originalText),
compactedLineCount: countLines(compactedText),
};
return {
changed: true,
content: nextContent,
techniques,
metadata,
};
}