Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions packages/agent/src/tools/system/shellMessage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,72 @@ describe('shellMessageTool', () => {
expect(checkResult.completed).toBe(true);
expect(processStates.has(instanceId)).toBe(true);
});

it('should respect showStdIn and showStdout parameters', async () => {
// Start a process with default visibility settings
const startResult = await shellStartTool.execute(
{
command: 'cat',
description: 'Test with stdin/stdout visibility',
timeout: 50, // Force async mode
},
toolContext,
);

const instanceId = getInstanceId(startResult);

// Verify process state has default visibility settings
const processState = processStates.get(instanceId);
expect(processState?.showStdIn).toBe(false);
expect(processState?.showStdout).toBe(false);

// Send input with explicit visibility settings
await shellMessageTool.execute(
{
instanceId,
stdin: 'test input',
description: 'Test with explicit visibility settings',
showStdIn: true,
showStdout: true,
},
toolContext,
);

// Verify process state still exists
expect(processStates.has(instanceId)).toBe(true);
});

it('should inherit visibility settings from process state', async () => {
// Start a process with explicit visibility settings
const startResult = await shellStartTool.execute(
{
command: 'cat',
description: 'Test with inherited visibility settings',
timeout: 50, // Force async mode
showStdIn: true,
showStdout: true,
},
toolContext,
);

const instanceId = getInstanceId(startResult);

// Verify process state has the specified visibility settings
const processState = processStates.get(instanceId);
expect(processState?.showStdIn).toBe(true);
expect(processState?.showStdout).toBe(true);

// Send input without specifying visibility settings
await shellMessageTool.execute(
{
instanceId,
stdin: 'test input',
description: 'Test with inherited visibility settings',
},
toolContext,
);

// Verify process state still exists
expect(processStates.has(instanceId)).toBe(true);
});
});
44 changes: 42 additions & 2 deletions packages/agent/src/tools/system/shellMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ const parameterSchema = z.object({
description: z
.string()
.describe('The reason for this shell interaction (max 80 chars)'),
showStdIn: z
.boolean()
.optional()
.describe(
'Whether to show the input in the logs (default: false or value from shellStart)',
),
showStdout: z
.boolean()
.optional()
.describe(
'Whether to show output in the logs (default: false or value from shellStart)',
),
});

