Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/agent/src/adapters/claude/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ export const AGENT_TOOLS: Set<string> = new Set([
"Skill",
]);

export const RESEARCH_BACKGROUND_TOOLS: string[] = [
...READ_TOOLS,
...SEARCH_TOOLS,
...AGENT_TOOLS,
"EnterPlanMode",
"ExitPlanMode",
];

const BASE_ALLOWED_TOOLS = [
...READ_TOOLS,
...SEARCH_TOOLS,
Expand Down
36 changes: 32 additions & 4 deletions packages/agent/src/server/agent-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
type InProcessAcpConnection,
} from "../adapters/acp-connection";
import { selectRecentTurns } from "../adapters/claude/session/jsonl-hydration";
import { RESEARCH_BACKGROUND_TOOLS } from "../adapters/claude/tools";
import { PostHogAPIClient } from "../posthog-api";
import {
type ConversationTurn,
Expand Down Expand Up @@ -633,18 +634,24 @@ export class AgentServer {
this.detectedPrUrl = prUrl;
}

const claudeCodeOptions: Record<string, unknown> = {};
if (this.config.claudeCode?.plugins?.length) {
claudeCodeOptions.plugins = this.config.claudeCode.plugins;
}
if (this.config.toolsPreset === "research_background_agent") {
claudeCodeOptions.tools = RESEARCH_BACKGROUND_TOOLS;
}

const sessionResponse = await clientConnection.newSession({
cwd: this.config.repositoryPath ?? "/tmp/workspace",
mcpServers: this.config.mcpServers ?? [],
_meta: {
sessionId: payload.run_id,
taskRunId: payload.run_id,
systemPrompt: this.buildSessionSystemPrompt(prUrl),
...(this.config.claudeCode?.plugins?.length && {
...(Object.keys(claudeCodeOptions).length > 0 && {
claudeCode: {
options: {
plugins: this.config.claudeCode.plugins,
},
options: claudeCodeOptions,
},
}),
},
Expand Down Expand Up @@ -1095,6 +1102,27 @@ Important:
options: params.options,
});

// Defense-in-depth: deny tools not in the restricted allowlist
if (this.config.toolsPreset === "research_background_agent") {
const meta = params.toolCall?._meta as
| Record<string, unknown>
| undefined;
const toolName =
(meta?.codeToolKind as string) ?? (meta?.toolName as string);
if (toolName && !RESEARCH_BACKGROUND_TOOLS.includes(toolName)) {
this.logger.warn(
"Denied restricted tool in research_background_agent mode",
{ toolName },
);
return {
outcome: { outcome: "cancelled" as const },
_meta: {
message: `Tool "${toolName}" is not available in research mode. You can only use read, search, and planning tools.`,
},
};
}
}

const allowOption = params.options.find(
(o) => o.kind === "allow_once" || o.kind === "allow_always",
);
Expand Down
6 changes: 6 additions & 0 deletions packages/agent/src/server/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ program
"--claudeCodeConfig <json>",
"Claude Code config as JSON (systemPrompt, systemPromptAppend, plugins)",
)
.option(
"--toolsPreset <preset>",
"Tools preset: default or research_background_agent",
"default",
)
.action(async (options) => {
const envResult = envSchema.safeParse(process.env);

Expand Down Expand Up @@ -118,6 +123,7 @@ program
mcpServers,
baseBranch: options.baseBranch,
claudeCode,
toolsPreset: options.toolsPreset,
});

process.on("SIGINT", async () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/agent/src/server/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { AgentMode } from "../types";
import type { RemoteMcpServer } from "./schemas";

export type ToolsPreset = "default" | "research_background_agent";

export interface ClaudeCodeConfig {
systemPrompt?:
| string
Expand All @@ -22,4 +24,5 @@ export interface AgentServerConfig {
mcpServers?: RemoteMcpServer[];
baseBranch?: string;
claudeCode?: ClaudeCodeConfig;
toolsPreset?: ToolsPreset;
}
Loading