feat: add dynamic truncation to rules/readme/agents injectors (#257)

- Apply dynamic truncation to rules-injector, directory-readme-injector, and directory-agents-injector
- Add truncation notice encouraging users to read full content
- Save context window space while maintaining awareness of complete documentation
- Resolves #221 (part 1)

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
This commit is contained in:
Sisyphus
2025-12-26 15:38:28 +09:00
committed by GitHub
parent 109fb50028
commit 1b427570c8
3 changed files with 33 additions and 15 deletions

View File

@@ -7,6 +7,7 @@ import {
clearInjectedPaths, clearInjectedPaths,
} from "./storage"; } from "./storage";
import { AGENTS_FILENAME } from "./constants"; import { AGENTS_FILENAME } from "./constants";
import { createDynamicTruncator } from "../../shared/dynamic-truncator";
interface ToolExecuteInput { interface ToolExecuteInput {
tool: string; tool: string;
@@ -39,6 +40,7 @@ interface EventInput {
export function createDirectoryAgentsInjectorHook(ctx: PluginInput) { export function createDirectoryAgentsInjectorHook(ctx: PluginInput) {
const sessionCaches = new Map<string, Set<string>>(); const sessionCaches = new Map<string, Set<string>>();
const pendingBatchReads = new Map<string, string[]>(); const pendingBatchReads = new Map<string, string[]>();
const truncator = createDynamicTruncator(ctx);
function getSessionCache(sessionID: string): Set<string> { function getSessionCache(sessionID: string): Set<string> {
if (!sessionCaches.has(sessionID)) { if (!sessionCaches.has(sessionID)) {
@@ -73,11 +75,11 @@ export function createDirectoryAgentsInjectorHook(ctx: PluginInput) {
return found.reverse(); return found.reverse();
} }
function processFilePathForInjection( async function processFilePathForInjection(
filePath: string, filePath: string,
sessionID: string, sessionID: string,
output: ToolExecuteOutput, output: ToolExecuteOutput,
): void { ): Promise<void> {
const resolved = resolveFilePath(filePath); const resolved = resolveFilePath(filePath);
if (!resolved) return; if (!resolved) return;
@@ -91,7 +93,11 @@ export function createDirectoryAgentsInjectorHook(ctx: PluginInput) {
try { try {
const content = readFileSync(agentsPath, "utf-8"); const content = readFileSync(agentsPath, "utf-8");
output.output += `\n\n[Directory Context: ${agentsPath}]\n${content}`; const { result, truncated } = await truncator.truncate(sessionID, content);
const truncationNotice = truncated
? `\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ${agentsPath}]`
: "";
output.output += `\n\n[Directory Context: ${agentsPath}]\n${result}${truncationNotice}`;
cache.add(agentsDir); cache.add(agentsDir);
} catch {} } catch {}
} }
@@ -127,7 +133,7 @@ export function createDirectoryAgentsInjectorHook(ctx: PluginInput) {
const toolName = input.tool.toLowerCase(); const toolName = input.tool.toLowerCase();
if (toolName === "read") { if (toolName === "read") {
processFilePathForInjection(output.title, input.sessionID, output); await processFilePathForInjection(output.title, input.sessionID, output);
return; return;
} }
@@ -135,7 +141,7 @@ export function createDirectoryAgentsInjectorHook(ctx: PluginInput) {
const filePaths = pendingBatchReads.get(input.callID); const filePaths = pendingBatchReads.get(input.callID);
if (filePaths) { if (filePaths) {
for (const filePath of filePaths) { for (const filePath of filePaths) {
processFilePathForInjection(filePath, input.sessionID, output); await processFilePathForInjection(filePath, input.sessionID, output);
} }
pendingBatchReads.delete(input.callID); pendingBatchReads.delete(input.callID);
} }

View File

@@ -7,6 +7,7 @@ import {
clearInjectedPaths, clearInjectedPaths,
} from "./storage"; } from "./storage";
import { README_FILENAME } from "./constants"; import { README_FILENAME } from "./constants";
import { createDynamicTruncator } from "../../shared/dynamic-truncator";
interface ToolExecuteInput { interface ToolExecuteInput {
tool: string; tool: string;
@@ -39,6 +40,7 @@ interface EventInput {
export function createDirectoryReadmeInjectorHook(ctx: PluginInput) { export function createDirectoryReadmeInjectorHook(ctx: PluginInput) {
const sessionCaches = new Map<string, Set<string>>(); const sessionCaches = new Map<string, Set<string>>();
const pendingBatchReads = new Map<string, string[]>(); const pendingBatchReads = new Map<string, string[]>();
const truncator = createDynamicTruncator(ctx);
function getSessionCache(sessionID: string): Set<string> { function getSessionCache(sessionID: string): Set<string> {
if (!sessionCaches.has(sessionID)) { if (!sessionCaches.has(sessionID)) {
@@ -73,11 +75,11 @@ export function createDirectoryReadmeInjectorHook(ctx: PluginInput) {
return found.reverse(); return found.reverse();
} }
function processFilePathForInjection( async function processFilePathForInjection(
filePath: string, filePath: string,
sessionID: string, sessionID: string,
output: ToolExecuteOutput, output: ToolExecuteOutput,
): void { ): Promise<void> {
const resolved = resolveFilePath(filePath); const resolved = resolveFilePath(filePath);
if (!resolved) return; if (!resolved) return;
@@ -91,7 +93,11 @@ export function createDirectoryReadmeInjectorHook(ctx: PluginInput) {
try { try {
const content = readFileSync(readmePath, "utf-8"); const content = readFileSync(readmePath, "utf-8");
output.output += `\n\n[Project README: ${readmePath}]\n${content}`; const { result, truncated } = await truncator.truncate(sessionID, content);
const truncationNotice = truncated
? `\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ${readmePath}]`
: "";
output.output += `\n\n[Project README: ${readmePath}]\n${result}${truncationNotice}`;
cache.add(readmeDir); cache.add(readmeDir);
} catch {} } catch {}
} }
@@ -127,7 +133,7 @@ export function createDirectoryReadmeInjectorHook(ctx: PluginInput) {
const toolName = input.tool.toLowerCase(); const toolName = input.tool.toLowerCase();
if (toolName === "read") { if (toolName === "read") {
processFilePathForInjection(output.title, input.sessionID, output); await processFilePathForInjection(output.title, input.sessionID, output);
return; return;
} }
@@ -135,7 +141,7 @@ export function createDirectoryReadmeInjectorHook(ctx: PluginInput) {
const filePaths = pendingBatchReads.get(input.callID); const filePaths = pendingBatchReads.get(input.callID);
if (filePaths) { if (filePaths) {
for (const filePath of filePaths) { for (const filePath of filePaths) {
processFilePathForInjection(filePath, input.sessionID, output); await processFilePathForInjection(filePath, input.sessionID, output);
} }
pendingBatchReads.delete(input.callID); pendingBatchReads.delete(input.callID);
} }

View File

@@ -15,6 +15,7 @@ import {
loadInjectedRules, loadInjectedRules,
saveInjectedRules, saveInjectedRules,
} from "./storage"; } from "./storage";
import { createDynamicTruncator } from "../../shared/dynamic-truncator";
interface ToolExecuteInput { interface ToolExecuteInput {
tool: string; tool: string;
@@ -59,6 +60,7 @@ export function createRulesInjectorHook(ctx: PluginInput) {
{ contentHashes: Set<string>; realPaths: Set<string> } { contentHashes: Set<string>; realPaths: Set<string> }
>(); >();
const pendingBatchFiles = new Map<string, string[]>(); const pendingBatchFiles = new Map<string, string[]>();
const truncator = createDynamicTruncator(ctx);
function getSessionCache(sessionID: string): { function getSessionCache(sessionID: string): {
contentHashes: Set<string>; contentHashes: Set<string>;
@@ -76,11 +78,11 @@ export function createRulesInjectorHook(ctx: PluginInput) {
return resolve(ctx.directory, path); return resolve(ctx.directory, path);
} }
function processFilePathForInjection( async function processFilePathForInjection(
filePath: string, filePath: string,
sessionID: string, sessionID: string,
output: ToolExecuteOutput output: ToolExecuteOutput
): void { ): Promise<void> {
const resolved = resolveFilePath(filePath); const resolved = resolveFilePath(filePath);
if (!resolved) return; if (!resolved) return;
@@ -125,7 +127,11 @@ export function createRulesInjectorHook(ctx: PluginInput) {
toInject.sort((a, b) => a.distance - b.distance); toInject.sort((a, b) => a.distance - b.distance);
for (const rule of toInject) { for (const rule of toInject) {
output.output += `\n\n[Rule: ${rule.relativePath}]\n[Match: ${rule.matchReason}]\n${rule.content}`; const { result, truncated } = await truncator.truncate(sessionID, rule.content);
const truncationNotice = truncated
? `\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ${rule.relativePath}]`
: "";
output.output += `\n\n[Rule: ${rule.relativePath}]\n[Match: ${rule.matchReason}]\n${result}${truncationNotice}`;
} }
saveInjectedRules(sessionID, cache); saveInjectedRules(sessionID, cache);
@@ -167,7 +173,7 @@ export function createRulesInjectorHook(ctx: PluginInput) {
const toolName = input.tool.toLowerCase(); const toolName = input.tool.toLowerCase();
if (TRACKED_TOOLS.includes(toolName)) { if (TRACKED_TOOLS.includes(toolName)) {
processFilePathForInjection(output.title, input.sessionID, output); await processFilePathForInjection(output.title, input.sessionID, output);
return; return;
} }
@@ -175,7 +181,7 @@ export function createRulesInjectorHook(ctx: PluginInput) {
const filePaths = pendingBatchFiles.get(input.callID); const filePaths = pendingBatchFiles.get(input.callID);
if (filePaths) { if (filePaths) {
for (const filePath of filePaths) { for (const filePath of filePaths) {
processFilePathForInjection(filePath, input.sessionID, output); await processFilePathForInjection(filePath, input.sessionID, output);
} }
pendingBatchFiles.delete(input.callID); pendingBatchFiles.delete(input.callID);
} }