const returnSchema = z
Expand Down Expand Up @@ -82,7 +94,7 @@ export const shellMessageTool: Tool<Parameters, ReturnType> = {
returnsJsonSchema: zodToJsonSchema(returnSchema),

execute: async (
{ instanceId, stdin, signal },
{ instanceId, stdin, signal, showStdIn, showStdout },
{ logger },
): Promise<ReturnType> => {
logger.verbose(
Expand Down Expand Up @@ -115,6 +127,14 @@ export const shellMessageTool: Tool<Parameters, ReturnType> = {
if (!processState.process.stdin?.writable) {
throw new Error('Process stdin is not available');
}

// Determine whether to show stdin (prefer explicit parameter, fall back to process state)
const shouldShowStdIn =
showStdIn !== undefined ? showStdIn : processState.showStdIn;
if (shouldShowStdIn) {
logger.info(`[${instanceId}] stdin: ${stdin}`);
}

processState.process.stdin.write(`${stdin}\n`);
}

Expand All @@ -130,11 +150,22 @@ export const shellMessageTool: Tool<Parameters, ReturnType> = {
processState.stderr = [];

logger.verbose('Interaction completed successfully');

// Determine whether to show stdout (prefer explicit parameter, fall back to process state)
const shouldShowStdout =
showStdout !== undefined ? showStdout : processState.showStdout;

if (stdout) {
logger.verbose(`stdout: ${stdout.trim()}`);
if (shouldShowStdout) {
logger.info(`[${instanceId}] stdout: ${stdout.trim()}`);
}
}
if (stderr) {
logger.verbose(`stderr: ${stderr.trim()}`);
if (shouldShowStdout) {
logger.info(`[${instanceId}] stderr: ${stderr.trim()}`);
}
}

return {
Expand Down Expand Up @@ -168,8 +199,17 @@ export const shellMessageTool: Tool<Parameters, ReturnType> = {

logParameters: (input, { logger }) => {
const processState = processStates.get(input.instanceId);
const showStdIn =
input.showStdIn !== undefined
? input.showStdIn
: processState?.showStdIn || false;
const showStdout =
input.showStdout !== undefined
? input.showStdout
: processState?.showStdout || false;

logger.info(
`Interacting with shell command "${processState ? processState.command : '<unknown instanceId>'}", ${input.description}`,
`Interacting with shell command "${processState ? processState.command : '<unknown instanceId>'}", ${input.description} (showStdIn: ${showStdIn}, showStdout: ${showStdout})`,
);
},
logReturns: () => {},
Expand Down
33 changes: 33 additions & 0 deletions packages/agent/src/tools/system/shellStart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,37 @@ describe('shellStartTool', () => {

expect(result.mode).toBe('sync');
});

it('should store showStdIn and showStdout settings in process state', async () => {
const result = await shellStartTool.execute(
{
command: 'echo "test"',
description: 'Test with stdout visibility',
showStdIn: true,
showStdout: true,
},
toolContext,
);

expect(result.mode).toBe('sync');

// For async mode, check the process state directly
const asyncResult = await shellStartTool.execute(
{
command: 'sleep 1',
description: 'Test with stdin/stdout visibility in async mode',
timeout: 50, // Force async mode
showStdIn: true,
showStdout: true,
},
toolContext,
);

if (asyncResult.mode === 'async') {
const processState = processStates.get(asyncResult.instanceId);
expect(processState).toBeDefined();
expect(processState?.showStdIn).toBe(true);
expect(processState?.showStdout).toBe(true);
}
});
});
40 changes: 37 additions & 3 deletions packages/agent/src/tools/system/shellStart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type ProcessState = {
signaled: boolean;
exitCode: number | null;
};
showStdIn: boolean;
showStdout: boolean;
};

// Global map to store process state
Expand All @@ -36,6 +38,14 @@ const parameterSchema = z.object({
.describe(
'Timeout in ms before switching to async mode (default: 10s, which usually is sufficient)',
),
showStdIn: z
.boolean()
.optional()
.describe('Whether to show the command input in the logs (default: false)'),
showStdout: z
.boolean()
.optional()
.describe('Whether to show command output in the logs (default: false)'),
});

const returnSchema = z.union([
Expand Down Expand Up @@ -77,9 +87,17 @@ export const shellStartTool: Tool<Parameters, ReturnType> = {
returnsJsonSchema: zodToJsonSchema(returnSchema),

execute: async (
{ command, timeout = DEFAULT_TIMEOUT },
{
command,
timeout = DEFAULT_TIMEOUT,
showStdIn = false,
showStdout = false,
},
{ logger, workingDirectory },
): Promise<ReturnType> => {
if (showStdIn) {
logger.info(`Command input: ${command}`);
}
logger.verbose(`Starting shell command: ${command}`);

return new Promise((resolve) => {
Expand All @@ -100,6 +118,8 @@ export const shellStartTool: Tool<Parameters, ReturnType> = {
stdout: [],
stderr: [],
state: { completed: false, signaled: false, exitCode: null },
showStdIn,
showStdout,
};

// Initialize combined process state
Expand All @@ -111,13 +131,19 @@ export const shellStartTool: Tool<Parameters, ReturnType> = {
const output = data.toString();
processState.stdout.push(output);
logger.verbose(`[${instanceId}] stdout: ${output.trim()}`);
if (processState.showStdout) {
logger.info(`[${instanceId}] stdout: ${output.trim()}`);
}
});

if (process.stderr)
process.stderr.on('data', (data) => {
const output = data.toString();
processState.stderr.push(output);
logger.verbose(`[${instanceId}] stderr: ${output.trim()}`);
if (processState.showStdout) {
logger.info(`[${instanceId}] stderr: ${output.trim()}`);
}
});

process.on('error', (error) => {
Expand Down Expand Up @@ -186,10 +212,18 @@ export const shellStartTool: Tool<Parameters, ReturnType> = {
},

logParameters: (
{ command, description, timeout = DEFAULT_TIMEOUT },
{
command,
description,
timeout = DEFAULT_TIMEOUT,
showStdIn = false,
showStdout = false,
},
{ logger },
) => {
logger.info(`Running "${command}", ${description} (timeout: ${timeout}ms)`);
logger.info(
`Running "${command}", ${description} (timeout: ${timeout}ms, showStdIn: ${showStdIn}, showStdout: ${showStdout})`,
);
},
logReturns: (output, { logger }) => {
if (output.mode === 'async') {
Expand Down
Loading