Skip to content
Draft
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
47 changes: 46 additions & 1 deletion apps/desktop/src/agents/soc/agent-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,51 @@ Secure and monitor network infrastructure. You:
- CLI commands on network devices require human approval
- Show/read commands are safe
- When blocking IPs, apply across ALL connected firewall devices
- Document the IOC and reason for every block`,
- Document the IOC and reason for every block`,
};

export const CYBER_SECURITY_REVIEWER: AgentRole = {
id: 'cyber-security-reviewer',
name: 'Cyber Security Reviewer',
description: 'Second-opinion coding reviewer focused on secure coding, practical enhancements, and risk-based remediation guidance.',
domain: 'threat-intel',
permissionLevel: 'observe',
autoActivateOn: ['github', 'gitlab', 'bitbucket'],
allowedTools: [
'repo_list_files',
'repo_fetch_file',
'repo_search_code',
'repo_get_commit',
'repo_create_issue',
],
blockedTools: [],
escalatesTo: 'incident-commander',
requiresApprovalFor: ['repo_create_issue'],
model: 'claude-sonnet-4-6',
maxActionsPerIncident: 40,
cooldownMs: 2000,
enabled: true,
systemPrompt: `You are the CrowByte Cyber Security Reviewer — a second-opinion coding security reviewer.

## Your Mission
Review code for security risks and recommend practical enhancements.
1. Find vulnerabilities and insecure patterns
2. Explain impact in plain language
3. Provide concrete secure-code fixes
4. Suggest architecture and process improvements that reduce repeat risk

## Review Scope
- OWASP Top 10 and common CWE weaknesses
- Secrets exposure, auth/authz flaws, input validation, injection risks
- Cryptography misuse, insecure defaults, and unsafe dependency usage
- Logging, error handling, and data protection gaps

## Rules
- Prioritize findings by exploitability and business impact
- Include file paths, line numbers, and minimally invasive fixes
- Prefer secure-by-default recommendations
- Keep guidance actionable for developers and reviewers
- Ask clarifying questions when threat model or runtime context is unclear`,
};

// ─── Full Agent Registry ─────────────────────────────────────────────────────
Expand All @@ -860,6 +904,7 @@ export const AGENT_REGISTRY: AgentRole[] = [
INFRA_CONTAINER_AGENT,
CLOUD_SECURITY_AGENT,
NETWORK_AGENT,
CYBER_SECURITY_REVIEWER,
];

export function getAgentById(id: string): AgentRole | undefined {
Expand Down
96 changes: 72 additions & 24 deletions apps/desktop/src/pages/AgentBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ interface Tool {
endpoint: string;
}

const MAX_DISPLAYED_AGENTS = 5;

const AgentBuilder = () => {
const { toast } = useToast();
const [activeTab, setActiveTab] = useState("configure");
Expand Down Expand Up @@ -51,19 +53,34 @@ const AgentBuilder = () => {
loadAgents();
}, []);

const loadAgents = async () => {
// TODO: Enable when custom_agents table exists in Supabase
// CREATE TABLE custom_agents (id uuid PK, user_id uuid, name text, description text,
// system_prompt text, model text, category text, example_prompts text[], capabilities jsonb,
// enable_web_search bool, enable_code_execution bool, enable_file_upload bool,
// status text DEFAULT 'active', created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now());
//
// Uncomment when table is created:
// try {
// const data = await customAgentsService.getAgents();
// setAgents(data);
// } catch { /* silent */ }
};
const loadAgents = async () => {
try {
const data = await customAgentsService.getAgents();
setAgents(data);
} catch (error) {
console.warn('[AgentBuilder] Failed to load agents:', error);
}
};

const loadAgentIntoBuilder = (agent: CustomAgent) => {
setAgentName(agent.name);
setDescription(agent.description || "");
setInstructions(agent.system_prompt);
setSelectedModel(agent.model);
setCategory(agent.category || "security");
setConversationStarters(agent.example_prompts.length > 0 ? agent.example_prompts : [""]);
setCapabilities({
webSearch: agent.enable_web_search,
codeExecution: agent.enable_code_execution,
mcpTools: agent.enable_mcp,
fileAccess: agent.enable_file_access,
});
setActiveTab("configure");
toast({
title:"Agent Loaded",
description:`Loaded ${agent.name} into builder`,
});
};

const handleSaveAgent = async () => {
try {
Expand Down Expand Up @@ -294,18 +311,49 @@ const AgentBuilder = () => {
</ul>
</Card>

<div className="space-y-3">
<Input
placeholder="Describe your AI agent..."
className="bg-background border-border terminal-text"
<div className="space-y-3">
<Input
placeholder="Describe your AI agent..."
className="bg-background border-border terminal-text"
/>
<Button className="w-full bg-primary hover:bg-primary/90 text-primary-foreground">
Generate Configuration
</Button>
</div>
</div>
</ScrollArea>
</TabsContent>
<Button className="w-full bg-primary hover:bg-primary/90 text-primary-foreground">
Generate Configuration
</Button>
</div>

<Card className="p-4 ring-1 ring-white/[0.06]">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-medium text-white">Saved Agents</h3>
<Badge variant="secondary">{agents.length}</Badge>
</div>
{agents.length === 0 ? (
<p className="text-sm text-muted-foreground">
No saved agents yet. Configure and save one to reuse it here.
</p>
) : (
<div className="space-y-2">
{agents.slice(0, MAX_DISPLAYED_AGENTS).map((agent) => (
<div key={agent.id} className="flex items-center justify-between rounded-md bg-background/50 px-3 py-2">
<div className="min-w-0">
<p className="text-sm text-white truncate">{agent.name}</p>
<p className="text-xs text-muted-foreground truncate">{agent.category || "general"} • {agent.model}</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => loadAgentIntoBuilder(agent)}
className="border-border text-white hover:bg-primary/10"
>
Load
</Button>
</div>
))}
</div>
)}
</Card>
</div>
</ScrollArea>
</TabsContent>

<TabsContent value="configure" className="flex-1 mt-0">
<ScrollArea className="h-full p-6">
Expand Down
21 changes: 20 additions & 1 deletion apps/desktop/src/services/claude-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ const CLAUDE_MODELS: ClaudeModel[] = [
{ id: 'haiku', name: 'Claude Haiku 4.5', provider: 'Anthropic' },
];

function formatStreamError(error: string): string {
const normalized = error.trim();
const lower = normalized.toLowerCase();

const copilotRateLimitMatch = normalized.match(/please try again in\s+(.+)$/i);
if (lower.includes('copilot') && lower.includes('rate limit') && copilotRateLimitMatch) {
const waitTime = copilotRateLimitMatch[1].trim();
return waitTime
? `Copilot rate limit reached. Please try again in ${waitTime}.`
: 'Copilot rate limit reached. Please wait a few minutes and try again.';
}

if (lower.includes('rate limit') || lower.includes('too many requests') || lower.includes('http 429')) {
return 'Rate limit reached. Please wait a few minutes and try again.';
}

return normalized || 'An unexpected error occurred. Please try again.';
}

class ClaudeProvider {
private currentModel = 'sonnet';
private sessionId: string | null = null;
Expand Down Expand Up @@ -82,7 +101,7 @@ class ClaudeProvider {

window.electronAPI!.onClaudeStreamError!((error: string) => {
for (const listener of this.listeners) {
listener({ type: 'error', content: error });
listener({ type: 'error', content: formatStreamError(error) });
}
this.active = false;
window.electronAPI!.removeClaudeListeners!();
Expand Down