@agenticmail/enterprise 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +183 -0
- package/agenticmail-enterprise.db +0 -0
- package/dashboards/README.md +120 -0
- package/dashboards/dotnet/Program.cs +261 -0
- package/dashboards/express/app.js +146 -0
- package/dashboards/go/main.go +513 -0
- package/dashboards/html/index.html +535 -0
- package/dashboards/java/AgenticMailDashboard.java +376 -0
- package/dashboards/php/index.php +414 -0
- package/dashboards/python/app.py +273 -0
- package/dashboards/ruby/app.rb +195 -0
- package/dist/chunk-77IDQJL3.js +7 -0
- package/dist/chunk-7RGCCHIT.js +115 -0
- package/dist/chunk-DXNKR3TG.js +1355 -0
- package/dist/chunk-IQWA44WT.js +970 -0
- package/dist/chunk-LCUZGIDH.js +965 -0
- package/dist/chunk-N2JVTNNJ.js +2553 -0
- package/dist/chunk-O462UJBH.js +363 -0
- package/dist/chunk-PNKVD2UK.js +26 -0
- package/dist/cli.js +218 -0
- package/dist/dashboard/index.html +558 -0
- package/dist/db-adapter-DEWEFNIV.js +7 -0
- package/dist/dynamodb-CCGL2E77.js +426 -0
- package/dist/engine/index.js +1261 -0
- package/dist/index.js +522 -0
- package/dist/mongodb-ODTXIVPV.js +319 -0
- package/dist/mysql-RM3S2FV5.js +521 -0
- package/dist/postgres-LN7A6MGQ.js +518 -0
- package/dist/routes-2JEPIIKC.js +441 -0
- package/dist/routes-74ZLKJKP.js +399 -0
- package/dist/server.js +7 -0
- package/dist/sqlite-3K5YOZ4K.js +439 -0
- package/dist/turso-LDWODSDI.js +442 -0
- package/package.json +49 -0
- package/src/admin/routes.ts +331 -0
- package/src/auth/routes.ts +130 -0
- package/src/cli.ts +260 -0
- package/src/dashboard/index.html +558 -0
- package/src/db/adapter.ts +230 -0
- package/src/db/dynamodb.ts +456 -0
- package/src/db/factory.ts +51 -0
- package/src/db/mongodb.ts +360 -0
- package/src/db/mysql.ts +472 -0
- package/src/db/postgres.ts +479 -0
- package/src/db/sql-schema.ts +123 -0
- package/src/db/sqlite.ts +391 -0
- package/src/db/turso.ts +411 -0
- package/src/deploy/fly.ts +368 -0
- package/src/deploy/managed.ts +213 -0
- package/src/engine/activity.ts +474 -0
- package/src/engine/agent-config.ts +429 -0
- package/src/engine/agenticmail-bridge.ts +296 -0
- package/src/engine/approvals.ts +278 -0
- package/src/engine/db-adapter.ts +682 -0
- package/src/engine/db-schema.ts +335 -0
- package/src/engine/deployer.ts +595 -0
- package/src/engine/index.ts +134 -0
- package/src/engine/knowledge.ts +486 -0
- package/src/engine/lifecycle.ts +635 -0
- package/src/engine/openclaw-hook.ts +371 -0
- package/src/engine/routes.ts +528 -0
- package/src/engine/skills.ts +473 -0
- package/src/engine/tenant.ts +345 -0
- package/src/engine/tool-catalog.ts +189 -0
- package/src/index.ts +64 -0
- package/src/lib/resilience.ts +326 -0
- package/src/middleware/index.ts +286 -0
- package/src/server.ts +310 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill & Tool Registry + Permission Engine
|
|
3
|
+
*
|
|
4
|
+
* This is the core of enterprise: companies control exactly what their
|
|
5
|
+
* AI agents can and cannot do. Every skill and tool is cataloged,
|
|
6
|
+
* categorized, and gated behind permissions.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ─── Types ──────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export interface SkillDefinition {
|
|
12
|
+
id: string; // e.g. "github", "imsg", "browser"
|
|
13
|
+
name: string; // Human-readable: "GitHub"
|
|
14
|
+
description: string; // What it does
|
|
15
|
+
category: SkillCategory;
|
|
16
|
+
risk: RiskLevel; // How dangerous is this skill
|
|
17
|
+
tools: ToolDefinition[]; // Tools this skill provides
|
|
18
|
+
requires?: string[]; // System requirements (e.g. "macos", "docker", "node")
|
|
19
|
+
configSchema?: Record<string, ConfigField>; // Skill-specific config fields
|
|
20
|
+
icon?: string; // Emoji or icon URL
|
|
21
|
+
source?: 'builtin' | 'clawhub' | 'custom';
|
|
22
|
+
version?: string;
|
|
23
|
+
author?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ToolDefinition {
|
|
27
|
+
id: string; // e.g. "exec", "web_search", "agenticmail_send"
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
category: ToolCategory;
|
|
31
|
+
risk: RiskLevel;
|
|
32
|
+
skillId: string; // Parent skill
|
|
33
|
+
parameters?: Record<string, any>;
|
|
34
|
+
sideEffects: SideEffect[]; // What external effects can this tool cause
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ConfigField {
|
|
38
|
+
type: 'string' | 'number' | 'boolean' | 'select' | 'secret';
|
|
39
|
+
label: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
required?: boolean;
|
|
42
|
+
default?: any;
|
|
43
|
+
options?: { label: string; value: string }[]; // For select type
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type SkillCategory =
|
|
47
|
+
| 'communication' // Email, SMS, messaging
|
|
48
|
+
| 'development' // GitHub, coding, git
|
|
49
|
+
| 'productivity' // Calendar, notes, reminders, tasks
|
|
50
|
+
| 'research' // Web search, web fetch, summarize
|
|
51
|
+
| 'media' // Image gen, TTS, video, audio
|
|
52
|
+
| 'automation' // Browser, shell, scripting
|
|
53
|
+
| 'smart-home' // Hue, Sonos, cameras, Eight Sleep
|
|
54
|
+
| 'data' // Files, databases, storage
|
|
55
|
+
| 'security' // 1Password, healthcheck
|
|
56
|
+
| 'social' // Twitter/X, social media
|
|
57
|
+
| 'platform'; // Core OpenClaw/AgenticMail internals
|
|
58
|
+
|
|
59
|
+
export type ToolCategory =
|
|
60
|
+
| 'read' // Read-only, no side effects
|
|
61
|
+
| 'write' // Creates or modifies data
|
|
62
|
+
| 'execute' // Runs code/commands
|
|
63
|
+
| 'communicate' // Sends messages externally
|
|
64
|
+
| 'destroy'; // Deletes data
|
|
65
|
+
|
|
66
|
+
export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
67
|
+
|
|
68
|
+
export type SideEffect =
|
|
69
|
+
| 'sends-email'
|
|
70
|
+
| 'sends-message'
|
|
71
|
+
| 'sends-sms'
|
|
72
|
+
| 'posts-social'
|
|
73
|
+
| 'runs-code'
|
|
74
|
+
| 'modifies-files'
|
|
75
|
+
| 'deletes-data'
|
|
76
|
+
| 'network-request'
|
|
77
|
+
| 'controls-device'
|
|
78
|
+
| 'accesses-secrets'
|
|
79
|
+
| 'financial';
|
|
80
|
+
|
|
81
|
+
// ─── Agent Permission Profile ───────────────────────────
|
|
82
|
+
|
|
83
|
+
export interface AgentPermissionProfile {
|
|
84
|
+
id: string;
|
|
85
|
+
name: string; // e.g. "Customer Support Agent", "Research Agent"
|
|
86
|
+
description?: string;
|
|
87
|
+
|
|
88
|
+
// Skill-level controls
|
|
89
|
+
skills: {
|
|
90
|
+
mode: 'allowlist' | 'blocklist'; // Allow only listed, or allow all except listed
|
|
91
|
+
list: string[]; // Skill IDs
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Tool-level overrides (more granular than skill-level)
|
|
95
|
+
tools: {
|
|
96
|
+
blocked: string[]; // Always block these tools even if skill is allowed
|
|
97
|
+
allowed: string[]; // Always allow these tools even if skill is blocked
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Risk threshold — auto-block tools above this level
|
|
101
|
+
maxRiskLevel: RiskLevel;
|
|
102
|
+
|
|
103
|
+
// Side-effect restrictions
|
|
104
|
+
blockedSideEffects: SideEffect[]; // Block any tool with these side effects
|
|
105
|
+
|
|
106
|
+
// Approval workflows
|
|
107
|
+
requireApproval: {
|
|
108
|
+
enabled: boolean;
|
|
109
|
+
forRiskLevels: RiskLevel[]; // Which risk levels need human approval
|
|
110
|
+
forSideEffects: SideEffect[]; // Which side effects need approval
|
|
111
|
+
approvers: string[]; // User IDs who can approve
|
|
112
|
+
timeoutMinutes: number; // Auto-deny after timeout
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Rate limits
|
|
116
|
+
rateLimits: {
|
|
117
|
+
toolCallsPerMinute: number;
|
|
118
|
+
toolCallsPerHour: number;
|
|
119
|
+
toolCallsPerDay: number;
|
|
120
|
+
externalActionsPerHour: number; // Actions with side effects
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Execution constraints
|
|
124
|
+
constraints: {
|
|
125
|
+
maxConcurrentTasks: number;
|
|
126
|
+
maxSessionDurationMinutes: number;
|
|
127
|
+
allowedWorkingHours?: { start: string; end: string; timezone: string };
|
|
128
|
+
allowedIPs?: string[]; // Restrict to specific networks
|
|
129
|
+
sandboxMode: boolean; // If true, all external actions are simulated
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
createdAt: string;
|
|
133
|
+
updatedAt: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Preset Permission Profiles ─────────────────────────
|
|
137
|
+
|
|
138
|
+
export const PRESET_PROFILES: Omit<AgentPermissionProfile, 'id' | 'createdAt' | 'updatedAt'>[] = [
|
|
139
|
+
{
|
|
140
|
+
name: 'Research Assistant',
|
|
141
|
+
description: 'Can search the web, read files, and summarize content. Cannot send messages, run code, or modify anything.',
|
|
142
|
+
skills: { mode: 'allowlist', list: ['research', 'summarize', 'data-read'] },
|
|
143
|
+
tools: { blocked: ['exec', 'write', 'edit'], allowed: ['web_search', 'web_fetch', 'read', 'memory_search', 'memory_get'] },
|
|
144
|
+
maxRiskLevel: 'low',
|
|
145
|
+
blockedSideEffects: ['sends-email', 'sends-message', 'sends-sms', 'posts-social', 'runs-code', 'modifies-files', 'deletes-data', 'controls-device', 'financial'],
|
|
146
|
+
requireApproval: { enabled: false, forRiskLevels: [], forSideEffects: [], approvers: [], timeoutMinutes: 30 },
|
|
147
|
+
rateLimits: { toolCallsPerMinute: 30, toolCallsPerHour: 500, toolCallsPerDay: 5000, externalActionsPerHour: 0 },
|
|
148
|
+
constraints: { maxConcurrentTasks: 3, maxSessionDurationMinutes: 480, sandboxMode: false },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'Customer Support Agent',
|
|
152
|
+
description: 'Can read/send emails, search knowledge base, and manage tickets. Cannot run code or access files.',
|
|
153
|
+
skills: { mode: 'allowlist', list: ['communication', 'research', 'agenticmail'] },
|
|
154
|
+
tools: { blocked: ['exec', 'browser', 'write', 'edit'], allowed: ['agenticmail_send', 'agenticmail_reply', 'agenticmail_inbox', 'agenticmail_read', 'agenticmail_search', 'web_search', 'web_fetch'] },
|
|
155
|
+
maxRiskLevel: 'medium',
|
|
156
|
+
blockedSideEffects: ['runs-code', 'modifies-files', 'deletes-data', 'controls-device', 'financial', 'posts-social'],
|
|
157
|
+
requireApproval: { enabled: true, forRiskLevels: ['high', 'critical'], forSideEffects: ['sends-email'], approvers: [], timeoutMinutes: 60 },
|
|
158
|
+
rateLimits: { toolCallsPerMinute: 20, toolCallsPerHour: 300, toolCallsPerDay: 3000, externalActionsPerHour: 50 },
|
|
159
|
+
constraints: { maxConcurrentTasks: 5, maxSessionDurationMinutes: 480, sandboxMode: false },
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'Developer Assistant',
|
|
163
|
+
description: 'Full development capabilities: code, git, GitHub, shell. Cannot send external messages or access smart home.',
|
|
164
|
+
skills: { mode: 'allowlist', list: ['development', 'github', 'coding-agent', 'research', 'data'] },
|
|
165
|
+
tools: { blocked: ['agenticmail_send', 'message', 'tts', 'nodes'], allowed: ['exec', 'read', 'write', 'edit', 'web_search', 'web_fetch', 'browser'] },
|
|
166
|
+
maxRiskLevel: 'high',
|
|
167
|
+
blockedSideEffects: ['sends-email', 'sends-message', 'sends-sms', 'posts-social', 'controls-device', 'financial'],
|
|
168
|
+
requireApproval: { enabled: true, forRiskLevels: ['critical'], forSideEffects: [], approvers: [], timeoutMinutes: 15 },
|
|
169
|
+
rateLimits: { toolCallsPerMinute: 60, toolCallsPerHour: 1000, toolCallsPerDay: 10000, externalActionsPerHour: 100 },
|
|
170
|
+
constraints: { maxConcurrentTasks: 3, maxSessionDurationMinutes: 720, sandboxMode: false },
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'Full Access (Owner)',
|
|
174
|
+
description: 'Unrestricted access to all skills and tools. Use with caution.',
|
|
175
|
+
skills: { mode: 'blocklist', list: [] },
|
|
176
|
+
tools: { blocked: [], allowed: [] },
|
|
177
|
+
maxRiskLevel: 'critical',
|
|
178
|
+
blockedSideEffects: [],
|
|
179
|
+
requireApproval: { enabled: false, forRiskLevels: [], forSideEffects: [], approvers: [], timeoutMinutes: 30 },
|
|
180
|
+
rateLimits: { toolCallsPerMinute: 120, toolCallsPerHour: 5000, toolCallsPerDay: 50000, externalActionsPerHour: 500 },
|
|
181
|
+
constraints: { maxConcurrentTasks: 10, maxSessionDurationMinutes: 1440, sandboxMode: false },
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: 'Sandbox (Testing)',
|
|
185
|
+
description: 'All tools available but in simulation mode. No real external actions are taken.',
|
|
186
|
+
skills: { mode: 'blocklist', list: [] },
|
|
187
|
+
tools: { blocked: [], allowed: [] },
|
|
188
|
+
maxRiskLevel: 'critical',
|
|
189
|
+
blockedSideEffects: [],
|
|
190
|
+
requireApproval: { enabled: false, forRiskLevels: [], forSideEffects: [], approvers: [], timeoutMinutes: 30 },
|
|
191
|
+
rateLimits: { toolCallsPerMinute: 60, toolCallsPerHour: 1000, toolCallsPerDay: 10000, externalActionsPerHour: 500 },
|
|
192
|
+
constraints: { maxConcurrentTasks: 5, maxSessionDurationMinutes: 480, sandboxMode: true },
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
// ─── Built-in Skill Catalog ─────────────────────────────
|
|
197
|
+
|
|
198
|
+
export const BUILTIN_SKILLS: Omit<SkillDefinition, 'tools'>[] = [
|
|
199
|
+
// Communication
|
|
200
|
+
{ id: 'agenticmail', name: 'AgenticMail', description: 'Full email system — send, receive, organize, search, forward, reply. Agent-to-agent messaging and task delegation.', category: 'communication', risk: 'medium', icon: '📧', source: 'builtin' },
|
|
201
|
+
{ id: 'imsg', name: 'iMessage', description: 'Send and receive iMessages and SMS via macOS.', category: 'communication', risk: 'high', icon: '💬', source: 'builtin', requires: ['macos'] },
|
|
202
|
+
{ id: 'wacli', name: 'WhatsApp', description: 'Send WhatsApp messages and search chat history.', category: 'communication', risk: 'high', icon: '📱', source: 'builtin' },
|
|
203
|
+
|
|
204
|
+
// Development
|
|
205
|
+
{ id: 'github', name: 'GitHub', description: 'Manage issues, PRs, CI runs, and repositories via gh CLI.', category: 'development', risk: 'medium', icon: '🐙', source: 'builtin' },
|
|
206
|
+
{ id: 'coding-agent', name: 'Coding Agent', description: 'Run Codex CLI, Claude Code, or other coding agents as background processes.', category: 'development', risk: 'high', icon: '💻', source: 'builtin' },
|
|
207
|
+
|
|
208
|
+
// Productivity
|
|
209
|
+
{ id: 'gog', name: 'Google Workspace', description: 'Gmail, Calendar, Drive, Contacts, Sheets, and Docs.', category: 'productivity', risk: 'medium', icon: '📅', source: 'builtin' },
|
|
210
|
+
{ id: 'apple-notes', name: 'Apple Notes', description: 'Create, search, edit, and manage Apple Notes.', category: 'productivity', risk: 'low', icon: '📝', source: 'builtin', requires: ['macos'] },
|
|
211
|
+
{ id: 'apple-reminders', name: 'Apple Reminders', description: 'Manage Apple Reminders lists and items.', category: 'productivity', risk: 'low', icon: '✅', source: 'builtin', requires: ['macos'] },
|
|
212
|
+
{ id: 'bear-notes', name: 'Bear Notes', description: 'Create, search, and manage Bear notes.', category: 'productivity', risk: 'low', icon: '🐻', source: 'builtin', requires: ['macos'] },
|
|
213
|
+
{ id: 'obsidian', name: 'Obsidian', description: 'Work with Obsidian vaults and automate via CLI.', category: 'productivity', risk: 'low', icon: '💎', source: 'builtin' },
|
|
214
|
+
{ id: 'things-mac', name: 'Things 3', description: 'Manage tasks and projects in Things 3.', category: 'productivity', risk: 'low', icon: '☑️', source: 'builtin', requires: ['macos'] },
|
|
215
|
+
|
|
216
|
+
// Research
|
|
217
|
+
{ id: 'web-search', name: 'Web Search', description: 'Search the web via Brave Search API.', category: 'research', risk: 'low', icon: '🔍', source: 'builtin' },
|
|
218
|
+
{ id: 'web-fetch', name: 'Web Fetch', description: 'Fetch and extract readable content from URLs.', category: 'research', risk: 'low', icon: '🌐', source: 'builtin' },
|
|
219
|
+
{ id: 'summarize', name: 'Summarize', description: 'Summarize or transcribe URLs, podcasts, and files.', category: 'research', risk: 'low', icon: '📄', source: 'builtin' },
|
|
220
|
+
{ id: 'blogwatcher', name: 'Blog Watcher', description: 'Monitor blogs and RSS/Atom feeds for updates.', category: 'research', risk: 'low', icon: '📡', source: 'builtin' },
|
|
221
|
+
|
|
222
|
+
// Media
|
|
223
|
+
{ id: 'openai-image-gen', name: 'Image Generation', description: 'Generate images via OpenAI Images API.', category: 'media', risk: 'low', icon: '🎨', source: 'builtin' },
|
|
224
|
+
{ id: 'nano-banana-pro', name: 'Gemini Image', description: 'Generate or edit images via Gemini 3 Pro.', category: 'media', risk: 'low', icon: '🖼️', source: 'builtin' },
|
|
225
|
+
{ id: 'tts', name: 'Text-to-Speech', description: 'Convert text to speech audio.', category: 'media', risk: 'low', icon: '🔊', source: 'builtin' },
|
|
226
|
+
{ id: 'openai-whisper', name: 'Whisper Transcription', description: 'Transcribe audio via OpenAI Whisper API.', category: 'media', risk: 'low', icon: '🎙️', source: 'builtin' },
|
|
227
|
+
{ id: 'video-frames', name: 'Video Frames', description: 'Extract frames or clips from videos.', category: 'media', risk: 'low', icon: '🎬', source: 'builtin' },
|
|
228
|
+
{ id: 'gifgrep', name: 'GIF Search', description: 'Search and download GIFs.', category: 'media', risk: 'low', icon: '🎭', source: 'builtin' },
|
|
229
|
+
|
|
230
|
+
// Automation
|
|
231
|
+
{ id: 'browser', name: 'Browser Control', description: 'Automate web browsers — navigate, click, type, screenshot.', category: 'automation', risk: 'high', icon: '🌍', source: 'builtin' },
|
|
232
|
+
{ id: 'exec', name: 'Shell Commands', description: 'Execute shell commands on the host machine.', category: 'automation', risk: 'critical', icon: '⚡', source: 'builtin' },
|
|
233
|
+
{ id: 'peekaboo', name: 'macOS UI Automation', description: 'Capture and automate macOS UI with Peekaboo.', category: 'automation', risk: 'high', icon: '👁️', source: 'builtin', requires: ['macos'] },
|
|
234
|
+
{ id: 'cron', name: 'Scheduled Tasks', description: 'Create and manage cron jobs and reminders.', category: 'automation', risk: 'medium', icon: '⏰', source: 'builtin' },
|
|
235
|
+
|
|
236
|
+
// Smart Home
|
|
237
|
+
{ id: 'openhue', name: 'Philips Hue', description: 'Control Hue lights and scenes.', category: 'smart-home', risk: 'low', icon: '💡', source: 'builtin' },
|
|
238
|
+
{ id: 'sonoscli', name: 'Sonos', description: 'Control Sonos speakers.', category: 'smart-home', risk: 'low', icon: '🔈', source: 'builtin' },
|
|
239
|
+
{ id: 'blucli', name: 'BluOS', description: 'Control BluOS speakers.', category: 'smart-home', risk: 'low', icon: '🎵', source: 'builtin' },
|
|
240
|
+
{ id: 'eightctl', name: 'Eight Sleep', description: 'Control Eight Sleep pod temperature and alarms.', category: 'smart-home', risk: 'low', icon: '🛏️', source: 'builtin' },
|
|
241
|
+
{ id: 'camsnap', name: 'IP Cameras', description: 'Capture frames from RTSP/ONVIF cameras.', category: 'smart-home', risk: 'medium', icon: '📷', source: 'builtin' },
|
|
242
|
+
|
|
243
|
+
// Data
|
|
244
|
+
{ id: 'files', name: 'File System', description: 'Read, write, and edit files on the host.', category: 'data', risk: 'medium', icon: '📁', source: 'builtin' },
|
|
245
|
+
{ id: 'memory', name: 'Agent Memory', description: 'Persistent memory search and storage.', category: 'data', risk: 'low', icon: '🧠', source: 'builtin' },
|
|
246
|
+
|
|
247
|
+
// Security
|
|
248
|
+
{ id: '1password', name: '1Password', description: 'Read and manage secrets via 1Password CLI.', category: 'security', risk: 'critical', icon: '🔐', source: 'builtin' },
|
|
249
|
+
{ id: 'healthcheck', name: 'Security Audit', description: 'Host security hardening and risk checks.', category: 'security', risk: 'medium', icon: '🛡️', source: 'builtin' },
|
|
250
|
+
|
|
251
|
+
// Social
|
|
252
|
+
{ id: 'twitter', name: 'Twitter/X', description: 'Post tweets, read timeline, manage social presence.', category: 'social', risk: 'high', icon: '🐦', source: 'builtin' },
|
|
253
|
+
|
|
254
|
+
// Platform
|
|
255
|
+
{ id: 'gateway', name: 'OpenClaw Gateway', description: 'Restart, configure, and update the OpenClaw gateway.', category: 'platform', risk: 'critical', icon: '⚙️', source: 'builtin' },
|
|
256
|
+
{ id: 'sessions', name: 'Session Management', description: 'Spawn sub-agents, list sessions, send messages between sessions.', category: 'platform', risk: 'medium', icon: '🔄', source: 'builtin' },
|
|
257
|
+
{ id: 'nodes', name: 'Node Control', description: 'Discover and control paired devices (camera, screen, location).', category: 'platform', risk: 'high', icon: '📡', source: 'builtin' },
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
// ─── Permission Engine ──────────────────────────────────
|
|
261
|
+
|
|
262
|
+
export class PermissionEngine {
|
|
263
|
+
private skills: Map<string, SkillDefinition> = new Map();
|
|
264
|
+
private profiles: Map<string, AgentPermissionProfile> = new Map();
|
|
265
|
+
|
|
266
|
+
constructor(skills?: SkillDefinition[]) {
|
|
267
|
+
if (skills) {
|
|
268
|
+
for (const s of skills) this.skills.set(s.id, s);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
registerSkill(skill: SkillDefinition) {
|
|
273
|
+
this.skills.set(skill.id, skill);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
setProfile(agentId: string, profile: AgentPermissionProfile) {
|
|
277
|
+
this.profiles.set(agentId, profile);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getProfile(agentId: string): AgentPermissionProfile | undefined {
|
|
281
|
+
return this.profiles.get(agentId);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Core permission check: Can this agent use this tool right now?
|
|
286
|
+
* Returns { allowed, reason, requiresApproval }
|
|
287
|
+
*/
|
|
288
|
+
checkPermission(
|
|
289
|
+
agentId: string,
|
|
290
|
+
toolId: string,
|
|
291
|
+
context?: { timestamp?: Date; ip?: string }
|
|
292
|
+
): PermissionResult {
|
|
293
|
+
const profile = this.profiles.get(agentId);
|
|
294
|
+
if (!profile) {
|
|
295
|
+
return { allowed: false, reason: 'No permission profile assigned', requiresApproval: false };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// 1. Check sandbox mode
|
|
299
|
+
if (profile.constraints.sandboxMode) {
|
|
300
|
+
return { allowed: true, reason: 'Sandbox mode — action will be simulated', requiresApproval: false, sandbox: true };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 2. Check working hours
|
|
304
|
+
if (profile.constraints.allowedWorkingHours) {
|
|
305
|
+
const now = context?.timestamp || new Date();
|
|
306
|
+
const { start, end, timezone } = profile.constraints.allowedWorkingHours;
|
|
307
|
+
const hour = parseInt(new Intl.DateTimeFormat('en-US', { hour: 'numeric', hour12: false, timeZone: timezone }).format(now));
|
|
308
|
+
const startHour = parseInt(start.split(':')[0]);
|
|
309
|
+
const endHour = parseInt(end.split(':')[0]);
|
|
310
|
+
if (hour < startHour || hour >= endHour) {
|
|
311
|
+
return { allowed: false, reason: `Outside working hours (${start}-${end} ${timezone})`, requiresApproval: false };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 3. Check IP restrictions
|
|
316
|
+
if (profile.constraints.allowedIPs?.length && context?.ip) {
|
|
317
|
+
if (!profile.constraints.allowedIPs.includes(context.ip)) {
|
|
318
|
+
return { allowed: false, reason: `IP ${context.ip} not in allowlist`, requiresApproval: false };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// 4. Tool-level explicit overrides (highest priority)
|
|
323
|
+
if (profile.tools.blocked.includes(toolId)) {
|
|
324
|
+
return { allowed: false, reason: `Tool "${toolId}" is explicitly blocked`, requiresApproval: false };
|
|
325
|
+
}
|
|
326
|
+
if (profile.tools.allowed.includes(toolId)) {
|
|
327
|
+
// Explicitly allowed — skip skill checks, but still check approval requirements
|
|
328
|
+
return this._checkApproval(profile, toolId);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 5. Find which skill this tool belongs to
|
|
332
|
+
const tool = this._findTool(toolId);
|
|
333
|
+
if (!tool) {
|
|
334
|
+
// Unknown tool — block by default
|
|
335
|
+
return { allowed: false, reason: `Unknown tool "${toolId}"`, requiresApproval: false };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 6. Skill-level check
|
|
339
|
+
const skillAllowed = profile.skills.mode === 'allowlist'
|
|
340
|
+
? profile.skills.list.includes(tool.skillId)
|
|
341
|
+
: !profile.skills.list.includes(tool.skillId);
|
|
342
|
+
|
|
343
|
+
if (!skillAllowed) {
|
|
344
|
+
return { allowed: false, reason: `Skill "${tool.skillId}" is not permitted`, requiresApproval: false };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 7. Risk level check
|
|
348
|
+
const riskOrder: RiskLevel[] = ['low', 'medium', 'high', 'critical'];
|
|
349
|
+
const toolRiskIdx = riskOrder.indexOf(tool.risk);
|
|
350
|
+
const maxRiskIdx = riskOrder.indexOf(profile.maxRiskLevel);
|
|
351
|
+
if (toolRiskIdx > maxRiskIdx) {
|
|
352
|
+
return { allowed: false, reason: `Tool risk "${tool.risk}" exceeds max allowed "${profile.maxRiskLevel}"`, requiresApproval: false };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// 8. Side-effect restrictions
|
|
356
|
+
for (const effect of tool.sideEffects) {
|
|
357
|
+
if (profile.blockedSideEffects.includes(effect)) {
|
|
358
|
+
return { allowed: false, reason: `Side effect "${effect}" is blocked`, requiresApproval: false };
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// 9. Check if approval is required
|
|
363
|
+
return this._checkApproval(profile, toolId, tool);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private _checkApproval(profile: AgentPermissionProfile, toolId: string, tool?: ToolDefinition): PermissionResult {
|
|
367
|
+
if (!profile.requireApproval.enabled) {
|
|
368
|
+
return { allowed: true, reason: 'Permitted', requiresApproval: false };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (tool) {
|
|
372
|
+
// Check risk-level approval
|
|
373
|
+
if (profile.requireApproval.forRiskLevels.includes(tool.risk)) {
|
|
374
|
+
return { allowed: true, reason: 'Requires human approval (risk level)', requiresApproval: true };
|
|
375
|
+
}
|
|
376
|
+
// Check side-effect approval
|
|
377
|
+
for (const effect of tool.sideEffects) {
|
|
378
|
+
if (profile.requireApproval.forSideEffects.includes(effect)) {
|
|
379
|
+
return { allowed: true, reason: `Requires human approval (${effect})`, requiresApproval: true };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return { allowed: true, reason: 'Permitted', requiresApproval: false };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private _findTool(toolId: string): ToolDefinition | undefined {
|
|
388
|
+
// Check registered skills first
|
|
389
|
+
for (const skill of this.skills.values()) {
|
|
390
|
+
const tool = skill.tools.find(t => t.id === toolId);
|
|
391
|
+
if (tool) return tool;
|
|
392
|
+
}
|
|
393
|
+
// Fall back to global tool index (real OpenClaw + AgenticMail tools)
|
|
394
|
+
try {
|
|
395
|
+
const { TOOL_INDEX } = require('./tool-catalog.js');
|
|
396
|
+
return TOOL_INDEX.get(toolId);
|
|
397
|
+
} catch {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get the full resolved tool list for an agent — what they can actually use
|
|
404
|
+
*/
|
|
405
|
+
getAvailableTools(agentId: string): { tool: ToolDefinition; status: 'allowed' | 'approval-required' | 'sandbox' }[] {
|
|
406
|
+
const result: { tool: ToolDefinition; status: 'allowed' | 'approval-required' | 'sandbox' }[] = [];
|
|
407
|
+
|
|
408
|
+
for (const skill of this.skills.values()) {
|
|
409
|
+
for (const tool of skill.tools) {
|
|
410
|
+
const perm = this.checkPermission(agentId, tool.id);
|
|
411
|
+
if (perm.allowed) {
|
|
412
|
+
result.push({
|
|
413
|
+
tool,
|
|
414
|
+
status: perm.sandbox ? 'sandbox' : perm.requiresApproval ? 'approval-required' : 'allowed',
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Generate the OpenClaw tool policy config for an agent based on their profile
|
|
425
|
+
*/
|
|
426
|
+
generateToolPolicy(agentId: string): {
|
|
427
|
+
allowedTools: string[];
|
|
428
|
+
blockedTools: string[];
|
|
429
|
+
approvalRequired: string[];
|
|
430
|
+
rateLimits: AgentPermissionProfile['rateLimits'];
|
|
431
|
+
} {
|
|
432
|
+
const profile = this.profiles.get(agentId);
|
|
433
|
+
if (!profile) return { allowedTools: [], blockedTools: [], approvalRequired: [], rateLimits: { toolCallsPerMinute: 10, toolCallsPerHour: 100, toolCallsPerDay: 1000, externalActionsPerHour: 10 } };
|
|
434
|
+
|
|
435
|
+
const allowed: string[] = [];
|
|
436
|
+
const blocked: string[] = [];
|
|
437
|
+
const approval: string[] = [];
|
|
438
|
+
|
|
439
|
+
for (const skill of this.skills.values()) {
|
|
440
|
+
for (const tool of skill.tools) {
|
|
441
|
+
const perm = this.checkPermission(agentId, tool.id);
|
|
442
|
+
if (perm.allowed) {
|
|
443
|
+
allowed.push(tool.id);
|
|
444
|
+
if (perm.requiresApproval) approval.push(tool.id);
|
|
445
|
+
} else {
|
|
446
|
+
blocked.push(tool.id);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return { allowedTools: allowed, blockedTools: blocked, approvalRequired: approval, rateLimits: profile.rateLimits };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
getAllSkills(): SkillDefinition[] {
|
|
455
|
+
return Array.from(this.skills.values());
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
getSkillsByCategory(): Record<SkillCategory, SkillDefinition[]> {
|
|
459
|
+
const result = {} as Record<SkillCategory, SkillDefinition[]>;
|
|
460
|
+
for (const skill of this.skills.values()) {
|
|
461
|
+
if (!result[skill.category]) result[skill.category] = [];
|
|
462
|
+
result[skill.category].push(skill);
|
|
463
|
+
}
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export interface PermissionResult {
|
|
469
|
+
allowed: boolean;
|
|
470
|
+
reason: string;
|
|
471
|
+
requiresApproval: boolean;
|
|
472
|
+
sandbox?: boolean;
|
|
473
|
+
}
|