Files
oh-my-pi/packages/coding-agent/src/tools/bash-interceptor.ts
T
can1357 e66d71f6b9 refactor(tools): consolidated directory listing functionality from ls tool into read tool
- Consolidated directory listing functionality from dedicated `ls` tool into the `read` tool.
- Removed the `ls` tool and all related exports, types, and configurations from the public API.
- Updated `read` tool to return formatted directory listings with modification times instead of redirecting to `ls` tool.
- Removed `bashInterceptor.simpleLs` setting and related interception logic from bash tool.
- Removed `ls.enabled` setting and related tool registration logic.
- Removed test cases for the `ls` tool and updated tool type definitions to remove `LsToolResultEvent`.
2026-02-02 14:55:48 +01:00

106 lines
3.2 KiB
TypeScript

/**
* Bash intent interceptor - redirects common shell patterns to proper tools.
*
* When an LLM calls bash with patterns like `grep`, `cat`, `find`, etc.,
* this interceptor provides helpful error messages directing them to use
* the specialized tools instead.
*/
import type { BashInterceptorRule } from "../config/settings-schema";
export const DEFAULT_BASH_INTERCEPTOR_RULES: BashInterceptorRule[] = [
{
pattern: "^\\s*(cat|head|tail|less|more)\\s+",
tool: "read",
message: "Use the `read` tool instead of cat/head/tail. It provides better context and handles binary files.",
},
{
pattern: "^\\s*(grep|rg|ripgrep|ag|ack)\\s+",
tool: "grep",
message: "Use the `grep` tool instead of grep/rg. It respects .gitignore and provides structured output.",
},
{
pattern: "^\\s*(find|fd|locate)\\s+.*(-name|-iname|-type|--type|-glob)",
tool: "find",
message: "Use the `find` tool instead of find/fd. It respects .gitignore and is faster for glob patterns.",
},
{
pattern: "^\\s*sed\\s+(-i|--in-place)",
tool: "edit",
message: "Use the `edit` tool instead of sed -i. It provides diff preview and fuzzy matching.",
},
{
pattern: "^\\s*perl\\s+.*-[pn]?i",
tool: "edit",
message: "Use the `edit` tool instead of perl -i. It provides diff preview and fuzzy matching.",
},
{
pattern: "^\\s*awk\\s+.*-i\\s+inplace",
tool: "edit",
message: "Use the `edit` tool instead of awk -i inplace. It provides diff preview and fuzzy matching.",
},
{
pattern: "^\\s*(echo|printf|cat\\s*<<)\\s+.*[^|]>\\s*\\S",
tool: "write",
message: "Use the `write` tool instead of echo/cat redirection. It handles encoding and provides confirmation.",
},
];
export interface InterceptionResult {
/** If true, the bash command should be blocked */
block: boolean;
/** Error message to return instead of executing */
message?: string;
/** Suggested tool to use instead */
suggestedTool?: string;
}
/**
* Compile bash interceptor rules into regexes, skipping invalid patterns.
*/
function compileRules(rules: BashInterceptorRule[]): Array<{ rule: BashInterceptorRule; regex: RegExp }> {
const compiled: Array<{ rule: BashInterceptorRule; regex: RegExp }> = [];
for (const rule of rules) {
const flags = rule.flags ?? "";
try {
compiled.push({ rule, regex: new RegExp(rule.pattern, flags) });
} catch {
// Skip invalid regex patterns
}
}
return compiled;
}
/**
* Check if a bash command should be intercepted.
*
* @param command The bash command to check
* @param availableTools Set of tool names that are available
* @returns InterceptionResult indicating if the command should be blocked
*/
export function checkBashInterception(
command: string,
availableTools: string[],
rules: BashInterceptorRule[] = DEFAULT_BASH_INTERCEPTOR_RULES,
): InterceptionResult {
// Normalize command for pattern matching
const normalizedCommand = command.trim();
const compiled = compileRules(rules);
for (const { rule, regex } of compiled) {
// Only block if the suggested tool is actually available
if (!availableTools.includes(rule.tool)) {
continue;
}
if (regex.test(normalizedCommand)) {
return {
block: true,
message: `Blocked: ${rule.message}\n\nOriginal command: ${command}`,
suggestedTool: rule.tool,
};
}
}
return { block: false };
}