Skip to content

Commit 9bf8dc9

Browse files
committed
feat: agent-server improvements
1 parent ad2dbc8 commit 9bf8dc9

9 files changed

Lines changed: 67 additions & 4 deletions

File tree

apps/code/src/renderer/features/environments/components/EnvironmentSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function EnvironmentSelector({
4343
setOpen(false);
4444
useSettingsDialogStore
4545
.getState()
46-
.open("cloud-environments", { repoPath: repoPath ?? undefined });
46+
.open("environments", { repoPath: repoPath ?? undefined });
4747
};
4848

4949
const triggerContent = (

apps/code/src/renderer/features/settings/components/SettingsDialog.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Code,
1111
Folder,
1212
GearSix,
13+
HardDrives,
1314
Keyboard,
1415
Palette,
1516
Plugs,
@@ -25,6 +26,7 @@ import { AccountSettings } from "./sections/AccountSettings";
2526
import { AdvancedSettings } from "./sections/AdvancedSettings";
2627
import { ClaudeCodeSettings } from "./sections/ClaudeCodeSettings";
2728
import { CloudEnvironmentsSettings } from "./sections/CloudEnvironmentsSettings";
29+
import { EnvironmentsSettings } from "./sections/environments/EnvironmentsSettings";
2830
import { GeneralSettings } from "./sections/GeneralSettings";
2931
import { McpServersSettings } from "./sections/McpServersSettings";
3032
import { PersonalizationSettings } from "./sections/PersonalizationSettings";
@@ -46,6 +48,11 @@ const SIDEBAR_ITEMS: SidebarItem[] = [
4648
{ id: "account", label: "Account", icon: <User size={16} /> },
4749
{ id: "workspaces", label: "Workspaces", icon: <Folder size={16} /> },
4850
{ id: "worktrees", label: "Worktrees", icon: <TreeStructure size={16} /> },
51+
{
52+
id: "environments",
53+
label: "Environments",
54+
icon: <HardDrives size={16} />,
55+
},
4956
{
5057
id: "cloud-environments",
5158
label: "Cloud environments",
@@ -74,6 +81,7 @@ const CATEGORY_TITLES: Record<SettingsCategory, string> = {
7481
account: "Account",
7582
workspaces: "Workspaces",
7683
worktrees: "Worktrees",
84+
environments: "Environments",
7785
"cloud-environments": "Cloud environments",
7886
personalization: "Personalization",
7987
"claude-code": "Claude Code",
@@ -90,6 +98,7 @@ const CATEGORY_COMPONENTS: Record<SettingsCategory, React.ComponentType> = {
9098
account: AccountSettings,
9199
workspaces: WorkspacesSettings,
92100
worktrees: WorktreesSettings,
101+
environments: EnvironmentsSettings,
93102
"cloud-environments": CloudEnvironmentsSettings,
94103
personalization: PersonalizationSettings,
95104
"claude-code": ClaudeCodeSettings,

apps/code/src/renderer/features/settings/stores/settingsDialogStore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type SettingsCategory =
55
| "account"
66
| "workspaces"
77
| "worktrees"
8+
| "environments"
89
| "cloud-environments"
910
| "personalization"
1011
| "claude-code"

packages/agent/src/adapters/claude/claude-agent.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
817817
cwd,
818818
mcpServers,
819819
permissionMode,
820-
canUseTool: this.createCanUseTool(sessionId),
820+
canUseTool: this.createCanUseTool(sessionId, meta?.allowedDomains),
821821
logger: this.logger,
822822
systemPrompt,
823823
userProvidedOptions: meta?.claudeCode?.options,
@@ -978,7 +978,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
978978
return { sessionId, modes, models, configOptions };
979979
}
980980

981-
private createCanUseTool(sessionId: string): CanUseTool {
981+
private createCanUseTool(
982+
sessionId: string,
983+
allowedDomains?: string[],
984+
): CanUseTool {
982985
return async (toolName, toolInput, { suggestions, toolUseID, signal }) =>
983986
canUseTool({
984987
session: this.session,
@@ -993,6 +996,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
993996
logger: this.logger,
994997
updateConfigOption: (configId: string, value: string) =>
995998
this.updateConfigOption(configId, value),
999+
allowedDomains,
9961000
});
9971001
}
9981002

packages/agent/src/adapters/claude/permissions/permission-handlers.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ interface ToolHandlerContext {
4949
fileContentCache: { [key: string]: string };
5050
logger: Logger;
5151
updateConfigOption: (configId: string, value: string) => Promise<void>;
52+
allowedDomains?: string[];
5253
}
5354

5455
async function emitToolDenial(
@@ -422,10 +423,43 @@ function handlePlanFileException(
422423
};
423424
}
424425

426+
function extractDomainFromUrl(url: string): string | null {
427+
try {
428+
return new URL(url).hostname;
429+
} catch {
430+
return null;
431+
}
432+
}
433+
434+
function isDomainAllowed(hostname: string, allowedDomains: string[]): boolean {
435+
return allowedDomains.some((pattern) => {
436+
if (pattern.startsWith("*.")) {
437+
const suffix = pattern.slice(1); // ".example.com"
438+
return hostname === pattern.slice(2) || hostname.endsWith(suffix);
439+
}
440+
return hostname === pattern;
441+
});
442+
}
443+
425444
export async function canUseTool(
426445
context: ToolHandlerContext,
427446
): Promise<ToolPermissionResult> {
428-
const { toolName, toolInput, session } = context;
447+
const { toolName, toolInput, session, allowedDomains } = context;
448+
449+
// Enforce domain allowlist for web tools
450+
if (allowedDomains && allowedDomains.length > 0) {
451+
if (toolName === "WebFetch" || toolName === "WebSearch") {
452+
const url = toolInput.url as string | undefined;
453+
if (url) {
454+
const hostname = extractDomainFromUrl(url);
455+
if (hostname && !isDomainAllowed(hostname, allowedDomains)) {
456+
const message = `Domain "${hostname}" is not in the allowed list: ${allowedDomains.join(", ")}`;
457+
await emitToolDenial(context, message);
458+
return { behavior: "deny", message, interrupt: false };
459+
}
460+
}
461+
}
462+
}
429463

430464
if (isToolAllowedForMode(toolName, session.permissionMode)) {
431465
return {

packages/agent/src/adapters/claude/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export type NewSessionMeta = {
107107
permissionMode?: string;
108108
persistence?: { taskId?: string; runId?: string; logUrl?: string };
109109
additionalRoots?: string[];
110+
allowedDomains?: string[];
110111
claudeCode?: {
111112
options?: Options;
112113
};

packages/agent/src/server/agent-server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ export class AgentServer {
707707
sessionId: payload.run_id,
708708
taskRunId: payload.run_id,
709709
systemPrompt: this.buildSessionSystemPrompt(prUrl),
710+
allowedDomains: this.config.allowedDomains,
710711
...(this.config.claudeCode?.plugins?.length && {
711712
claudeCode: {
712713
options: {

packages/agent/src/server/bin.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ program
7979
"--claudeCodeConfig <json>",
8080
"Claude Code config as JSON (systemPrompt, systemPromptAppend, plugins)",
8181
)
82+
.option(
83+
"--allowedDomains <domains>",
84+
"Comma-separated list of domains allowed for web tools (WebFetch, WebSearch)",
85+
)
8286
.action(async (options) => {
8387
const envResult = envSchema.safeParse(process.env);
8488

@@ -105,6 +109,13 @@ program
105109
"--claudeCodeConfig",
106110
);
107111

112+
const allowedDomains = options.allowedDomains
113+
? options.allowedDomains
114+
.split(",")
115+
.map((d: string) => d.trim())
116+
.filter(Boolean)
117+
: undefined;
118+
108119
const server = new AgentServer({
109120
port: parseInt(options.port, 10),
110121
jwtPublicKey: env.JWT_PUBLIC_KEY,
@@ -118,6 +129,7 @@ program
118129
mcpServers,
119130
baseBranch: options.baseBranch,
120131
claudeCode,
132+
allowedDomains,
121133
});
122134

123135
process.on("SIGINT", async () => {

packages/agent/src/server/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ export interface AgentServerConfig {
2222
mcpServers?: RemoteMcpServer[];
2323
baseBranch?: string;
2424
claudeCode?: ClaudeCodeConfig;
25+
allowedDomains?: string[];
2526
}

0 commit comments

Comments
 (0)