mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 10:01:21 +00:00
Port truncation logic from coding-agent to mom
- Add truncate.ts with 2000 lines / 50KB limits - Update bash tool with tail truncation and temp file output - Update read tool with head truncation and offset hints - Remove redundant context history truncation (tools already provide actionable hints) fixes #155
This commit is contained in:
parent
de3fd172a9
commit
02c7f9ea51
5 changed files with 380 additions and 92 deletions
|
|
@ -1,6 +1,19 @@
|
|||
import { randomBytes } from "node:crypto";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { AgentTool } from "@mariozechner/pi-ai";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import type { Executor } from "../sandbox.js";
|
||||
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from "./truncate.js";
|
||||
|
||||
/**
|
||||
* Generate a unique temp file path for bash output
|
||||
*/
|
||||
function getTempFilePath(): string {
|
||||
const id = randomBytes(8).toString("hex");
|
||||
return join(tmpdir(), `mom-bash-${id}.log`);
|
||||
}
|
||||
|
||||
const bashSchema = Type.Object({
|
||||
label: Type.String({ description: "Brief description of what this command does (shown to user)" }),
|
||||
|
|
@ -8,18 +21,26 @@ const bashSchema = Type.Object({
|
|||
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
||||
});
|
||||
|
||||
interface BashToolDetails {
|
||||
truncation?: TruncationResult;
|
||||
fullOutputPath?: string;
|
||||
}
|
||||
|
||||
export function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {
|
||||
return {
|
||||
name: "bash",
|
||||
label: "bash",
|
||||
description:
|
||||
"Execute a bash command in the current working directory. Returns stdout and stderr. Optionally provide a timeout in seconds.",
|
||||
description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,
|
||||
parameters: bashSchema,
|
||||
execute: async (
|
||||
_toolCallId: string,
|
||||
{ command, timeout }: { label: string; command: string; timeout?: number },
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
// Track output for potential temp file writing
|
||||
let tempFilePath: string | undefined;
|
||||
let tempFileStream: ReturnType<typeof createWriteStream> | undefined;
|
||||
|
||||
const result = await executor.exec(command, { timeout, signal });
|
||||
let output = "";
|
||||
if (result.stdout) output += result.stdout;
|
||||
|
|
@ -28,11 +49,49 @@ export function createBashTool(executor: Executor): AgentTool<typeof bashSchema>
|
|||
output += result.stderr;
|
||||
}
|
||||
|
||||
if (result.code !== 0) {
|
||||
throw new Error(`${output}\n\nCommand exited with code ${result.code}`.trim());
|
||||
const totalBytes = Buffer.byteLength(output, "utf-8");
|
||||
|
||||
// Write to temp file if output exceeds limit
|
||||
if (totalBytes > DEFAULT_MAX_BYTES) {
|
||||
tempFilePath = getTempFilePath();
|
||||
tempFileStream = createWriteStream(tempFilePath);
|
||||
tempFileStream.write(output);
|
||||
tempFileStream.end();
|
||||
}
|
||||
|
||||
return { content: [{ type: "text", text: output || "(no output)" }], details: undefined };
|
||||
// Apply tail truncation
|
||||
const truncation = truncateTail(output);
|
||||
let outputText = truncation.content || "(no output)";
|
||||
|
||||
// Build details with truncation info
|
||||
let details: BashToolDetails | undefined;
|
||||
|
||||
if (truncation.truncated) {
|
||||
details = {
|
||||
truncation,
|
||||
fullOutputPath: tempFilePath,
|
||||
};
|
||||
|
||||
// Build actionable notice
|
||||
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
||||
const endLine = truncation.totalLines;
|
||||
|
||||
if (truncation.lastLinePartial) {
|
||||
// Edge case: last line alone > 50KB
|
||||
const lastLineSize = formatSize(Buffer.byteLength(output.split("\n").pop() || "", "utf-8"));
|
||||
outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;
|
||||
} else if (truncation.truncatedBy === "lines") {
|
||||
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;
|
||||
} else {
|
||||
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.code !== 0) {
|
||||
throw new Error(`${outputText}\n\nCommand exited with code ${result.code}`.trim());
|
||||
}
|
||||
|
||||
return { content: [{ type: "text", text: outputText }], details };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue