Refactor (#109)
This commit is contained in:
committed by
pullfrog[bot]
parent
101c666610
commit
69b9b96ddd
+8
-7
@@ -21,16 +21,17 @@ const claudeEffortModels: Record<Effort, string> = {
|
||||
// This approach could replace model selection if effort proves effective for controlling capability.
|
||||
|
||||
/**
|
||||
* Build disallowedTools list from ToolPermissions.
|
||||
* Build disallowedTools list from payload permissions.
|
||||
*/
|
||||
function buildDisallowedTools(ctx: AgentRunContext): string[] {
|
||||
const disallowed: string[] = [];
|
||||
if (ctx.tools.web === "disabled") disallowed.push("WebFetch");
|
||||
if (ctx.tools.search === "disabled") disallowed.push("WebSearch");
|
||||
if (ctx.tools.write === "disabled") disallowed.push("Write");
|
||||
if (ctx.payload.web === "disabled") disallowed.push("WebFetch");
|
||||
if (ctx.payload.search === "disabled") disallowed.push("WebSearch");
|
||||
if (ctx.payload.write === "disabled") disallowed.push("Write");
|
||||
// both "disabled" and "restricted" block native bash
|
||||
// "restricted" means use MCP bash tool instead
|
||||
if (ctx.tools.bash !== "enabled") disallowed.push("Bash");
|
||||
const bash = ctx.payload.bash;
|
||||
if (bash !== "enabled") disallowed.push("Bash");
|
||||
return disallowed;
|
||||
}
|
||||
|
||||
@@ -51,8 +52,8 @@ export const claude = agent({
|
||||
const cliPath = await installClaude();
|
||||
|
||||
// select model based on effort level
|
||||
const model = claudeEffortModels[ctx.effort];
|
||||
log.info(`» using model: ${model} (effort: ${ctx.effort})`);
|
||||
const model = claudeEffortModels[ctx.payload.effort];
|
||||
log.info(`» using model: ${model} (effort: ${ctx.payload.effort})`);
|
||||
|
||||
// build disallowedTools based on tool permissions
|
||||
const disallowedTools = buildDisallowedTools(ctx);
|
||||
|
||||
+14
-10
@@ -42,8 +42,9 @@ function writeCodexConfig(ctx: AgentRunContext): string {
|
||||
// build features section for tool control
|
||||
// disable native shell if bash is "disabled" or "restricted"
|
||||
// when "restricted", agent uses MCP bash tool which filters secrets
|
||||
const bash = ctx.payload.bash;
|
||||
const features: string[] = [];
|
||||
if (ctx.tools.bash !== "enabled") {
|
||||
if (bash !== "enabled") {
|
||||
features.push("shell_command_tool = false");
|
||||
features.push("unified_exec = false");
|
||||
}
|
||||
@@ -58,9 +59,7 @@ ${mcpServerSections.join("\n\n")}
|
||||
`.trim() + "\n"
|
||||
);
|
||||
|
||||
log.info(
|
||||
`» Codex config written to ${configPath} (shell: ${ctx.tools.bash === "enabled" ? "enabled" : "disabled"})`
|
||||
);
|
||||
log.info(`» Codex config written to ${configPath} (shell: ${bash === "enabled" ? "enabled" : "disabled"})`);
|
||||
|
||||
return codexDir;
|
||||
}
|
||||
@@ -90,16 +89,21 @@ export const codex = agent({
|
||||
process.env.CODEX_HOME = codexDir;
|
||||
|
||||
// get model and reasoning effort based on effort level
|
||||
const model = codexModel[ctx.effort];
|
||||
const modelReasoningEffort = codexReasoningEffort[ctx.effort];
|
||||
const model = codexModel[ctx.payload.effort];
|
||||
const modelReasoningEffort = codexReasoningEffort[ctx.payload.effort];
|
||||
log.info(`» using model: ${model}`);
|
||||
if (modelReasoningEffort) {
|
||||
log.info(`» using modelReasoningEffort: ${modelReasoningEffort}`);
|
||||
}
|
||||
|
||||
// Configure Codex
|
||||
const apiKey = process.env.OPENAI_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error("OPENAI_API_KEY is required for codex agent");
|
||||
}
|
||||
|
||||
const codexOptions: CodexOptions = {
|
||||
apiKey: ctx.apiKey,
|
||||
apiKey,
|
||||
codexPathOverride: cliPath,
|
||||
};
|
||||
|
||||
@@ -110,11 +114,11 @@ export const codex = agent({
|
||||
model,
|
||||
approvalPolicy: "never" as const,
|
||||
// write: "disabled" → read-only sandbox, otherwise full access for git ops
|
||||
sandboxMode: ctx.tools.write === "disabled" ? "read-only" : "danger-full-access",
|
||||
sandboxMode: ctx.payload.write === "disabled" ? "read-only" : "danger-full-access",
|
||||
// web: controls network access
|
||||
networkAccessEnabled: ctx.tools.web !== "disabled",
|
||||
networkAccessEnabled: ctx.payload.web !== "disabled",
|
||||
// search: controls web search
|
||||
webSearchEnabled: ctx.tools.search !== "disabled",
|
||||
webSearchEnabled: ctx.payload.search !== "disabled",
|
||||
...(modelReasoningEffort && { modelReasoningEffort }),
|
||||
};
|
||||
|
||||
|
||||
+11
-10
@@ -116,19 +116,19 @@ export const cursor = agent({
|
||||
if (projectConfig.model) {
|
||||
log.info(`» using model from project .cursor/cli.json: ${projectConfig.model}`);
|
||||
} else {
|
||||
modelOverride = cursorEffortModels[ctx.effort];
|
||||
modelOverride = cursorEffortModels[ctx.payload.effort];
|
||||
}
|
||||
} catch {
|
||||
modelOverride = cursorEffortModels[ctx.effort];
|
||||
modelOverride = cursorEffortModels[ctx.payload.effort];
|
||||
}
|
||||
} else {
|
||||
modelOverride = cursorEffortModels[ctx.effort];
|
||||
modelOverride = cursorEffortModels[ctx.payload.effort];
|
||||
}
|
||||
|
||||
if (modelOverride) {
|
||||
log.info(`» using model: ${modelOverride}, effort=${ctx.effort}`);
|
||||
log.info(`» using model: ${modelOverride}, effort=${ctx.payload.effort}`);
|
||||
} else if (!existsSync(projectCliConfigPath)) {
|
||||
log.info(`» using default model, effort=${ctx.effort}`);
|
||||
log.info(`» using default model, effort=${ctx.payload.effort}`);
|
||||
}
|
||||
|
||||
// track logged model_call_ids to avoid duplicates
|
||||
@@ -354,22 +354,23 @@ function configureCursorTools(ctx: AgentRunContext): void {
|
||||
mkdirSync(cursorConfigDir, { recursive: true });
|
||||
|
||||
// build deny list based on tool permissions
|
||||
const bash = ctx.payload.bash;
|
||||
const deny: string[] = [];
|
||||
if (ctx.tools.search === "disabled") deny.push("WebSearch");
|
||||
if (ctx.tools.write === "disabled") deny.push("Write(**)");
|
||||
if (ctx.payload.search === "disabled") deny.push("WebSearch");
|
||||
if (ctx.payload.write === "disabled") deny.push("Write(**)");
|
||||
// both "disabled" and "restricted" block native shell
|
||||
if (ctx.tools.bash !== "enabled") deny.push("Shell(*)");
|
||||
if (bash !== "enabled") deny.push("Shell(*)");
|
||||
|
||||
const config: CursorCliConfig = {
|
||||
permissions: {
|
||||
allow: ctx.tools.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
|
||||
allow: ctx.payload.write === "disabled" ? ["Read(**)"] : ["Read(**)", "Write(**)"],
|
||||
deny,
|
||||
},
|
||||
};
|
||||
|
||||
// web: "disabled" requires sandbox with network blocking
|
||||
// sandbox.networkAccess: "allowlist" blocks network in shell subprocesses via seatbelt
|
||||
if (ctx.tools.web === "disabled") {
|
||||
if (ctx.payload.web === "disabled") {
|
||||
config.sandbox = {
|
||||
mode: "enabled",
|
||||
networkAccess: "allowlist",
|
||||
|
||||
+8
-7
@@ -176,8 +176,8 @@ export const gemini = agent({
|
||||
|
||||
const model = configureGeminiSettings(ctx);
|
||||
|
||||
if (!ctx.apiKey) {
|
||||
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
||||
if (!process.env.GOOGLE_API_KEY && !process.env.GEMINI_API_KEY) {
|
||||
throw new Error("GOOGLE_API_KEY or GEMINI_API_KEY is required for gemini agent");
|
||||
}
|
||||
|
||||
// build CLI args - --yolo for auto-approval
|
||||
@@ -278,7 +278,7 @@ export const gemini = agent({
|
||||
* See: https://github.com/google-gemini/gemini-cli/blob/main/docs/get-started/configuration.md
|
||||
*/
|
||||
function configureGeminiSettings(ctx: AgentRunContext): string {
|
||||
const { model, thinkingLevel } = geminiEffortConfig[ctx.effort];
|
||||
const { model, thinkingLevel } = geminiEffortConfig[ctx.payload.effort];
|
||||
log.info(`» using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
||||
|
||||
const realHome = homedir();
|
||||
@@ -319,11 +319,12 @@ function configureGeminiSettings(ctx: AgentRunContext): string {
|
||||
};
|
||||
|
||||
// build tools.exclude based on permissions (v0.3.0+ nested format)
|
||||
const bash = ctx.payload.bash;
|
||||
const exclude: string[] = [];
|
||||
if (ctx.tools.bash !== "enabled") exclude.push("run_shell_command");
|
||||
if (ctx.tools.write === "disabled") exclude.push("write_file");
|
||||
if (ctx.tools.web === "disabled") exclude.push("web_fetch");
|
||||
if (ctx.tools.search === "disabled") exclude.push("google_web_search");
|
||||
if (bash !== "enabled") exclude.push("run_shell_command");
|
||||
if (ctx.payload.write === "disabled") exclude.push("write_file");
|
||||
if (ctx.payload.web === "disabled") exclude.push("web_fetch");
|
||||
if (ctx.payload.search === "disabled") exclude.push("google_web_search");
|
||||
|
||||
// merge with existing settings, overwriting mcpServers and modelConfig
|
||||
const newSettings: Record<string, unknown> = {
|
||||
|
||||
+4
-3
@@ -186,10 +186,11 @@ function configureOpenCode(ctx: AgentRunContext): void {
|
||||
|
||||
// build permission object based on tool permissions
|
||||
// note: OpenCode has no built-in web search tool
|
||||
const bash = ctx.payload.bash;
|
||||
const permission = {
|
||||
edit: ctx.tools.write === "disabled" ? "deny" : "allow",
|
||||
bash: ctx.tools.bash !== "enabled" ? "deny" : "allow",
|
||||
webfetch: ctx.tools.web === "disabled" ? "deny" : "allow",
|
||||
edit: ctx.payload.write === "disabled" ? "deny" : "allow",
|
||||
bash: bash !== "enabled" ? "deny" : "allow",
|
||||
webfetch: ctx.payload.web === "disabled" ? "deny" : "allow",
|
||||
doom_loop: "allow",
|
||||
external_directory: "allow",
|
||||
};
|
||||
|
||||
+9
-25
@@ -1,6 +1,7 @@
|
||||
import type { show } from "@ark/util";
|
||||
import { type AgentManifest, type AgentName, agentsManifest, type Effort } from "../external.ts";
|
||||
import { type AgentManifest, type AgentName, agentsManifest } from "../external.ts";
|
||||
import { log } from "../utils/cli.ts";
|
||||
import type { ResolvedPayload } from "../utils/payload.ts";
|
||||
|
||||
/**
|
||||
* Result returned by agent execution
|
||||
@@ -12,44 +13,27 @@ export interface AgentResult {
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool permission levels
|
||||
*/
|
||||
export type ToolPermission = "disabled" | "enabled";
|
||||
export type BashPermission = "disabled" | "restricted" | "enabled";
|
||||
|
||||
/**
|
||||
* Granular tool permissions for agents
|
||||
*/
|
||||
export interface ToolPermissions {
|
||||
web: ToolPermission;
|
||||
search: ToolPermission;
|
||||
write: ToolPermission;
|
||||
bash: BashPermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal context passed to agent.run()
|
||||
*/
|
||||
export interface AgentRunContext {
|
||||
effort: Effort;
|
||||
tools: ToolPermissions;
|
||||
payload: ResolvedPayload;
|
||||
mcpServerUrl: string;
|
||||
tmpdir: string;
|
||||
instructions: string;
|
||||
apiKey: string;
|
||||
apiKeys: Record<string, string>;
|
||||
}
|
||||
|
||||
export const agent = <const input extends AgentInput>(input: input): defineAgent<input> => {
|
||||
return {
|
||||
...input,
|
||||
run: async (ctx: AgentRunContext): Promise<AgentResult> => {
|
||||
log.info(`» running ${input.name} with effort=${ctx.effort}...`);
|
||||
const bash = ctx.payload.bash;
|
||||
const web = ctx.payload.web;
|
||||
const search = ctx.payload.search;
|
||||
const write = ctx.payload.write;
|
||||
log.info(`» running ${input.name} with effort=${ctx.payload.effort}...`);
|
||||
log.box(ctx.instructions, { title: "Instructions" });
|
||||
log.info(
|
||||
`» tool permissions: web=${ctx.tools.web}, search=${ctx.tools.search}, write=${ctx.tools.write}, bash=${ctx.tools.bash}`
|
||||
);
|
||||
log.info(`» tool permissions: web=${web}, search=${search}, write=${write}, bash=${bash}`);
|
||||
return input.run(ctx);
|
||||
},
|
||||
...agentsManifest[input.name],
|
||||
|
||||
Reference in New Issue
Block a user