add support for -w,--writable-root to add more writable roots for sandbox (#263)
This adds support for a new flag, `-w,--writable-root`, that can be specified multiple times to _amend_ the list of folders that should be configured as "writable roots" by the sandbox used in `full-auto` mode. Values that are passed as relative paths will be resolved to absolute paths. Incidentally, this required updating a number of the `agent*.test.ts` files: it feels like some of the setup logic across those tests could be consolidated. In my testing, it seems that this might be slightly out of distribution for the model, as I had to explicitly tell it to run `apply_patch` and that it had the permissions to write those files (initially, it just showed me a diff and told me to apply it myself). Nevertheless, I think this is a good starting point.
This commit is contained in:
@@ -22,6 +22,7 @@ type Props = {
|
|||||||
imagePaths?: Array<string>;
|
imagePaths?: Array<string>;
|
||||||
rollout?: AppRollout;
|
rollout?: AppRollout;
|
||||||
approvalPolicy: ApprovalPolicy;
|
approvalPolicy: ApprovalPolicy;
|
||||||
|
additionalWritableRoots: ReadonlyArray<string>;
|
||||||
fullStdout: boolean;
|
fullStdout: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ export default function App({
|
|||||||
rollout,
|
rollout,
|
||||||
imagePaths,
|
imagePaths,
|
||||||
approvalPolicy,
|
approvalPolicy,
|
||||||
|
additionalWritableRoots,
|
||||||
fullStdout,
|
fullStdout,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const app = useApp();
|
const app = useApp();
|
||||||
@@ -97,6 +99,7 @@ export default function App({
|
|||||||
prompt={prompt}
|
prompt={prompt}
|
||||||
imagePaths={imagePaths}
|
imagePaths={imagePaths}
|
||||||
approvalPolicy={approvalPolicy}
|
approvalPolicy={approvalPolicy}
|
||||||
|
additionalWritableRoots={additionalWritableRoots}
|
||||||
fullStdout={fullStdout}
|
fullStdout={fullStdout}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -53,13 +53,14 @@ const cli = meow(
|
|||||||
$ codex completion <bash|zsh|fish>
|
$ codex completion <bash|zsh|fish>
|
||||||
|
|
||||||
Options
|
Options
|
||||||
-h, --help Show usage and exit
|
-h, --help Show usage and exit
|
||||||
-m, --model <model> Model to use for completions (default: o4-mini)
|
-m, --model <model> Model to use for completions (default: o4-mini)
|
||||||
-i, --image <path> Path(s) to image files to include as input
|
-i, --image <path> Path(s) to image files to include as input
|
||||||
-v, --view <rollout> Inspect a previously saved rollout instead of starting a session
|
-v, --view <rollout> Inspect a previously saved rollout instead of starting a session
|
||||||
-q, --quiet Non-interactive mode that only prints the assistant's final output
|
-q, --quiet Non-interactive mode that only prints the assistant's final output
|
||||||
-c, --config Open the instructions file in your editor
|
-c, --config Open the instructions file in your editor
|
||||||
-a, --approval-mode <mode> Override the approval policy: 'suggest', 'auto-edit', or 'full-auto'
|
-w, --writable-root <path> Writable folder for sandbox in full-auto mode (can be specified multiple times)
|
||||||
|
-a, --approval-mode <mode> Override the approval policy: 'suggest', 'auto-edit', or 'full-auto'
|
||||||
|
|
||||||
--auto-edit Automatically approve file edits; still prompt for commands
|
--auto-edit Automatically approve file edits; still prompt for commands
|
||||||
--full-auto Automatically approve edits and commands when executed in the sandbox
|
--full-auto Automatically approve edits and commands when executed in the sandbox
|
||||||
@@ -122,6 +123,13 @@ const cli = meow(
|
|||||||
description:
|
description:
|
||||||
"Determine the approval mode for Codex (default: suggest) Values: suggest, auto-edit, full-auto",
|
"Determine the approval mode for Codex (default: suggest) Values: suggest, auto-edit, full-auto",
|
||||||
},
|
},
|
||||||
|
writableRoot: {
|
||||||
|
type: "string",
|
||||||
|
isMultiple: true,
|
||||||
|
aliases: ["w"],
|
||||||
|
description:
|
||||||
|
"Writable folder for sandbox in full-auto mode (can be specified multiple times)",
|
||||||
|
},
|
||||||
noProjectDoc: {
|
noProjectDoc: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
description: "Disable automatic inclusion of project‑level codex.md",
|
description: "Disable automatic inclusion of project‑level codex.md",
|
||||||
@@ -276,6 +284,11 @@ if (fullContextMode) {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that all values in additionalWritableRoots are absolute paths.
|
||||||
|
const additionalWritableRoots: ReadonlyArray<string> = (
|
||||||
|
cli.flags.writableRoot ?? []
|
||||||
|
).map((p) => path.resolve(p));
|
||||||
|
|
||||||
// If we are running in --quiet mode, do that and exit.
|
// If we are running in --quiet mode, do that and exit.
|
||||||
const quietMode = Boolean(cli.flags.quiet);
|
const quietMode = Boolean(cli.flags.quiet);
|
||||||
const autoApproveEverything = Boolean(
|
const autoApproveEverything = Boolean(
|
||||||
@@ -298,6 +311,7 @@ if (quietMode) {
|
|||||||
approvalPolicy: autoApproveEverything
|
approvalPolicy: autoApproveEverything
|
||||||
? AutoApprovalMode.FULL_AUTO
|
? AutoApprovalMode.FULL_AUTO
|
||||||
: AutoApprovalMode.SUGGEST,
|
: AutoApprovalMode.SUGGEST,
|
||||||
|
additionalWritableRoots,
|
||||||
config,
|
config,
|
||||||
});
|
});
|
||||||
onExit();
|
onExit();
|
||||||
@@ -332,6 +346,7 @@ const instance = render(
|
|||||||
rollout={rollout}
|
rollout={rollout}
|
||||||
imagePaths={imagePaths}
|
imagePaths={imagePaths}
|
||||||
approvalPolicy={approvalPolicy}
|
approvalPolicy={approvalPolicy}
|
||||||
|
additionalWritableRoots={additionalWritableRoots}
|
||||||
fullStdout={fullStdout}
|
fullStdout={fullStdout}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
@@ -393,11 +408,13 @@ async function runQuietMode({
|
|||||||
prompt,
|
prompt,
|
||||||
imagePaths,
|
imagePaths,
|
||||||
approvalPolicy,
|
approvalPolicy,
|
||||||
|
additionalWritableRoots,
|
||||||
config,
|
config,
|
||||||
}: {
|
}: {
|
||||||
prompt: string;
|
prompt: string;
|
||||||
imagePaths: Array<string>;
|
imagePaths: Array<string>;
|
||||||
approvalPolicy: ApprovalPolicy;
|
approvalPolicy: ApprovalPolicy;
|
||||||
|
additionalWritableRoots: ReadonlyArray<string>;
|
||||||
config: AppConfig;
|
config: AppConfig;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const agent = new AgentLoop({
|
const agent = new AgentLoop({
|
||||||
@@ -405,6 +422,7 @@ async function runQuietMode({
|
|||||||
config: config,
|
config: config,
|
||||||
instructions: config.instructions,
|
instructions: config.instructions,
|
||||||
approvalPolicy,
|
approvalPolicy,
|
||||||
|
additionalWritableRoots,
|
||||||
onItem: (item: ResponseItem) => {
|
onItem: (item: ResponseItem) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(formatResponseItemForQuietMode(item));
|
console.log(formatResponseItemForQuietMode(item));
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type Props = {
|
|||||||
prompt?: string;
|
prompt?: string;
|
||||||
imagePaths?: Array<string>;
|
imagePaths?: Array<string>;
|
||||||
approvalPolicy: ApprovalPolicy;
|
approvalPolicy: ApprovalPolicy;
|
||||||
|
additionalWritableRoots: ReadonlyArray<string>;
|
||||||
fullStdout: boolean;
|
fullStdout: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,6 +123,7 @@ export default function TerminalChat({
|
|||||||
prompt: _initialPrompt,
|
prompt: _initialPrompt,
|
||||||
imagePaths: _initialImagePaths,
|
imagePaths: _initialImagePaths,
|
||||||
approvalPolicy: initialApprovalPolicy,
|
approvalPolicy: initialApprovalPolicy,
|
||||||
|
additionalWritableRoots,
|
||||||
fullStdout,
|
fullStdout,
|
||||||
}: Props): React.ReactElement {
|
}: Props): React.ReactElement {
|
||||||
const [model, setModel] = useState<string>(config.model);
|
const [model, setModel] = useState<string>(config.model);
|
||||||
@@ -183,6 +185,7 @@ export default function TerminalChat({
|
|||||||
config,
|
config,
|
||||||
instructions: config.instructions,
|
instructions: config.instructions,
|
||||||
approvalPolicy,
|
approvalPolicy,
|
||||||
|
additionalWritableRoots,
|
||||||
onLastResponseId: setLastResponseId,
|
onLastResponseId: setLastResponseId,
|
||||||
onItem: (item) => {
|
onItem: (item) => {
|
||||||
log(`onItem: ${JSON.stringify(item)}`);
|
log(`onItem: ${JSON.stringify(item)}`);
|
||||||
@@ -248,7 +251,13 @@ export default function TerminalChat({
|
|||||||
agentRef.current = undefined;
|
agentRef.current = undefined;
|
||||||
forceUpdate(); // re‑render after teardown too
|
forceUpdate(); // re‑render after teardown too
|
||||||
};
|
};
|
||||||
}, [model, config, approvalPolicy, requestConfirmation]);
|
}, [
|
||||||
|
model,
|
||||||
|
config,
|
||||||
|
approvalPolicy,
|
||||||
|
requestConfirmation,
|
||||||
|
additionalWritableRoots,
|
||||||
|
]);
|
||||||
|
|
||||||
// whenever loading starts/stops, reset or start a timer — but pause the
|
// whenever loading starts/stops, reset or start a timer — but pause the
|
||||||
// timer while a confirmation overlay is displayed so we don't trigger a
|
// timer while a confirmation overlay is displayed so we don't trigger a
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ type AgentLoopParams = {
|
|||||||
onItem: (item: ResponseItem) => void;
|
onItem: (item: ResponseItem) => void;
|
||||||
onLoading: (loading: boolean) => void;
|
onLoading: (loading: boolean) => void;
|
||||||
|
|
||||||
|
/** Extra writable roots to use with sandbox execution. */
|
||||||
|
additionalWritableRoots: ReadonlyArray<string>;
|
||||||
|
|
||||||
/** Called when the command is not auto-approved to request explicit user review. */
|
/** Called when the command is not auto-approved to request explicit user review. */
|
||||||
getCommandConfirmation: (
|
getCommandConfirmation: (
|
||||||
command: Array<string>,
|
command: Array<string>,
|
||||||
@@ -58,6 +61,7 @@ export class AgentLoop {
|
|||||||
private instructions?: string;
|
private instructions?: string;
|
||||||
private approvalPolicy: ApprovalPolicy;
|
private approvalPolicy: ApprovalPolicy;
|
||||||
private config: AppConfig;
|
private config: AppConfig;
|
||||||
|
private additionalWritableRoots: ReadonlyArray<string>;
|
||||||
|
|
||||||
// Using `InstanceType<typeof OpenAI>` sidesteps typing issues with the OpenAI package under
|
// Using `InstanceType<typeof OpenAI>` sidesteps typing issues with the OpenAI package under
|
||||||
// the TS 5+ `moduleResolution=bundler` setup. OpenAI client instance. We keep the concrete
|
// the TS 5+ `moduleResolution=bundler` setup. OpenAI client instance. We keep the concrete
|
||||||
@@ -213,6 +217,7 @@ export class AgentLoop {
|
|||||||
onLoading,
|
onLoading,
|
||||||
getCommandConfirmation,
|
getCommandConfirmation,
|
||||||
onLastResponseId,
|
onLastResponseId,
|
||||||
|
additionalWritableRoots,
|
||||||
}: AgentLoopParams & { config?: AppConfig }) {
|
}: AgentLoopParams & { config?: AppConfig }) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.instructions = instructions;
|
this.instructions = instructions;
|
||||||
@@ -229,6 +234,7 @@ export class AgentLoop {
|
|||||||
model,
|
model,
|
||||||
instructions: instructions ?? "",
|
instructions: instructions ?? "",
|
||||||
} as AppConfig);
|
} as AppConfig);
|
||||||
|
this.additionalWritableRoots = additionalWritableRoots;
|
||||||
this.onItem = onItem;
|
this.onItem = onItem;
|
||||||
this.onLoading = onLoading;
|
this.onLoading = onLoading;
|
||||||
this.getCommandConfirmation = getCommandConfirmation;
|
this.getCommandConfirmation = getCommandConfirmation;
|
||||||
@@ -358,6 +364,7 @@ export class AgentLoop {
|
|||||||
args,
|
args,
|
||||||
this.config,
|
this.config,
|
||||||
this.approvalPolicy,
|
this.approvalPolicy,
|
||||||
|
this.additionalWritableRoots,
|
||||||
this.getCommandConfirmation,
|
this.getCommandConfirmation,
|
||||||
this.execAbortController?.signal,
|
this.execAbortController?.signal,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ const DEFAULT_TIMEOUT_MS = 10_000; // 10 seconds
|
|||||||
* mapped to a non-zero exit code and the error message should be in stderr.
|
* mapped to a non-zero exit code and the error message should be in stderr.
|
||||||
*/
|
*/
|
||||||
export function exec(
|
export function exec(
|
||||||
{ cmd, workdir, timeoutInMillis }: ExecInput,
|
{
|
||||||
|
cmd,
|
||||||
|
workdir,
|
||||||
|
timeoutInMillis,
|
||||||
|
additionalWritableRoots,
|
||||||
|
}: ExecInput & { additionalWritableRoots: ReadonlyArray<string> },
|
||||||
sandbox: SandboxType,
|
sandbox: SandboxType,
|
||||||
abortSignal?: AbortSignal,
|
abortSignal?: AbortSignal,
|
||||||
): Promise<ExecResult> {
|
): Promise<ExecResult> {
|
||||||
@@ -30,7 +35,12 @@ export function exec(
|
|||||||
timeout: timeoutInMillis || DEFAULT_TIMEOUT_MS,
|
timeout: timeoutInMillis || DEFAULT_TIMEOUT_MS,
|
||||||
...(workdir ? { cwd: workdir } : {}),
|
...(workdir ? { cwd: workdir } : {}),
|
||||||
};
|
};
|
||||||
const writableRoots = [process.cwd(), os.tmpdir()];
|
// Merge default writable roots with any user-specified ones.
|
||||||
|
const writableRoots = [
|
||||||
|
process.cwd(),
|
||||||
|
os.tmpdir(),
|
||||||
|
...additionalWritableRoots,
|
||||||
|
];
|
||||||
return execForSandbox(cmd, opts, writableRoots, abortSignal);
|
return execForSandbox(cmd, opts, writableRoots, abortSignal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export async function handleExecCommand(
|
|||||||
args: ExecInput,
|
args: ExecInput,
|
||||||
config: AppConfig,
|
config: AppConfig,
|
||||||
policy: ApprovalPolicy,
|
policy: ApprovalPolicy,
|
||||||
|
additionalWritableRoots: ReadonlyArray<string>,
|
||||||
getCommandConfirmation: (
|
getCommandConfirmation: (
|
||||||
command: Array<string>,
|
command: Array<string>,
|
||||||
applyPatch: ApplyPatchCommand | undefined,
|
applyPatch: ApplyPatchCommand | undefined,
|
||||||
@@ -91,6 +92,7 @@ export async function handleExecCommand(
|
|||||||
args,
|
args,
|
||||||
/* applyPatch */ undefined,
|
/* applyPatch */ undefined,
|
||||||
/* runInSandbox */ false,
|
/* runInSandbox */ false,
|
||||||
|
additionalWritableRoots,
|
||||||
abortSignal,
|
abortSignal,
|
||||||
).then(convertSummaryToResult);
|
).then(convertSummaryToResult);
|
||||||
}
|
}
|
||||||
@@ -138,6 +140,7 @@ export async function handleExecCommand(
|
|||||||
args,
|
args,
|
||||||
applyPatch,
|
applyPatch,
|
||||||
runInSandbox,
|
runInSandbox,
|
||||||
|
additionalWritableRoots,
|
||||||
abortSignal,
|
abortSignal,
|
||||||
);
|
);
|
||||||
// If the operation was aborted in the meantime, propagate the cancellation
|
// If the operation was aborted in the meantime, propagate the cancellation
|
||||||
@@ -170,7 +173,13 @@ export async function handleExecCommand(
|
|||||||
} else {
|
} else {
|
||||||
// The user has approved the command, so we will run it outside of the
|
// The user has approved the command, so we will run it outside of the
|
||||||
// sandbox.
|
// sandbox.
|
||||||
const summary = await execCommand(args, applyPatch, false, abortSignal);
|
const summary = await execCommand(
|
||||||
|
args,
|
||||||
|
applyPatch,
|
||||||
|
false,
|
||||||
|
additionalWritableRoots,
|
||||||
|
abortSignal,
|
||||||
|
);
|
||||||
return convertSummaryToResult(summary);
|
return convertSummaryToResult(summary);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -202,6 +211,7 @@ async function execCommand(
|
|||||||
execInput: ExecInput,
|
execInput: ExecInput,
|
||||||
applyPatchCommand: ApplyPatchCommand | undefined,
|
applyPatchCommand: ApplyPatchCommand | undefined,
|
||||||
runInSandbox: boolean,
|
runInSandbox: boolean,
|
||||||
|
additionalWritableRoots: ReadonlyArray<string>,
|
||||||
abortSignal?: AbortSignal,
|
abortSignal?: AbortSignal,
|
||||||
): Promise<ExecCommandSummary> {
|
): Promise<ExecCommandSummary> {
|
||||||
let { workdir } = execInput;
|
let { workdir } = execInput;
|
||||||
@@ -239,7 +249,11 @@ async function execCommand(
|
|||||||
const execResult =
|
const execResult =
|
||||||
applyPatchCommand != null
|
applyPatchCommand != null
|
||||||
? execApplyPatch(applyPatchCommand.patch)
|
? execApplyPatch(applyPatchCommand.patch)
|
||||||
: await exec(execInput, await getSandbox(runInSandbox), abortSignal);
|
: await exec(
|
||||||
|
{ ...execInput, additionalWritableRoots },
|
||||||
|
await getSandbox(runInSandbox),
|
||||||
|
abortSignal,
|
||||||
|
);
|
||||||
const duration = Date.now() - start;
|
const duration = Date.now() - start;
|
||||||
const { stdout, stderr, exitCode } = execResult;
|
const { stdout, stderr, exitCode } = execResult;
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ describe("cancel before first function_call", () => {
|
|||||||
const { _test } = (await import("openai")) as any;
|
const { _test } = (await import("openai")) as any;
|
||||||
|
|
||||||
const agent = new AgentLoop({
|
const agent = new AgentLoop({
|
||||||
|
additionalWritableRoots: [],
|
||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ describe("cancel clears previous_response_id", () => {
|
|||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: () => {},
|
onItem: () => {},
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ describe("Agent cancellation race", () => {
|
|||||||
const items: Array<any> = [];
|
const items: Array<any> = [];
|
||||||
|
|
||||||
const agent = new AgentLoop({
|
const agent = new AgentLoop({
|
||||||
|
additionalWritableRoots: [],
|
||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
config: { model: "any", instructions: "" },
|
config: { model: "any", instructions: "" },
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ describe("Agent cancellation", () => {
|
|||||||
instructions: "",
|
instructions: "",
|
||||||
config: { model: "any", instructions: "" },
|
config: { model: "any", instructions: "" },
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: (item) => {
|
onItem: (item) => {
|
||||||
received.push(item);
|
received.push(item);
|
||||||
},
|
},
|
||||||
@@ -136,6 +137,7 @@ describe("Agent cancellation", () => {
|
|||||||
const received: Array<any> = [];
|
const received: Array<any> = [];
|
||||||
|
|
||||||
const agent = new AgentLoop({
|
const agent = new AgentLoop({
|
||||||
|
additionalWritableRoots: [],
|
||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
config: { model: "any", instructions: "" },
|
config: { model: "any", instructions: "" },
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ describe("function_call_output includes original call ID", () => {
|
|||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: () => {},
|
onItem: () => {},
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ describe("AgentLoop – generic network/server errors", () => {
|
|||||||
const received: Array<any> = [];
|
const received: Array<any> = [];
|
||||||
|
|
||||||
const agent = new AgentLoop({
|
const agent = new AgentLoop({
|
||||||
|
additionalWritableRoots: [],
|
||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
@@ -99,6 +100,7 @@ describe("AgentLoop – generic network/server errors", () => {
|
|||||||
const received: Array<any> = [];
|
const received: Array<any> = [];
|
||||||
|
|
||||||
const agent = new AgentLoop({
|
const agent = new AgentLoop({
|
||||||
|
additionalWritableRoots: [],
|
||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ describe("Agent interrupt and continue", () => {
|
|||||||
|
|
||||||
// Create the agent
|
// Create the agent
|
||||||
const agent = new AgentLoop({
|
const agent = new AgentLoop({
|
||||||
|
additionalWritableRoots: [],
|
||||||
model: "test-model",
|
model: "test-model",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ describe("AgentLoop – invalid request / 4xx errors", () => {
|
|||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: (i) => received.push(i),
|
onItem: (i) => received.push(i),
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ describe("AgentLoop – max_tokens too large error", () => {
|
|||||||
const received: Array<any> = [];
|
const received: Array<any> = [];
|
||||||
|
|
||||||
const agent = new AgentLoop({
|
const agent = new AgentLoop({
|
||||||
|
additionalWritableRoots: [],
|
||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ describe("AgentLoop – network resilience", () => {
|
|||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: (i) => received.push(i),
|
onItem: (i) => received.push(i),
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
@@ -150,6 +151,7 @@ describe("AgentLoop – network resilience", () => {
|
|||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: (i) => received.push(i),
|
onItem: (i) => received.push(i),
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ describe("AgentLoop", () => {
|
|||||||
expect(config.instructions).toContain("Hello docs!");
|
expect(config.instructions).toContain("Hello docs!");
|
||||||
|
|
||||||
const agent = new AgentLoop({
|
const agent = new AgentLoop({
|
||||||
|
additionalWritableRoots: [],
|
||||||
model: "o3", // arbitrary
|
model: "o3", // arbitrary
|
||||||
instructions: config.instructions,
|
instructions: config.instructions,
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ describe("AgentLoop – rate‑limit handling", () => {
|
|||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: (i) => received.push(i),
|
onItem: (i) => received.push(i),
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ describe("AgentLoop – automatic retry on 5xx errors", () => {
|
|||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: (i) => received.push(i),
|
onItem: (i) => received.push(i),
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
@@ -134,6 +135,7 @@ describe("AgentLoop – automatic retry on 5xx errors", () => {
|
|||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: (i) => received.push(i),
|
onItem: (i) => received.push(i),
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
|
|||||||
@@ -82,7 +82,14 @@ describe("Agent terminate (hard cancel)", () => {
|
|||||||
it("suppresses function_call_output and stops processing once terminate() is invoked", async () => {
|
it("suppresses function_call_output and stops processing once terminate() is invoked", async () => {
|
||||||
// Simulate a long‑running exec that would normally resolve with output.
|
// Simulate a long‑running exec that would normally resolve with output.
|
||||||
vi.spyOn(handleExec, "handleExecCommand").mockImplementation(
|
vi.spyOn(handleExec, "handleExecCommand").mockImplementation(
|
||||||
async (_args, _config, _policy, _getConf, abortSignal) => {
|
async (
|
||||||
|
_args,
|
||||||
|
_config,
|
||||||
|
_policy,
|
||||||
|
_additionalWritableRoots,
|
||||||
|
_getConf,
|
||||||
|
abortSignal,
|
||||||
|
) => {
|
||||||
// Wait until the abort signal is fired or 2s (whichever comes first).
|
// Wait until the abort signal is fired or 2s (whichever comes first).
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
if (abortSignal?.aborted) {
|
if (abortSignal?.aborted) {
|
||||||
@@ -106,6 +113,7 @@ describe("Agent terminate (hard cancel)", () => {
|
|||||||
instructions: "",
|
instructions: "",
|
||||||
config: { model: "any", instructions: "" },
|
config: { model: "any", instructions: "" },
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: (item) => received.push(item),
|
onItem: (item) => received.push(item),
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
@@ -141,6 +149,7 @@ describe("Agent terminate (hard cancel)", () => {
|
|||||||
instructions: "",
|
instructions: "",
|
||||||
config: { model: "any", instructions: "" },
|
config: { model: "any", instructions: "" },
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: () => {},
|
onItem: () => {},
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ describe("thinking time counter", () => {
|
|||||||
model: "any",
|
model: "any",
|
||||||
instructions: "",
|
instructions: "",
|
||||||
approvalPolicy: { mode: "auto" } as any,
|
approvalPolicy: { mode: "auto" } as any,
|
||||||
|
additionalWritableRoots: [],
|
||||||
onItem: (i) => items.push(i),
|
onItem: (i) => items.push(i),
|
||||||
onLoading: () => {},
|
onLoading: () => {},
|
||||||
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
getCommandConfirmation: async () => ({ review: "yes" } as any),
|
||||||
|
|||||||
@@ -53,10 +53,12 @@ describe("handleExecCommand – invalid executable", () => {
|
|||||||
const policy = { mode: "auto" } as any;
|
const policy = { mode: "auto" } as any;
|
||||||
const getConfirmation = async () => ({ review: "yes" } as any);
|
const getConfirmation = async () => ({ review: "yes" } as any);
|
||||||
|
|
||||||
|
const additionalWritableRoots: Array<string> = [];
|
||||||
const { outputText, metadata } = await handleExecCommand(
|
const { outputText, metadata } = await handleExecCommand(
|
||||||
execInput,
|
execInput,
|
||||||
config,
|
config,
|
||||||
policy,
|
policy,
|
||||||
|
additionalWritableRoots,
|
||||||
getConfirmation,
|
getConfirmation,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user