mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 08:03:46 +00:00
161 lines
4.7 KiB
TypeScript
161 lines
4.7 KiB
TypeScript
import { createInterface } from "node:readline/promises";
|
|
import { stdin as input, stdout as output } from "node:process";
|
|
import {
|
|
SandboxAgent,
|
|
type PermissionReply,
|
|
type SessionPermissionRequest,
|
|
} from "sandbox-agent";
|
|
|
|
const agent = (process.env.PERMISSIONS_AGENT?.trim() || "claude").toLowerCase();
|
|
const requestedPermissionMode = process.env.PERMISSION_MODE?.trim();
|
|
const autoReply = parsePermissionReply(process.env.PERMISSION_REPLY);
|
|
const promptText =
|
|
process.env.PERMISSION_PROMPT?.trim() ||
|
|
`Create ./permission-example.txt with the text 'hello from the ${agent} permissions example'.`;
|
|
|
|
const sdk = await SandboxAgent.start({
|
|
spawn: {
|
|
enabled: true,
|
|
log: "inherit",
|
|
},
|
|
});
|
|
|
|
try {
|
|
await sdk.installAgent(agent);
|
|
|
|
const agents = await sdk.listAgents({ config: true });
|
|
const selectedAgent = agents.agents.find((entry) => entry.id === agent);
|
|
const configOptions = Array.isArray(selectedAgent?.configOptions)
|
|
? (selectedAgent.configOptions as Array<{ category?: string; currentValue?: string; options?: unknown[] }>)
|
|
: [];
|
|
const modeOption = configOptions.find((option) => option.category === "mode");
|
|
const availableModes = extractOptionValues(modeOption);
|
|
const permissionMode = requestedPermissionMode || modeOption?.currentValue || availableModes[0] || "default";
|
|
|
|
console.log(`Agent: ${agent}`);
|
|
console.log(`Permission mode: ${permissionMode}`);
|
|
if (availableModes.length > 0) {
|
|
console.log(`Available modes: ${availableModes.join(", ")}`);
|
|
}
|
|
console.log(`Working directory: ${process.cwd()}`);
|
|
console.log(`Prompt: ${promptText}`);
|
|
if (autoReply) {
|
|
console.log(`Automatic permission reply: ${autoReply}`);
|
|
} else {
|
|
console.log("Interactive permission replies enabled.");
|
|
}
|
|
|
|
const session = await sdk.createSession({
|
|
agent,
|
|
permissionMode,
|
|
sessionInit: {
|
|
cwd: process.cwd(),
|
|
mcpServers: [],
|
|
},
|
|
});
|
|
|
|
const rl = autoReply
|
|
? null
|
|
: createInterface({
|
|
input,
|
|
output,
|
|
});
|
|
|
|
session.onPermissionRequest((request: SessionPermissionRequest) => {
|
|
void handlePermissionRequest(session, request, autoReply, rl);
|
|
});
|
|
|
|
const response = await session.prompt([{ type: "text", text: promptText }]);
|
|
console.log(`Prompt finished with stopReason=${response.stopReason}`);
|
|
|
|
await rl?.close();
|
|
} finally {
|
|
await sdk.dispose();
|
|
}
|
|
|
|
async function handlePermissionRequest(
|
|
session: {
|
|
replyPermission(permissionId: string, reply: PermissionReply): Promise<void>;
|
|
},
|
|
request: SessionPermissionRequest,
|
|
auto: PermissionReply | null,
|
|
rl: ReturnType<typeof createInterface> | null,
|
|
): Promise<void> {
|
|
const reply = auto ?? (await promptForReply(request, rl));
|
|
console.log(`Permission ${reply}: ${request.toolCall.title ?? request.toolCall.toolCallId}`);
|
|
await session.replyPermission(request.id, reply);
|
|
}
|
|
|
|
async function promptForReply(
|
|
request: SessionPermissionRequest,
|
|
rl: ReturnType<typeof createInterface> | null,
|
|
): Promise<PermissionReply> {
|
|
if (!rl) {
|
|
return "reject";
|
|
}
|
|
|
|
const title = request.toolCall.title ?? request.toolCall.toolCallId;
|
|
const available = request.availableReplies;
|
|
console.log("");
|
|
console.log(`Permission request: ${title}`);
|
|
console.log(`Available replies: ${available.join(", ")}`);
|
|
const answer = (await rl.question("Reply [once|always|reject]: ")).trim().toLowerCase();
|
|
const parsed = parsePermissionReply(answer);
|
|
if (parsed && available.includes(parsed)) {
|
|
return parsed;
|
|
}
|
|
|
|
console.log("Invalid reply, defaulting to reject.");
|
|
return "reject";
|
|
}
|
|
|
|
function extractOptionValues(option: { options?: unknown[] } | undefined): string[] {
|
|
if (!option?.options) {
|
|
return [];
|
|
}
|
|
|
|
const values: string[] = [];
|
|
for (const entry of option.options) {
|
|
if (!entry || typeof entry !== "object") {
|
|
continue;
|
|
}
|
|
const value = "value" in entry && typeof entry.value === "string" ? entry.value : null;
|
|
if (value) {
|
|
values.push(value);
|
|
continue;
|
|
}
|
|
if (!("options" in entry) || !Array.isArray(entry.options)) {
|
|
continue;
|
|
}
|
|
for (const nested of entry.options) {
|
|
if (!nested || typeof nested !== "object") {
|
|
continue;
|
|
}
|
|
const nestedValue =
|
|
"value" in nested && typeof nested.value === "string" ? nested.value : null;
|
|
if (nestedValue) {
|
|
values.push(nestedValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
return [...new Set(values)];
|
|
}
|
|
|
|
function parsePermissionReply(value: string | undefined): PermissionReply | null {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
|
|
switch (value.trim().toLowerCase()) {
|
|
case "once":
|
|
return "once";
|
|
case "always":
|
|
return "always";
|
|
case "reject":
|
|
case "deny":
|
|
return "reject";
|
|
default:
|
|
return null;
|
|
}
|
|
}
|