Skip to content

Commit 56bffec

Browse files
Merge pull request #1542 from corbitsdev/cl-10856-gpt-shell-args-on-main
fix(agent): keep gpt shell arguments through dispatch
2 parents 48a4aaf + 487f6c3 commit 56bffec

3 files changed

Lines changed: 57 additions & 14 deletions

File tree

‎src/agent/tool-aliases.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,43 @@ describe("hidden alias dispatch", () => {
191191
expect(seen.timeout).toBe(5000);
192192
});
193193

194+
test("gpt shell keeps run_shell cwd, timeout, background, and stop", async () => {
195+
let seen: Record<string, unknown> = {};
196+
const runner = createDynamicToolRunner([
197+
{
198+
kind: "string",
199+
definition: posixDef("run_shell"),
200+
handler: async (args) => {
201+
seen = args;
202+
return "ok";
203+
},
204+
},
205+
]);
206+
runner.setCallGate(() => true);
207+
const result = await runner.run(
208+
{
209+
id: "1",
210+
name: "shell",
211+
arguments: {
212+
command: "npm test",
213+
cwd: "/tmp/proj",
214+
timeout: 5000,
215+
background: true,
216+
stop: "sh_1",
217+
},
218+
},
219+
new AbortController().signal,
220+
);
221+
expect(result.content).toBe("ok");
222+
expect(seen).toEqual({
223+
command: "npm test",
224+
cwd: "/tmp/proj",
225+
timeout: 5000,
226+
background: true,
227+
stop: "sh_1",
228+
});
229+
});
230+
194231
test("update_plan hidden-dispatches onto manage_tasks; apply_patch does not", async () => {
195232
const runner = createDynamicToolRunner([
196233
{

‎src/agent/tool-aliases.ts‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -428,18 +428,16 @@ function incomingAlias(requested: string): string {
428428
}
429429

430430
/** Coerce hidden Codex-shaped arguments onto the engine tool. Callers pass
431-
* the already-resolved engine. */
431+
* the already-resolved engine. gpt advertises `shell` with run_shell's own
432+
* arguments (`cwd`, `timeout`, `background`, `stop`); those pass through.
433+
* Only Codex argv, `workdir`, and `timeout_ms` are rewritten. */
432434
export function prepareDispatchedToolCall(
433435
call: ToolCall,
434436
engine: string,
435437
): ToolCall {
436438
const incoming = incomingAlias(call.name);
437439
let args = call.arguments;
438-
if (
439-
incoming === "shell" ||
440-
incoming.toLowerCase() === "shell" ||
441-
(engine === "run_shell" && looksLikeCodexShellArgs(args))
442-
) {
440+
if (engine === "run_shell" && looksLikeCodexShellArgs(args)) {
443441
args = coerceShellArgs(args);
444442
}
445443
if (

‎src/permission/worker-grant-dispatch-identity.test.ts‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
// gpt-family workers call run_shell as `shell`. Reactor authz sees that raw
2-
// wire call; the permission middleware sees DynamicToolRunner's
3-
// prepareDispatchedToolCall output, whose coerceShellArgs rebuilds the
4-
// arguments. The authorized retry must match the same envelope at both
5-
// stages.
1+
// gpt-family workers call run_shell as `shell` with run_shell's own
2+
// arguments. Reactor authz sees that raw wire call; the permission
3+
// middleware sees prepareDispatchedToolCall, which renames `shell` to
4+
// `run_shell` and leaves cwd, timeout, background, and stop in place.
5+
// The authorized retry must match the same envelope at both stages.
66
import { describe, expect, test } from "bun:test";
77
import type { ToolCall } from "@intx/types/runtime";
88

@@ -68,13 +68,21 @@ describe("gpt-family worker authorized retry identity", () => {
6868
expect(retryAuthz).toEqual({ effect: "allow" });
6969
expect(store.peek(requestId)?.status).toBe("authorized");
7070

71-
// 4. Retry, stage 2: DynamicToolRunner coerces the `shell` call onto
72-
// run_shell, then the posix middleware runs gateToolCall.
71+
// 4. Retry, stage 2: DynamicToolRunner renames `shell` onto run_shell
72+
// and keeps the gpt-native arguments, then the posix middleware runs
73+
// gateToolCall. The grant still matches the authorized envelope.
7374
const dispatched = prepareDispatchedToolCall(
7475
wireCall("call-2"),
7576
"run_shell",
7677
);
77-
expect(dispatched.arguments).not.toEqual(wireCall("x").arguments);
78+
expect(dispatched.name).toBe("run_shell");
79+
expect(dispatched.arguments).toEqual(wireCall("x").arguments);
80+
expect(dispatched.arguments).toMatchObject({
81+
cwd: CWD,
82+
timeout: 120000,
83+
background: false,
84+
stop: "",
85+
});
7886

7987
let executed = 0;
8088
const result = await gateToolCall(

0 commit comments

Comments
 (0)