This commit is contained in:
Hardik Mer 2026-01-30 19:10:52 +05:30 committed by GitHub
commit 22a533e501
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 38 additions and 2 deletions

View File

@ -48,6 +48,7 @@ Notes:
- `tools.exec.node` (default: unset)
- `tools.exec.pathPrepend`: list of directories to prepend to `PATH` for exec runs.
- `tools.exec.safeBins`: stdin-only safe binaries that can run without explicit allowlist entries.
- `tools.exec.pty` (default: false): enable PTY mode by default for exec commands. Useful when commands hang without a TTY. Ignored when sandboxed.
Example:
```json5

View File

@ -18,3 +18,28 @@ test("exec supports pty output", async () => {
const text = result.content?.[0]?.text ?? "";
expect(text).toContain("ok");
});
test("exec uses pty when defaults.pty is true", async () => {
const tool = createExecTool({ allowBackground: false, pty: true });
// Note: pty is NOT passed in params - should use default
const result = await tool.execute("toolcall", {
command: 'node -e "process.stdout.write(String.fromCharCode(111,107))"',
});
expect(result.details.status).toBe("completed");
const text = result.content?.[0]?.text ?? "";
expect(text).toContain("ok");
});
test("exec params.pty overrides defaults.pty", async () => {
const tool = createExecTool({ allowBackground: false, pty: true });
// Explicitly set pty: false to override default
const result = await tool.execute("toolcall", {
command: "echo override",
pty: false,
});
expect(result.details.status).toBe("completed");
const text = result.content?.[0]?.text ?? "";
expect(text).toContain("override");
});

View File

@ -134,6 +134,7 @@ export type ExecToolDefaults = {
messageProvider?: string;
notifyOnExit?: boolean;
cwd?: string;
pty?: boolean;
};
export type { BashSandboxConfig } from "./bash-tools.shared.js";
@ -1295,7 +1296,7 @@ export function createExecTool(
env,
sandbox: undefined,
containerWorkdir: null,
usePty: params.pty === true && !sandbox,
usePty: (params.pty ?? defaults?.pty) === true && !sandbox,
warnings,
maxOutput,
pendingMaxOutput,
@ -1381,7 +1382,7 @@ export function createExecTool(
const effectiveTimeout =
typeof params.timeout === "number" ? params.timeout : defaultTimeoutSec;
const getWarningText = () => (warnings.length ? `${warnings.join("\n")}\n\n` : "");
const usePty = params.pty === true && !sandbox;
const usePty = (params.pty ?? defaults?.pty) === true && !sandbox;
const run = await runExecProcess({
command: params.command,
workdir,

View File

@ -92,6 +92,7 @@ function resolveExecConfig(cfg: OpenClawConfig | undefined) {
approvalRunningNoticeMs: globalExec?.approvalRunningNoticeMs,
cleanupMs: globalExec?.cleanupMs,
notifyOnExit: globalExec?.notifyOnExit,
pty: globalExec?.pty,
applyPatch: globalExec?.applyPatch,
};
}
@ -269,6 +270,7 @@ export function createOpenClawCodingTools(options?: {
approvalRunningNoticeMs:
options?.exec?.approvalRunningNoticeMs ?? execConfig.approvalRunningNoticeMs,
notifyOnExit: options?.exec?.notifyOnExit ?? execConfig.notifyOnExit,
pty: options?.exec?.pty ?? execConfig.pty,
sandbox: sandbox
? {
containerName: sandbox.containerName,

View File

@ -180,6 +180,7 @@ const FIELD_LABELS: Record<string, string> = {
"tools.exec.node": "Exec Node Binding",
"tools.exec.pathPrepend": "Exec PATH Prepend",
"tools.exec.safeBins": "Exec Safe Bins",
"tools.exec.pty": "Exec PTY Mode",
"tools.message.allowCrossContextSend": "Allow Cross-Context Messaging",
"tools.message.crossContext.allowWithinProvider": "Allow Cross-Context (Same Provider)",
"tools.message.crossContext.allowAcrossProviders": "Allow Cross-Context (Across Providers)",
@ -421,6 +422,8 @@ const FIELD_HELP: Record<string, string> = {
"tools.exec.pathPrepend": "Directories to prepend to PATH for exec runs (gateway/sandbox).",
"tools.exec.safeBins":
"Allow stdin-only safe binaries to run without explicit allowlist entries.",
"tools.exec.pty":
"Enable PTY mode by default for exec commands. Ignored when sandboxed. (default: false).",
"tools.message.allowCrossContextSend":
"Legacy override: allow cross-context sends across all providers.",
"tools.message.crossContext.allowWithinProvider":

View File

@ -183,6 +183,8 @@ export type ExecToolConfig = {
cleanupMs?: number;
/** Emit a system event and heartbeat when a backgrounded exec exits. */
notifyOnExit?: boolean;
/** Enable PTY mode by default for exec commands (ignored when sandboxed). */
pty?: boolean;
/** apply_patch subtool configuration (experimental). */
applyPatch?: {
/** Enable apply_patch for OpenAI models (default: false). */

View File

@ -271,6 +271,7 @@ export const AgentToolsSchema = z
approvalRunningNoticeMs: z.number().int().nonnegative().optional(),
cleanupMs: z.number().int().positive().optional(),
notifyOnExit: z.boolean().optional(),
pty: z.boolean().optional(),
applyPatch: z
.object({
enabled: z.boolean().optional(),
@ -512,6 +513,7 @@ export const ToolsSchema = z
timeoutSec: z.number().int().positive().optional(),
cleanupMs: z.number().int().positive().optional(),
notifyOnExit: z.boolean().optional(),
pty: z.boolean().optional(),
applyPatch: z
.object({
enabled: z.boolean().optional(),