fix(coding-agent): remove process-cwd tool singletons and use tool-name allowlists
- switch SDK/CLI tool selection to name-based allowlists - apply allowlists across built-in, extension, and SDK tools - remove ambient process.cwd defaults from core tooling and resource helpers - update tests, examples, and TUI callers for explicit cwd plumbing - add regression coverage for extension tool filtering closes #3452 closes #2835
This commit is contained in:
@@ -133,9 +133,6 @@ export class AgentSessionRuntime {
|
||||
}
|
||||
|
||||
private apply(result: CreateAgentSessionRuntimeResult): void {
|
||||
if (process.cwd() !== result.services.cwd) {
|
||||
process.chdir(result.services.cwd);
|
||||
}
|
||||
this._session = result.session;
|
||||
this._services = result.services;
|
||||
this._diagnostics = result.diagnostics;
|
||||
@@ -346,9 +343,6 @@ export async function createAgentSessionRuntime(
|
||||
): Promise<AgentSessionRuntime> {
|
||||
assertSessionCwdExists(options.sessionManager, options.cwd);
|
||||
const result = await createRuntime(options);
|
||||
if (process.cwd() !== result.services.cwd) {
|
||||
process.chdir(result.services.cwd);
|
||||
}
|
||||
return new AgentSessionRuntime(
|
||||
result.session,
|
||||
result.services,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import stripAnsi from "strip-ansi";
|
||||
import { sanitizeBinaryOutput } from "../utils/shell.js";
|
||||
import { type BashOperations, createLocalBashOperations } from "./tools/bash.js";
|
||||
import type { BashOperations } from "./tools/bash.js";
|
||||
import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
|
||||
|
||||
// ============================================================================
|
||||
@@ -43,23 +43,6 @@ export interface BashResult {
|
||||
// Implementation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Execute a bash command with optional streaming and cancellation support.
|
||||
*
|
||||
* Uses the same local BashOperations backend as createBashTool() so interactive
|
||||
* user bash and tool-invoked bash share the same process spawning behavior.
|
||||
* Sanitization, newline normalization, temp-file capture, and truncation still
|
||||
* happen in executeBashWithOperations(), so reusing the local backend does not
|
||||
* change output processing behavior.
|
||||
*
|
||||
* @param command - The bash command to execute
|
||||
* @param options - Optional streaming callback and abort signal
|
||||
* @returns Promise resolving to execution result
|
||||
*/
|
||||
export function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
|
||||
return executeBashWithOperations(command, process.cwd(), createLocalBashOperations(), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a bash command using custom BashOperations.
|
||||
* Used for remote execution (SSH, containers, etc.).
|
||||
|
||||
@@ -104,7 +104,7 @@ export type TerminalInputHandler = (data: string) => { consume?: boolean; data?:
|
||||
|
||||
/** Working indicator configuration for the interactive streaming loader. */
|
||||
export interface WorkingIndicatorOptions {
|
||||
/** Animation frames. Use an empty array to hide the indicator entirely. */
|
||||
/** Animation frames. Use an empty array to hide the indicator entirely. Custom frames are rendered verbatim. */
|
||||
frames?: string[];
|
||||
/** Frame interval in milliseconds for animated indicators. */
|
||||
intervalMs?: number;
|
||||
@@ -142,6 +142,7 @@ export interface ExtensionUIContext {
|
||||
* - Omit the argument to restore the default animated spinner.
|
||||
* - Use `frames: ["●"]` for a static indicator.
|
||||
* - Use `frames: []` to hide the indicator entirely.
|
||||
* - Custom frames are rendered as provided, so extensions must add their own colors.
|
||||
*/
|
||||
setWorkingIndicator(options?: WorkingIndicatorOptions): void;
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ export class FooterDataProvider {
|
||||
private refreshPending = false;
|
||||
private disposed = false;
|
||||
|
||||
constructor(cwd: string = process.cwd()) {
|
||||
constructor(cwd: string) {
|
||||
this.cwd = cwd;
|
||||
this.gitPaths = findGitPaths(cwd);
|
||||
this.setupGitWatcher();
|
||||
|
||||
@@ -25,7 +25,7 @@ export {
|
||||
createAgentSessionFromServices,
|
||||
createAgentSessionServices,
|
||||
} from "./agent-session-services.js";
|
||||
export { type BashExecutorOptions, type BashResult, executeBash, executeBashWithOperations } from "./bash-executor.js";
|
||||
export { type BashExecutorOptions, type BashResult, executeBashWithOperations } from "./bash-executor.js";
|
||||
export type { CompactionResult } from "./compaction/index.js";
|
||||
export { createEventBus, type EventBus, type EventBusController } from "./event-bus.js";
|
||||
// Extensions system
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { basename, dirname, isAbsolute, join, resolve, sep } from "path";
|
||||
import { CONFIG_DIR_NAME, getPromptsDir } from "../config.js";
|
||||
import { CONFIG_DIR_NAME } from "../config.js";
|
||||
import { parseFrontmatter } from "../utils/frontmatter.js";
|
||||
import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js";
|
||||
|
||||
@@ -175,14 +175,14 @@ function loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) =>
|
||||
}
|
||||
|
||||
export interface LoadPromptTemplatesOptions {
|
||||
/** Working directory for project-local templates. Default: process.cwd() */
|
||||
cwd?: string;
|
||||
/** Agent config directory for global templates. Default: from getPromptsDir() */
|
||||
agentDir?: string;
|
||||
/** Explicit prompt template paths (files or directories) */
|
||||
promptPaths?: string[];
|
||||
/** Include default prompt directories. Default: true */
|
||||
includeDefaults?: boolean;
|
||||
/** Working directory for project-local templates. */
|
||||
cwd: string;
|
||||
/** Agent config directory for global templates. */
|
||||
agentDir: string;
|
||||
/** Explicit prompt template paths (files or directories). */
|
||||
promptPaths: string[];
|
||||
/** Include default prompt directories. */
|
||||
includeDefaults: boolean;
|
||||
}
|
||||
|
||||
function normalizePath(input: string): string {
|
||||
@@ -204,11 +204,11 @@ function resolvePromptPath(p: string, cwd: string): string {
|
||||
* 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/
|
||||
* 3. Explicit prompt paths
|
||||
*/
|
||||
export function loadPromptTemplates(options: LoadPromptTemplatesOptions = {}): PromptTemplate[] {
|
||||
const resolvedCwd = options.cwd ?? process.cwd();
|
||||
const resolvedAgentDir = options.agentDir ?? getPromptsDir();
|
||||
const promptPaths = options.promptPaths ?? [];
|
||||
const includeDefaults = options.includeDefaults ?? true;
|
||||
export function loadPromptTemplates(options: LoadPromptTemplatesOptions): PromptTemplate[] {
|
||||
const resolvedCwd = options.cwd;
|
||||
const resolvedAgentDir = options.agentDir;
|
||||
const promptPaths = options.promptPaths;
|
||||
const includeDefaults = options.includeDefaults;
|
||||
|
||||
const templates: PromptTemplate[] = [];
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join, resolve, sep } from "node:path";
|
||||
import chalk from "chalk";
|
||||
import { CONFIG_DIR_NAME, getAgentDir } from "../config.js";
|
||||
import { CONFIG_DIR_NAME } from "../config.js";
|
||||
import { loadThemeFromPath, type Theme } from "../modes/interactive/theme/theme.js";
|
||||
import type { ResourceDiagnostic } from "./diagnostics.js";
|
||||
|
||||
@@ -73,11 +73,12 @@ function loadContextFileFromDir(dir: string): { path: string; content: string }
|
||||
return null;
|
||||
}
|
||||
|
||||
export function loadProjectContextFiles(
|
||||
options: { cwd?: string; agentDir?: string } = {},
|
||||
): Array<{ path: string; content: string }> {
|
||||
const resolvedCwd = options.cwd ?? process.cwd();
|
||||
const resolvedAgentDir = options.agentDir ?? getAgentDir();
|
||||
export function loadProjectContextFiles(options: {
|
||||
cwd: string;
|
||||
agentDir: string;
|
||||
}): Array<{ path: string; content: string }> {
|
||||
const resolvedCwd = options.cwd;
|
||||
const resolvedAgentDir = options.agentDir;
|
||||
|
||||
const contextFiles: Array<{ path: string; content: string }> = [];
|
||||
const seenPaths = new Set<string>();
|
||||
@@ -113,8 +114,8 @@ export function loadProjectContextFiles(
|
||||
}
|
||||
|
||||
export interface DefaultResourceLoaderOptions {
|
||||
cwd?: string;
|
||||
agentDir?: string;
|
||||
cwd: string;
|
||||
agentDir: string;
|
||||
settingsManager?: SettingsManager;
|
||||
eventBus?: EventBus;
|
||||
additionalExtensionPaths?: string[];
|
||||
@@ -204,8 +205,8 @@ export class DefaultResourceLoader implements ResourceLoader {
|
||||
private lastThemePaths: string[];
|
||||
|
||||
constructor(options: DefaultResourceLoaderOptions) {
|
||||
this.cwd = options.cwd ?? process.cwd();
|
||||
this.agentDir = options.agentDir ?? getAgentDir();
|
||||
this.cwd = options.cwd;
|
||||
this.agentDir = options.agentDir;
|
||||
this.settingsManager = options.settingsManager ?? SettingsManager.create(this.cwd, this.agentDir);
|
||||
this.eventBus = options.eventBus ?? createEventBus();
|
||||
this.packageManager = new DefaultPackageManager({
|
||||
|
||||
@@ -144,7 +144,7 @@ export class FileSettingsStorage implements SettingsStorage {
|
||||
private globalSettingsPath: string;
|
||||
private projectSettingsPath: string;
|
||||
|
||||
constructor(cwd: string = process.cwd(), agentDir: string = getAgentDir()) {
|
||||
constructor(cwd: string, agentDir: string) {
|
||||
this.globalSettingsPath = join(agentDir, "settings.json");
|
||||
this.projectSettingsPath = join(cwd, CONFIG_DIR_NAME, "settings.json");
|
||||
}
|
||||
@@ -256,7 +256,7 @@ export class SettingsManager {
|
||||
}
|
||||
|
||||
/** Create a SettingsManager that loads from files */
|
||||
static create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): SettingsManager {
|
||||
static create(cwd: string, agentDir: string = getAgentDir()): SettingsManager {
|
||||
const storage = new FileSettingsStorage(cwd, agentDir);
|
||||
return SettingsManager.fromStorage(storage);
|
||||
}
|
||||
|
||||
@@ -374,14 +374,14 @@ function escapeXml(str: string): string {
|
||||
}
|
||||
|
||||
export interface LoadSkillsOptions {
|
||||
/** Working directory for project-local skills. Default: process.cwd() */
|
||||
cwd?: string;
|
||||
/** Agent config directory for global skills. Default: ~/.pi/agent */
|
||||
agentDir?: string;
|
||||
/** Working directory for project-local skills. */
|
||||
cwd: string;
|
||||
/** Agent config directory for global skills. */
|
||||
agentDir: string;
|
||||
/** Explicit skill paths (files or directories) */
|
||||
skillPaths?: string[];
|
||||
/** Include default skills directories. Default: true */
|
||||
includeDefaults?: boolean;
|
||||
skillPaths: string[];
|
||||
/** Include default skills directories. */
|
||||
includeDefaults: boolean;
|
||||
}
|
||||
|
||||
function normalizePath(input: string): string {
|
||||
@@ -401,8 +401,8 @@ function resolveSkillPath(p: string, cwd: string): string {
|
||||
* Load skills from all configured locations.
|
||||
* Returns skills and any validation diagnostics.
|
||||
*/
|
||||
export function loadSkills(options: LoadSkillsOptions = {}): LoadSkillsResult {
|
||||
const { cwd = process.cwd(), agentDir, skillPaths = [], includeDefaults = true } = options;
|
||||
export function loadSkills(options: LoadSkillsOptions): LoadSkillsResult {
|
||||
const { cwd, agentDir, skillPaths, includeDefaults } = options;
|
||||
|
||||
// Resolve agentDir - if not provided, use default from config
|
||||
const resolvedAgentDir = agentDir ?? getAgentDir();
|
||||
|
||||
@@ -16,8 +16,8 @@ export interface BuildSystemPromptOptions {
|
||||
promptGuidelines?: string[];
|
||||
/** Text to append to system prompt. */
|
||||
appendSystemPrompt?: string;
|
||||
/** Working directory. Default: process.cwd() */
|
||||
cwd?: string;
|
||||
/** Working directory. */
|
||||
cwd: string;
|
||||
/** Pre-loaded context files. */
|
||||
contextFiles?: Array<{ path: string; content: string }>;
|
||||
/** Pre-loaded skills. */
|
||||
@@ -25,7 +25,7 @@ export interface BuildSystemPromptOptions {
|
||||
}
|
||||
|
||||
/** Build the system prompt with tools, guidelines, and context */
|
||||
export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
|
||||
export function buildSystemPrompt(options: BuildSystemPromptOptions): string {
|
||||
const {
|
||||
customPrompt,
|
||||
selectedTools,
|
||||
@@ -36,7 +36,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
|
||||
contextFiles: providedContextFiles,
|
||||
skills: providedSkills,
|
||||
} = options;
|
||||
const resolvedCwd = cwd ?? process.cwd();
|
||||
const resolvedCwd = cwd;
|
||||
const promptCwd = resolvedCwd.replace(/\\/g, "/");
|
||||
|
||||
const now = new Date();
|
||||
|
||||
@@ -444,7 +444,3 @@ export function createBashToolDefinition(
|
||||
export function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {
|
||||
return wrapToolDefinition(createBashToolDefinition(cwd, options));
|
||||
}
|
||||
|
||||
/** Default bash tool using process.cwd() for backwards compatibility. */
|
||||
export const bashToolDefinition = createBashToolDefinition(process.cwd());
|
||||
export const bashTool = createBashTool(process.cwd());
|
||||
|
||||
@@ -485,7 +485,3 @@ export function createEditToolDefinition(
|
||||
export function createEditTool(cwd: string, options?: EditToolOptions): AgentTool<typeof editSchema> {
|
||||
return wrapToolDefinition(createEditToolDefinition(cwd, options));
|
||||
}
|
||||
|
||||
/** Default edit tool using process.cwd() for backwards compatibility. */
|
||||
export const editToolDefinition = createEditToolDefinition(process.cwd());
|
||||
export const editTool = createEditTool(process.cwd());
|
||||
|
||||
@@ -368,7 +368,3 @@ export function createFindToolDefinition(
|
||||
export function createFindTool(cwd: string, options?: FindToolOptions): AgentTool<typeof findSchema> {
|
||||
return wrapToolDefinition(createFindToolDefinition(cwd, options));
|
||||
}
|
||||
|
||||
/** Default find tool using process.cwd() for backwards compatibility. */
|
||||
export const findToolDefinition = createFindToolDefinition(process.cwd());
|
||||
export const findTool = createFindTool(process.cwd());
|
||||
|
||||
@@ -382,7 +382,3 @@ export function createGrepToolDefinition(
|
||||
export function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {
|
||||
return wrapToolDefinition(createGrepToolDefinition(cwd, options));
|
||||
}
|
||||
|
||||
/** Default grep tool using process.cwd() for backwards compatibility. */
|
||||
export const grepToolDefinition = createGrepToolDefinition(process.cwd());
|
||||
export const grepTool = createGrepTool(process.cwd());
|
||||
|
||||
@@ -227,7 +227,3 @@ export function createLsToolDefinition(
|
||||
export function createLsTool(cwd: string, options?: LsToolOptions): AgentTool<typeof lsSchema> {
|
||||
return wrapToolDefinition(createLsToolDefinition(cwd, options));
|
||||
}
|
||||
|
||||
/** Default ls tool using process.cwd() for backwards compatibility. */
|
||||
export const lsToolDefinition = createLsToolDefinition(process.cwd());
|
||||
export const lsTool = createLsTool(process.cwd());
|
||||
|
||||
@@ -271,7 +271,3 @@ export function createReadToolDefinition(
|
||||
export function createReadTool(cwd: string, options?: ReadToolOptions): AgentTool<typeof readSchema> {
|
||||
return wrapToolDefinition(createReadToolDefinition(cwd, options));
|
||||
}
|
||||
|
||||
/** Default read tool using process.cwd() for backwards compatibility. */
|
||||
export const readToolDefinition = createReadToolDefinition(process.cwd());
|
||||
export const readTool = createReadTool(process.cwd());
|
||||
|
||||
@@ -279,7 +279,3 @@ export function createWriteToolDefinition(
|
||||
export function createWriteTool(cwd: string, options?: WriteToolOptions): AgentTool<typeof writeSchema> {
|
||||
return wrapToolDefinition(createWriteToolDefinition(cwd, options));
|
||||
}
|
||||
|
||||
/** Default write tool using process.cwd() for backwards compatibility. */
|
||||
export const writeToolDefinition = createWriteToolDefinition(process.cwd());
|
||||
export const writeTool = createWriteTool(process.cwd());
|
||||
|
||||
@@ -183,8 +183,6 @@ export {
|
||||
createReadTool,
|
||||
createWriteTool,
|
||||
type PromptTemplate,
|
||||
// Pre-built tools (use process.cwd())
|
||||
readOnlyTools,
|
||||
} from "./core/sdk.js";
|
||||
export {
|
||||
type BranchSummaryEntry,
|
||||
@@ -235,9 +233,6 @@ export {
|
||||
type BashToolDetails,
|
||||
type BashToolInput,
|
||||
type BashToolOptions,
|
||||
bashTool,
|
||||
bashToolDefinition,
|
||||
codingTools,
|
||||
createBashToolDefinition,
|
||||
createEditToolDefinition,
|
||||
createFindToolDefinition,
|
||||
@@ -252,33 +247,23 @@ export {
|
||||
type EditToolDetails,
|
||||
type EditToolInput,
|
||||
type EditToolOptions,
|
||||
editTool,
|
||||
editToolDefinition,
|
||||
type FindOperations,
|
||||
type FindToolDetails,
|
||||
type FindToolInput,
|
||||
type FindToolOptions,
|
||||
findTool,
|
||||
findToolDefinition,
|
||||
formatSize,
|
||||
type GrepOperations,
|
||||
type GrepToolDetails,
|
||||
type GrepToolInput,
|
||||
type GrepToolOptions,
|
||||
grepTool,
|
||||
grepToolDefinition,
|
||||
type LsOperations,
|
||||
type LsToolDetails,
|
||||
type LsToolInput,
|
||||
type LsToolOptions,
|
||||
lsTool,
|
||||
lsToolDefinition,
|
||||
type ReadOperations,
|
||||
type ReadToolDetails,
|
||||
type ReadToolInput,
|
||||
type ReadToolOptions,
|
||||
readTool,
|
||||
readToolDefinition,
|
||||
type ToolsOptions,
|
||||
type TruncationOptions,
|
||||
type TruncationResult,
|
||||
@@ -289,8 +274,6 @@ export {
|
||||
type WriteToolInput,
|
||||
type WriteToolOptions,
|
||||
withFileMutationQueue,
|
||||
writeTool,
|
||||
writeToolDefinition,
|
||||
} from "./core/tools/index.js";
|
||||
// Main entry point
|
||||
export { type MainOptions, main } from "./main.js";
|
||||
|
||||
@@ -301,7 +301,7 @@ export async function showDeprecationWarnings(warnings: string[]): Promise<void>
|
||||
*
|
||||
* @returns Object with migration results and deprecation warnings
|
||||
*/
|
||||
export function runMigrations(cwd: string = process.cwd()): {
|
||||
export function runMigrations(cwd: string): {
|
||||
migratedAuthProviders: string[];
|
||||
deprecationWarnings: string[];
|
||||
} {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, type Component, Container, getCapabilities, Image, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
|
||||
import type { ToolDefinition, ToolRenderContext } from "../../../core/extensions/types.js";
|
||||
import { allToolDefinitions } from "../../../core/tools/index.js";
|
||||
import { createAllToolDefinitions, type ToolName } from "../../../core/tools/index.js";
|
||||
import { getTextOutput as getRenderedTextOutput } from "../../../core/tools/render-utils.js";
|
||||
import { convertToPng } from "../../../utils/image-convert.js";
|
||||
import { theme } from "../theme/theme.js";
|
||||
@@ -45,14 +45,14 @@ export class ToolExecutionComponent extends Container {
|
||||
options: ToolExecutionOptions = {},
|
||||
toolDefinition: ToolDefinition<any, any> | undefined,
|
||||
ui: TUI,
|
||||
cwd: string = process.cwd(),
|
||||
cwd: string,
|
||||
) {
|
||||
super();
|
||||
this.toolName = toolName;
|
||||
this.toolCallId = toolCallId;
|
||||
this.args = args;
|
||||
this.toolDefinition = toolDefinition;
|
||||
this.builtInToolDefinition = allToolDefinitions[toolName as keyof typeof allToolDefinitions];
|
||||
this.builtInToolDefinition = createAllToolDefinitions(cwd)[toolName as ToolName];
|
||||
this.showImages = options.showImages ?? true;
|
||||
this.ui = ui;
|
||||
this.cwd = cwd;
|
||||
|
||||
@@ -53,7 +53,7 @@ export function getShellConfig(): { shell: string; args: string[] } {
|
||||
return cachedShellConfig;
|
||||
}
|
||||
|
||||
const settings = SettingsManager.create();
|
||||
const settings = SettingsManager.create(process.cwd());
|
||||
const customShellPath = settings.getShellPath();
|
||||
|
||||
// 1. Check user-specified shell path
|
||||
|
||||
Reference in New Issue
Block a user